Add a port adapter
Implement a new provider behind an existing port.
Ports-and-adapters is the product's core architecture (see Ports and adapters). This tutorial adds an adapter for a capability that already has a port — e.g. a new transcription engine or storage backend.
Prerequisites
- Understand the port interface you are implementing — it lives in a
packages/<domain>package (e.g.packages/transcriptiondefinesTranscriber). - The dev environment set up.
Steps
-
Read the port. Find the interface in the domain package. Note what the port promises (events, capabilities like
supported, optional hooks such assessionStats()/recording()). -
Create the adapter package. Follow the sibling convention — e.g.
packages/transcription-whisperandpackages/transcription-webspeechare two adapters for the sameTranscriberport. Createpackages/<capability>-<vendor>/. -
Implement the interface exactly. Adapters live at the edge: they may use
node:*, vendor SDKs, and provider literals — that's their job. Keep domain types from the port; don't leak vendor shapes upward. -
Wire selection at the composition root. Which adapter runs is decided where the app is assembled (
apps/webroute composition / driver registries), never inside plugins. Look at how the transcription driver registry selectswhispervswebspeech. -
Test the adapter in isolation. Unit-test against the port contract; the arch lint will fail the build if adapter code leaks into ports/plugins.
Verify
bun run checkand package-scopedbunx vitest runpass.- The feature works end-to-end with your adapter selected, and still works with the previous adapter — that's the point of the port.