From 05674a349fe3850751c04873bdf06bc74b8e353e Mon Sep 17 00:00:00 2001 From: hypercross Date: Sun, 31 May 2026 16:24:59 +0800 Subject: [PATCH] refactor: improve type safety in World and tests Replace `any` types with specific interfaces like `WorldEvent`, `QueryUpdate`, and `Entity` to strengthen type checking. This includes refining the deserialization logic in `World.fromSnapshot` to use properly typed component definitions. --- src/world.ts | 11 ++++++++--- test/serialization.test.ts | 14 ++++++++------ test/world.test.ts | 5 +++-- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/world.ts b/src/world.ts index 03463d5..e4f26b6 100644 --- a/src/world.ts +++ b/src/world.ts @@ -7,6 +7,7 @@ import { ObservableLayer } from "./observable/observe"; import type { QueryUpdate, RelationshipUpdate } from "./observable/events"; import type { RelationshipDef } from "./relationship"; import { Observable } from "rxjs"; +import type { WorldEvent } from "./observable/events"; import type { WorldSnapshot } from "./serialization"; // ── World ───────────────────────────────────────────── @@ -40,7 +41,7 @@ export class World { private _observable = new ObservableLayer(); /** Global event stream. */ - get events$(): Observable { + get events$(): Observable { return this._observable.events$.asObservable(); } @@ -435,7 +436,10 @@ export class World { ): World { const world = new World(); - const compByName = new Map(components.map((c) => [c.name, c])); + const compByName = new Map(components.map((c) => [c.name, c])) as Map< + string, + ComponentDef> + >; const relByName = new Map((relationships ?? []).map((r) => [r.name, r])); // Map string ids → real Entity handles @@ -453,7 +457,8 @@ export class World { `Pass it in the components array.`, ); } - world.add(entity, def, value as any); + // Unknown at deserialization boundary; shape matches ComponentDef.defaults + world.add(entity, def, value as Partial>); } } diff --git a/test/serialization.test.ts b/test/serialization.test.ts index fcaa496..669e1a4 100644 --- a/test/serialization.test.ts +++ b/test/serialization.test.ts @@ -6,6 +6,7 @@ import { type WorldSnapshot, query, type RelationshipUpdate, + type WorldEvent, } from "../src/index"; // ── Definitions ───────────────────────────────────── @@ -269,8 +270,9 @@ describe("Serialization — complex state", () => { const playerId = snap.relationships.ownedBy[bulletId]; // Player should have a name - expect(snap.entities[playerId]).toHaveProperty("name"); - expect((snap.entities[playerId].name as any).value).toBe("Hero"); + const playerComps = snap.entities[playerId]; + expect(playerComps).toHaveProperty("name"); + expect((playerComps.name as { value: string }).value).toBe("Hero"); }); }); @@ -403,8 +405,8 @@ describe("Serialization — observables after load", () => { w.spawn(); const loaded = roundTrip(w); - const events: any[] = []; - loaded.events$.subscribe((e: any) => events.push(e)); + const events: WorldEvent[] = []; + loaded.events$.subscribe((e) => events.push(e)); const e = loaded.spawn(); expect(events).toHaveLength(1); @@ -417,8 +419,8 @@ describe("Serialization — observables after load", () => { w.add(e, Position, { x: 1, y: 2 }); const loaded = roundTrip(w); - const updates: any[] = []; - loaded.observe(query(Position)).subscribe((u: any) => { + const updates: QueryUpdate[] = []; + loaded.observe(query(Position)).subscribe((u) => { if (u.added.length || u.removed.length || u.changed.length) { updates.push(u); } diff --git a/test/world.test.ts b/test/world.test.ts index f3b3485..00f5c11 100644 --- a/test/world.test.ts +++ b/test/world.test.ts @@ -5,6 +5,7 @@ import { query, type QueryUpdate, type WorldEvent, + type Entity, } from "../src/index"; // ── Components ────────────────────────────────────── @@ -84,11 +85,11 @@ describe("Entity lifecycle", () => { // ── Components ────────────────────────────────────── describe("Components", () => { let world: World; - let entity = 0 as any; + let entity: Entity; beforeEach(() => { world = new World(); - entity = world.spawn(); + entity = world.spawn() as Entity; }); it("add returns defaults", () => {