feat(crud-typeorm): scoped transactions, row predicates, and scope-create fields - #15
Merged
Merged
Conversation
…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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Brings the TypeORM adapter to parity with the Drizzle one, so a scoped and authorized resource can be expressed on either.
transactionRunnerwith explicit access mode, isolation level and commit ownership, all validated fail-closed. Where the adapter opens its own transaction it drives aQueryRunner, because only a runner can issueSET TRANSACTION READ ONLYand that must be the first statement.rowPredicate, fail-closed, on every read/update/delete. It receives the alias as an argument rather than lettingplanToBracketsinfer it: two entities sharing a column name would otherwise produce valid SQL against the wrong table.scopeCreateFieldsonbindTypeOrmCrud. Note this does not relax the create mapper's types the way the Drizzle binder does — TypeORM create values are alreadyDeepPartial<Entity>, so the enforcement is the runtime assertion in CRUD's service.40001/40P01→ retryable conflicts, matching Drizzle; they previously surfaced asunknownunder repeatable read.Tests
tests/typeorm-adapter.spec.tscovers 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:packclean.Known gap, worth a follow-up
InsertQueryBuilder.orIgnore()discards its argument on Postgres (InsertQueryBuilder.js:356) and emits a bareON CONFLICT DO NOTHING; a conflict target with an index predicate is reachable only viaorUpdate, which writes. A consumer needing a partial-index conflict target has to drop to raw SQL.