Repository navigation
build(spec): one module graph with shared chunks, so each schema ships once and is one object across entries - #25748
Conversation
Every JS output of @objectstack/spec is now one esbuild graph with splitting, so a module two entries reach is one module in one shared chunk. The browser-conditioned Node outputs are built from a node-side copy of only the modules that reach the pg-grammar seam, measured per build by a metafile pre-pass. Claude-Session: https://claude.ai/code/session_01Q3XCv7AXcUSSD479Vt1LXy Co-authored-by: Claude <noreply@anthropic.com>
… layout Claude-Session: https://claude.ai/code/session_01Q3XCv7AXcUSSD479Vt1LXy Co-authored-by: Claude <noreply@anthropic.com>
📓 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 — 144 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 196ac021819c13fba9f6a89756888791af33649a && git checkout 196ac021819c13fba9f6a89756888791af33649a
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6679644a1a56b552d6864b125900221823ec78b3 5e9381000524a32cc8f9bebf09212e2b51cc93e9 && git checkout -B drift-repro 6679644a1a56b552d6864b125900221823ec78b3 && git merge --no-ff 5e9381000524a32cc8f9bebf09212e2b51cc93e9
node scripts/docs-audit/affected-docs.mjs --json 6679644a1a56b552d6864b125900221823ec78b3
|
席位核对ACCEPT · #8133 ·
|
Fixes #8133
Clause-②: no
Metadata-protocol: none
Ruling-ref: none
What changes
@objectstack/spec's JS build is now one esbuild module graph with shared chunks for every output (ESM and CJS, Node andbrowser-conditioned). A module two entries reach is one module in onedist/chunk-HASH.{mjs,js}file, so it ships once and is the same object through either entry. Onlypackages/spec/tsup.config.tschanges behaviour. Theexportsmap and the declaration build are untouched.Why a bare
splitting: truewas not enough (measured, then rejected)With
splitting: trueon the existing two passes,./sharedand./migrations(subpaths with nobrowsercondition, so a browser bundler loads their ordinary bundles) started linking the chunk that importspg-connection-string. esbuild assigns a module to a chunk by file-level reachability, so an entry links every chunk its import graph touches, used or not. Per-entry tree-shaking had kept them clean. That is the breakage classcheck:browser-reachable-entriesrule 2 refuses: the controlled experiment below failed to bundle withCould not resolve "fs". A plain two-pass split would also have left the browser graph and the Node graph as two copies of everything they share.What the config does instead (
oneModuleGraph)dist/ENTRYplus the 5browser-conditioned outputs atdist/browser/ENTRY. The browser swap of the pg-grammar arm (the 2026-08-22 ruling's mechanism, keyed to the same seam specifier) now applies to the whole graph.browser-conditioned entries are built from anode-sideesbuild namespace. It re-instantiates exactly the modules from which the seam is reachable (measured at BASE: 28 of 459 source modules: the entry barrels, the driver configs, the datasource, stack and conversion/migration registries). Every other module resolves back to the shared graph.browserconditions are moot, and withdrawing them is a decision.Measurements
All readings are on this worktree. BASE =
origin/main47af3dd. HEAD =5a40a420(this branch withorigin/mainc61f75d1merged; every HEAD reading below was re-taken there, and was identical at the pre-merge head19a8800aexcept the experiment, 602.9 KB there). Fullpnpm --filter @objectstack/spec buildboth times.a.defineConnector === b.defineConnector(root vs/integration), ESMbrowserview (119 pairs).mjsfiles definingvar FieldSchema =.mjsfiles definingvar ConnectorSchema =packages/spec/dist.d.ts)/shared:MAP_SUPPORTED_FIELDS,METADATA_ALIASES,normalizeMetadataCollection,normalizePluginMetadata,normalizeStackInput. They live inshared/metadata-collection.zod.ts, which reaches the seam, so the root's Node output carries the node-side copy and/shared(unconditioned) the shared one. Under thebrowsercondition they are one object.ConnectorSchemaon the card. The card'sa.ConnectorSchema === b.ConnectorSchemacomparesundefinedto an object: the root entry does not exportConnectorSchemaat top level, before or after. Only/integrationdoes.defineConnectoris the meaningful half, and it holds.a3c0da9(currentmain) value imports: 256 symbols across 11 subpaths, parsed with the TypeScript parser, type-only imports excluded. Each import comes from the subpath objectui uses. esbuildbundle+minify+platform: browser, gzip level 9, zod bundled in every arm.dist: 1,628.4 KB.dist: 603.6 KB.srcwith the same pure-annotation and swap plugins: 538.5 KB.18.0.0, 232 symbols) are the same shape on an older tree.treeshake, with no partially-initializedrequire).check:dual-build-cjs-loadsloads everyrequireentry and is green.NOT MEASURED: objectui
Bundle Analysisvendor-objectstack. That number comes from objectui CI on a pin that carries this change: objectui's own pin move, which also re-baselines its eager-closure ceilings. The experiment above is the local stand-in, not that number.Deep-path consumers (risk: chunk names are not stable)
a3c0da9: three@objectstack/spec/dist/index.{js,mjs}strings, all regex probe inputs in tests, not imports (scripts/__tests__/vite-objectstack-spec-dist.test.ts:481,:946,:1165;scripts/__tests__/vite-objectstack-client-dist.test.ts:398). Both files still exist.scripts/objectstack-pin.mjshard-links the package's whole tree, so chunks travel with it.vendor-objectstackgroup test matches any file under the package directory, so chunks group there.a4b3560: zero@objectstack/spec/dist/src/json-schemadeep imports.Gates and tests
All on
5a40a420unless stated. Exit codes were captured before any pipe.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack): 72 commands. 71 exit 0, 1 exit 1 (pnpm --filter @objectstack/spec run check:liveness, the main-red below).--ranreconciliation:72 derived, 72 run, 0 NOT-MEASURED, 0 UNRUN, a derived zero from 72 recorded exit codes. On the pre-merge head19a8800a, all 72 exited 0. One of them,check-plugin-teardown-shape --self-test, first exited 3 because of a shallow clone, then 0 aftergit fetch --depth=1of its pinned fixture commit.5a40a420:check:browser-reachable-entries(48 bundles walked transitively; 10 server-only links, all on the Node side of the 5 conditioned entries),check:dual-build-cjs-loads,check:lean-entry-closure,check:dts-closure,check:sourcemap-no-sources-content,check:published-filesandcheck:dev-prereqs(local-only; after rebuilding the two packages the merge moved): all exit 0.19a8800a,pnpm build:Tasks: 76 successful, 76 total,examples/*included.19a8800a, rootpnpm test(turbo run test test:repo, all 152 tasks, every package that reads@objectstack/specthroughdist):Tasks: 152 successful, 152 total, lockVERDICT command-exit 0. The run was interrupted once by a worker restart and resumed from turbo's cache: 125 cached, 27 executed in the second run, and no failure in either run.@objectstack/cli(unit + integration): 500 passed, 1 skipped (501); Tests 6291 passed.packages/qa/downstream-contract: 3 files / 31 tests passed.@objectstack/spec:test721/721 files,test:repo79/79.@objectstack/specon5a40a420:typecheckexit 0, including the test layer that compilestsup.config.tsthroughpure-schema-construction.test.ts(that reading was taken at19a8800a).testgives 720 passed, 1 failed (721); the failing file isscripts/liveness/check-liveness.test.ts(23 tests), the same main-red.test:repowas not reached after it.BUILD_DTS=true, plugins by name) is identical at BASE and HEAD (sha256 prefixccf850853f3c0699). There are 134 declaration files, the same set once hashes are stripped. The DTS pass is not byte-stable from run to run (tsup.config.ts's docblock), so it is compared by its inputs and its file set, not by a digest.scripts/ablation-replace.mjs, each leg verified on disk, each restored blob-identical toHEAD9c9b83dc, then rebuilt). Predicted, then observed:dist/browser/**bundles plus./sharedand./migrationsin both formats. The first attempt was refused by the tool as a no-op (the replacement was a substring of the anchor), so nothing ran; re-run with a unique replacement.POSITIVE CONTROL FAILED.one-module-graph: no module of the Node graph of … imports the pg-grammar armin both the ESM and CJS runs.check:browser-reachable-entriesgreen, every measurement identical.Main-red, not this PR's:
check:livenessandcheck-liveness.test.ts. Green on19a8800a, red after mergingorigin/mainc61f75d1, and still red onorigin/mainf9a1e93f. This PR's diff vsmaintouches no ledger, noservice-automationfile and nothing undersrc/.translation/flows.screenscitespackages/services/service-automation/src/engine.ts#renderFlowTextSlot.engine.tsnamedscreensonly through a comment ("two screens down").0189524a(refactor(service-automation): move AutomationEngine.resumeInternal's body into run-resume.ts (#25222 slice 8b) #25734) moved that comment intorun-resume.ts.renderFlowTextSlotitself now lives innode-text-rendering.ts, which never namesscreenseither.engine.ts#renderFlowTextSlotclause from that row'sevidence.builtin/screen-nodes.ts#registerScreenNodes, already cited, is the reader that spells the key. Or citenode-text-rendering.ts#renderFlowTextSlotwith an evidence-scope note. Fix PR known to this run: none.CI on this PR is the authority for the families above: the required five, Build Core's bundle-size step, Test Core.
Acceptance notes
Comments under
packages/spec/src/**that describe the old layout. The dispatch putssrc/**out of scope, so they are not edited here. Each is now inaccurate about the layout, not about behaviour:src/index.ts:73: "Every subpath entry is a self-contained bundle".src/conversions/registry.ts:18144: "Each published entry is one flat file (tsup.config.ts,splitting: false)". The PURE-annotation reasoning it supports still holds per chunk.src/api/package-api-assembled.zod.ts:40: a historical sentence. Its TSDoc renders into the generatedcontent/docs/references/api/package-api-assembled.mdx.Proposed: one comment-only follow-up, routed by the seat.
packages/spec/README.md(published) is corrected here.check:browser-reachable-entries' docblock said "this tree cannot exercise" a chunk walk; now it does, and the docblock says so.Generated by Claude Code