Skip to content

Commit 8734bbf

Browse files
krisnyeclaude
andcommitted
test(ecs): prove standalone-computed factory aggregation type-checks cleanly
Adds a compile-time test for a common app pattern: computed factories authored as standalone functions (annotated with the exported `Database.Plugin.ToDatabase<typeof basePlugin>`) aggregated into one `computed: {}` block in a createPlugin call. Covers a plain resource read, a factory composing on a base computed, and a parameterized computed; asserts the aggregated result types exactly, with no `Omit` and no cast. Verified load-bearing: reverting the FullDBForPlugin CV slot to `unknown` breaks this exact assignment with TS2322 (`unknown` not assignable to the factory's `computed`), which is the contravariance failure that forced the old `Omit<ToDatabase<...>, 'computed'>` workaround. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 750daf5 commit 8734bbf

1 file changed

Lines changed: 66 additions & 0 deletions

File tree

packages/data/src/ecs/database/create-plugin.type-test.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,72 @@ function validTypeInferenceTests() {
329329
});
330330
}
331331

332+
// ============================================================================
333+
// Standalone computed factories aggregated in a create call
334+
// ============================================================================
335+
//
336+
// A common app pattern: computed factories are authored as standalone
337+
// functions in their own files, then aggregated into one `computed: {}` block
338+
// in a single createPlugin call. Each standalone factory must annotate its
339+
// `db` parameter with a NAMED, exported type (it lives in a different module
340+
// from the plugin).
341+
//
342+
// Before the FullDBForPlugin fix the factory db's `computed` slot was
343+
// `unknown`, so the annotation had to strip it:
344+
// type CoreStateDatabase = Omit<Database.Plugin.ToDatabase<typeof core>, 'computed'>;
345+
// Now the factory sees the base plugin's already-resolved computeds, so the
346+
// plain exported `Database.Plugin.ToDatabase<typeof core>` is the correct,
347+
// cast-free annotation — AND it lets a standalone factory compose on a base
348+
// computed. This test proves the round trip type-checks cleanly: no `as`, no
349+
// `Omit`, no `@ts-expect-error`.
350+
function standaloneComputedFactoryAggregation() {
351+
// The base "state" plugin owns the resources and one base computed.
352+
const coreStatePlugin = createPlugin({
353+
resources: {
354+
count: { default: 0 as number },
355+
},
356+
computed: {
357+
count: (db) => db.observe.resources.count,
358+
},
359+
});
360+
361+
// The single exported alias every standalone factory annotates against.
362+
type CoreStateDatabase = Database.Plugin.ToDatabase<typeof coreStatePlugin>;
363+
364+
// Standalone factory (own file): reads a base resource.
365+
const doubled = (db: CoreStateDatabase) =>
366+
Observe.withMap(db.observe.resources.count, (v) => v * 2);
367+
368+
// Standalone factory (own file): composes on the base plugin's computed.
369+
// This is the case the fix unlocks — `db.computed.count` is fully typed.
370+
const isPositive = (db: CoreStateDatabase) =>
371+
Observe.withMap(db.computed.count, (v) => v > 0);
372+
373+
// Standalone factory (own file): a parameterized computed that also reads
374+
// a base computed.
375+
const atLeast = (db: CoreStateDatabase) => (min: number) =>
376+
Observe.withMap(db.computed.count, (v) => v >= min);
377+
378+
// Aggregate the independently-authored factories into the derived plugin's
379+
// computed block — clean assignment, no cast.
380+
const derived = createPlugin({
381+
extends: coreStatePlugin,
382+
computed: {
383+
doubled,
384+
isPositive,
385+
atLeast,
386+
},
387+
});
388+
389+
// The resulting plugin exposes the aggregated computeds with exact types,
390+
// alongside the inherited base computed.
391+
type DerivedComputed = Database.FromPlugin<typeof derived>['computed'];
392+
type _CheckDoubled = Assert<Equal<DerivedComputed['doubled'], Observe<number>>>;
393+
type _CheckIsPositive = Assert<Equal<DerivedComputed['isPositive'], Observe<boolean>>>;
394+
type _CheckAtLeast = Assert<Equal<DerivedComputed['atLeast'], (min: number) => Observe<boolean>>>;
395+
type _CheckInheritedCount = Assert<Equal<DerivedComputed['count'], Observe<number>>>;
396+
}
397+
332398
// ============================================================================
333399
// INVALID TYPE INFERENCE TESTS
334400
// ============================================================================

0 commit comments

Comments
 (0)