Skip to content

refactor(driver-sql): put SqlDriver into facade form with the sql-driver codemod (sql-driver.ts 18,334 -> 10,979, body-identical) - #30139

Merged
os-zhuang merged 4 commits into
mainfrom
claude/project-thread-y0okk4
Nov 5, 2026
Merged

os-zhuang merged 4 commits into
mainfrom
claude/project-thread-y0okk4

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Yibo Wang · project thread

Part of #30105

维护者速读

  • 改了什么:给 scripts/codemod-split-sql-driver.mjs(Codemod split pilot: generate the sql-driver.ts split with a deterministic script, verified byte-identical #29520 的试点脚本)加上 engine.ts / protocol.ts 已有的门面层,并重新生成 sql-driver.ts。SqlDriver 成为门面:158 个方法体移入 21 个无状态 facet 类(静态方法,this 是一个具名 host),类上各留一行 .call(xHost.of(this), …) 委派;私有方法的文档块随方法体移走(21 个),公开和受保护方法的文档块留在门面;状态全部留在门面。生成文件未手改。
  • 为什么改:[Decision] Large source files: replace the hand-cut split with a script-generated split per file, so the remaining monoliths split in days, not months #29465 大文件拆分,裁决 6632500982 把门面形态(6624258595)扩展到本文件;.d.ts 门槛按 6624345766 取「公开面相等」。
  • 风险与回滚:纯搬移。158/158 个方法体逐字节等于 base,撤销拆分逐字节重建 base 的 sql-driver.ts;两次 --write 输出树 sha256 相同。公开面 .d.ts 相等(对照抓到)。driver-sql 测试 base 与 head 都是 3,859 通过;turso、sqlite-wasm 两个子类驱动计数不变。回滚即 revert。
  • 先看:① 「Facade reading」一节的 28 条例外(其中哪些是 codemod 限制、需另立卡);② codemod 的 CLASS_HEAD_SECTION 和新加的 logDurabilityFailure pin;③ check-tenant-chokepoint.mjs 新登记的 4 个模块。
  • 两分钟验证:pnpm install --frozen-lockfile 后运行 node scripts/codemod-split-sql-driver.mjs --verify --base 21510bba,预期 158/158 moved bodies identical to base、sql-driver.ts 18334 -> 10979 lines,退出码 0;--self-test 67 例通过;node scripts/check-facade-delegation-only.mjs 退出码 0。
  • 需要你做的:改动超过 3,000 行(+4,990 / −3,121,含生成文件),需您批准后落地,并在您定的窗口冻结 sql-driver.ts;本 PR 保持草稿。

Clause-②: no
Metadata-protocol: none

What changes

  • scripts/codemod-split-sql-driver.mjs gains the facade layer scripts/codemod-split-engine.mjs carries (refactor(objectql): generate the engine.ts section split with a deterministic codemod #30012, refactor(objectql): one shared host per facet in the engine.ts codemod stubs (body-identical; engine.ts 14,140 -> 13,548) #30083):
    • Each section module's moved bodies become the static methods of one stateless facet class (sql-driver-crud.ts → CrudFacet), at class indentation, unedited. Their this is one named host interface typed AS the class members (SqlDriver['x']), so a private member is reached like a public one.
    • The stub on the class keeps its modifiers and signature byte-for-byte (only async is dropped) and makes one call: return CrudFacet.x.call(crudHost.of(this), …). Each host is built once per driver by a factory the class's static block installs into sql-driver-facet-hosts.ts (types-only module). Methods go as closures called on the driver at call time, so a subclass override or an instance spy is still the one reached; getBuilder goes as a bound getter (BOUND_HOST_METHODS) so check-tenant-chokepoint still counts the binding in the moved body; fields and accessors go as getters, assigned fields with a setter.
    • Private statics the moved bodies read go through SqlDriverStatics, which a facet module imports AS SqlDriver (private-static-through-host, 6624174693).
    • Docblock ownership (6624345766 item 1): a private method's docblock moves with its body; a public or protected one stays on the facade and the facet member carries a one-line placeholder pointing back.
    • The members above the first banner form one more section, the class head (CLASS_HEAD_SECTION → sql-driver-class-head.ts). Three sections whose hand-series seam exists already take a -section suffix; sql-driver-deferred-ddl.ts and sql-driver-transactions-section.ts are new.
    • No import prune: driver-sql compiles without noUnusedLocals, so an import only a moved body reads stays, as the pilot left them.
    • The pilot's --dts byte-equality reading is dropped: a facade cannot meet it by construction (the bundled .d.ts prints every private member). The bar is now the public-surface reading below, taken outside scripts/ (6441032442 item 3).
    • parameter-shadows now fires only for a parameter named like the stub's facet class or host variable (it freed 4 methods).
    • One new PATH_READER_PINS row, logDurabilityFailure (see readers below).
    • --self-test: 67 cases in 5 pinned batteries (class-head section, the bound getter and two banner refusals added; the import-prune battery removed).
  • The generated split is --write on the unsplit sql-driver.ts of 21510bba (the parent of the pilot's landing), unedited. 158/254 methods move into 21 facet modules; sql-driver.ts goes 18,334 → 10,979 lines (12,460 on main). The leaf sql-driver-top-level.ts is byte-identical to main. index.ts re-exports none of the new modules.
  • scripts/facade-delegation-registry.json: the sql-driver.ts / SqlDriver facade row (card Codemod split: put driver-sql sql-driver.ts into facade form with a deterministic script, like protocol.ts and engine.ts #30105) and 28 exception rows, all carded refactor: move shared state out of the protocol.ts and engine.ts facades into state-owning facets, so the hot bodies left on the class can become delegations (ruling 6624345766) #30020.
  • By-path readers (6597555122 item 3):
    • check-durability-degradation-log-level judges the two MySQL widening catches that stay on the class (migrateMysqlDatetimeColumns, migrateMysqlTimeColumns) by following this.logDurabilityFailure(…) to its body in the same file. A one-call stub there cannot be followed, so both read "swallowed, no log" (measured red). The helper is pinned to stay rather than widening the gate's same-file helper walk across files; the gate's readings are then equal to base.
    • check-tenant-chokepoint's DECLARED_WATCH_HINTS names the four new sql-driver-*.ts modules (its self-test requires every scanned file to be declared).
    • Every other reader of sql-driver.ts by path (the packages/cli generate pins, check-keyed-text-bounds, sql-driver-compile-refusal-seam.test.ts, the liveness and checklist anchors, the ADR anchors) reads text the codemod keeps on the class through its existing pin and needle tables, and passes unchanged.
  • No changeset, as refactor(driver-sql): codemod pilot — generated, byte-identity-proved section split of sql-driver.ts (#29520) #29598 and refactor(objectql): generate the engine.ts section split with a deterministic codemod #30012 (both skip-changeset): no published behaviour or surface changes.

Tests

No test file changes. The suites below ran at base f68b47ca and at head; the counts are equal.

Suite Base Head
driver-sql (vitest run) 238 files passed, 12 skipped; 3,859 passed, 216 skipped the same
driver-turso (subclasses SqlDriver) 96 files; 2,496 passed, 34 skipped 96 files; 2,496 passed, 34 skipped
driver-sqlite-wasm (subclasses SqlDriver) 40 files; 750 passed 40 files; 750 passed
cli by-path pins (9 files: generate-* pins, clean.operator-text-21418) 9 files; 131 passed 9 files; 131 passed
types / metadata / metadata-protocol by-path tests, spec check-liveness.test 22 / 11 / 28 / 74 passed 22 / 11 / 28 / 74 passed

Acceptance notes

Verification

Reading Base Head
sql-driver.ts lines 18,334 (21510bba) · 12,460 (main) 10,979
Byte-identity (--write, and --verify --base 21510bba, exit 0) 158/158 bodies, 127/127 top-level declarations identical; undoing the split rebuilds the base byte-for-byte
Determinism two --write runs: output tree sha256 eab675141cad9efdb5217f65f044a246474c973777c575d9f5ee00b8c63d8154 both times; regeneration reproduces the committed tree
driver-sql typecheck exit 0 exit 0
check:facade-delegation-only (+ --self-test 36) exit 0: SqlDriver 352 members, 244 delegations, 80 fields, 28 named exceptions
check:tenant-chokepoint 24 bindings, 21 read doors, 3 insert builders, 4 tenant predicates, 0 D8 arms, 34 files the same readings, 38 files
check:durability-log-level 51 seams (9 propagating), 101 read seams (13 discriminated) the same; the output is byte-identical
pnpm build (whole repo, 119 tasks) exit 0
pnpm lint exit 0
dispatch-gates --commands (67) 67 run, every exit 0; --ran: 67 derived, 0 NOT-MEASURED, 0 UNRUN

.d.ts: ruling 6624345766 item 1, "public surface EQUAL"

Read with dts-public.mjs (project files issue-29985/dts-public.mjs, sha256 211b32062250606e658acd7fb8880e0079b4043121fb7b970c658b4b57bf5020, the reading #29985 used). driver-sql's .d.ts build is deterministic on both sides (two builds each, byte-identical), so one pair is read.

Reading index.d.ts index.d.mts
raw (sha256 base 69d9162d… / head fac28979…) differs (private members) differs
private members cut, union order, second normalisation equal (106 / 106 statements) equal
declared surface equal equal
  • The control that edits one public docblock is caught.
  • Dropping one whole private member (fileColumnsMovedDeclared with its docblock) passes.
  • The facet classes and hosts do not appear in the .d.ts.

Facade reading

Facet Members Docblocks moved
ClassHead 12 6
Lifecycle 11 6
Crud 7 2
SequenceSemantics 23 3
BulkOperations 7 0
TransactionsSection 1 0
Aggregation 3 0
Distinct 1 0
WindowFunctions 1 0
QueryPlan 2 0
SchemaSyncSection 3 0
Rotation 9 0
DeferredDdl 3 0
ManagedDrift 8 0
InternalHelpers 7 0
FilterHelpers 30 3
FieldMapping 6 0
WindowFunctionBuilder 1 0
ColumnCreation 14 1
DatabaseHelpers 7 0
SqliteSerialisation 2 0
Total 158 21

The 28 exception rows (all #30020):

Reason Members
state: constructor logic, field / static initializers holding arrows constructor, logger, static DIALECT_CONNECT_TIMEOUT
accessor (codemod moves methods only) get supports, get insertReturnsStoredRows, get dateGranularityCapabilities, get supportsWalJournal, get dialectName
a computed parameter default rotateShards, ensureRotation
path-reader logDurabilityFailure, ensureShardTable, initObjects, migrateMysqlDatetimeColumns, migrateMysqlTimeColumns, resolveWithheldFilterRefusal, createAuditTimestampColumn, varcharColumnChars, createColumn
field-call: knex listPhysicalColumns, getBuilder, explainUnkeyableTextColumn, explainHashShadowDuplicate
inferred-return-type applyFilters, applyFilterCondition, ensureDatabaseExists, createDatabase
comment-in-parameters assertOperatorAppliesToColumn

The 78 hand-series stubs stay as they were (their modules are the hand series', not facets) and read as delegations.


🤖 Generated with Claude Code

https://claude.ai/code/session_01WGufzhBZia3VxBbmZ7wCkS

…tyFailure on the class

The facade registry gains the sql-driver.ts / SqlDriver row and one
exception row per member that keeps a body (28). The codemod gains a
PATH_READER_PINS row for logDurabilityFailure: check-durability-degradation-
log-level judges the two staying MySQL widening catches by following that
helper to its body in the same file, and a one-call stub there reads as
"swallowed, no log". Regenerated: 158/254 methods move; sql-driver.ts
18,334 -> 10,979 lines.

Claude-Session: https://claude.ai/code/session_01WGufzhBZia3VxBbmZ7wCkS
Co-authored-by: Claude <noreply@anthropic.com>
…or dispatch-gates

The gate scans every sql-driver-*.ts module and its self-test requires each
scanned file to be named in DECLARED_WATCH_HINTS. The facade split adds
sql-driver-class-head.ts, sql-driver-deferred-ddl.ts,
sql-driver-facet-hosts.ts and sql-driver-transactions-section.ts. The
readings are unchanged (24 bindings, 21 read doors, 3 insert builders,
4 tenant predicates, 0 D8 arms; 34 -> 38 files).

Claude-Session: https://claude.ai/code/session_01WGufzhBZia3VxBbmZ7wCkS
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Nov 4, 2026
@github-actions

github-actions Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/driver-sql, touching 526 documentable anchor(s).

36 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json a9fb14bdeeb5e09fec341ead4e0fffd8d8ed00bb.

⛔ 12 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 1 anchor(s) matched too much of the corpus to be a work list: created_at (literal, 43 pages)
  • 16 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 61 of 219 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 103 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 13 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json a9fb14bdeeb5e09fec341ead4e0fffd8d8ed00bb → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 40849b0a2f99ef9c25ccbb8414f606f44a07011a — the merge of head 61c98d18b4957164bb4d3e2fe56dd37712ec4675 into base a9fb14bdeeb5e09fec341ead4e0fffd8d8ed00bb, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 40849b0a2f99ef9c25ccbb8414f606f44a07011a && git checkout 40849b0a2f99ef9c25ccbb8414f606f44a07011a
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a9fb14bdeeb5e09fec341ead4e0fffd8d8ed00bb 61c98d18b4957164bb4d3e2fe56dd37712ec4675 && git checkout -B drift-repro a9fb14bdeeb5e09fec341ead4e0fffd8d8ed00bb && git merge --no-ff 61c98d18b4957164bb4d3e2fe56dd37712ec4675

node scripts/docs-audit/affected-docs.mjs --json a9fb14bdeeb5e09fec341ead4e0fffd8d8ed00bb

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs a9fb14bdeeb5e09fec341ead4e0fffd8d8ed00bb → pass the list as
args.docs, on the commit named under Which tree this was computed on.

…#30147

The accessor, computed-parameter-default, path-reader, knex field-call,
inferred-return-type and comment-in-parameters rows move from #30020 to
#30147, the card filed for them. The three state-owning rows
(constructor, logger, static DIALECT_CONNECT_TIMEOUT) stay on #30020.

Claude-Session: https://claude.ai/code/session_01WGufzhBZia3VxBbmZ7wCkS
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:devx#1 · os-support-ai · session_01WGufzgBDcBNVmSmo7VFHpj

Stamp: 2026-11-04T16:28Z
Head-sha: 61c98d1

维护者速读

席位核对

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-user-decision Blocked on a maintainer decision — do not dispatch; decision cards only, never on a PR size/xl skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants