feat(data): insert-time default-factory components (creation-time identity seam) - #200
Merged
Merged
Conversation
A component schema may name a zero-arg `defaultFactory` (pure-JSON name, resolved by a store-level registry). Such a component is OPTIONAL at insert: omit it and the archetype mints a fresh value; supply it (replication inbound / load) and the supplied value wins. Threads a default-factory-keys type param (DFK) through Core and Store so the omission is type-checked. Naming a factory the registry lacks throws at archetype construction. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Transaction bodies and system stores now type default-factory components as optional at insert (DFK flows via TransactionDeclarations + Plugin.ToStore), so consumer insert sites can omit them and the archetype mints them. createDatabase accepts a defaultFactories registry. The read-only Database interface and FromPlugin extends-chain are deliberately untouched to avoid amplifying the plugin-composition instantiation cost. E2E + type tests added. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
Summary
Adds a general, guid-agnostic ECS capability: a component schema may name a
defaultFactorythat mints the component's value at insert time when the row omits it. Such a component becomes optional at insert — omit it and the archetype mints a fresh value; supply it (the replication-inbound / load path) and the supplied value wins.This is the enforcement seam FFP/Squirrel needs for FFP-108211 (deterministic sibling order via a creation-time GUID tiebreak), without teaching the core ECS anything about GUIDs.
guidstays entirely consumer-side: Squirrel declaresguid: { …, defaultFactory: "guid" }and registers{ guid: () => mintGuid() }once. The ECS only knows "a component may name a default factory."Why this shape (vs. type-requiring a guid on document-quadrant inserts)
row supplies value → use it; absent → mintis Squirrel's adopt-inbound-on-miss, moved to creation time.fromDataalready repopulates non-insert columns, so load supplies the persisted guid and the factory never runs — no re-mint, no escape hatch needed.interpolatorsprecedent. The factory (a function) lives inCreateStoreOptions.defaultFactories, never in the schema.What's threaded
DFK(default-factory keys, derived from component schemas viaDefaultFactoryKeys<CS>) flows so the omission is compile-checked end to end:Schema.defaultFactory→createArchetype(codegen) →Core→Store→TransactionDeclarations+Plugin.ToStore.Inside a transaction/system body,
t.archetypes.X.insert({ … })may omit default-factory components while every other component stays required (@ts-expect-error-verified).Deliberately NOT threaded
The read-only
Databaseinterface andDatabase.FromPluginextends-chain are left untouched. Actions take a read-onlyDatabase(they dispatch transactions, never insert), so inserts only ever flow through the mutable transaction/system store — which is threaded. KeepingDFKoff theDatabasegeneric list avoids amplifying the plugin-composition instantiation cost.Type-perf (plugin extends-chain,
scripts/typeperf/measure.mjs extends)The added cost is a fixed per-plugin
DefaultFactoryKeys<CS>pass that does not scale with extends depth — the quadratic composition coefficient is unchanged:inst/depth²before → afterThe absolute Δ is ~constant (+29k → +43k) across all depths; the marginal per-link is flat.
Consumer usage
Testing
create-archetype.test.ts— mint-on-omit (fresh per insert), supply-overrides, and the generic (non-identifier-name) insert path.archetype.tscompile-time tests —EntityInsertValues<C, DK>optionality.default-factory.test.ts— end-to-end through the full plugin chain (Database.create→ transaction → insert): mint, adopt, init-error, and the compile-time optionality ofPlugin.ToStore.Follow-ups for the consumer (Squirrel-side, not in this PR)
guid(closes the "new document archetype forgot to declare guid" residual gap — per explicit-over-magical).🤖 Generated with Claude Code