Skip to content

feat(crud-typeorm): scoped transactions, row predicates, and scope-create fields - #15

Merged
kauandotnet merged 1 commit into
mainfrom
feat/crud-typeorm-scoped-transactions
Aug 9, 2026
Merged

feat(crud-typeorm): scoped transactions, row predicates, and scope-create fields#15
kauandotnet merged 1 commit into
mainfrom
feat/crud-typeorm-scoped-transactions

Conversation

@kauandotnet

Copy link
Copy Markdown
Contributor

Brings the TypeORM adapter to parity with the Drizzle one, so a scoped and authorized resource can be expressed on either.

  • transactionRunner with explicit access mode, isolation level and commit ownership, all validated fail-closed. Where the adapter opens its own transaction it drives a QueryRunner, because only a runner can issue SET TRANSACTION READ ONLY and that must be the first statement.
  • rowPredicate, fail-closed, on every read/update/delete. It receives the alias as an argument rather than letting planToBrackets infer it: two entities sharing a column name would otherwise produce valid SQL against the wrong table.
  • scopeCreateFields on bindTypeOrmCrud. Note this does not relax the create mapper's types the way the Drizzle binder does — TypeORM create values are already DeepPartial<Entity>, so the enforcement is the runtime assertion in CRUD's service.
  • SQLSTATE 40001/40P01 → retryable conflicts, matching Drizzle; they previously surfaced as unknown under repeatable read.

Tests

tests/typeorm-adapter.spec.ts covers what the shared Postgres conformance suite structurally cannot — constructor validation, SQLSTATE classification without a live server, session ownership, and the fail-closed edges. The 3-adapter conformance suite gains a TypeORM secured adapter proving a row predicate genuinely hides rows in Postgres, and that a mutation against a hidden row reports not-found rather than mutating it.

221 unit tests, 50 Postgres conformance tests, verify:pack clean.

Known gap, worth a follow-up

InsertQueryBuilder.orIgnore() discards its argument on Postgres (InsertQueryBuilder.js:356) and emits a bare ON CONFLICT DO NOTHING; a conflict target with an index predicate is reachable only via orUpdate, which writes. A consumer needing a partial-index conflict target has to drop to raw SQL.

…eate fields

Brings the TypeORM adapter to parity with the Drizzle one, so a scoped and
authorized resource can be expressed on either.

Transaction runners now carry an explicit access mode, isolation level and
commit ownership, all validated fail-closed: a runner that weakens the
requested access mode, drops a required repeatable-read snapshot, or does
not own the real commit is refused rather than trusted. Where the adapter
opens its own transaction it drives a QueryRunner, because only a runner can
issue SET TRANSACTION READ ONLY and that has to be the first statement in
the transaction.

Row predicates are fail-closed and apply to every read, update and delete.
The predicate receives the query alias as an argument instead of letting
planToBrackets infer it from whichever builder it lands on: two entities
sharing a column name would otherwise produce valid SQL against the wrong
table, which returns plausible rows and no error. A predicate resolving to
anything but Brackets aborts the statement before it reaches the database.

scopeCreateFields routes scope-owned insert fields through
mappings.scopeCreate, so an insert-only column never has to be expressible
in mappings.persistence — the update path shares that mapper. Unlike the
Drizzle binder this does not relax the create mapper's types, because
TypeORM create values are already DeepPartial<Entity>; the enforcement is
the runtime assertion in CRUD's service.

SQLSTATE 40001 and 40P01 are now retryable conflicts, matching the Drizzle
adapter; under repeatable-read isolation they previously surfaced as unknown.

Adds tests/typeorm-adapter.spec.ts covering what the shared Postgres
conformance suite structurally cannot — constructor validation, SQLSTATE
classification without a live server, session ownership, and the fail-closed
edges — and extends the 3-adapter conformance suite with a TypeORM secured
adapter proving a row predicate genuinely hides rows in Postgres.
@kauandotnet
kauandotnet merged commit 9092c20 into main Aug 9, 2026
11 checks passed
@kauandotnet
kauandotnet deleted the feat/crud-typeorm-scoped-transactions branch August 9, 2026 04:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant