Inkress ADRsarchitecture decisions
Overview / Architecture / migration
ADR 0040

Migrate commerce-api's core from Elixir to TypeScript, incrementally over a shared database

ProposedArchitecture / migration

Context

commerce-api is Elixir/Phoenix, but the rest of the newer platform is TypeScript — the payment control plane and CDE (commerce-payments/-edge, ADR 0006/0009), the risk service, and the official SDK (ADR 0041). A 2026-06-19 platform strategy chose TypeScript as the destination for the commerce core too, to unify language, tooling and hiring across the platform. The hard part is not the choice but the method: the commerce core is the money system of record and cannot be rewritten big-bang.

Decision

Adopt TypeScript as the target for commerce-api's core and migrate incrementally, with Elixir and TypeScript coexisting on the same database. Several coexistence rules are already settled and built, even though the overall cutover is not:

  • Elixir keeps owning the schema. The TypeScript code reads the shared Postgres by introspection only (Drizzle, never DDL) — Elixir/Ecto remains the single migrator (ADR 0016), so there is never dual schema authority. Drizzle was chosen over Prisma for that introspect-only fit.
  • Jobs stay in Elixir Oban. Background work continues to run in commerce-worker's Oban; the TS side enqueues over HTTP (ADR 0014) rather than standing up a parallel job runtime.
  • Contract-first parity. A parity oracle compares TS vs Elixir responses across the API surfaces, with an explicit PRESERVE / FIX / ROTATE divergence ledger — bug-for-bug where a client depends on current behaviour, corrected where safe.

The intended path is strangler-fig (route a domain to TS behind a gateway, then delete the Elixir for it). The current in-tree vehicle, commerce-api-ts, is instead a parallel rewrite to parity (its route-coverage ratchet reached ~94% — roughly 10 of 175 routes uncovered — but stopped short of full money-path parity and has been idle since early September), which the strategy doc itself flags as the riskier approach.

Consequences

  • Positive: a concrete route to one language across the platform; introspect-only + Ecto-owns-DDL avoids dual schema authority; the parity oracle makes any cutover measurable rather than a leap.
  • Neutral / to revisit: two codebases run against one database during the migration; parity work is expensive and has stalled short of full parity, and the strangler API gateway the preferred method needs does not exist (the gateway repo is the Caddy edge, not that gateway) — so the effective method today is the parallel-parity one the strategy warned against.
  • Status is Proposed, not Accepted: the direction is adopted, but Elixir on version/4.1-beta remains the deployed system of record, no domain has cut over, and the method is unsettled.

Open questions

  • Which method actually ships — strangler-fig (needs the gateway built) or the parallel-parity rewrite?
  • Per-domain cutover order, and the criteria for promoting a TS domain to the deployed truth.
  • Whether the parity target stays bug-for-bug or rotates to corrected behaviour at cutover.

Alternatives considered

  • Stay on Elixir indefinitely: rejected by the strategy — it keeps the core on a different stack from the payments/edge/SDK services and the team's TypeScript focus.
  • Big-bang rewrite and cutover: rejected — far too risky for the money core (the same reasoning that drove the incremental payments cutover, ADR 0009).
  • Parallel rewrite to full parity, then swap: partially pursued (commerce-api-ts) but stalled; the strategy names strangler-fig as preferred precisely to avoid a long-lived parity race.
← 0039 Product options and custom data are schemaless JSON frozen onto order lines 0041 The official client is a hand-maintained TypeScript SDK with an enum-translation layer →