Stone.jsDocs
Paradigm

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

src/MyPlatformAdapter.tsts
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.

src/blueprint.tsts
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.

HelperTypeDescription
defineAdapterMiddleware(fn)raw pipeRuns on the platform context (raw cause/response), around the kernel call.
defineAdapterErrorHandler(fn)boundary errorsMaps a failure that happens before or around the kernel into a native error response.
src/blueprint.tsts
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' })
})

Stone.js

Your app exists in every runtime. Until you run it.

An open-source project by Stone Foundation
Created by Mr. Stone (Evens Pierre)