Skip to content

No first-party transaction / unit-of-work boundary #57

Description

@btravers

From a gap analysis against NestJS and AdonisJS.

There is no first-party answer to "these three repository calls commit or roll back together". examples/order-infrastructure hand-rolls it, and examples/order-amqp-worker's outbox relay already depends on the property without a framework primitive backing it — a placement and its outbox row must land in one transaction or the outbox pattern is decorative.

This is the highest-value gap because every non-trivial application needs it and retrofitting a transaction boundary after adapters exist is expensive.

The primitives are already here. A transaction is a unit-scoped resource, and the kernel already has all three parts:

  • a unit per request / delivery / activity attempt, with an ambient UnitRecord (packages/core/src/units.ts);
  • StartOptions.unit plus di's Module.forkScope, which build a per-unit fork of the graph;
  • resourceful providers (acquire/release), which di already guarantees are closed on every exit path including startup failure.

So the shape is likely a Transaction port provided by a resourceful, unit-scoped provider: acquire opens, release commits or rolls back on the unit's outcome. What does not exist is the package, the decision about who decides commit-vs-rollback (the unit's Result? an explicit call?), and how a repository adapter reaches the ambient transaction without becoming the hidden-dependency thesis #2 exists to prevent — note thesis #2 already lists "a database adapter" as a legitimate currentUnit() reader, which is the opening.

Adjacent and deliberately not folded in here: this repo has no first-party migrations opinion either. Prisma appears in an example; nothing states a stance.

Acceptance

  • A decision recorded in packages/core/CLAUDE.md or a new package's own: what owns the transaction boundary, and how an adapter reaches it.
  • Commit/rollback is driven by the unit's outcome rather than by every handler remembering, or the reason it cannot be is written down.
  • Nested/joined transactions have a stated answer, even if it is "not supported".
  • examples/order-infrastructure stops hand-rolling it, and order-amqp-worker's outbox write becomes genuinely atomic with the placement.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions