Connector add-ons · Domain recipes

Recipes — real data,
through the connector.

The BDI core stays domain-neutral on purpose: BVAD says who, BVOD says which chain, the Connector enforces the boundary. Recipes are the layer on top — small, optional add-ons that teach the connector about a specific data shape (a transport consignment, a customs declaration, a clinical referral, an interbank settlement) so it can validate it, tag it, and route it without your backend reinventing the protocol. Five recipes ship today: OTM 5.8, eFTI, FHIR R5, UN/CEFACT MMT-RSM, and ISO 20022 pacs.008.

§1What is a recipe?

A recipe is a TypeScript package that plugs into the @transportial/con connector at composition time. It implements one small port — PayloadInspectorPort — and the connector wires it into the proxy-forward path, ahead of the policy decision point.

Each recipe gets to do four useful things:

Why not just put schema validation in the upstream backend? Because the same payload travels through two parties, and only the connector sees the BVAD + BVOD + content together. A bad payload that reaches the upstream has already been authorised. Recipes catch the mistake at the protocol boundary, where it's cheapest to reject and where the diagnostic ("OTM consignment is missing required field") is addressable to a counterparty rather than buried in an internal log.

§2Available recipes

Five recipes ship today, covering transport, freight, healthcare, customs and financial settlement. Each follows the same shape — a composeXRecipe(...) factory that returns a PayloadInspectorPort the connector wires in ahead of the PDP — so swapping or composing them is mechanical.

Transport v0.1.0

@transportial/recipe-otm

OTM 5.8 — Open Transportation Model

Validates and transports payloads against the OTM 5.8 specification. Matches application/vnd.otm+json on POST/PUT/PATCH (and application/json when pinned to a path prefix), structurally validates the body against the pinned OTM 5.8 entity surface, and surfaces otm.entityType, otm.id, and otm.version as resource tags so your PDP can authorise on them.

Install

bun add @transportial/recipe-otm
# or: npm install @transportial/recipe-otm

Wire it into the connector

import { composeCon } from '@transportial/con';
import { composeOtmRecipe } from '@transportial/recipe-otm';

const otm = composeOtmRecipe({ pathPrefixes: ['/otm'] });

const con = composeCon({
  asrIssuer: 'https://asr.example.org',
  orsIssuer: 'https://ors.example.org',
  associationId: 'eu.nl.bdi.acme',
  ownConnectorId: 'urn:bdi:connector:me',
  audience: 'urn:bdi:association:eu.nl.bdi.acme',
  inspectors: otm.inspectors,
});

What you get on the wire

  • Malformed JSON or unknown entityType422 invalid-payload with a structured details array.
  • Valid OTM payload → request flows through to the upstream, with PDP resource tags otm.entityType, otm.id, otm.version available to your policy set.
  • Non-OTM requests on the same connector → untouched.

Plugging in a richer validator

The bundled MinimalOtmValidator performs a fast structural check against the pinned 5.8 entity surface — useful out of the box, deliberately not a replacement for full schema validation. Production deployments that want every JSON-Schema constraint enforced can implement the OtmValidator interface against the upstream OpenAPI document (Ajv, valibot, anything you like) and pass it as composeOtmRecipe({ validator: yourValidator }).

Freight · EU regulation v0.1.0

@transportial/recipe-efti

eFTI 1.0 — EU electronic Freight Transport Information

Validates and transports payloads against the eFTI common dataset for cross-border road transport, aligned with Regulation (EU) 2020/1056 and the implementing acts that fix the data subset. Matches application/vnd.efti+json, validates the eftiType discriminator and per-entity required fields (sender + consignee on consignments, occurrence time on transport events, UN number on dangerous goods), and surfaces efti.entityType, efti.id and efti.version as resource tags.

import { composeEftiRecipe } from '@transportial/recipe-efti';

const efti = composeEftiRecipe({ pathPrefixes: ['/efti'] });
// pass efti.inspectors into composeCon(...)
Healthcare · HL7 v0.1.0

@transportial/recipe-fhir-r5

FHIR R5 — clinical referrals + patient summary

