Summary
Tango's core transaction boundary is now solid: transaction.atomic(async (tx) => ...) supports nested savepoints and post-commit work.
The next ergonomics gap is that adapters still hand requests directly to viewsets/APIViews without any first-class request-scoped transaction wrapper. Applications have to decide and wire this policy themselves even when they want a uniform request-level unit of work.
Current codebase context
packages/orm/src/transaction/atomic.ts exposes the application-facing atomic API against the default runtime.
packages/orm/src/transaction/internal/context/AsyncLocalTransactionEngine.ts already owns request-local transaction state once a transaction exists.
packages/adapters/express/src/adapter/ExpressAdapter.ts, packages/adapters/next/src/adapter/NextAdapter.ts, and packages/adapters/nuxt/src/adapter/NuxtAdapter.ts adapt handlers directly and do not offer a transaction wrapper option.
- Runtime-bound managers already enroll in an active
transaction.atomic(...) boundary when one exists.
Problem
A large class of applications wants one of these policies:
- wrap mutating requests in a transaction automatically
- wrap all resource dispatches in one transaction boundary
- opt in per route/viewset/action to request-level atomicity
Right now Tango has the low-level primitive but not the adapter-level ergonomic path.
Proposed implementation plan
- Define the adapter-facing policy surface.
- Decide whether the feature is global, per-adapter, per-route, or per-viewset/action.
- Preserve an opt-in path so frameworks with their own transaction policies are not forced into one mode.
- Implement adapter-level wrappers.
- Extend the three host adapters with a shared option or helper that runs the adapted handler inside
transaction.atomic(...).
- Keep host-specific error propagation and response handling intact.
- Clarify scope and lifecycle semantics.
- Decide how post-commit callbacks, streaming responses, and non-mutating endpoints should behave.
- Ensure nested atomic usage inside handlers remains valid.
- Add tests and docs.
- Cover success, rollback, nested usage, and adapter parity across Express, Next, and Nuxt.
Acceptance criteria
- Tango adapters expose a documented, opt-in way to run request handling inside
transaction.atomic(...).
- The contract works consistently across Express, Next, and Nuxt.
- Rollback and post-commit behavior are covered by tests.
- Nested transactional work inside handlers remains correct.
- Docs explain when to use request-scoped wrapping versus explicit local
atomic(...) blocks.
Summary
Tango's core transaction boundary is now solid:
transaction.atomic(async (tx) => ...)supports nested savepoints and post-commit work.The next ergonomics gap is that adapters still hand requests directly to viewsets/APIViews without any first-class request-scoped transaction wrapper. Applications have to decide and wire this policy themselves even when they want a uniform request-level unit of work.
Current codebase context
packages/orm/src/transaction/atomic.tsexposes the application-facing atomic API against the default runtime.packages/orm/src/transaction/internal/context/AsyncLocalTransactionEngine.tsalready owns request-local transaction state once a transaction exists.packages/adapters/express/src/adapter/ExpressAdapter.ts,packages/adapters/next/src/adapter/NextAdapter.ts, andpackages/adapters/nuxt/src/adapter/NuxtAdapter.tsadapt handlers directly and do not offer a transaction wrapper option.transaction.atomic(...)boundary when one exists.Problem
A large class of applications wants one of these policies:
Right now Tango has the low-level primitive but not the adapter-level ergonomic path.
Proposed implementation plan
transaction.atomic(...).Acceptance criteria
transaction.atomic(...).atomic(...)blocks.