Extending
Write your own adapter
When a runtime has no first-party adapter, you write one, and your existing domain runs there unchanged. An adapter is the integration dimension: it turns a platform's raw cause into an intention, runs the kernel, and turns the result back into a native effect.
#What an adapter is
The principle
An adapter is a translator at the boundary. Inbound, it turns a raw platform cause into a normalised intention. Outbound, it turns the domain's resolution into a native effect. Nothing inward needs to know the platform exists.
In Stone.js
It is the only long-lived thing in the app. It builds an IncomingEvent from the cause, hands it to the kernel (a fresh container per event), and maps the returned response back to the platform's response type.
#The three responsibilities
- Capture the raw cause the platform delivers (a request, a message, an argv).
- Normalise it into an
IncomingEvent, the intention the kernel reads. - Emit the kernel's response as the platform's native effect.
Capture the cause. Collapse it to an intention. Emit the effect. That is a context, in three verbs.
#The shape of one
export class MyPlatformAdapter {
static create (blueprint) { return new MyPlatformAdapter(blueprint) }
constructor (blueprint) { this.blueprint = blueprint }
async run (kernel) {
platform.on('request', async (raw) => {
const event = this.toIncomingEvent(raw) // 1. capture -> 2. normalise
const response = await kernel.handle(event) // 3. resolve (fresh container per event)
raw.respond(this.fromResponse(response)) // 4. emit the native effect
})
}
}#Register it with a blueprint
Adapters graft onto the kernel through a blueprint, never the other way around. Publish a blueprint (and, if you like, a decorator), and your adapter stacks alongside the first-party ones exactly as they stack with each other.
export const myPlatformAdapterBlueprint = {
stone: { adapters: [{ name: 'my-platform', adapter: MyPlatformAdapter, default: false }] }
}
// consumers add it like any other:
// defineStoneApp({ name: 'app' }, [routerBlueprint, myPlatformAdapterBlueprint])#Adapter-level concerns
Some work belongs at the boundary, on the raw cause and response, before the kernel builds an event: raw timing, platform headers, translating a low-level failure. Adapter authors have two public tools for that, distinct from kernel middleware.
| Helper | Type | Description |
|---|---|---|
| defineAdapterMiddleware(fn) | raw pipe | Runs on the platform context (raw cause/response), around the kernel call. |
| defineAdapterErrorHandler(fn) | boundary errors | Maps a failure that happens before or around the kernel into a native error response. |
import { defineAdapterErrorHandler } from '@stone-js/core'
// Turn a boundary failure into a platform-shaped response, even if the kernel
// never ran (e.g. a malformed raw request).
export const onBoundaryError = defineAdapterErrorHandler((error, context) => {
return context.rawResponse({ status: 400, body: 'Bad request' })
})