Repository navigation
Commit 9f3b4bb
refactor(objectql): one shared host per facet in the engine.ts codemod stubs (body-identical; engine.ts 14,140 -> 13,548) (#30083)
<!-- claude-projects-attribution: {"github_login":"os-support-ai"} -->
_Requested by **Yibo Wang** · [project
thread](https://claude.ai/code/project/chan_01BVUyyPie8gHwoPM18iABwB?thread=cmsg_01BVUyyPie8gHwoPM18iABwBQdjPLZDve4g9Vssqoo6oK8)_
Fixes #30051
## 维护者速读
- 改了什么:`scripts/codemod-split-engine.mjs` 改为每个 facet 只建一个共享
host(每个引擎实例一份,首次调用时由类的 static 块安装的工厂建成,存在新模块
`engine-facet-hosts.ts`),类上的 stub 从「每次调用现建一个 host 字面量」变成一行
`Facet.x.call(xHost.of(this), …)`。从 base 重新生成,生成物未手改。`engine.ts` 14,140
→ 13,548 行(−592)。`protocol.ts` 不动:它的 codemod 存根本来就走共享的
`protocolHost(this)`,没有可压缩的。
- 为什么改:维护者在 #30012 上要求进一步压缩
`engine.ts`(「我觉得文件还是太长,还有进一步的拆分优化空间吗」「可以后续立卡处理」),本卡是其中的 stub 压缩一项。
- 风险与回滚:只改 stub 和新增 host 模块,161/161 方法体、146/146 顶层声明逐字节不变,撤销拆分逐字节还原 base
`engine.ts`;打包后 6 个 `.d.ts` 与 main 逐字节相同;objectql 测试 base 547 文件/10,833
= head;回滚就是 revert。
- 先看:① `packages/objectql/src/engine-facet-hosts.ts`(`FacetHost` 缓存,78
行);② `engine.ts` 类末尾第二个 static 块(16 个 host 工厂);③ codemod 里的 `hostsTexts`
/ `stubBody` 和 `verify` 新增的 stub 与 host 校验。
- 两分钟验证:`pnpm install --frozen-lockfile` 后运行 `node
scripts/codemod-split-engine.mjs --verify --base c2d353d`,应看到 `161/161
moved bodies identical`、`23245 -> 13548 lines`,退出码 0;`--self-test` 应通过
68 例。
- 需要你做的:改动共 1,996 行(未超过 3,000 行线),但按席位裁决本 PR 保持草稿,在您定的窗口里落地。
Clause-②: no
Metadata-protocol: none
## What changes
- **`scripts/codemod-split-engine.mjs`.** Each facet's host is now built
ONCE per engine instead of once per call.
- `hostsTexts` writes a new module, `engine-facet-hosts.ts`. It holds
`FacetHost`, a `WeakMap` cache keyed by engine with a factory slot, and
one exported instance per facet module (`actionsHost`,
`cascadePlanningHost`, …, 16 in all). The module imports types only, so
it forms no evaluation cycle and is evaluated before the class whichever
module loads first. This matters because 4 facet modules import
`ObjectQLStatics` from `engine.ts` by value, so a cache inside a facet
module could be in its temporal dead zone when the class's static block
runs.
- A second static block at the end of `ObjectQL` installs each facet's
factory: `xHost.install((engine) => ({ … }))`. The factory forwards each
member as the per-call literal did. A method is a closure called on the
engine at call time (`m: (...a) => engine.m(...a)`), so a spy or
subclass override installed later is still reached. A field or accessor
is a getter, and a field a body assigns also gets a setter. The host is
the facet's whole host interface (`ActionsHost` and so on), which
satisfies every body's `Pick`. A static block adds no class member, so
the `.d.ts` does not move.
- `stubBody` writes `return Facet.x.call(xHost.of(this), …args);` (a
generic method still calls its body instantiated with its own type
parameters). A stub whose body reads no member is unchanged.
- A facet host can be told apart from a per-call literal only by
identity across calls. No moved body can observe that: a body that reads
`this` bare already stays on the class (`bare-this`).
- `verify` now also checks that (1) every stub equals the generated
one-call stub for its method, facet and host, (2)
`engine-facet-hosts.ts` equals the generated text for the host
interfaces on disk, and (3) the install block equals the generated
block. Each member is forwarded by its BASE class kind: a method as a
closure, anything else as a getter, plus a setter when the interface
does not mark it `readonly`. The undo drops the block and the import.
- Generation refuses when `engine.ts` already binds a host name, or when
a moving method has a parameter named after its facet's host.
- `--self-test`: 68 cases in 6 batteries. "facets, statics and
docblocks" goes from 11 to 17 cases, and its floor is raised to 17. The
new refusals: a stub handing another facet's host, an edited host
module, and a written field's setter left out.
- **The regenerated split.** `--write` on `engine.ts` at `c2d353d2`
(main's last unsplit `engine.ts`; nothing has touched `engine*.ts` since
#30012 merged as `237f5137`), unedited. The diff: `engine.ts` (145 stubs
lose their literal, plus the install block and one import), the new
`engine-facet-hosts.ts`, and the header prose of the 16 facet modules
(the host paragraph). No body moved.
- **`protocol.ts` / `scripts/codemod-split-protocol.mjs`: no change.**
The card's premise does not hold for this file. Its 85 codemod stubs
already reach their bodies through one shared host per instance,
`ProtocolClassHeadFacet.x(protocolHost(this), …)` (`protocol-host.ts`, a
`WeakMap` cache installed by the class's static block). That is the
shape this PR gives `engine.ts`. `grep -c 'const host:
ThisParameterType' scripts/codemod-split-protocol.mjs` reads 0. The
per-call literals left in `protocol.ts` (164 stubs, 774 lines) are all
hand-series stubs that the codemod did not write (see Acceptance notes).
### Line counts
| File | Before (main `c23501b2`) | After | Δ |
|:--|--:|--:|--:|
| `packages/objectql/src/engine.ts` | 14,140 | 13,548 | −592 |
| `packages/objectql/src/engine-facet-hosts.ts` | — | 78 | +78 |
| `packages/metadata-protocol/src/protocol.ts` | 13,647 | 13,647 | 0 |
The card estimated about 1,000 lines off `engine.ts`. The measured
figure is 592. The 145 per-call literals were 869 lines, and the install
block adds back one line per host member (207 across 16 facets, plus 31
setters and 2 lines per facet). PR size: 847 additions + 1,149 deletions
= 1,996 changed lines.
## Tests
<details><summary>Proofs (all at head 73a7605 unless stated)</summary>
- `node scripts/codemod-split-engine.mjs --verify --base c2d353d`: exit
0. 161/161 moved bodies identical (body, parameters, return type,
async), 146/146 top-level declarations identical, every stub equals the
generated one, the host module and install block are the generated ones,
and the undo rebuilds base `engine.ts` byte for byte once 107 import
bindings are pruned. `engine.ts 23245 -> 13548 lines`.
- Determinism: two `--write` runs from base `engine.ts` (sha256
`dc5677f3…`), one in a separate worktree, print the same output tree
sha256
`09c80319211a965b89820ab7bb650d74cf6aaaec0afc0afb9cd75f2b867e3554`. The
second worktree's regenerated tree equals the committed head (`git
status` empty).
- `.d.ts`: `packages/objectql/dist` built at main (`c23501b2`) ×2 and at
head ×2. All 6 declaration files (`index`, `core`, `util-BWl1U61F`, each
`.d.ts` + `.d.mts`) are **byte-identical** base vs head. The
public-surface reading (`pubsurface.mjs` from #29986) passes
base1=base2, head1=head2, base1=head1 and base2=head2, and its controls
behave (a private member made public is caught, a public type change is
caught, a private docblock edit is not).
- objectql `test`: base (main `c23501b2`) 547 files / 10,833 passed =
head 547 / 10,833. `test:repo`: 1 file / 5 = 5.
- objectql `typecheck` (tsc + check:test-typecheck): exit 0.
- Built-dist smoke (ESM `index.mjs` and CJS `index.js`): two engines
keep separate hosts (`residualOwnerSource` set on one reads `undefined`
on the other), a host setter writes through (`setDefaultBodyRunner`
first-wins `true`, then `false`).
- Facade gate `node scripts/check-facade-delegation-only.mjs`: exit 0,
registry unchanged (2 facades, 60 exceptions). The `engine.ts`
delegation kinds move from 217 host-literal stubs to 144 host
delegations plus 73 host-literal stubs (the hand series). The new
install block reads as a second `static host factory`.
- Durability census `check-durability-degradation-log-level.mjs`: base
(main) and head output byte-identical (51 seams, 9 propagating; 101 read
seams, 13 discriminated). `--self-test` exit 0.
</details>
<details><summary>Ablations (each red, then restored; file hash back to
its HEAD blob)</summary>
Done through `scripts/ablation-replace.mjs`.
1. verify's stub check turned off: the self-test case "a stub handing
another facet's host fails" goes red.
2. verify's host-module comparison turned off: "an edited host module
fails" goes red.
3. verify's install-block comparison turned off: "a written field's
setter left out of the host fails" goes red.
4. On the real tree, one `engine.ts` stub pointed at another facet's
host: `--verify --base c2d353d` exits 1 with `recordResidualOwner: its
stub is not the generated one-call stub`.
</details>
## Acceptance notes
- **The hand-series stubs keep their per-call literals.** In `engine.ts`
that is 73 stubs (454 lines; the #25522 hand modules). In `protocol.ts`
it is 164 stubs (774 lines; the #25467 hand modules). The codemods do
not write those stubs, and this card's ruling is codemod-only. The same
mechanism would apply (route them through the facet hosts or
`protocolHost`, after widening `ProtocolHost`). By arithmetic, not a
generated run, that is about −400 lines in `engine.ts` and about −700 in
`protocol.ts`. Carrier: the seat, if it wants a card.
- No changeset, following #30012 and pilot #29598 (`skip-changeset`):
the behaviour and the published declaration files do not change.
## Verification
Local, at head `73a7605b`:
- `npx turbo run build --concurrency=2`: 120/120 tasks, exit 0. `pnpm
lint`: exit 0. objectql `typecheck`: exit 0.
- `node scripts/pm/dispatch-gates.mjs --commands`: 64 families derived,
each run, all exit 0. Three exited 3 on the first sweep because a
prerequisite was missing (`check-plugin-teardown-shape --self-test`
needs the pinned fixture commit fetched; `check:dual-build-cjs-loads`
and `check:lean-entry-closure` need the full build). Each passed when
rerun after its prerequisite was in place. `--ran`: 64 derived, 64 run,
0 NOT-MEASURED, 0 UNRUN.
- Run by hand, outside the derivation:
`check-facade-delegation-only.mjs` (exit 0, registry unchanged),
`check-durability-degradation-log-level.mjs` (base = head output),
`codemod-split-engine.mjs --self-test` (68 cases).
- Declared to CI: the full lint workflow battery, core `test:repo`, and
dogfood (the title has no `!` and there is no narrowing arm).
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(not touched)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes.
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset.
(`skip-changeset`, see above)
- [x] 6. `packages/spec` touched: its generated artifacts are
regenerated and committed. (not touched)
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01WGufzfeJWRytJdoFWR9Pc8)_
<!-- signed: domain:server#6 · os-support-ai ·
session_01WGufzfeJWRytJdoFWR9Pc8 -->
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 61b487b commit 9f3b4bb
19 files changed
Lines changed: 873 additions & 1160 deletions
File tree
- packages/objectql/src
- scripts
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
22 | | - | |
23 | | - | |
24 | | - | |
25 | | - | |
26 | | - | |
27 | | - | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
28 | 29 | | |
29 | 30 | | |
30 | 31 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
27 | | - | |
28 | | - | |
29 | | - | |
30 | | - | |
31 | | - | |
32 | | - | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
33 | 35 | | |
34 | 36 | | |
35 | 37 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
19 | | - | |
20 | | - | |
21 | | - | |
22 | | - | |
23 | | - | |
24 | | - | |
25 | | - | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
26 | 28 | | |
27 | 29 | | |
28 | 30 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
20 | | - | |
21 | | - | |
22 | | - | |
23 | | - | |
24 | | - | |
25 | | - | |
26 | | - | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
27 | 29 | | |
28 | 30 | | |
29 | 31 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
17 | | - | |
18 | | - | |
19 | | - | |
20 | | - | |
21 | | - | |
22 | | - | |
23 | | - | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
24 | 26 | | |
25 | 27 | | |
26 | 28 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
19 | | - | |
20 | | - | |
21 | | - | |
22 | | - | |
23 | | - | |
24 | | - | |
25 | | - | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
26 | 27 | | |
27 | 28 | | |
28 | 29 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
24 | | - | |
25 | | - | |
26 | | - | |
27 | | - | |
28 | | - | |
29 | | - | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
30 | 32 | | |
31 | 33 | | |
32 | 34 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
18 | | - | |
19 | | - | |
20 | | - | |
21 | | - | |
22 | | - | |
23 | | - | |
24 | | - | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
25 | 26 | | |
26 | 27 | | |
27 | 28 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
23 | | - | |
24 | | - | |
25 | | - | |
26 | | - | |
27 | | - | |
28 | | - | |
29 | | - | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
30 | 32 | | |
31 | 33 | | |
32 | 34 | | |
| |||
0 commit comments