§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:
- Match the requests it cares about (by content type, path prefix, or method) and ignore everything else.
- Validate the body against a domain schema and reject malformed payloads with HTTP 422 before the upstream sees them.
- Extract domain identifiers from the payload and surface them as resource tags, so policies can authorise on real attributes (e.g. only consignments tagged for this chain context).
- Stay optional — the connector core works fine without any recipes, and recipes ship as separate packages with their own release cadence.
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.
@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
entityType → 422 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 }).
Source
·
npm
·
OTM 5.8 spec
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(...)
Source
·
npm
·
Regulation (EU) 2020/1056
@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(...)
Source
·
npm
·
HL7 FHIR R5
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(...)
Source
·
npm
·
UN/CEFACT
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(...)
Source
·
npm
·
ISO 20022
§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:
- DCSA Track & Trace and eBL — Digital Container Shipping Association events and electronic bills of lading.
- EDI X12 / EDIFACT bridges — for parties that still ship structured EDI alongside JSON.
- ENTSO-E / EBIX — European energy-market message profiles for grid balancing and metering.
- SBDH-wrapped UBL — for Peppol-style document exchange where the wrapper carries the routing.
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.