ecs: name archetype rows without a cast + flatten combine's IntersectAll#122
Merged
Conversation
…t a cast db.archetypes.<Name> is a ReadonlyArchetype<Row> whose Row is derived from the declared columns. Consumers exposing an archetype under a hand-authored service interface had to launder it through `as unknown as` at every boundary. The cast only arises when the service type re-declares archetype rows by hand; a service type *derived* from the plugin (Database.Plugin.ToDatabase) already has the same row type, so db.archetypes assigns with no cast. Add Database.Archetype.RowOf<S, K> (reusing the existing FromArchetype) so a row can be *named* without re-spelling its columns. No runtime change, no branding — the full structural row is preserved through inference. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
combine-plugins.ts intersected each of the 9 plugin buckets with a linearly recursive, Simplify-wrapped IntersectAll<T> = Simplify<H & IntersectAll<R>>. Two cost multipliers: the per-level Simplify forced eager re-materialization of the intersection (×9 buckets, the bulk of the instantiation count), and the linear recursion depth scaled with plugin count (the TS2589 ceiling). Reformulate using the already-shipped non-recursive UnionToIntersection over the element union, with an empty-tuple guard preserving the old `unknown`. The systems slot stays a real union, now via X[number] (UnionAll deleted). Behavior-preserving: the merged buckets are object maps that intersect soundly; the === identity conflict guard and merge order are value-level and unchanged. Measured ~29% fewer instantiations (296,595 → 210,305) on a 24-plugin single combine; all 2585 tests pass; type-check clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Two related type-system improvements in
@adobe/data(packages/data/src/ecs).Part 1 — name archetype rows without a cast (
Database.Archetype.RowOf)db.archetypes.<Name>is aReadonlyArchetype<Row>whoseRowis derived from the declared columns. Consumers exposing an archetype under a hand-authored service interface had to launder it throughas unknown asat every public boundary (and per-entity indb.observe.entity(id, archetype)).We considered carrying an explicit "brand" type through inference, but confirmed branding adds no type checking — it only lets you hide internal columns behind a narrower public row. The full structural row is fine here, so we drop branding entirely. The cast only ever arose because a service type re-declared archetype rows by hand; a service type derived from the plugin (
Database.Plugin.ToDatabase<typeof plugin>) already has the same row type, sodb.archetypesassigns with no cast.Database.Archetype.RowOf<S, K>(reusing the existingFromArchetype) so a row can be named without re-spelling its columns.ArchetypeComponents, the store mapped type, orcreate-plugin.archetype-row.type-test.tsproves the no-cast assignment,RowOfequality,ReadonlyArchetype<RowOf<…>>, and theobserve.entityrow flow. README note steers consumers to derive, not re-declare or cast.Part 2 — flatten
combine'sIntersectAllcombine-plugins.tsintersected each of the 9 plugin buckets with a linearly-recursive,Simplify-wrappedIntersectAll<T> = Simplify<H & IntersectAll<R>>. The per-levelSimplifyforced eager re-materialization of the intersection (×9 buckets — the bulk of the instantiation count).Reformulated using the already-shipped non-recursive
UnionToIntersection<T[number]>(empty-tuple guarded), andUnionAll→X[number]for thesystemsunion.combine(measured locally; everything else constant).===identity conflict guard and merge order are value-level and unchanged.systemsstays a real union.combine-plugins.type-test.tsguards correct merged-type resolution over a wide single combine.Verification
tsc -bclean; eslint clean on changed files.extendstypeperf baseline unchanged (that path goes throughCreatePluginResult, notcombine).Related PRs
Follows #120 (thread index declarations into the computed-factory db type) and #117 (index API).