Repository navigation
refactor(driver-sql): move the canonical-encoding convergence out of sql-driver.ts into sql-driver-schema-sync.ts (#25521 cut 12) - #26357
Conversation
…sql-driver.ts into sql-driver-schema-sync.ts Cut 12 of the sql-driver.ts split. The bodies of eight SqlDriver methods move unedited into the existing schema-sync seam module through SchemaSyncHost: the SQLite canonical backfills (backfillCanonicalDatetimes, backfillCanonicalTimes, backfillCanonicalJsonEncoding, countNonTemporalTextRows, convergeJsonColumn) and, of the MySQL widening, the two column reads and the ALTER runner (legacyMysqlTimestampColumns, legacyMysqlTimeColumns, runWideningAlters). migrateMysqlDatetimeColumns and migrateMysqlTimeColumns stay whole: their catches report through logDurabilityFailure, which the durability log-level gate resolves only as a same-file helper. The four module-level declarations only the moved bodies read move with them and stay module-local; the five they share with members that stay gain `export` in place and are imported back. Move-only: no behaviour change, the published .d.ts does not move. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011aqraB3u125sQzkMQNboVa
📓 Docs Drift CheckThis PR changes 1 package(s): 13 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 6 release-owned page(s) also name something this change touched. These are read-only:
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 624542f8455b05f843355e9c7128e1dfe2239d13 && git checkout 624542f8455b05f843355e9c7128e1dfe2239d13
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 66d5cd6897fee4a5c8a8eaf468f82e0c91ed60a9 274ac85416d191eec6c844531e34dd9609fb5e5a && git checkout -B drift-repro 66d5cd6897fee4a5c8a8eaf468f82e0c91ed60a9 && git merge --no-ff 274ac85416d191eec6c844531e34dd9609fb5e5a
node scripts/docs-audit/affected-docs.mjs --json 66d5cd6897fee4a5c8a8eaf468f82e0c91ed60a9
|
席位核对ACCEPT · The maintainer's standing authorisation for landing reviewed PRs, verbatim: 「按照 pm-dispatch 规则复核过的都授权」.
Generated by Claude Code |
Requested by Leehom · project thread
Part of #25521
Clause-②: no
Metadata-protocol: none
Ruling-ref: 6270097840
Ruling-ref: 6348891160
Cut 12 of the
sql-driver.tssplit (#24586 ruling A, recorded in6270097840). The claim is6427355170. The cut opens with a fresh seam table (below), taken onorigin/main83e9d7187(cut 11's merge), and takes the area that pays best without breaking a series limit. The bodies of eightSqlDrivermethods move unedited into the existing seam modulepackages/drivers/driver-sql/src/sql-driver-schema-sync.ts, through its existing hostSchemaSyncHost. Together they are the canonical-encoding convergence that schema sync runs at init:backfillCanonicalDatetimes,backfillCanonicalTimesandbackfillCanonicalJsonEncoding, withcountNonTemporalTextRowsandconvergeJsonColumn, which they call;information_schemareadslegacyMysqlTimestampColumnsandlegacyMysqlTimeColumns, andrunWideningAlters, which runs the ALTERs under the [Decision] Should the MySQL deferred-DDL flush bound itslock_wait_timeoutso a blockedos migratefails loudly instead of hanging (default: one year)? #9354 lock-wait bound.The change is move-only, and the card stays open for later cuts. There is no
docs/adr/**edit, nopackages/spec/**edit, no anchor entry change, no gate edit and no new gate. Branchclaude/project-thread-c22v4e(the platform assigned it to this card thread, as the claim records). The move is one commit,ff5b18102, on83e9d7187. Thenorigin/mainwas merged once before opening (274ac8541), which brought nodriver-sqlfile.Net line count (the seat's rule).
sql-driver.tsgoes from 20,827 lines to 20,492: -335.SQLITE_JSON_BACKFILL_PAGE_SIZE(6),DEFERRED_DDL_LOCK_WAIT_TIMEOUT_SECONDS(52),isMysqlLockWaitTimeout(27) anddeferredDdlLockWaitError(54). Finally, the import line ofrecoverUnencodedJsonText, whose last reader moved; two{@link}mentions of it remain in docblocks.Serial with the file's in-flight PRs. I read the full file list of every open PR twice: at start, on cut 11's head (29 PRs), and again after cut 11 merged, before the first edit (28 PRs). No open PR touches
sql-driver.ts,sql-driver-schema-sync.ts,turso-driver.ts,check-tenant-chokepoint.mjs,check-durability-degradation-log-level.mjsor the driver-sql anchor entries. #26138 touchesdriver-sql/src/builtin-column-collision.tsonly. #26267 (thevitest.config.tsof driver-turso and driver-sqlite-wasm) merged in between, and the merge commit carries it. #25640 is the release PR (changelogs andpackage.jsonversions).Seam table, fresh, on
83e9d7187sql-driver.tsis 20,827 lines. The 30-day column countsgit blamelines from commits on or after 2026-09-26T22:04:20Z, 30 days before the base commit. History reaches 2026-09-12.scripts/pm/git-history.mjs countproves 72 commits on the file in 30 days and 30 in the last 7. In all, 3,527 lines (16.9%) are from the last 30 days.SqlDriverspans lines 5594-20752 with 350 members: 243 instance methods, 13 accessors and 32 statics, all 32 private or protected.!andsatisfiesbefore it readsthis. Sections are the class's own banner comments.name…assertDialectEmitsconnect…disconnectfind…deletebulkCreate…countrawStatementFault…noteRuntimeIndexDdlbeginTransaction…rollbackTransactionaggregate…unresolvableAggregateColumnRefusaldistinct…unresolvableDistinctColumnRefusalfindWithWindowFunctions…findWithWindowFunctionsexplain…analyzeQuerysyncSchema…reclaimSpacerotationStateByTable…migrateMysqlTimeColumnssetDeferredDdl…flushDeferredSchemaDdldialectName…createNullSafeUniqueIndexlistPhysicalColumns…introspectSchemagetKnex…auditMissingTenanttableNameForBuilder…applyFalseConstantmapSortField…mapAggregateFuncbuildWindowFunction…buildWindowFunctionnowColumnDefault…applyDeclaredColumnDefaultsqliteFilename…isJsonFieldformatInput…formatOutputintrospectColumnOrder…introspectUniqueConstraintsPricing the areas
Eligibility is as cut 11 defined it: an annotated return type or not public; no
getBuilder,this.FIELD.delete/insert/update(, private-static,super, bare-thisorthis-write site; notinsertOnlyUpsertColumns. 158 members are eligible on this base. Each is priced as its stub minus its body, and the sum per section counts every eligible body that has a negative price and nosql-driver.ts#…pointer: Filter helpers -550 (22 bodies), the Rotation sub-banner -388 (15), CRUD -157 (12), every other section -93 or less. This cut then priced what each area nets once the module-level declarations it shares with members that stay are kept in place (⛔ no second copy of a helper). The declarations that only the moved bodies read move with them.migrateMysqlDatetimeColumns,migrateMysqlTimeColumns(same block)isDeferredDdlLockWaitRefusal)$nowcomparison cluster (applyCrossFieldComparison,applyNowAnchorComparison,crossFieldOffsetExpr,nowAnchorBaseExpr)uncompilableFieldReferenceError,uncompilableNowAnchorError) are seam callers thatsql-driver-compile-refusal-seam.test.tsenumerates by parsingsql-driver.tsalone and pins equal to its DOORS. A new module would be needed, and #26261 added$nowthere on 2026-10-26.rotationShardKey,ensureRotation,warnBuiltinColumnCollisions,rotationColumnList)ensureShardTable(-64) is named by path incheck-object-def-param-keys.mjs.upsert.autoNumberValueExistsandscanMaxNumericTailhold 2 of the 4 D8 tenant predicates.Why this area
sql-driver-schema-sync.tsalready carries schema sync's drift pipeline (cuts 6, 8-10). The canonical backfills and the MySQL widening are the rest of whatinitObjectsruns against a physical table.scripts/adr-anchors/has an entry forsql-driver.tsand forsql-driver-schema-sync.ts, so ADR citations inside the moved bodies stay on the anchored surface, and the decision-letter count does not move (below).migrateMysqlDatetimeColumnsandmigrateMysqlTimeColumnsstay whole in the class. They were part of the first plan. With them moved,check:durability-log-levelwent red (exit 1). Each of them catches aroundrunWideningAlters(a vocabulary callee) and reports throughthis.logDurabilityFailure(…), and the gate resolves that helper only when it is declared in the same file as the catch. In the module the helper is not declared, so the gate read both catches as "swallows the failure with no log at all". Keeping the two catches beside the helper keeps the gate's reading exact: 48 seams at base and 48 at head, all loud. ⛔ No gate edit and no second copy of the helper.withPinnedSession(+4 if moved) and thesqlite*Sqlexpression builders stay. The moved bodies reach them through the host.AUDIT_TIMESTAMP_COLUMNS,SQLITE_NON_TEMPORAL_TEXT_REFS,SQLITE_TIME_EXPR_REFS,isMultiValuedColumnandLOCK_WAIT_REFUSALare each also read by members that stay.LOCK_WAIT_REFUSALis the marker the moveddeferredDdlLockWaitErrorsets andisDeferredDdlLockWaitRefusalreads in the two migrate catches. Each gains a leadingexportand nothing else, and the module imports them back. The module reads them only inside function bodies, so the import cycle is evaluation-safe.index.tsnames its exports one by one; the export lists below prove none of the five leaks.getBuilder, reads a static, has athis.FIELD.delete/insert/update(site, usessuperor a barethis, or writes tothis. All eight are protected or private with annotated returns.check:tenant-chokepoint's D8 predicates, measured with its ownnullArmsOf: 4 at base and 4 at head, all insql-driver.ts(0 in the moved bodies).TursoDriverandSqliteWasmDriveroverride none of the eight. The stubs keep their signatures, and every member the bodies call is a late-bound closure on the instance.sql-driver.ts#…pointer or asql-driver.ts::…key. The five cligenerate-*.pin.test.tsfiles readsql-driver.tstext: thecreateColumnswitch,DEFAULT_STRING_VARCHAR_CHARS,MEDIA_ID_VARCHAR_CHARS,fieldHasColumn, andif (isMultiValuedColumn(insidecreateColumn, all unchanged. Four of those files ran green at head (62 tests); the fifth sits in the cli integration tier, which this run did not include and CI runs.logger-receiver-detach.test.tsscanssql-driver.ts; the repo-wide gatecheck:logger-receiver-detachwalks every package (0 detaches at both ends).The shape (#23050 shape B, as cuts 1-11 apply it)
async. Its body is now one host literal plusNAME.call(host, ...), marked[#25521 cut 12], and returns the moved function's own promise.canonicalDatetimeFields,canonicalTimeFields,datetimeFields,flushingDeferredDdl,jsonFields,timeFields(new to the host), andisMysql,isSqlite,knex,logger(already there). Methods go as closures that call the method on the driver at call time. So a subclass override, an instance spy, or a field replaced after construction is still the one the body reaches (probe below).SchemaSyncHostgrew by exactly the members these bodies read that it did not list yet: the six fields above andconvergeJsonColumn,countNonTemporalTextRows,legacyMysqlTimeColumns,legacyMysqlTimestampColumns,runWideningAlters,sqliteCanonicalDatetimeSql,sqliteCanonicalTimeSql,sqliteNonTemporalTextSqlandwithPinnedSession. Each is typed as the class member's indexed access, inserted in the list's own order. There is no cast, and no member becomes public.StandardErrorCode(@objectstack/spec/api, for the moved refusal),recoverUnencodedJsonText(./unencoded-json-text.js) and the five shared declarations from./sql-driver.js. The formerimport typeline becomes one import withtypemarkers onIntrospectedColumnandSqlDriver.[#25521 cut 12]paragraph in the module header says what moved, what stayed and why. Thesql-driver.tsimport comment names cut 12 in 2 lines. The header cites noADR-NNNN Dn.information_schemaqueries. Those keep their bytes, indentation included, because they are the SQL statement's own text.ADR anchors
check:adr-anchorscounts 1879 decision-letter citations at base83e9d7187(a clean detached worktree) and 1879 at headff5b18102, over 90 anchored files at both ends. Both runs exit 0.sql-driver.tsentry's ADR-0053 paragraph describesformatOutputandpresentReadValue; the schema-sync entry's ADR-0120 D4 paragraph describes the tightening ceremony. Neither describes a moved body. Noscripts/adr-anchors/**file changed.The published
.d.tsdoes not changeThis is measured under the series bar (ruling
6348891160, item 1).pnpm turbo run build --filter=@objectstack/driver-sql --forceat each end, against the same dependency tree. The base leg writes basesql-driver.tsandsql-driver-schema-sync.tsinto this worktree, under an EXIT/INT/TERM trap that restores HEAD. The restore is proved by the HEAD blob hashes (b38c9983,f191f7ca), an emptygit diff HEADand 0 status entries.dist/index.d.tsanddist/index.d.mtsis the same at base and at head,0f33bf3febaac3c7aa96f68a4226d752766b8203ab7acade523b69f9aaa44d74(unchanged since cut 8), andcmpfinds them identical.protectedfrombackfillCanonicalDatetimesin the headindex.d.ts. The result hashes tob6627836855a2c0adf2bbed61211d8d467f5ce088f81f5299336f847d45d397b, with a 1-line diff andcmpexit 1.requireand ESMimportof the built dist give 59 and 59 names at both ends, the same set (sha105757ce5ae). None of the five newly exported declarations is among them..call(hostsites inindex.mjsgo from 35 at base to 43 at head.Move-only proof
The proof compares TypeScript compiler AST leaf tokens (JSDoc nodes skipped), base
83e9d7187against headff5b18102. It is a scratch script and is not committed.thisbackfillCanonicalDatetimescountNonTemporalTextRowsbackfillCanonicalJsonEncodingconvergeJsonColumnbackfillCanonicalTimeslegacyMysqlTimestampColumnsrunWideningAlterslegacyMysqlTimeColumnsasyncmatches, and there are no type parameters. Each body line is the base line less two leading spaces, or byte-identical where it starts inside a template literal (1 line inlegacyMysqlTimestampColumns, 2 inlegacyMysqlTimeColumns).async.sql-driver.ts. The five shared ones differ from base by a leadingexportand nothing else. Atsql-driver.ts's top level, 199 of 211 base statements are byte-identical. The rest are the two edited imports, the five exports, the four moved declarations and the class.PROOF OK, exit 0..limit(SQLITE_JSON_BACKFILL_PAGE_SIZE)to.limit(SQLITE_JSON_BACKFILL_PAGE_SIZE + 1)in a module copy (1 hit, checked on disk). The proof then reportsconvergeJsonColumnwith its body tokens and lines differing, printsPROOF FAILED, and exits 1.Tests
Each end ran under one
os-verify-lockhold, after building that end's driver-sql dist. I compared the vitest JSON per test name and status, base83e9d7187against headff5b18102. Every part's exit code was captured on its own (build, probe, three suites: all 0 at both ends).@objectstack/driver-sql@objectstack/driver-turso, against that end's built driver-sql dist@objectstack/driver-sqlite-wasmtsc --noEmit -p .in driver-sql exits 0, also after the merge.pnpm exec eslint --no-inline-config packages/drivers/driver-sqlexits 0.diffis empty). They cover:countNonTemporalTextRowsand the threesqlite*Sqlbuilders, withcanonicalDatetimeFieldsandcanonicalTimeFieldsreplaced after construction (the certified sets land in the replaced objects);countNonTemporalTextRows, reached with its arguments (the withheld-rows warning follows), anddatetimeFieldsemptied after construction (nothing scanned);convergeJsonColumn, then a subclass override that throws (warned, not thrown), andjsonFieldsemptied after construction;isMysqlis shadowed on the instance and whoseknex.raw,withPinnedSession,nowColumnDefault,runWideningAltersandlogDurabilityFailureare replaced on the instance:flushingDeferredDdlflipped on the instance, theDATABASE_ERROR/500 refusal.migrateMysqlTimeColumns(in the class) recognises it through the exportedLOCK_WAIT_REFUSALand rethrows it. Outside a flush the failure goes tologDurabilityFailure;test:repo. Therepoprojects of types, core, rest and objectql pass locally. Runtime's two files could not load (@objectstack/service-datasourcehas no dist in this container), so that one is NOT MEASURED locally. CI's Test Core runs all of them.Gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderives 53 commands for the 2 changed paths (merge base83e9d7187). Onff5b18102, 52 exit 0.check:dual-build-cjs-loadsexits 3,PREREQUISITE NOT MET(no full build here): NOT MEASURED locally, CI runs it. The teardown self-test's pinned commit was fetched first, and objectql was built forcheck:lean-entry-closure.dispatch-gates --ran: 53 derived families accounted for, 52 run, 1 NOT-MEASURED (derived from the recorded exit 3), exit 0. After the merge the derived list is still 53 commands.83e9d7187) and at head, all exit 0:check:adr-anchorscheck:adr-symbol-anchorscheck:tenant-chokepointcheck:durability-log-levelcheck:driver-conformancecheck:keyed-text-boundscheck:object-def-param-keyscheck:system-context-censuscheck-tenant-audit-censuscheck:logger-receiver-detachpackages/spec/src/migrations/registry.ts)check:nul-bytespasses. The pre-pushcheck:commit-card-trailerspassed on push.Acceptance notes
lock_wait_timeoutso a blockedos migratefails loudly instead of hanging (default: one year)? #9354 lock-wait family now spans two files. The bound, its recognizer and the refusal builder (deferredDdlLockWaitError) moved withrunWideningAlters. The markerLOCK_WAIT_REFUSALand its readerisDeferredDdlLockWaitRefusalstay insql-driver.tswith the two migrate catches.isDeferredDdlLockWaitRefusal's docblock links{@link deferredDdlLockWaitError}, which is now in the module. Left as is: the cut is move-only.check:durability-log-levelresolves loud helpers by file. The gate readsthis.logDurabilityFailure(…)as loud only when that helper is declared in the same file as the catch. Any later cut that moves a catch around a vocabulary callee out ofsql-driver.tsmeets the same red. Noted, not filed: the gate is right to refuse an unresolved callee, and changing its resolver would be a gate change.backfillCanonicalDatetimesandbackfillCanonicalTimeswithout a path.TursoDriver's docblocks (remote-canonical-backfill.ts,remote-codec-residue-backfill.ts,turso-driver.ts) nameSqlDriver.backfillCanonicalDatetimes,backfillCanonicalTimes,backfillCanonicalJsonEncodingandcountNonTemporalTextRows. Each is still a member ofSqlDriver. Nodocs/adr/**edit in this series.$nowcluster pays about -460 gross. It needssql-driver-compile-refusal-seam.test.ts's enumeration migrated to read the receiving module, and a new seam module. The CRUD upsert conflict-target path needs four shared declarations exported in place.Generated by Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_011aqraB3u125sQzkMQNboVa