Repository navigation
refactor(driver-sql): move date bucketing out of sql-driver.ts into sql-driver-date-bucket.ts (#25521 cut 20) - #27127
Conversation
…ql-driver-date-bucket.ts buildDateBucketExpr's body (the per-dialect GROUP BY expression of a date dimension) moves unedited into a new seam module. The method stays on the class with its docblock and signature, and hands the body exactly the members it reads through DateBucketHost: the dialect flags and the dateGranularityCapabilities getter as getters, temporalFieldKind and sqliteTemporalArg as late-bound closures. Move-only: no behaviour change. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5pPv99puvEK3Nmm1Mc
📓 Docs Drift Check14 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 61799fb2b7cea33c7beed5d3916d4120f2c4dd2e && git checkout 61799fb2b7cea33c7beed5d3916d4120f2c4dd2e
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 07d18e69275ba54653e217cb2bd61cb2b4efb1ff 7c6f6f1fe88db4e0a60c78e4dc8e5b73ab6d22ac && git checkout -B drift-repro 07d18e69275ba54653e217cb2bd61cb2b4efb1ff && git merge --no-ff 7c6f6f1fe88db4e0a60c78e4dc8e5b73ab6d22ac
node scripts/docs-audit/affected-docs.mjs --json 07d18e69275ba54653e217cb2bd61cb2b4efb1ff |
席位核对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里「按日/周/月/季/年分组时,各数据库方言生成什么 SQL 分桶表达式」这一个机制(buildDateBucketExpr的方法体)原样搬进新建的sql-driver-date-bucket.ts;类上的方法、文档注释和签名全部保留,只把方法体转交过去,宿主接口DateBucketHost恰好列出方法体读的 6 个成员。src/sql-driver.ts(22,096 lines, +2,533 in 30 days) along itsSqlDriverseams, one move-only slice per PR #25521 拆分超大文件的第 20 刀;第 19 刀合入后重新测量,这是仍成「一个机制」、收益最大且不碰任何系列限制的区域,sql-driver.ts减少 39 行。sql-driver-date-bucket.ts(头注释、DateBucketHost、函数体);sql-driver.ts中buildDateBucketExpr的转交桩;sql-driver.ts新增的 3 行 import。pnpm --filter @objectstack/driver-sql exec vitest run src/sql-driver-date-bucket.test.ts src/sql-driver-21485-date-bucket-calendar-day.test.ts src/sql-driver-temporal-dialect.test.ts,预期全部通过。What changes
Cut 20 of the
sql-driver.tssplit (#24586 ruling A, recorded in6270097840). The claim is6456805734. Cut 19 (#26960) merged first, asd7f76b6d97; this cut opens with a fresh seam table taken onorigin/main6845035c6b(below) and takes the largest area that is one mechanism and moves cleanly: date bucketing,buildDateBucketExpr, from the class head. Its body moves unedited into a new seam module,packages/drivers/driver-sql/src/sql-driver-date-bucket.ts, through its hostDateBucketHost.buildDateBucketExpris the per-dialect SQL fragment a date dimension groups by atday,week,month,quarteroryear: PostgreSQLto_char, MySQLdate_format, SQLitestrftime, with the #21485 calendar-day arm for aField.dateand the #21595 Thursday-rule ISO week on SQLite. It returnsnullfor a granularity the dialect does not model. Its two callers stay in the class:aggregate(the GROUP BY) anddateBucketSql(the printed formservice-analyticsechoes).Why a new module. No existing seam module holds anything about aggregation or temporal read expressions; the nearest (
sql-driver-schema-sync.ts) is DDL. The new module importssql-driver.tsfor a type only (import type { SqlDriver }), so it adds no evaluation cycle, and the body reads no module-level declaration, so nothing gainsexportand nothing else moves.DateBucketHostlists exactly the six members the body reads, each typed as the class member (SqlDriver['NAME']): theisPostgres,isMysqlandisSqliteflags and the protecteddateGranularityCapabilitiesgetter (as getters), and the protectedtemporalFieldKindandsqliteTemporalArgmethods (as late-bound closures). All six are read by members that stay as well.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-p875md(the platform assigned it to this card thread, as the claim records). The move is one commit,7c6f6f1fe, on6845035c6b.Net line count (the seat's rule).
sql-driver.tsgoes from 18,406 lines to 18,367: -39../sql-driver-date-bucket.jsimport, placed after cut 16's seam import).sql-driver-date-bucket.tsis new: 119 lines (a 40-line header, the type import, the 15-line host and the function with its one-line docblock).Path readers. No test, script, doc, ADR or anchor entry reads the body 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 (41) and again before opening this one (26). None touchespackages/drivers/driver-sql/**.Seam table, fresh, on
6845035c6bsql-driver.tsis 18,406 lines. The 30-day column countsgit blamelines from commits on or after 2026-09-28T06:00Z.scripts/pm/git-history.mjs count --ref=origin/maincounts 77 first-parent commits on the file in 30 days and 31 in the last 7 (the split cuts are most of them). In all, 3,326 lines (18.1%) are from the last 30 days.SqlDriverspans lines 4247-18331 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. The file is byte-identical to refactor(driver-sql): move the declared column DEFAULT emission out of sql-driver.ts into sql-driver-schema-sync.ts (#25521 cut 19) #26960's head, so only the 30-day column moved from the pre-measure I took while refactor(driver-sql): move the declared column DEFAULT emission out of sql-driver.ts into sql-driver-schema-sync.ts (#25521 cut 19) #26960 was in the queue.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-19 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 per mechanism (cut 19's correction), as its stubs minus its bodies, then net of its import edits. The two leading rows were measured by running the move script on a scratch copy of the file; the rest are the stub estimate (a stub costs 4 lines, +2 per field read, +1 per method read).buildDateBucketExpr)updateWithPresumedTimestamp-31,resolveUpdatedAtColumnPresence-6)attemptWithoutPoisoning, which it calls, is shared with the autonumber module and stays.migrateMysqlDatetimeColumns-39,migrateMysqlTimeColumns-32logDurabilityFailure, whichcheck:durability-log-levelresolves within one file only (cut 12).presentReadValue-29)compileFilters(-75) is refused (inferred return type).Internal helperstenant members (auditMissingTenant-26,applyTenantScope-25)applyTenantScopeholds 2 of the file's 4 D8 predicates (series limit);auditMissingTenantis below -30 alone.buildWindowFunction) -28,orderKeysFor-27,ensureDatabaseExists-24,installQueryTiming-20explainUnkeyableTextColumn-83,explainHashShadowDuplicate-66,createHashShadowUniqueIndex-62, …),ensureSequencesTable-41ensureSequencesTablewritesthis.Why
Why this area, as one area
getBuilder, reads no static, has nothis.FIELD.delete/insert/update(site, uses nosuperor barethis, and assigns nothing tothis. The method is protected with an annotated return type ({ sql: string; bindings: any[] } | null).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 body and 0 in the module.tryand calls nologDurabilityFailure.check:durability-log-level --listprints 165 lines at both ends, identical except that tworunWideningAltersseams insql-driver.tsmove 39 lines up andlogDurabilityFailure's line 3 down (the import).TursoDriveroverrides neither the method nor a member it reads.SqliteWasmDriveroverrides theisSqlitegetter; the host readsisSqlitethrough a getter on the driver at call time, so the override is still the one the body reads, andsqlite-wasm-driver-date-bucket.test.ts(in the wasm suite above) pins the SQL that driver emits.turso-driver.tsnamesSqlDriver.buildDateBucketExprin prose, still true. The dogfooddate-bucket-parity-turso.test.tsredefinesbuildDateBucketExpron an instance andsql-driver-temporal-dialect.test.tscalls it through(d as any); both still reach the method, which is still on the class.sql-driver.ts#…pointer or asql-driver.ts::…key, and noscripts/adr-anchors/**record names it or date bucketing.The shape (#23050 shape B, as cuts 1-19 apply it)
buildDateBucketExpr.call(host, field, granularity, table), marked[#25521 cut 20]. The method is synchronous at both ends, so a failure is still a synchronous throw (probe reading 7).sql-driver.tsimport comment names cut 20 in 2 lines. Neither cites anADR-NNNNletter. The method's docblock (which cites ADR-0053) stays insql-driver.ts.Verification
ADR anchors
check:adr-anchorscounts 1900 decision-letter citations at base6845035c6b(a clean detached worktree) and 1900 at the move commit, over 91 anchored files at both ends; the two outputs are byte-identical. Both runs exit 0.check:adr-symbol-anchorsoutput is byte-identical at both ends.mainmoved.mainmoved 20 commits under the cut (to07d18e692, none inpackages/drivers/**). Its tip counts 1900 letters (a clean detached worktree), and so does a local, unpushed merge of the move commit with it; both exit 0. I did not mergemaininto this branch:git merge-tree --write-treeof the head with it exits 0, so the tree is not stale.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.tsinto this worktree and removes the new module, 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 fb5f2d3313… 0bb2ccab65…).dist/index.d.tsanddist/index.d.mtsis the same at base and at the move commit:0f33bf3febaac3c7aa96f68a4226d752766b8203ab7acade523b69f9aaa44d74(unchanged since cut 8). These two are the dist's only declaration files (no chunk), so the declaration-reorder bar of ruling6441032442does not come into play.protectedfrombuildDateBucketExprin a copy of the headindex.d.ts(1 hit, then 0). The copy hashes to1fda05673f202a32293501dc46b90afa75edd56c1dc632f66d4699af09ef3197, withcmpexit 1.requireof each dist gives 59 names (sha105757ce5aeat both ends); the ESM namespace ofindex.mjsgives the same 59 at both ends (sha105757ce5ae). Neither the moved function norDateBucketHostis among them..call(hostsites inindex.mjsgo from 69 at base to 70 at head (the stub).Move-only proof
The proof compares TypeScript compiler AST leaf tokens (JSDoc nodes skipped), base
6845035c6bagainst the move commit7c6f6f1fe. It is a scratch script and is not committed.thisbuildDateBucketExprasyncat either end.sql-driver.ts. 179 of 180 base statements are byte-identical; the other is the class. The head adds one statement, the new import (its 2-line comment is its leading trivia).PROOF OK, exit 0.scripts/ablation-replace.mjs, I changed'%x-W%v'to'%x-W%V'in the module's MySQLweekarm (anchor 1 hit, then 0, checked on disk). The proof then reportsbuildDateBucketExpras differing, printsPROOF FAILED, and exits 1. The restore was proved (blob0bb2ccab65equals 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, base6845035c6bagainst the move commit7c6f6f1fe. Every part's exit code was captured on its own (build, probe, three suites: all 0 at both legs).@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 on the move commit.pnpm exec eslint --no-inline-config packages/drivers/driver-sqlexits 0 (driver-sql has nolintscript of its own).buildDateBucketExpron SQLite, PostgreSQL, MySQL and MSSQL drivers (never connected), for all five granularities plushourand none, over a declareddate,datetime,timeandtextfield, an undeclared field, and no / an unknown table;sqliteTemporalArg's other branch);isSqlite/isPostgresgetters, and one overridingdateGranularityCapabilities(the overrides are the ones read);temporalFieldKind(PostgreSQL, MySQL) and ofsqliteTemporalArg(likewise);temporalFieldKindandsqliteTemporalArg(they are the ones the body reaches, with the arguments recorded);knexreplaced on the instance after construction, then restored;temporalFieldKind; an undefined capability row): synchronous throws;dateBucketSqlon three dialects, and real SQLiteaggregateGROUP BYs over adateand adatetimefield at all five granularities;NullCaps.buildDateBucketExprat base andObject.buildDateBucketExprat head. That is shape B's receiver, the same at every earlier cut, and no test or consumer reads it.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 48 cuts 17-19 derived). I ran each one on its own and captured its exit code before any pipe, after a fullpnpm buildof the move commit (116 of 116 tasks).7c6f6f1fe: 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.)check-adr-anchors,check-adr-symbol-anchors,check-tenant-audit-census,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 a clean detached worktree at base6845035c6band on the move commit,check-adr-anchors,check-adr-symbol-anchorsandcheck-tenant-audit-censusprint byte-identical output;check-object-def-param-keysdiffers only in its file count (3388 to 3389, the new module);check-durability-degradation-log-level --listprints the same 165 lines, line numbers aside.check:object-def-param-keyscompares 7 override parameter positions over 3389 files with every control firing;check:tenant-chokepointreads 24getBuilder()bindings across 3 files;check:durability-log-levelreads 89 read seams;check:dual-build-cjs-loadsloads 112 entry points across 69 packages;check:driver-conformancescores 11 of 11 dialect-scored cells).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-date-bucket.ts. The moved body holds 0 tenant predicates and builds no query (it returns{ sql, bindings }text). 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 name set is identical). The module has none.updateWithPresumedTimestampwithresolveUpdatedAtColumnPresence, -37 in bodies, about -34 with a new module's import). Everything else is -29 or less per mechanism, or pinned or blocked by a series limit (the MySQL widenings, the tenant members, the private-static readers).Human Minimum
packages/specchange.DateBucketHost, in a new module thatindex.tsdoes not re-export.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_01FEkQ5pPv99puvEK3Nmm1Mc