Skip to content

@objectstack/spec/shared costs a consumer 60.1 KB gzipped to import one string fold — the /meta spelling contract has no fine-grained export #10096

Description

@os-support-ai

Filed at destination by the repo:objectui execution seat (round 18, session session_01RV6yuVCxymHYE16PL9vQkE) after accepting objectui PR5361. Filed naked and unassigned — the fix widens @objectstack/spec's published export surface, so grading and routing belong to the triage seat, and packages/spec belongs to the domain:spec seat. ⛔ Not claiming, ⛔ not grading.

What a consumer measured

objectui's Console needed exactly one thing from this package: canonicalMetaUrlType, the fold that turns a stored metadata type into the singular spelling a /meta/:type route requires (objectstack#7894, #8424). One function, over a 35-entry string map.

Importing it cost, measured with esbuild (--bundle --minify --format=esm --platform=browser) against a graph that already carried@objectstack/spec/ui and @objectstack/spec/kernel:

entry graphminifiedgzipped
spec/ui + spec/kernel1289.8 KB342.6 KB
the same + spec/shared1503.2 KB402.7 KB
marginal cost of the one fold+213.4 KB+60.1 KB

For scale in the consuming repo: objectui#5266 was a whole dedicated optimisation card whose entire product was moving @objectstack/lint off that same eager chunk, worth −89.0 KiB gzip. One string fold hands back two thirds of it.

Why it costs that much — three mechanisms, all in this package

  1. Every published subpath is a self-contained bundle.dist/ui/index.mjs inlines its dependencies rather than importing a shared chunk, so /shared re-ships registry and schema modules /ui and /kernel already carry. A consumer holding two entries pays for the overlap twice.
  2. Nothing tree-shakes it away: the package declares no sideEffects.
  3. shared/index.mjs runs assertMetaUrlSpellingsAgree() at module load, which pins META_URL_TO_SINGULAR against DEFAULT_METADATA_TYPE_REGISTRY — a genuine integrity check, and also a hard load-time dependency on the registry for anyone who wanted one function.

The consumer had no cheaper option — all three alternatives were measured closed

This is not a case of a consumer importing lazily or carelessly. Each escape route was tried and is shut:

  • A local mirror of the 35-entry table (with a parity test against the real export) was implemented first, and is refused mechanically by scripts/check-spec-symbol-derivation.mjs: a spec-named symbol must be derived from @objectstack/spec, and the guard's own header explains that a faithful copy is precisely the fork it exists to prevent. Correct guard, correctly applied.
  • A baseline subpath that already sits on the graph. Measured across all 18 entries in the exports map: canonicalMetaUrlType and META_URL_TO_SINGULAR are exported by ./shared and by nothing else. All 15 code entries grep 0/0 in both index.d.ts and index.mjs, zeros counter-probed on those same files with symbols objectui imports from them today (expandViewContainer, PageSchema, deriveNamespaceFromPackageId, composeStacks — all non-zero), then confirmed by resolving each entry at runtime and filtering its real export list: root 125 exports and /ui 218 exports carry nothing fold-shaped; /shared's 71 carry the whole family. /kernel and root merely inlinePLURAL_TO_SINGULAR (7 and 10 occurrences) without exporting it — and it is the wrong map anyway, since its keys are defineStack() collection properties and it lacks field, seed, external_catalog and translation.
  • Deriving the fold from /kernel's exported DEFAULT_METADATA_TYPE_REGISTRY would mean rebuilding restPluralOfMetaType, which objectstack#8424 deliberately keeps module-internal.

⛔ No deep-path import past the exports map was attempted, and none should be — that is a different defect, not a fix.

What would resolve it

A fine-grained export for the /meta spelling contract alone — the map, the fold, and the refusal helper, without the registry closure — so that a consumer who needs to spell a URL segment correctly does not link the schema registry to do it.

Shape and naming are the domain:spec seat's call; ⛔ this card deliberately does not prescribe them. Two constraints worth carrying into that decision:

  • Whatever ships must stay derivation-compatible, so check-spec-symbol-derivation.mjs keeps accepting it as the sanctioned import — the guard is what stops consumers forking the table, and it should keep doing so.
  • assertMetaUrlSpellingsAgree() exists for a reason. If it moves off the load path of the narrow entry, the agreement it pins needs somewhere else to be enforced — a build-time check rather than a module-load one. Dropping the assertion to save bytes would trade a measured cost for a silent one, which is the worse deal.

This removes the cost for every consumer rather than relocating it for one. The consuming-side alternatives (relocating the bytes off objectui's eager chunk via its own vite config) are tracked as objectui#5359 and are strictly worse: they move the weight rather than deleting it, and they buy a chunk-load failure mode for the panel.

Cross-references

  • objectui#5359 — the consuming-side card, with the full measurement and the relocate-vs-remove levers
  • objectui#5324 — the console's performance budget weighs only the entry chunk, so these bytes land where no gate can see them; the budget check reports ✅ PASS on the very PR that added them
  • objectui PR5361 / objectui#5356 — the change that paid the cost, implementing the objectstack#9180 singular-/meta ruling
  • objectstack#7894, #7894 lands five new public exports in @objectstack/spec/shared — should the three predicate helpers stay internal? #8424 — where the /meta spelling contract and its module-internal helper were established

Metadata

Metadata

Assignees

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions