Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Mapper authoring

import { defineMapper, MapperRegistry } from "@health-samurai/interbox";

defineMapper is exported from the root and re-exported (with all its types) from @health-samurai/interbox/core’s mapper types. Unlike sources/senders/parsers, mappers are the one stage kind a workspace defines itself — see Stages for why.

defineMapper

function defineMapper<K extends ParserType, Cfg = unknown, Out = unknown>(
  spec: MapperSpec<K, Cfg, Out>,
): MapperDescriptor<K, Cfg>;

interface MapperSpec<K extends ParserType, Cfg, Out> {
  readonly type: string;
  // pass a parser descriptor, e.g. hl7v2Parser — types `input` below
  readonly parser: { readonly type: K };
  map(
    config: Cfg,
    input: ParserOutputMap[K],
    ctx: MapperContext,
  ): Out | undefined | Promise<Out | undefined>;
}

Calling defineMapper(...) registers the definition into MapperRegistry immediately (for the engine to read back) and returns a callable descriptor — calling that produces a ConfiguredMapper for a pipeline’s .mapper():

const v2ToFhir = defineMapper({
  type: "v2-to-fhir",
  parser: hl7v2Parser,
  map(config, input, ctx) {
    // input: parsed HL7v2 segments
    // return a FHIR resource, an array, or undefined to skip
  },
});

pipeline("hl7-to-aidbox")
  .source(/* ... */)
  .mapper(v2ToFhir(/* config, if Cfg isn't unknown */));

map may return undefined (skip — no resource produced), a single resource, an array of resources, or a Promise of either. Throw a domainError (see Errors) for anything the engine should classify rather than treat as an internal bug.

MapperRegistry

const MapperRegistry: {
  mappers: Map<string, MapperDefinition>;
  register(def: MapperDefinition): void;  // throws on a duplicate `type`
  get(type: string): MapperDefinition | undefined;
  getAll(): MapperDefinition[];
  clear(): void;                          // test-only
};

The engine reads this registry back from the workspace bundle at load time to know which mappers exist and which parser each one consumes.

Supporting types

interface ConfiguredMapper<MK extends ParserType = ParserType> {
  readonly type: string;
  readonly parser: MK;
  readonly config: unknown;
}

interface MapperDescriptor<MK extends ParserType, Cfg> {
  readonly type: string;
  readonly parser: MK;
  // config is optional only when Cfg has no required fields
  (...config: {} extends Cfg ? [config?: Cfg] : [config: Cfg]): ConfiguredMapper<MK>;
}

interface MapperDefinition<
  K extends ParserType = ParserType,
  Cfg = unknown,
  Out = unknown,
> {
  readonly type: string;
  // parser type the engine uses to parse inbound messages before map()
  readonly parser: K;
  map(
    config: Cfg,
    input: ParserOutputMap[K],
    ctx: MapperContext,
  ): Out | undefined | Promise<Out | undefined>;
}

// Runtime services the engine hands a mapper. Terminology lookups resolve against
// the engine-owned ConceptMap store; the workspace never touches the DB directly.
interface MapperContext {
  translate(conceptMapId: string, code: string): Promise<MappedCode | undefined>;
}

// A resolved terminology mapping (ConceptMap target).
interface MappedCode {
  targetCode: string;
  targetDisplay?: string;
}

// Parser type -> the parsed value the engine hands map(). Extend via declaration
// merge if a future built-in parser adds another key.
interface ParserOutputMap {
  hl7v2: HL7v2Segment[];
}

type ParserType = keyof ParserOutputMap;

ParserOutputMap is the type-level link between a source’s parser and what a mapper attached to it receives as input — it’s why .mapper() can type-check input against the exact parser the mapper declared, and why Pipelines’s SP tracking works.