Design decisions

Why the API is shaped the way it is — the architecture decision records, indexed by what each one locked in.

View as Markdown

Every consequential choice in Postel is written down as an ADR in decisions/. This index is the adopter's cut: what each decision locked in, and where you feel it in the API. The records themselves carry the full context, options considered, and consequences.

The decisions you feel every day

ADRWhat it locked in
0001 — Library shapeNo service, no broker, no separate dispatcher — ever. Postel runs in your process against your database. Everything on Why Postel follows from this one.
0002 — Code-first configurationNo YAML, no DSL. Postel({...}) is a typed factory over a literal config — which is why postel.inbound.<source> is typed to your sources and why definePostelConfig exists.
0007 — Storage strategyThe adapter matrix (standalone / client / ORM) and host-transaction passthrough — the { tx } option on send() that makes the transactional outbox work through your connection.
0012 — Package granularityOne @postel/core for both halves rather than split sender/receiver packages; adapters stay separate. Why installing one package gives you inbound and outbound.
0016 — House API idiomsThe conventions every surface follows: options-object factories, duration strings ("5m"), typed errors over booleans, readonly shapes.
0017 — Framework-adapter patternOne framework-agnostic core (@postel/http) + thin per-framework gate bindings — why every adapter behaves identically and a new framework is a ~100-line package.
0015 — Pagination envelopeThe { items, nextCursor } page shape and opaque cursor contract every list API and admin route shares.

The decisions behind the polyglot bet

ADRWhat it locked in
0003 — OpenSpec as the spineEvery behavior is a testable spec scenario before it's code; CI fails if a requirement has no matching test.
0004 — AsyncAPI wire formatThe wire format is a machine-readable AsyncAPI 3.0 document, not prose.
0005 — Staged polyglot rolloutTypeScript first, then Go, Python, Rust — each gated on the same compliance suite. See Polyglot.
0008 — Conformance levelsCONTRACT vs PORT-SPECIFIC — the compliance suite is the executable boundary. The reason specs can be narrow and ports can be idiomatic.
0011 — Compliance suite toolingA Go runner + YAML vectors, deliberately not TypeScript — the oracle must be independent of the implementation it judges.
0014 — Release & versioning flowVersion-matched conformance: a port at X.Y.Z claims conformance by passing compliance@X.Y.

The full record

The remaining ADRs cover narrower calls — monorepo layout, compliance-suite evolution, TypeScript toolchain, dropping @postel/edge, and why the API reference is hand-curated (with CI drift gates instead of a generator).

A change that would contradict one of these needs a superseding ADR, not a quiet diff — that's the point of writing them down.

On this page