Validates and transports FHIR R5 resources relevant to clinical referral pathways and patient-summary exchange between care providers (ServiceRequest, Composition, Bundle, Patient, Encounter, Condition, Observation, MedicationRequest, …). Matches application/fhir+json (with the fhirVersion media-type parameter recognised), checks the FHIR id pattern when present, and surfaces fhir.resourceType, fhir.id and fhir.version as resource tags. The R5 rule that id is server-assigned on create is honoured — the inspector simply omits the id tag when no id is sent.

import { composeFhirR5Recipe } from '@transportial/recipe-fhir-r5';

const fhir = composeFhirR5Recipe({ pathPrefixes: ['/fhir'] });
// pass fhir.inspectors into composeCon(...)
Customs · Multimodal v0.1.0

@transportial/recipe-mmt-rsm

UN/CEFACT MMT-RSM — multimodal transport reference semantic model

Validates and transports payloads aligned with the UN/CEFACT Buy-Ship-Pay reference data model, scoped to the MMT (multimodal transport) subset used for customs and shipping. Matches application/vnd.uncefact.mmt-rsm+json, validates the entityType discriminator across consignment, customsDeclaration, transportMovement, transportDocument, party, goodsItem, equipment and friends, and surfaces mmt-rsm.entityType, mmt-rsm.id and mmt-rsm.version as resource tags.

import { composeMmtRsmRecipe } from '@transportial/recipe-mmt-rsm';

const mmt = composeMmtRsmRecipe({ pathPrefixes: ['/customs'] });
// pass mmt.inspectors into composeCon(...)
Finance · Settlement v0.1.0

@transportial/recipe-iso20022-pacs008

ISO 20022 pacs.008 — FI-to-FI customer credit transfer

Validates and transports ISO 20022 pacs.008.001.10 messages — the canonical financial settlement message along a trade-finance chain. Matches application/vnd.iso20022+json, structurally validates the Document → FIToFICstmrCdtTrf → GrpHdr + CdtTrfTxInf envelope (MsgId, CreDtTm, NbOfTxs, SttlmInf; per-tx PmtId, IntrBkSttlmAmt, ChrgBr, Dbtr, DbtrAgt, Cdtr, CdtrAgt), and surfaces iso20022.message, iso20022.msgId, iso20022.txCount, and (when present) iso20022.endToEndId as resource tags so policies can authorise on the underlying payment.

import { composePacs008Recipe } from '@transportial/recipe-iso20022-pacs008';

const pacs008 = composePacs008Recipe({ pathPrefixes: ['/payments'] });
// pass pacs008.inspectors into composeCon(...)

§3Building your own recipe

A recipe is just a package that exposes a factory which returns one or more PayloadInspectorPort implementations. The interface is intentionally small — three members, all synchronous-or-async — so you can ship a recipe in an afternoon and iterate on the validation surface independently of the connector itself.

import type { PayloadInspectorPort } from '@transportial/con';

class FhirBundleInspector implements PayloadInspectorPort {
  readonly name = 'fhir';

  matches(req) {
    return req.method === 'POST'
      && req.contentType.startsWith('application/fhir+json');
  }

  async inspect(req) {
    const parsed = JSON.parse(req.body);
    if (parsed.resourceType !== 'Bundle') {
      return { ok: false, reason: 'fhir-not-bundle' };
    }
    return {
      ok: true,
      resourceTags: { 'fhir.resourceType': parsed.resourceType, 'fhir.id': parsed.id ?? '' },
    };
  }
}

That's the whole hook. Hand a list of these to composeCon via the inspectors field and the connector will run them ahead of the PDP, short-circuit on failure, and merge any tags they produce into the PDP resource. The same seam is where future recipe artefacts (default policies, OTM-aware metrics, webhook validators) will land — without changing the connector contract.

§4Roadmap

Recipes are deliberately a thin layer — the value comes from breadth. The five recipes above cover transport (OTM, eFTI, MMT-RSM), healthcare (FHIR R5), and financial settlement (ISO 20022 pacs.008). Adjacent shapes we'd like to add next, demand permitting:

If you're building one of these and want to coordinate, open an issue on GitHub — we're keeping the recipe surface stable so external packages can ship without depending on private internals.