Repository navigation
refactor(driver-sql): move the deferred-DDL previews out of sql-driver.ts into sql-driver-schema-sync.ts (#25521 cut 18) - #26894
Conversation
…r.ts into sql-driver-schema-sync.ts previewDeferredSchemaWork, previewDatetimeConvergence and previewTimeConvergence move unedited into the existing schema-sync module behind SchemaSyncHost; the class keeps all three methods as delegating stubs (shape B). fieldHasColumn's last value reader moved, so sql-driver.ts imports it as a type for the staying docblock link. sql-driver.ts goes from 18,521 to 18,447 lines. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5kNG4UuwLvtVwPNQqK
📓 Docs Drift CheckThis PR changes 1 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
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 6e01b6f7e171c7a8010fba841801cbc11e244c2b && git checkout 6e01b6f7e171c7a8010fba841801cbc11e244c2b
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a51ba639c165861a8c4ac51015e554911960bf24 7065d8e1d038d1cfd2887b3b5b96984c9d1985d3 && git checkout -B drift-repro a51ba639c165861a8c4ac51015e554911960bf24 && git merge --no-ff 7065d8e1d038d1cfd2887b3b5b96984c9d1985d3
node scripts/docs-audit/affected-docs.mjs --json a51ba639c165861a8c4ac51015e554911960bf24
|
Seat CI note2026-10-27T20:20Z ·
The seat judges this a flake, and will re-run the failed jobs ONCE. A second failure is real.
Generated by Claude Code |
席位核对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
维护者速读
sql-driver.ts里「延迟建表 DDL」小节的三个预览方法体(os migrate plan用来列出待执行工作的previewDeferredSchemaWork,以及它调用的日期时间、时间两类存储收敛预估previewDatetimeConvergence、previewTimeConvergence)原样搬进已有的sql-driver-schema-sync.ts;类上三个方法全部保留,只把方法体转交过去,SchemaSyncHost按需增补 6 个成员。src/sql-driver.ts(22,096 lines, +2,533 in 30 days) along itsSqlDriverseams, one move-only slice per PR #25521 拆分超大文件的第 18 刀;第 17 刀合入后重新测量,这是仍成一个区域、收益最大且不碰任何系列限制的部分,sql-driver.ts减少 74 行。sql-driver-schema-sync.ts头注释新增的第 18 刀段落与SchemaSyncHost新增成员;sql-driver.ts中三个方法的转交桩;sql-driver.ts顶部fieldHasColumn改为类型导入的那一行。pnpm --filter @objectstack/driver-sql exec vitest run src/sql-driver-deferred-ddl.test.ts src/sql-driver-deferred-datetime-convergence.test.ts src/sql-driver-time-canonical-storage.test.ts src/sql-driver-6009-backfill-julian-day-guard.test.ts,预期全部通过。What changes
Cut 18 of the
sql-driver.tssplit (#24586 ruling A, recorded in6270097840). The claim is6449288193. Cut 17 (#26808) merged first, asf1cd529dd2; this cut opens with a fresh seam table taken on that commit (below) and takes the area cut 17 priced next, which still pays best without breaking a series limit: the deferred-DDL previews (Schema Sync › Deferred schema DDL (#3917)). Their bodies move unedited into the existing seam modulepackages/drivers/driver-sql/src/sql-driver-schema-sync.ts, through its hostSchemaSyncHost, beside the canonical-encoding convergence (cut 12) whose predicates andinformation_schemareads they call:previewDeferredSchemaWork, the read-only planos migrate planandos migrate applyrender: per deferred object,create_tableoradd_columns(only fields that materialize a column), theos migrate apply --allow-destructivecannot dropsys_account.issueron a 17.4.0-created SQLite database, whileos migrate account-issuerand the boot's schema-drift line keep prescribing it (17.7.0) #22506 rotation answer, and the two convergence entries below;previewDatetimeConvergenceandpreviewTimeConvergence, which it calls: the datetime and time storage-convergence work the SQLite backfills and the MySQL widenings would do, measured and not performed (os migrate planomits the datetime storage-convergence work, so the plan understates what apply will do #3954).No module-level declaration moves.
fieldHasColumn(from./schema-drift.js) loses its last value reader insql-driver.ts; the staying docblock ofpreviewDeferredSchemaWorkstill links it, so the import there becomestype fieldHasColumnin place (a link-only import is a type import, erased from the build, as cut 17 did forundeliveredStorageAttributes). The module importsfieldHasColumnandtype PendingSchemaWorkfrom./schema-drift.jsthrough its existing import line;SQLITE_NON_TEMPORAL_TEXT_REFSandSQLITE_TIME_EXPR_REFSit already imported from./sql-driver.js.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, no test edit and no new gate. Branchclaude/project-thread-m210nw(the platform assigned it to this card thread, as the claim records). The move is one commit,028cda6cf, onf1cd529dd2, followed by one merge oforigin/main(5787df4d4b, 15 commits, none inpackages/drivers/**); the head is7065d8e1d0.Net line count (the seat's rule).
sql-driver.tsgoes from 18,521 lines to 18,447: -74.sql-driver-schema-sync.jsimport, whose one line gains the three names. ThefieldHasColumnimport line changes in place.sql-driver-schema-sync.tsgoes from 1,914 to 2,066 lines (+152): the three functions, 6 newSchemaSyncHostmembers (74 to 80) and a header paragraph. No import line is added.Path readers. No test, script, doc, ADR or anchor entry reads any of the three bodies by path, so none is migrated (screen below).
Serial with the file's in-flight PRs. I read the file list of every open PR on
objectstack-ai/objectstackat the start (38) and again before opening this one (40). None touchessql-driver.ts,sql-driver-schema-sync.ts,turso-driver.ts,sqlite-wasm-driver.ts,check-tenant-chokepoint.mjsor a driver-sql anchor entry; #26808 (cut 17) was the one that did, and it merged before this cut was measured. #26556 (dependabot) touches onlydriver-sql/package.json.Seam table, fresh, on
f1cd529dd2sql-driver.tsis 18,521 lines. The 30-day column countsgit blamelines from commits on or after 2026-09-27T17:26:58Z, 30 days before the base commit.scripts/pm/git-history.mjs countproves 77 commits on the file in 30 days and 31 in the last 7. In all, 3,388 lines (18.3%) are from the last 30 days.SqlDriverspans lines 4243-18446 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 cuts 11-17 defined it: an annotated return type or not public; no
getBuilder,this.FIELD.delete/insert/update(, private-static,super, bare-thisorthis-write site; notinsertOnlyUpsertColumns; not a member pinned in place (createColumn,applyFilterCondition,formatInput/formatOutput,initObjects,ensureShardTable,computeTenantField,migrateMysqlDatetimeColumns/migrateMysqlTimeColumns,compileFilters). Each area is priced as its stubs minus its bodies (a stub costs 4 lines, +2 per field read, +1 per method read; one reading no member costs 2), then net of the module-level declarations only it reads and of its import edits.previewDeferredSchemaWork-32,previewDatetimeConvergence-22,previewTimeConvergence-22)fieldHasColumn,PendingSchemaWorkand the twoSQLITE_*counts are shared); +2 for the import commentpresentReadValue-29)logWithheldFilterDiagnostic-10,resolveWithheldFilterRefusal-7,withWithheldFilterLog-6), pays -23.compileFilters(-75) is refused (inferred return type).buildDateBucketExpr-42,installQueryTiming-20)nowColumnDefault-39 and four small members)Internal helperstenant members (auditMissingTenant-26,applyTenantScope-25,getBuilder-5,injectTenantOnInsert-2)applyTenantScopeholds 2 of the file's 4 D8 predicates andgetBuilderis the builder itself, whichcheck:tenant-chokepointreads only insql-driver.ts(series limit).updateWithPresumedTimestamp-31 and seven small members)Why
Why this area, as one area
Deferred schema DDL (#3917)sub-banner that compute the plan;previewDeferredSchemaWorkis the only caller of the other two in the class. They go beside the convergence code (cut 12) whose predicates (sqliteNonTemporalTextSql,sqliteCanonicalDatetimeSql,sqliteCanonicalTimeSql) andinformation_schemareads (legacyMysqlTimestampColumns,legacyMysqlTimeColumns) they reuse, so the plan and the work it predicts now live in one module, behind the host that already exists.setDeferredDdlandflushDeferredSchemaDdlassignthis.deferredDdl/this.flushingDeferredDdl(this-write);getSchemaSyncStatsreads two fields in one line (its stub would cost more than its body);deferredSchemaObjectCountis an accessor.getBuilder, reads a static, has athis.FIELD.delete/insert/update(site, usessuperor a barethis, or assigns tothis.previewDeferredSchemaWorkis public with an annotated return (a promise ofPendingSchemaWork[]); the other two are protected with the same annotated return.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.[](by contract: under-reporting costs an unlisted step, never a wrong one). Theirtryblocks call no member of the gate's durability vocabulary, andcheck:durability-log-level --listgives the same seams at both ends, line numbers aside (164 lines of output at each end). I screened this before choosing the area, by moving the three bodies on cut 17's head and diffing the census.TursoDriverandSqliteWasmDriverextendSqlDriverand override none of the three.TursoDriver's remote-face ledger listspreviewDeferredSchemaWorkasinherited, andturso-remote-inherited-members.test.tschecks thatSqlDriverdeclares it andTursoDriverdoes not redeclare it; both still hold (the method keeps its name and signature onSqlDriver). Every member a body calls is a late-bound closure on the instance (probe below).sql-driver.ts#…pointer or asql-driver.ts::…key, and noscripts/adr-anchors/**record names them. Tests and the CLI (packages/cli/src/utils/schema-migrate.ts) call them as driver methods, which they still are;turso-driver.tsandremote-canonical-backfill.tsname them in prose, which is still true.The shape (#23050 shape B, as cuts 1-17 apply it)
async. Its body is now one host literal plusNAME.call(host, ...), marked[#25521 cut 18]. Each method returns the moved async function's own promise, so the declared return type is unchanged and a failure is still a rejection, never a synchronous throw (probe reading 7).deferredSchemaObjects,knex,datetimeFields,timeFields, and the getterssupportsRotation,isSqlite,isMysql) go as GETTERS that read the driver at call time; the methods (previewDatetimeConvergence,previewTimeConvergence,rotationShardKey,legacyMysqlTimestampColumns,legacyMysqlTimeColumns,sqliteCanonicalDatetimeSql,sqliteCanonicalTimeSql,sqliteNonTemporalTextSql) 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; in particularpreviewDeferredSchemaWorkstill reaches the two convergence previews through the driver, not the module functions directly.SchemaSyncHostgrows by exactly the 6 members these bodies read that it did not list yet (deferredSchemaObjects,legacyMysqlTimeColumns,legacyMysqlTimestampColumns,previewDatetimeConvergence,previewTimeConvergence,supportsRotation), each typed as the class member's indexed access, in the interface's sorted order (74 to 80 members). There is no cast, and no member becomes public.[#25521 cut 18]paragraph saying what moved, what stayed and where each read declaration comes from. Thesql-driver.tsimport comment names cut 18 in 2 lines. Neither cites anADR-NNNNletter. The movedpreviewDeferredSchemaWorkbody keeps its[#22506]comment, which mentionsADR-0057 P2(not a decision letter; the count below is unchanged).Verification
ADR anchors
check:adr-anchorscounts 1898 decision-letter citations at basef1cd529dd2(a clean detached worktree) and 1898 at the move commit (a clean detached worktree), over 91 anchored files at both ends; the two outputs are byte-identical. Both runs exit 0.ADR-…mention in them stays insql-driver.ts.check:adr-symbol-anchors: 2687 resolve at both ends (output byte-identical).mainmoved under the cut, so I measured its tip5787df4d4bagainst the merge head7065d8e1d0: 1898 letters at both (91 anchored files, exit 0), and 2687 symbol anchors resolve at both.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.tsand basesql-driver-schema-sync.tsinto this worktree under an EXIT/INT/TERM trap that restores HEAD. The restore is proved by the HEAD blob hashes, an emptygit diff HEADand 0 status entries (RESTORE-OK d2ff2d1b98… d29e275e18…).dist/index.d.tsanddist/index.d.mtsis the same at base and at head,0f33bf3febaac3c7aa96f68a4226d752766b8203ab7acade523b69f9aaa44d74(unchanged since cut 8), on the move commit and again on the merge head. These two are the dist's only declaration files (no chunk), so nothing was re-ordered and the declaration-reorder bar of ruling6441032442does not come into play.protectedfrompreviewDatetimeConvergencein the headindex.d.ts(1 hit, then 0). The result hashes tofd2f04595d02e10a86dd26debff0b40b4f8e3428699c28383e74d5a64193c0ad, withcmpexit 1.requireof each dist gives 59 names (sha105757ce5aeat both ends); the ESM namespace of the base and headindex.mjsgives the same 59 names (sha105757ce5aeboth). None of the three moved functions orSchemaSyncHostis among them (fieldHasColumnis, at both ends:index.tsre-exports it fromschema-drift.ts, which this cut does not touch)..call(hostsites inindex.mjsgo from 64 at base to 67 at head (the three stubs).Move-only proof
The proof compares TypeScript compiler AST leaf tokens (JSDoc nodes skipped), base
f1cd529dd2against the move commit028cda6cf. It is a scratch script and is not committed.thispreviewDeferredSchemaWorkpreviewDatetimeConvergencepreviewTimeConvergenceasyncat both ends.async.sql-driver.ts. 177 of 180 base statements are byte-identical; the other three are the class and the two edited imports (fieldHasColumngainstype; the schema-sync import gains three names and its 2-line comment)../schema-drift.jsimport (fieldHasColumnandtype PendingSchemaWorkadded) andSchemaSyncHost(6 members added). The head adds the three functions.PROOF OK, exit 0; on the merge head its output is byte-identical.scripts/ablation-replace.mjs, I changedkind: 'widen_time_columns'tokind: 'widen_time_columnz'in the module'spreviewTimeConvergence(anchor 1 hit, then 0, checked on disk). The proof then reportspreviewTimeConvergenceas differing, printsPROOF FAILED, and exits 1. The restore was proved (blobd29e275e18equals HEAD, emptygit diff HEAD).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, basef1cd529dd2against the move commit028cda6cf, and then again on the merge head7065d8e1d0. Every part's exit code was captured on its own (build, probe, three suites: all 0 at every leg).@objectstack/driver-sql@objectstack/driver-turso, against that end's built driver-sql dist@objectstack/driver-sqlite-wasmOn the merge head (it brings in
mainup to5787df4d4b: 15 commits, none inpackages/drivers/**), the three suites give the same totals and the same per-test statuses as base, 0 / 0 / 0 in each.tsc --noEmit -p .in driver-sql exits 0 on the move commit and on the merge head.pnpm exec eslint --no-inline-config packages/drivers/driver-sqlexits 0.code,status, own keys and top stack frame, plus the tablessqlite_masterholds, the stored cells and the log lines:previewDeferredSchemaWorkon SQLite: a table not created yet; legacy datetime and time rows (epoch numbers, space-separated and offset strings, a julian-day real, junk text,null) beside an added column and two formula fields; a second preview (idempotent); the flush (the plan it returns, the rewritten cells) and a re-plan after it; a canonical table with two new ones (the sort);os migrate apply --allow-destructivecannot dropsys_account.issueron a 17.4.0-created SQLite database, whileos migrate account-issuerand the boot's schema-drift line keep prescribing it (17.7.0) #22506 rotation answer: no shard yet, the flush, current shard plus read view present, a view without a shard, and a rotation policy without a unit;previewDatetimeConvergenceandpreviewTimeConvergencecalled directly with every column subset, an unknown table, a declared table that does not exist, and bad arguments;[]);legacyMysqlTimestampColumns/legacyMysqlTimeColumnsstubbed on the instance and a fakeknexanswering the count in both result shapes, with none, and throwing;rotationShardKey, the three SQLite predicates and thesupportsRotationgetter; then instance spies on two of them; thendatetimeFieldsanddeferredSchemaObjectsreplaced after construction,knexbehind a getter,isSqliteforced false, and anulldeferral map (the new ones are the ones reached, in the same order);nulldeferral map throws inside the moved body:Sub.previewDeferredSchemaWork/_SqlDriver.previewDeferredSchemaWorkat base andObject.previewDeferredSchemaWorkat head. That is shape B's receiver, the same at every earlier cut, and no test or consumer reads it. One reading depends on the calendar day (the shard name of the flushed rotation object); every leg ran on 2026-10-27. On the merge head the probe output is byte-identical to the move commit's.test:repo. Not run locally; it is CI's run on this PR.Gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderives 48 families for this diff, the same list on the move commit and on the merge head (and the same 48 cut 17 derived). I ran each one on its own and captured its exit code before any pipe, after a fullpnpm buildof that tree (116 of 116 tasks).028cda6cf: 48 run, all 48 exit 0.--ranreconciles:48 derived famil(ies) accounted for — 48 run, 0 NOT-MEASURED, exit 0. (I fetched the pinned fixture commit ofcheck-plugin-teardown-shape.mjs --self-testinto this shallow clone first, as cut 17 learned to.)7065d8e1d0: 48 run, all 48 exit 0.--ranreconciles the same way, exit 0.check-adr-anchors,check-adr-symbol-anchors,check-tenant-audit-census(244 write call sites),check-platform-checklist --changed-from origin/main,check-commit-card-trailers --range origin/main..HEAD,eslint --no-inline-config packages/drivers/driver-sql,check-durability-degradation-log-level --list. In clean detached worktrees at basef1cd529dd2and at the move commit,check-adr-anchors,check-adr-symbol-anchors,check-tenant-audit-censusandcheck-object-def-param-keysprint byte-identical output, andcheck-durability-degradation-log-level --listprints the same seams, line numbers aside.check:object-def-param-keyscompares 7 override parameter positions over 3370 files with every control firing;check:tenant-chokepointreads 24getBuilder()bindings across 3 files;check:durability-log-levelreads 88 read seams;check:dual-build-cjs-loadsloads 112 entry points across 69 packages).The 48 derived commands:
node scripts/check-closing-keyword-parity.mjs,node scripts/check-closing-keyword-parity.mjs --self-test,node scripts/check-comment-mask-adoption.mjs,node scripts/check-comment-mask-adoption.mjs --self-test,node scripts/check-comment-mask-corpus.mjs,node scripts/check-dts-emitted.mjs --self-test,node scripts/check-issue-citations.mjs,node scripts/check-plugin-teardown-shape.mjs,node scripts/check-plugin-teardown-shape.mjs --self-test,node scripts/ci/assert-turbo-remote-cache.mjs --self-test,node scripts/ci/plan-test-core-shards.mjs --self-test,node scripts/ci/prune-turbo-cache.mjs --self-test,node scripts/docs-audit/check-affected-docs.mjs,node scripts/docs-audit/check-drift-comment.mjs,pnpm --filter @objectstack/spec run check:duration-unit-keys,pnpm check:ci-filter-parity,pnpm check:closed-ceremony-text,pnpm check:cross-package-test-inputs,pnpm check:dispatcher-error-vocabulary,pnpm check:doc-authoring,pnpm check:driver-conformance,pnpm check:driver-memory-census,pnpm check:dts-closure,pnpm check:dual-build-cjs-loads,pnpm check:durability-log-level,pnpm check:error-status-conformance,pnpm check:gitlink-declared,pnpm check:issue-citations,pnpm check:keyed-text-bounds,pnpm check:lean-entry-closure,pnpm check:logger-receiver-detach,pnpm check:nul-bytes,pnpm check:object-def-param-keys,pnpm check:org-identifier,pnpm check:page-declaration-shape,pnpm check:platform-object-tenancy-census,pnpm check:published-files,pnpm check:refd-timer-probe,pnpm check:registry-log-declared,pnpm check:rest-log-spy-declared,pnpm check:slot-lookup,pnpm check:sourcemap-no-sources-content,pnpm check:system-context-census,pnpm check:tenant-chokepoint,pnpm check:test-source-alias,pnpm check:tier-file-adoption,pnpm check:undeclared-dep-imports,pnpm check:watch-hint-literal.Acceptance notes
check:tenant-chokepoint'sSCAN_FILESdoes not listsql-driver-schema-sync.ts. The moved bodies hold 0 of the file's 4 tenant predicates and build no query throughgetBuilder(they readsqlite_master,columnInfoand row counts throughknex). Noted, not filed: wideningSCAN_FILESwould be a gate change.sql-driver.tshas the same 30{@link X}sites (21 names) that are neither a top-level binding nor aSqlDrivermember at base and at head (the set is identical); this cut adds none, becausefieldHasColumnstays linkable through its type import.Human Minimum
packages/specchange.SchemaSyncHostgains members only.eslint --no-inline-configpasses (repo-widepnpm lintis CI's run).main, one pull request.skip-changeset; both declaration lines above.packages/specnot touched.Generated by Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_01FEkQ5kNG4UuwLvtVwPNQqK