Repository navigation
refactor(driver-sql): move the transaction doors out of sql-driver.ts into sql-driver-transactions.ts (#25521 cut 3) - #25655
Conversation
… into sql-driver-transactions.ts (#25521 cut 3) Move-only: the bodies of beginTransaction, assertBareKnexSafe, commit, rollback, commitTransaction and rollbackTransaction move unedited into sql-driver-transactions.ts and reach the class through one named host, TransactionHost. The class keeps every method with its signature and docblock; releaseTransaction stays in the class whole. Claude-Session: https://claude.ai/code/session_01JVTd3PprStGa26aez5omLB Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check15 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 13 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin d9255a48a1dc69252764bcc7ee7fc2dece021e7e && git checkout d9255a48a1dc69252764bcc7ee7fc2dece021e7e
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 49653f91812815c72eb9194bf0a6cc451cf711f2 59d63a8d1bbd9fef22e594dc84f121eaaf25a4c2 && git checkout -B drift-repro 49653f91812815c72eb9194bf0a6cc451cf711f2 && git merge --no-ff 59d63a8d1bbd9fef22e594dc84f121eaaf25a4c2
node scripts/docs-audit/affected-docs.mjs --json 49653f91812815c72eb9194bf0a6cc451cf711f2 |
席位核对ACCEPT ·
One measured cost, and a rule for the next cuts: this cut moves 78 short lines, and the stubs with their host literals are longer than the bodies they replace. Landing: Generated by Claude Code |
Part of #25521
Clause-②: no
Metadata-protocol: none
Ruling-ref: 6270097840
Ruling-ref: 6348891160
Cut 3 of the
sql-driver.tssplit (#24586 ruling A, recorded in6270097840). The claim is6384369018. SixSqlDrivermethod bodies move unedited into a new module,packages/drivers/driver-sql/src/sql-driver-transactions.ts: the publicbeginTransaction,commitandrollback, the deprecated aliasescommitTransactionandrollbackTransaction, and the protected SQLite single-connection guardassertBareKnexSafe.releaseTransactionstays in the class whole.It is move-only, and the card stays open for the later cuts. Branch
claude/issue-25521-sql-driver-cut-3, cut fromorigin/mainat451bf61d0(cut 2 merged asb694e56e9before it).Serial with the file's in-flight PRs. At claim the PM read no open PR touching
sql-driver.ts,driver-turso/srcordriver-sqlite-wasm/src. Re-measured at my start (18 open PRs) and again just before this PR was opened (16 open; #25632 had merged and #25619 had left the list), each PR's file list read in full. No PR touchespackages/drivers/driver-sql/**,packages/drivers/driver-turso/**orpackages/drivers/driver-sqlite-wasm/**, except the Version Packages PR #25640, which touches only those packages'CHANGELOG.mdandpackage.json(its 183 files read page by page).origin/mainmoved one commit past the base meanwhile (#25632, now49653f918). It touches onlypackages/specand its tests, none of the three driver packages and no gate script this diff derives, somainis not merged in.Seam table (fresh, at
451bf61d0)sql-driver.tsis 21,651 lines. 30-day lines aregit blame --line-porcelainlines whose committer time is on or after 2026-09-24T12:16:02Z, 30 days before the base commit. The clone's history floor is 2026-09-15, andscripts/pm/git-history.mjs countproves 69 commits on the file in that window (20 in the last 7 days). In all, 3,882 lines (17.9%) are from the last 30 days. The classSqlDriverspans lines 5487-21576 with 347 members: 251 methods (49 public), 32 statics. No public method has an inferred return type; the one inferred public member is the gettersupports, in the class head. Sections are the class's own banner comments, cut by contiguous line range. "Public" counts public methods, plus public accessors where there are any:supports)runtimeCreatedIndexesfield)releaseTransaction)listPhysicalColumns,introspectSchema)UniqueConstraintMember,singleColumnUniqueColumnsThe limits per area are cut 1's (PR #25614), re-read on this tree:
getBuilderbuilders. Aggregation, Distinct, Window functions, Query plan analysis, CRUD and Bulk build throughgetBuilder, so a cut of any of them must first carry the new module intocheck:tenant-chokepoint'sSCAN_FILES, or the gate goes silently narrower. None of them is cut here.this.FIELD.delete/insert/update(. Transactions has one,releaseTransaction(this.openTransactions.delete(key)), and Schema sync has three. Those bodies stay. The six bodies moved here have none:beginTransactioncalls.add(onopenTransactions, and the other five read no field method at all.insertOnlyUpsertColumnsstays insql-driver.ts, because drivers(sql):upsert's empty-merge-set fallback is merge-ALL, which re-admits every insert-only column — currently unreachable, but it is the wrong shape for "nothing to merge" #8740's restart line names that symbol in that file. This cut does not touch it.Proposed cut 4: Schema introspection (15767-15902, 136 lines, 65 from the last 30 days): the public
listPhysicalColumnsandintrospectSchema, both with annotated return types. Neither builds throughgetBuilder(the word appears only in a docblock), neither reads a static, and neither has athis.FIELD.delete/insert/update(site. It is warmer than this cut, and its dev should price three things first: the two module-level helpers the bodies call (suggestFieldTypeForSqlType,postgresTableNameCollisionError), which may need cut 2's leading-exportshape;TursoDriver's overrides of both members (the'remote'and'refused'rows of its door table); and whethercheck:tenant-chokepoint's raw-fragment read covers theknex.raw(calls in those bodies, since a move out ofSCAN_FILESwould narrow it. Every cold area left after that builds throughgetBuilder.Why this area for cut 3
Cut 2 proposed it, and it re-measures clean: 78 lines, none from the last 30 days. All five public members (
beginTransaction,commit,rollback,commitTransaction,rollbackTransaction) have annotated return types. The section reads no static and nogetBuilder. Its onethis.FIELD.delete(site isreleaseTransaction, which stays. Between them, the six moved bodies readknex,openTransactions,activeTransactions,isSqlite,isProductionEnv,releaseTransaction,commitandrollback, and nothing else. They read no module-level declaration ofsql-driver.ts, so unlike cut 2 noexportwas added.Overrides and spies, measured before the move.
TursoDriveroverridesbeginTransaction,commitandrollback, and each override callssuperafter its remote-mode refusal. Its door table listscommitTransactionandrollbackTransactionas'inherited', because they callthis.commit/this.rollback, which refuse. So both aliases must keep reaching the subclass'scommit/rollback, and they do: they reach them through late-bound closures.SqliteWasmDriveroverrides the getterisSqlite, whichassertBareKnexSafereads. The host reads it through a getter that reads the driver, so the override is the one the guard sees.vi.spyOn(driver, 'beginTransaction')inservice-storageandplugin-authtests spies on the class method, which is still there.distchecked every moved member's late binding at base and at head: the counter and the open set afterbeginTransaction, an instance spy onisProductionEnv(reached byassertBareKnexSafe), onreleaseTransaction(reached bycommit), oncommitandrollback(reached by the aliases), a subclass override ofisSqlite, a subclasscommitthat refuses (reached bycommitTransaction), and a rejectingcommitthat still releases. Its 14 readings are identical at base and head. The probe is not committed.The shape (#23050 shape B, as cuts 1 and 2 apply it)
.d.ts. Each body is now one host literal plusfn.call(host, ...), marked[#25521 cut 3]. The fiveasyncmethods dropasyncand return the moved function's promise.assertBareKnexSafewas neverasync.TransactionHostis new. It types every member as the class member itself (an indexed access onSqlDriver). Each moved function types itsthisas thePickof the host over exactly the members its body reads. The generator measures those reads from the AST (this.NAMEproperty accesses) and asserts them, so the stringthis.knex.inside the guard's error text is not counted as a read. Each stub annotates its literal as theThisParameterTypeof the moved function. There is no cast, and no member becomes public.knex,openTransactions,activeTransactionsand the getterisSqlitego as getters, so each read happens when the body makes it.commit,rollback,releaseTransactionandisProductionEnvgo as late-bound closures.beginTransactionrunsthis.activeTransactions++, so its literal carries a setter beside the getter, which writes the field on the driver. The generator measures writes (++,--and assignment onthis.NAME) and asserts them, and the interface marksactiveTransactionsas the one non-readonlyfield. This is the accessor pairauth-manager.tsuses forjwtSigningFailureReported([maintainer] refactor(plugin-auth): splitsrc/auth-manager.ts(10,338 lines, +1,867 in 30 days) along itsAuthManagerseams, one move-only slice per PR #25224 cut 6). Both fields stay on the class:releaseTransaction,ensureSequencesTable's guard call and the tests read them.this.knex.transaction()keeps its receiver: the getter hands back the class's knex, and.transactionis called on it.+, so no line continues a template literal.The published
.d.tsdoes not changeUnder the series bar (ruling
6348891160, letter A), measured on base451bf61d0and on head59d63a8d1. Both were built against the same dependency tree:turbo run build --filter=@objectstack/driver-turso... --filter=@objectstack/driver-sqlite-wasm...at base, 9/9 tasks (driver-sql a cache miss). Thenpnpm --filter @objectstack/driver-sql buildat head.dist/index.d.tsanddist/index.d.mts(there is no.d.cts). The two are identical to each other at both ends.93da45168e82136b7613944525e47402d5dc6f9f8daa9efa4bdceeaa11b52a89(cuts 1 and 2's hash, unchanged). The raw diff is 0 lines.93da45168e82136b7613944525e47402d5dc6f9f8daa9efa4bdceeaa11b52a89. This file has 0 double-quoted unions, so that normaliser rewrites nothing.118e992ad8aad590044eb31509dca65e6b321e3410ce846e1e5f5dd34b956e95. With a raw diff of 0 lines, the proof rests on raw identity, and neither normaliser is relied on.dist/index.d.ts: line 4282,protected assertBareKnexSafe(op: string): void;, made public. That gives raw and double-quote-normalisedf492eb1c19c6832b1e13aee2f02596041b34c0252971720a0472a266c44eb7ccand single-quote-normalised8337de653b61ff24a59c29934ce1783051ba7c056523aa2e700107e9c69c6216, each different from the real hash.import()andrequireof the built bundles). The sorted list hashes tod61a9dd3a02661602b1bb2dbe18152ac43986148f36b2746f40c0884bd8ffdecon all four.dist/index.mjsanddist/index.jseach contain 14 host.call(sites (8 at base, plus these 6).No changeset:
skip-changeset, per ruling6348891160item 4.Proof of move-only
getChildrenleaves). Each moved function's parameters (less the addedthis), return type and body have the same token sequence as the base method, andasyncmatches on all six. 218 tokens compared, 0 differ.thisparameter, is byte-identical).assertBareKnexSafein a copy of the module makes the check report 1 token and 1 line different, and it exitsPROOF FAILED, exit 1.asyncon the five that had it.sql-driver.ts: all 201 base statements other than the class are byte-identical, in order. The one new statement is the import of the new module.sql-driver.tsgoes from 21,651 to 21,678 lines, andsql-driver-transactions.tshas 119. See the acceptance notes on why the file grows.Readers of
sql-driver.tsby path, checked against the moved textNone needed an edit. Each was read against the moved region (10899-10976) and run at both ends where it is a gate or test:
scripts/adr-anchors/packages__drivers__driver-sql__src__sql-driver.ts.jsoncarries ADR-0053 (formatOutput,presentReadValue) and ADR-0120 D4. Neither paragraph describes a moved body, so per card ask 3 the new module gets no entry of its own. The moved bodies cite no ADR.check:adr-anchors: 85 anchored files at base and head. Decision-letter citations on the anchored surface: 1,849 at base and 1,849 at head.packages/drivers/driver-sql/src/sql-driver.ts#beginTransaction. The stub keeps that declaration insql-driver.ts, so the anchor still resolves. No other anchor names a moved member.check:adr-symbol-anchors: 2,384 anchors resolve at both ends.check:tenant-chokepointreadssql-driver.tsforgetBuilderbindings and forIS NULLarms in raw fragments. The moved text has neither, so the gate still covers the same population: 24getBuilder()bindings across 3 files at both ends.check:logger-receiver-detach: 0 detaches at both ends (3,681 files walked at head, one more than base: the new module).logger-receiver-detach.test.ts③,sql-driver-compile-refusal-seam.test.ts, the fivepackages/cligenerate-*.pin.test.tsfiles,check-keyed-text-bounds(varcharColumnChars),check-object-def-param-keys(theinitObjectsparameter keys).turbo.json,scripts/cross-package-test-inputs.mjs,scripts/pm/check-half-states.mjs,scripts/check-changeset-no-major.mjs,packages/spec/liveness/*.jsonevidence, prose citations inobjectql,spec,driver-turso). None names a moved member.driver-tursoanddriver-sqlite-wasm: their readers of moved members reach the stubs unchanged. Both suites ran at both ends (below).check:driver-conformanceat base and head: 55 covered, 0 in the DEBT ledger, 0 exempt, both times.Verification
tsc --noEmit(driver-sql) exit 0.eslint --no-inline-configon both source files exit 0.packages/drivers/driver-sqlsuite: base 3,999 tests (3,787 passed, 0 failed, 212 skipped; 247 files), head the same. Compared per test name and status from vitest's JSON reporter: no test lost, none changed status, none added.packages/drivers/driver-tursosuite, run against the rebuilt headdist: base 2,526 tests (2,492 passed, 0 failed, 34 skipped; 95 files), head the same. Per test name and status there is no difference (2,513 distinct names; 13 names repeat).packages/drivers/driver-sqlite-wasmsuite, against the rebuilt headdist: base 704 tests (704 passed; 39 files), head the same, per test name no difference.dispatch-gates --commandson this diff, at head59d63a8d1: 55 derived commands, each run with its exit code.--ranreconciles 55 derived, 54 run, 1 NOT MEASURED, 0 unrun. The NOT MEASURED one ischeck:dual-build-cjs-loads(exit 3, PREREQUISITE NOT MET: it needs every package'sdist), which CI runs.check:lean-entry-closureran afterobjectql's closure was built.Acceptance notes
assertBareKnexSafenow builds its host literal before the production early return. Its docblock calls the guard zero-overhead on the hot path. Its one caller isensureSequencesTable's lazy fallback (a write that reaches the sequences table beforeinitObjectscreated it), and only when the runner is the bareknex, so this is not a hot path today. The docblock is unchanged, because this cut is move-only.TursoDriver's three overrides still callsuper, which now returns the moved function's promise instead of anasyncmethod's promise. A caller awaits the same settlement.beginTransactionatsql-driver.ts#beginTransaction. That declaration is still there, and the body is one module over. Nodocs/adr/**edit is made here.Generated by Claude Code