For AI agents: the documentation index is at /docs/interbox/llms.txt. A Markdown version of this page is available at /docs/interbox/concepts/stages.md or by requesting it with the Accept: text/markdown header.
Interbox Docs

Stages: source, parser, mapper, sender

A pipeline has three stage kinds plus a parser bound to each source. Each is identified by a type string; the SDK carries the config shape and a handful of built-in descriptors — the engine owns every actual implementation, looked up by type at runtime. This is what keeps a workspace's bundle free of database drivers, worker-thread code, and network clients.

Who implements what:

stagewho writes ityou configure it with
sourceenginemllpSource({ id, host, port, parser })
parserenginehl7v2Parser({ skipZSegments }), bound to a source
mapperyoudefineMapper({ type, parser, map })
senderengineaidboxSender({ url, auth, batchSize, maxRetries })

Source + parser

A source produces raw messages (e.g. an MLLP TCP listener); its bound parser turns each one into a typed structure and determines which inbound table it lands in — the table is derived from the parser, never configured separately.

You never implement a source or parser; you pick a built-in descriptor and configure it — see /builtins.

Mapper

A mapper reads a parser's output and produces the outbound domain model (FHIR resources, for Interbox today). This is the one stage kind meant to hold your business logic — the whole point of a workspace repo:

defineMapper({
  type: "v2-to-fhir",
  // by descriptor — types `input` below and ties the mapper to that
  // parser's output table
  parser: hl7v2Parser,
  // input: ParserOutputMap["hl7v2"], ctx: MapperContext
  map(config, input, ctx) {
    // ... return a FHIR resource, an array of them, or undefined to skip
  },
});

Everything a mapper returns is enqueued together and recorded against the message it came from, which ctx.source also exposes — so a mapper can emit a resource that refers to the message, such as a FHIR Provenance covering the rest of its output (provenanceFor).

See Mapper authoring for the full defineMapper/ MapperRegistry reference, and Errors for how a mapper reports a failure the engine can classify.

Sender

A sender delivers mapped output to a destination (e.g. posts FHIR to Aidbox). Delivery, batching, and retries are engine-owned; you tune them through the sender's config:

  • batchSize — max rows claimed per delivery loop.
  • maxRetries — consecutive transient failures (destination unreachable, 5xx) to retry before the batch is recorded as errored. Permanent rejections (e.g. Aidbox validation errors) don't retry — they land in the error queue as aidbox_rejected for triage.

What the destination receives

Exactly the resources your mapper returned. Interbox tracks which inbound message produced each one, but that record lives in the queue row's own source column — it is never written into the resource and never reaches your FHIR server. For provenance the destination should hold, emit a Provenance from the mapper.

A batch is also claimed a message at a time: the row cap falls only on a message boundary, so the last message pulls the rest of its rows in with it rather than being cut in half, and a single message larger than the cap still arrives whole.

Why the split

The engine implements the stages; the SDK's builtins/* module supplies typed descriptors (mllpSource, aidboxSender, hl7v2Parser) so a workspace references a built-in stage by value, with full type-checking on its config, instead of by a bare string. The only stage a workspace implements itself is the mapper, via defineMapper. (The underlying engine-side contracts live in /core — you'll rarely need them.)

Last updated: