ecspresso 0.4.1 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,18 +2,16 @@
2
2
 
3
3
  *(pronounced "ex-presso")*
4
4
 
5
- __Note: This is a VERY early work in progress. No work on performance has been done while the API is being nailed down. The documention is also being autogenerated while ECSpresso is being iterated on.__
6
-
7
- A type-safe, modular, and extensible Entity Component System (ECS) framework for TypeScript.
5
+ A type-safe, modular, and extensible Entity Component System (ECS) framework for TypeScript and JavaScript.
8
6
 
9
7
  ## Features
10
8
 
11
- - 🔒 **Type-Safe**: Full TypeScript support with component, event, and resource type inference
12
- - 🧩 **Modular**: Bundle-based architecture for organizing features
13
- - 💡 **Developer-Friendly**: Clean, fluent API with method chaining
14
- - 🔄 **Event-Driven**: Integrated event system for decoupled communication
15
- - 🗄️ **Resource Management**: Global state management with lazy loading
16
- - **Query System**: Powerful entity filtering with helper type utilities
9
+ - **Type-Safe**: Full TypeScript support with component, event, and resource type inference
10
+ - **Modular**: Bundle-based architecture for organizing features
11
+ - **Developer-Friendly**: Clean, fluent API with method chaining
12
+ - **Event-Driven**: Integrated event system for decoupled communication
13
+ - **Resource Management**: Global state management with lazy loading
14
+ - **Query System**: Powerful entity filtering with helper type utilities
17
15
 
18
16
  ## Installation
19
17
 
@@ -26,16 +24,17 @@ npm install ecspresso
26
24
  ```typescript
27
25
  import ECSpresso from 'ecspresso';
28
26
 
29
- // Define your component types
27
+ // 1. Define your component types
30
28
  interface Components {
31
29
  position: { x: number; y: number };
32
30
  velocity: { x: number; y: number };
33
31
  health: { value: number };
34
32
  }
35
33
 
36
- // Create a world and add a system
34
+ // 2. Create a world
37
35
  const world = new ECSpresso<Components>();
38
36
 
37
+ // 3. Add a movement system
39
38
  world.addSystem('movement')
40
39
  .addQuery('moving', { with: ['position', 'velocity'] })
41
40
  .setProcess((queries, deltaTime) => {
@@ -46,14 +45,15 @@ world.addSystem('movement')
46
45
  })
47
46
  .build();
48
47
 
49
- // Create entities and run
48
+ // 4. Create entities
50
49
  const player = world.spawn({
51
50
  position: { x: 0, y: 0 },
52
51
  velocity: { x: 10, y: 5 },
53
52
  health: { value: 100 }
54
53
  });
55
54
 
56
- world.update(1/60); // Run one frame
55
+ // 5. Run the game loop
56
+ world.update(1/60);
57
57
  ```
58
58
 
59
59
  ## Core Concepts
@@ -72,7 +72,7 @@ const entity = world.spawn({
72
72
  // Add components later
73
73
  world.entityManager.addComponent(entity.id, 'velocity', { x: 5, y: 0 });
74
74
 
75
- // Get component data
75
+ // Get component data (returns null if not found)
76
76
  const position = world.entityManager.getComponent(entity.id, 'position');
77
77
 
78
78
  // Remove components or entities
@@ -133,21 +133,32 @@ world.addSystem('scoring')
133
133
 
134
134
  ### Method Chaining
135
135
 
136
- ECSpresso supports fluent method chaining for creating multiple systems:
136
+ Chain multiple systems using `.and()` for cleaner code. The `.and()` method returns the parent container (ECSpresso or Bundle), enabling fluent chaining:
137
137
 
138
138
  ```typescript
139
+ // Chaining systems on ECSpresso
139
140
  world.addSystem('physics')
140
141
  .addQuery('moving', { with: ['position', 'velocity'] })
141
142
  .setProcess((queries, deltaTime) => {
142
143
  // Physics logic
143
144
  })
144
- .and() // Complete this system and continue chaining
145
+ .and() // Returns ECSpresso for continued chaining
145
146
  .addSystem('rendering')
146
147
  .addQuery('visible', { with: ['position', 'sprite'] })
147
148
  .setProcess((queries) => {
148
149
  // Rendering logic
149
150
  })
150
- .build(); // Only the final system needs .build()
151
+ .build();
152
+
153
+ // Chaining systems in a Bundle
154
+ const bundle = new Bundle<Components>()
155
+ .addSystem('movement')
156
+ .setProcess(() => { /* ... */ })
157
+ .and() // Returns Bundle for continued chaining
158
+ .addSystem('collision')
159
+ .setProcess(() => { /* ... */ })
160
+ .and()
161
+ .addResource('config', { speed: 10 });
151
162
  ```
152
163
 
153
164
  ### Query Type Utilities
@@ -158,9 +169,9 @@ Extract entity types from queries to create reusable helper functions:
158
169
  import { createQueryDefinition, QueryResultEntity } from 'ecspresso';
159
170
 
160
171
  // Create reusable query definitions
161
- const movingQuery = createQueryDefinition({
162
- with: ['position', 'velocity'],
163
- without: ['frozen']
172
+ const movingQuery = createQueryDefinition<Components>({
173
+ with: ['position', 'velocity'] as const,
174
+ without: ['frozen'] as const
164
175
  });
165
176
 
166
177
  // Extract entity type for helper functions
@@ -177,7 +188,7 @@ world.addSystem('movement')
177
188
  .addQuery('entities', movingQuery)
178
189
  .setProcess((queries, deltaTime) => {
179
190
  for (const entity of queries.entities) {
180
- updatePosition(entity, deltaTime); // Perfect type safety!
191
+ updatePosition(entity, deltaTime);
181
192
  }
182
193
  })
183
194
  .build();
@@ -207,23 +218,49 @@ Organize related systems and resources into reusable bundles:
207
218
  ```typescript
208
219
  import { Bundle } from 'ecspresso';
209
220
 
210
- const inputBundle = new Bundle<Components, Events, Resources>('input')
211
- .addSystem('playerInput')
212
- .addQuery('players', { with: ['position', 'velocity', 'player'] })
221
+ interface GameComponents {
222
+ position: { x: number; y: number };
223
+ velocity: { x: number; y: number };
224
+ sprite: { texture: string };
225
+ }
226
+
227
+ interface GameResources {
228
+ gravity: { value: number };
229
+ }
230
+
231
+ // Create a bundle with multiple systems using .and() for chaining
232
+ const physicsBundle = new Bundle<GameComponents, {}, GameResources>('physics')
233
+ .addSystem('applyVelocity')
234
+ .addQuery('moving', { with: ['position', 'velocity'] })
235
+ .setProcess((queries, deltaTime) => {
236
+ for (const entity of queries.moving) {
237
+ entity.components.position.x += entity.components.velocity.x * deltaTime;
238
+ entity.components.position.y += entity.components.velocity.y * deltaTime;
239
+ }
240
+ })
241
+ .and() // Returns the bundle for continued chaining
242
+ .addSystem('applyGravity')
243
+ .addQuery('falling', { with: ['velocity'] })
213
244
  .setProcess((queries, deltaTime, ecs) => {
214
- // Handle input
215
- });
245
+ const gravity = ecs.getResource('gravity');
246
+ for (const entity of queries.falling) {
247
+ entity.components.velocity.y += gravity.value * deltaTime;
248
+ }
249
+ })
250
+ .and()
251
+ .addResource('gravity', { value: 9.8 });
216
252
 
217
- const renderBundle = new Bundle<Components, Events, Resources>('render')
253
+ const renderBundle = new Bundle<GameComponents>('render')
218
254
  .addSystem('renderer')
219
255
  .addQuery('sprites', { with: ['position', 'sprite'] })
220
256
  .setProcess((queries) => {
221
257
  // Render sprites
222
- });
258
+ })
259
+ .and();
223
260
 
224
261
  // Create world with bundles
225
- const game = ECSpresso.create<Components, Events, Resources>()
226
- .withBundle(inputBundle)
262
+ const game = ECSpresso.create<GameComponents, {}, GameResources>()
263
+ .withBundle(physicsBundle)
227
264
  .withBundle(renderBundle)
228
265
  .build();
229
266
  ```
@@ -238,7 +275,7 @@ interface Events {
238
275
  levelComplete: { score: number };
239
276
  }
240
277
 
241
- const world = new ECSpresso<Components, Events, Resources>();
278
+ const world = new ECSpresso<Components, Events>();
242
279
 
243
280
  // Handle events in systems
244
281
  world.addSystem('gameLogic')
@@ -246,7 +283,7 @@ world.addSystem('gameLogic')
246
283
  playerDied: {
247
284
  handler: (data, ecs) => {
248
285
  console.log(`Player ${data.playerId} died`);
249
- // Respawn logic
286
+ // Respawn logic here
250
287
  }
251
288
  }
252
289
  })
@@ -261,6 +298,13 @@ world.eventBus.publish('playerDied', { playerId: 1 });
261
298
  Create resources lazily with factory functions:
262
299
 
263
300
  ```typescript
301
+ interface Resources {
302
+ config: { difficulty: string; soundEnabled: boolean };
303
+ assets: { textures: any[] };
304
+ }
305
+
306
+ const world = new ECSpresso<Components, {}, Resources>();
307
+
264
308
  // Sync factory
265
309
  world.addResource('config', () => ({
266
310
  difficulty: 'normal',
@@ -338,12 +382,12 @@ const bundle2 = new Bundle<{velocity: {x: number, y: number}}>('bundle2');
338
382
 
339
383
  const world = ECSpresso.create()
340
384
  .withBundle(bundle1)
341
- .withBundle(bundle2) // Types merge successfully
385
+ .withBundle(bundle2) // Types merge successfully
342
386
  .build();
343
387
 
344
- // ❌ Conflicting types cause TypeScript errors
388
+ // ❌ Conflicting types error at compile time
345
389
  const conflictingBundle = new Bundle<{position: string}>('conflict');
346
- // world.withBundle(conflictingBundle); // TypeScript error!
390
+ world.withBundle(conflictingBundle); // TypeScript prevents this
347
391
  ```
348
392
 
349
393
  ## Component Callbacks
@@ -361,10 +405,39 @@ world.entityManager.onComponentRemoved('health', (oldValue, entity) => {
361
405
  });
362
406
  ```
363
407
 
408
+ ## Error Handling
409
+
410
+ ECSpresso provides clear, contextual error messages for common issues:
411
+
412
+ ```typescript
413
+ // Resource not found with helpful context
414
+ try {
415
+ const missing = world.getResource('nonexistent');
416
+ } catch (error) {
417
+ console.error(error.message);
418
+ // "Resource 'nonexistent' not found. Available resources: [config, score, settings]"
419
+ }
420
+
421
+ // Entity operations with detailed context
422
+ try {
423
+ world.entityManager.addComponent(999, 'position', { x: 0, y: 0 });
424
+ } catch (error) {
425
+ console.error(error.message);
426
+ // "Cannot add component 'position': Entity with ID 999 does not exist"
427
+ }
428
+
429
+ // Component not found returns null
430
+ const component = world.entityManager.getComponent(123, 'position');
431
+ if (component === null) {
432
+ console.log('Component not found');
433
+ }
434
+ ```
435
+
364
436
  ## Performance Tips
365
437
 
366
- - Use query type utilities to extract business logic into testable helper functions
367
- - Bundle related systems for better organization
368
- - Use system priorities to ensure correct execution order
369
- - Leverage resource factories for expensive initialization
438
+ - Extract business logic into testable helper functions using query type utilities
439
+ - Bundle related systems for better organization and reusability
440
+ - Use system priorities to control execution order
441
+ - Use resource factories for expensive initialization (textures, audio, etc.)
370
442
  - Consider component callbacks for immediate reactions to state changes
443
+ - Minimize the number of components in queries when possible to leverage indexing
package/dist/bundle.d.ts CHANGED
@@ -60,17 +60,24 @@ export default class Bundle<ComponentTypes extends Record<string, any> = {}, Eve
60
60
  */
61
61
  hasResource<K extends keyof ResourceTypes>(key: K): boolean;
62
62
  }
63
+ /**
64
+ * Utility type to check if two types are exactly the same
65
+ */
63
66
  type Exactly<T, U> = T extends U ? U extends T ? true : false : false;
64
- type IncompatibleBundles<C1 extends Record<string, any>, C2 extends Record<string, any>, E1 extends Record<string, any>, E2 extends Record<string, any>, R1 extends Record<string, any>, R2 extends Record<string, any>> = {
65
- [K in keyof C1 & keyof C2]: Exactly<C1[K], C2[K]> extends false ? never : unknown;
67
+ /**
68
+ * Simplified type constraint for bundle compatibility
69
+ * Ensures that overlapping keys have exactly the same types
70
+ */
71
+ type CompatibleBundles<C1 extends Record<string, any>, C2 extends Record<string, any>, E1 extends Record<string, any>, E2 extends Record<string, any>, R1 extends Record<string, any>, R2 extends Record<string, any>> = {
72
+ [K in keyof C1 & keyof C2]: Exactly<C1[K], C2[K]> extends true ? C1[K] : never;
66
73
  } & {
67
- [K in keyof E1 & keyof E2]: Exactly<E1[K], E2[K]> extends false ? never : unknown;
74
+ [K in keyof E1 & keyof E2]: Exactly<E1[K], E2[K]> extends true ? E1[K] : never;
68
75
  } & {
69
- [K in keyof R1 & keyof R2]: Exactly<R1[K], R2[K]> extends false ? never : unknown;
76
+ [K in keyof R1 & keyof R2]: Exactly<R1[K], R2[K]> extends true ? R1[K] : never;
70
77
  };
71
78
  /**
72
79
  * Function that merges multiple bundles into a single bundle
73
80
  */
74
- export declare function mergeBundles<C1 extends Record<string, any>, E1 extends Record<string, any>, R1 extends Record<string, any>, C2 extends Record<string, any>, E2 extends Record<string, any>, R2 extends Record<string, any>>(id: string, bundle1: Bundle<C1, E1, R1>, bundle2: Bundle<C2, E2, R2> & IncompatibleBundles<C1, C2, E1, E2, R1, R2>): Bundle<C1 & C2, E1 & E2, R1 & R2>;
81
+ export declare function mergeBundles<C1 extends Record<string, any>, E1 extends Record<string, any>, R1 extends Record<string, any>, C2 extends Record<string, any>, E2 extends Record<string, any>, R2 extends Record<string, any>>(id: string, bundle1: Bundle<C1, E1, R1>, bundle2: Bundle<C2, E2, R2> & CompatibleBundles<C1, C2, E1, E2, R1, R2>): Bundle<C1 & C2, E1 & E2, R1 & R2>;
75
82
  export declare function mergeBundles<ComponentTypes extends Record<string, any>, EventTypes extends Record<string, any>, ResourceTypes extends Record<string, any>>(id: string, ...bundles: Array<Bundle<ComponentTypes, EventTypes, ResourceTypes>>): Bundle<ComponentTypes, EventTypes, ResourceTypes>;
76
83
  export {};
@@ -1,21 +1,8 @@
1
1
  import EntityManager from "./entity-manager";
2
2
  import EventBus from "./event-bus";
3
- import type { FilteredEntity, Entity } from "./types";
3
+ import type { System, FilteredEntity, Entity } from "./types";
4
4
  import type Bundle from "./bundle";
5
- /**
6
- * Type helper to detect conflicting types between two record types.
7
- * Returns a union of keys that exist in both T and U but have incompatible types.
8
- */
9
- type GetConflictingKeys<T, U> = {
10
- [K in keyof T & keyof U]: T[K] extends U[K] ? U[K] extends T[K] ? never : K : K;
11
- }[keyof T & keyof U];
12
- /**
13
- * Simplified type helper to check bundle type compatibility.
14
- * Returns true if bundles can be merged without type conflicts.
15
- */
16
- type BundlesAreCompatible<C1 extends Record<string, any>, C2 extends Record<string, any>, E1 extends Record<string, any>, E2 extends Record<string, any>, R1 extends Record<string, any>, R2 extends Record<string, any>> = [
17
- keyof C1
18
- ] extends [never] ? [keyof E1] extends [never] ? [keyof R1] extends [never] ? true : GetConflictingKeys<R1, R2> extends never ? true : false : GetConflictingKeys<E1, E2> extends never ? GetConflictingKeys<R1, R2> extends never ? true : false : false : GetConflictingKeys<C1, C2> extends never ? GetConflictingKeys<E1, E2> extends never ? GetConflictingKeys<R1, R2> extends never ? true : false : false : false;
5
+ import type { BundlesAreCompatible } from "./type-utils";
19
6
  /**
20
7
  * Interface declaration for ECSpresso constructor to ensure type augmentation works properly.
21
8
  * This merges with the class declaration below.
@@ -115,6 +102,11 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
115
102
  * @returns true if the system was found and removed, false otherwise
116
103
  */
117
104
  removeSystem(label: string): boolean;
105
+ /**
106
+ * Internal method to register a system with this ECSpresso instance
107
+ * @internal Used by SystemBuilder - replaces direct private property access
108
+ */
109
+ _registerSystem(system: System<ComponentTypes, any, any, EventTypes, ResourceTypes>): void;
118
110
  /**
119
111
  * Check if a resource exists
120
112
  */
@@ -204,4 +196,3 @@ export declare class ECSpressoBuilder<C extends Record<string, any> = {}, E exte
204
196
  */
205
197
  build(): ECSpresso<C, E, R>;
206
198
  }
207
- export {};
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- class Z{nextId=1;entities=new Map;componentIndices=new Map;addedCallbacks=new Map;removedCallbacks=new Map;createEntity(){let j=this.nextId++,G={id:j,components:{}};return this.entities.set(j,G),G}addComponent(j,G,H){let J=typeof j==="number"?this.entities.get(j):j;if(!J)throw new Error(`Entity ${j} does not exist`);if(J.components[G]=H,!this.componentIndices.has(G))this.componentIndices.set(G,new Set);this.componentIndices.get(G)?.add(J.id);let P=this.addedCallbacks.get(G);if(P)for(let X of P)X(H,J);return this}addComponents(j,G){let H=typeof j==="number"?this.entities.get(j):j;if(!H)throw new Error(`Entity ${j} does not exist`);for(let J in G)this.addComponent(H,J,G[J]);return this}removeComponent(j,G){let H=typeof j==="number"?this.entities.get(j):j;if(!H)throw new Error(`Entity ${j} does not exist`);let J=H.components[G];delete H.components[G];let P=this.removedCallbacks.get(G);if(P&&J!==void 0)for(let X of P)X(J,H);return this.componentIndices.get(G)?.delete(H.id),this}getComponent(j,G){let H=this.entities.get(j);if(!H)throw new Error(`Entity ${j} does not exist`);return H.components[G]||null}getEntitiesWithQuery(j=[],G=[]){if(j.length===0){if(G.length===0)return Array.from(this.entities.values());return Array.from(this.entities.values()).filter((P)=>{return G.every((X)=>!(X in P.components))})}let H=j.reduce((P,X)=>{let Y=this.componentIndices.get(X),g=Y?Y.size:0,w=this.componentIndices.get(P)?.size??1/0;return g<w?X:P},j[0]);return Array.from(this.componentIndices.get(H)||[]).filter((P)=>{let X=this.entities.get(P);return X&&j.every((Y)=>(Y in X.components))&&G.every((Y)=>!(Y in X.components))}).map((P)=>this.entities.get(P))}removeEntity(j){let G=typeof j==="number"?this.entities.get(j):j;if(!G)return!1;for(let H of Object.keys(G.components))this.componentIndices.get(H)?.delete(G.id);return this.entities.delete(G.id)}getEntity(j){return this.entities.get(j)}onComponentAdded(j,G){if(!this.addedCallbacks.has(j))this.addedCallbacks.set(j,new Set);return this.addedCallbacks.get(j).add(G),this}onComponentRemoved(j,G){if(!this.removedCallbacks.has(j))this.removedCallbacks.set(j,new Set);return this.removedCallbacks.get(j).add(G),this}}class _{handlers=new Map;subscribe(j,G){return this.addHandler(j,G,!1)}once(j,G){return this.addHandler(j,G,!0)}addHandler(j,G,H){if(!this.handlers.has(j))this.handlers.set(j,[]);let J={callback:G,once:H};return this.handlers.get(j).push(J),()=>{let P=this.handlers.get(j);if(P){let X=P.indexOf(J);if(X!==-1)P.splice(X,1)}}}publish(j,G){let H=this.handlers.get(j);if(!H)return;let J=[...H],P=[];for(let X of J)if(X.callback(G),X.once)P.push(X);if(P.length>0)for(let X of P){let Y=H.indexOf(X);if(Y!==-1)H.splice(Y,1)}}clear(){this.handlers.clear()}clearEvent(j){this.handlers.delete(j)}}class ${resources=new Map;resourceFactories=new Map;initializedResourceKeys=new Set;add(j,G){if(typeof G==="function"&&!/^class\s/.test(Function.prototype.toString.call(G)))this.resourceFactories.set(j,G);else this.resources.set(j,G),this.initializedResourceKeys.add(j);return this}get(j,G){let H=this.resources.get(j);if(H!==void 0)return H;let J=this.resourceFactories.get(j);if(J===void 0)throw new Error(`Resource ${String(j)} not found`);let P=J(G);if(!(P instanceof Promise))this.resources.set(j,P),this.initializedResourceKeys.add(j);return P}has(j){return this.resources.has(j)||this.resourceFactories.has(j)}remove(j){let G=this.resources.delete(j),H=this.resourceFactories.delete(j);if(this.initializedResourceKeys.has(j))this.initializedResourceKeys.delete(j);return G||H}getKeys(){let j=new Set([...this.resources.keys(),...this.resourceFactories.keys()]);return Array.from(j)}needsInitialization(j){return this.resourceFactories.has(j)&&!this.initializedResourceKeys.has(j)}getPendingInitializationKeys(){return Array.from(this.resourceFactories.keys()).filter((j)=>!this.initializedResourceKeys.has(j))}async initializeResource(j,G){if(!this.resourceFactories.has(j)||this.initializedResourceKeys.has(j))return;let J=await this.resourceFactories.get(j)(G);this.resources.set(j,J),this.initializedResourceKeys.add(j),this.resourceFactories.delete(j)}async initializeResources(j,...G){if(G.length===0){let H=this.getPendingInitializationKeys();await Promise.all(H.map((J)=>this.initializeResource(J,j)));return}await Promise.all(G.map((H)=>this.initializeResource(H,j)))}}class U{_label;_ecspresso;_bundle;queries={};processFunction;detachFunction;initializeFunction;eventHandlers;_priority=0;_isRegistered=!1;constructor(j,G=null,H=null){this._label=j;this._ecspresso=G;this._bundle=H}get label(){return this._label}get bundle(){return this._bundle}get ecspresso(){return this._ecspresso}_autoRegister(){if(this._isRegistered||!this._ecspresso)return;let j=this._buildSystemObject();D(j,this._ecspresso),this._isRegistered=!0}_buildSystemObject(){let j={label:this._label,entityQueries:this.queries,priority:this._priority};if(this.processFunction)j.process=this.processFunction;if(this.detachFunction)j.onDetach=this.detachFunction;if(this.initializeFunction)j.onInitialize=this.initializeFunction;if(this.eventHandlers)j.eventHandlers=this.eventHandlers;return j}setPriority(j){return this._priority=j,this}addQuery(j,G){let H=this;return H.queries={...this.queries,[j]:G},H}setProcess(j){return this.processFunction=j,this}registerAndContinue(){if(!this._ecspresso)throw new Error("Cannot register system: no ECSpresso instance attached");return this._autoRegister(),this._ecspresso}and(){if(!this._ecspresso)throw new Error("Cannot use and() method: no ECSpresso instance attached");return this._autoRegister(),this._ecspresso}setOnDetach(j){return this.detachFunction=j,this}setOnInitialize(j){return this.initializeFunction=j,this}setEventHandlers(j){return this.eventHandlers=j,this}build(j){let G={label:this._label,entityQueries:this.queries,priority:this._priority};if(this.processFunction)G.process=this.processFunction;if(this.detachFunction)G.onDetach=this.detachFunction;if(this.initializeFunction)G.onInitialize=this.initializeFunction;if(this.eventHandlers)G.eventHandlers=this.eventHandlers;if(this._ecspresso)D(G,this._ecspresso);if(j)D(G,j);return this}}function D(j,G){if(G._systems.push(j),G._sortSystems(),!j.eventHandlers)return;for(let H in j.eventHandlers){let J=j.eventHandlers[H]?.handler;J&&G.eventBus.subscribe(H,(P)=>{J(P,G)})}}function V(j,G){return new U(j,G)}function M(j,G){return new U(j,null,G)}var Q="0.4.1";var z={};class A{static VERSION=Q;_entityManager;_eventBus;_resourceManager;_systems=[];_sortedSystems=[];_installedBundles=new Set;constructor(){this._entityManager=new Z,this._eventBus=new _,this._resourceManager=new $,this._sortedSystems=[]}static create(){return new W}addSystem(j){return V(j,this)}update(j){for(let G of this._sortedSystems){if(!G.process)continue;let H={},J=!1,P=!1;if(G.entityQueries)for(let X in G.entityQueries){P=!0;let Y=G.entityQueries[X];if(Y){if(H[X]=this._entityManager.getEntitiesWithQuery(Y.with,Y.without||[]),H[X].length)J=!0}}if(J)G.process(H,j,this);else if(!P)G.process(z,j,this)}}async initialize(){await this.initializeResources();for(let j of this._systems)await j.onInitialize?.(this)}async initializeResources(...j){await this._resourceManager.initializeResources(this,...j)}_sortSystems(){this._sortedSystems=[...this._systems].sort((j,G)=>{let H=j.priority??0;return(G.priority??0)-H})}updateSystemPriority(j,G){let H=this._systems.find((J)=>J.label===j);if(!H)return!1;return H.priority=G,this._sortSystems(),!0}removeSystem(j){let G=this._systems.findIndex((J)=>J.label===j);if(G===-1)return!1;let H=this._systems[G];if(!H)return!1;if(H.onDetach)H.onDetach(this);return this._systems.splice(G,1),this._sortSystems(),!0}hasResource(j){return this._resourceManager.has(j)}getResource(j){let G=this._resourceManager.get(j,this);if(!G)throw new Error(`Resource "${j.toString()}" not found`);return G}addResource(j,G){return this._resourceManager.add(j,G),this}removeResource(j){return this._resourceManager.remove(j)}updateResource(j,G){let H=this.getResource(j),J=G(H);return this._resourceManager.add(j,J),this}getResourceKeys(){return this._resourceManager.getKeys()}resourceNeedsInitialization(j){return this._resourceManager.needsInitialization(j)}hasComponent(j,G){return this._entityManager.getComponent(j,G)!==null}spawn(j){let G=this._entityManager.createEntity();return this._entityManager.addComponents(G,j),G}getEntitiesWithQuery(j,G=[]){return this._entityManager.getEntitiesWithQuery(j,G)}get installedBundles(){return Array.from(this._installedBundles)}get entityManager(){return this._entityManager}get eventBus(){return this._eventBus}_installBundle(j){if(this._installedBundles.has(j.id))return this;this._installedBundles.add(j.id),j.registerSystemsWithEcspresso(this);let G=j.getResources();for(let[H,J]of G.entries())this._resourceManager.add(H,J);return this}}class W{ecspresso;constructor(){this.ecspresso=new A}withBundle(j){return this.ecspresso._installBundle(j),this}build(){return this.ecspresso}}function K(){return`bundle_${Date.now().toString(36)}_${Math.random().toString(36).substring(2,9)}`}class F{_systems=[];_resources=new Map;_id;constructor(j){this._id=j||K()}get id(){return this._id}set id(j){this._id=j}addSystem(j){if(typeof j==="string"){let G=M(j,this);return this._systems.push(G),G}else return this._systems.push(j),j}addResource(j,G){return this._resources.set(j,G),this}getSystems(){return this._systems.map((j)=>j.build())}registerSystemsWithEcspresso(j){for(let G of this._systems)G.build(j)}getResources(){return new Map(this._resources)}getResource(j){return this._resources.get(j)}getSystemBuilders(){return[...this._systems]}hasResource(j){return this._resources.has(j)}}function f(j,...G){if(G.length===0)return new F(j);let H=new F(j);for(let J of G){for(let P of J.getSystemBuilders())H.addSystem(P);for(let[P,X]of J.getResources().entries())H.addResource(P,X)}return H}function b(j){return j}var r=A;export{f as mergeBundles,r as default,b as createQueryDefinition,U as SystemBuilder,$ as ResourceManager,_ as EventBus,Z as EntityManager,F as Bundle};
1
+ class F{nextId=1;entities=new Map;componentIndices=new Map;addedCallbacks=new Map;removedCallbacks=new Map;createEntity(){let j=this.nextId++,G={id:j,components:{}};return this.entities.set(j,G),G}addComponent(j,G,H){let J=typeof j==="number"?this.entities.get(j):j;if(!J){let Y=typeof j==="number"?j:j.id;throw Error(`Cannot add component '${String(G)}': Entity with ID ${Y} does not exist`)}if(J.components[G]=H,!this.componentIndices.has(G))this.componentIndices.set(G,new Set);this.componentIndices.get(G)?.add(J.id);let X=this.addedCallbacks.get(G);if(X)for(let Y of X)Y(H,J);return this}addComponents(j,G){let H=typeof j==="number"?this.entities.get(j):j;if(!H){let J=typeof j==="number"?j:j.id;throw Error(`Cannot add components: Entity with ID ${J} does not exist`)}for(let J in G)this.addComponent(H,J,G[J]);return this}removeComponent(j,G){let H=typeof j==="number"?this.entities.get(j):j;if(!H){let Y=typeof j==="number"?j:j.id;throw Error(`Cannot remove component '${String(G)}': Entity with ID ${Y} does not exist`)}let J=H.components[G];delete H.components[G];let X=this.removedCallbacks.get(G);if(X&&J!==void 0)for(let Y of X)Y(J,H);return this.componentIndices.get(G)?.delete(H.id),this}getComponent(j,G){let H=this.entities.get(j);if(!H)throw Error(`Cannot get component '${String(G)}': Entity with ID ${j} does not exist`);return H.components[G]||null}getEntitiesWithQuery(j=[],G=[]){if(j.length===0){if(G.length===0)return Array.from(this.entities.values());return Array.from(this.entities.values()).filter((Z)=>{return G.every((_)=>!(_ in Z.components))})}let H=j.reduce((Z,_)=>{let $=this.componentIndices.get(_)?.size??0,w=this.componentIndices.get(Z)?.size??1/0;return $<w?_:Z},j[0]),J=this.componentIndices.get(H);if(!J||J.size===0)return[];let X=[],Y=G.length>0;for(let Z of J){let _=this.entities.get(Z);if(_&&j.every(($)=>($ in _.components))&&(!Y||G.every(($)=>!($ in _.components))))X.push(_)}return X}removeEntity(j){let G=typeof j==="number"?this.entities.get(j):j;if(!G)return!1;for(let H of Object.keys(G.components)){let J=G.components[H];if(J!==void 0){let X=this.removedCallbacks.get(H);if(X)for(let Y of X)Y(J,G)}this.componentIndices.get(H)?.delete(G.id)}return this.entities.delete(G.id)}getEntity(j){return this.entities.get(j)}onComponentAdded(j,G){if(!this.addedCallbacks.has(j))this.addedCallbacks.set(j,new Set);return this.addedCallbacks.get(j).add(G),this}onComponentRemoved(j,G){if(!this.removedCallbacks.has(j))this.removedCallbacks.set(j,new Set);return this.removedCallbacks.get(j).add(G),this}}class P{handlers=new Map;subscribe(j,G){return this.addHandler(j,G,!1)}once(j,G){return this.addHandler(j,G,!0)}addHandler(j,G,H){if(!this.handlers.has(j))this.handlers.set(j,[]);let J={callback:G,once:H};return this.handlers.get(j).push(J),()=>{let X=this.handlers.get(j);if(X){let Y=X.indexOf(J);if(Y!==-1)X.splice(Y,1)}}}publish(j,G){let H=this.handlers.get(j);if(!H)return;let J=[...H],X=[];for(let Y of J)if(Y.callback(G),Y.once)X.push(Y);if(X.length>0)for(let Y of X){let Z=H.indexOf(Y);if(Z!==-1)H.splice(Z,1)}}clear(){this.handlers.clear()}clearEvent(j){this.handlers.delete(j)}}class U{resources=new Map;resourceFactories=new Map;initializedResourceKeys=new Set;add(j,G){if(this._isFactoryFunction(G))this.resourceFactories.set(j,G);else this.resources.set(j,G),this.initializedResourceKeys.add(j);return this}_isFactoryFunction(j){if(typeof j!=="function")return!1;let G=j.toString();if(G.startsWith("class "))return!1;if(G.includes("[native code]"))return!1;if(j.prototype){let H=Object.getOwnPropertyNames(j.prototype);if(H.length>1||H.length===1&&H[0]!=="constructor")return!1}if(j.name&&j.name[0]===j.name[0].toUpperCase()&&j.name.length>1){if(G.includes("this.")||G.includes("new "))return!1}return!0}get(j,G){let H=this.resources.get(j);if(H!==void 0)return H;let J=this.resourceFactories.get(j);if(J===void 0)throw Error(`Resource ${String(j)} not found`);let X=J(G);if(!(X instanceof Promise))this.resources.set(j,X),this.initializedResourceKeys.add(j);return X}has(j){return this.resources.has(j)||this.resourceFactories.has(j)}remove(j){let G=this.resources.delete(j),H=this.resourceFactories.delete(j);if(this.initializedResourceKeys.has(j))this.initializedResourceKeys.delete(j);return G||H}getKeys(){let j=new Set([...this.resources.keys(),...this.resourceFactories.keys()]);return Array.from(j)}needsInitialization(j){return this.resourceFactories.has(j)&&!this.initializedResourceKeys.has(j)}getPendingInitializationKeys(){return Array.from(this.resourceFactories.keys()).filter((j)=>!this.initializedResourceKeys.has(j))}async initializeResource(j,G){if(!this.resourceFactories.has(j)||this.initializedResourceKeys.has(j))return;let J=await this.resourceFactories.get(j)(G);this.resources.set(j,J),this.initializedResourceKeys.add(j),this.resourceFactories.delete(j)}async initializeResources(j,...G){if(G.length===0){let H=this.getPendingInitializationKeys();await Promise.all(H.map((J)=>this.initializeResource(J,j)));return}await Promise.all(G.map((H)=>this.initializeResource(H,j)))}}class M{_label;_ecspresso;_bundle;queries={};processFunction;detachFunction;initializeFunction;eventHandlers;_priority=0;_isRegistered=!1;constructor(j,G=null,H=null){this._label=j;this._ecspresso=G;this._bundle=H}get label(){return this._label}get bundle(){return this._bundle}get ecspresso(){return this._ecspresso}_autoRegister(){if(this._isRegistered||!this._ecspresso)return;let j=this._buildSystemObject();Q(j,this._ecspresso),this._isRegistered=!0}_buildSystemObject(){return this._createSystemObject()}_createSystemObject(){let j={label:this._label,entityQueries:this.queries,priority:this._priority};if(this.processFunction)j.process=this.processFunction;if(this.detachFunction)j.onDetach=this.detachFunction;if(this.initializeFunction)j.onInitialize=this.initializeFunction;if(this.eventHandlers)j.eventHandlers=this.eventHandlers;return j}setPriority(j){return this._priority=j,this}addQuery(j,G){let H=this;return H.queries={...this.queries,[j]:G},H}setProcess(j){return this.processFunction=j,this}registerAndContinue(){if(!this._ecspresso)throw Error(`Cannot register system '${this._label}': SystemBuilder is not attached to an ECSpresso instance. Use Bundle.addSystem() or ECSpresso.addSystem() instead.`);return this._autoRegister(),this._ecspresso}and(){if(this._ecspresso)return this._autoRegister(),this._ecspresso;if(this._bundle)return this._bundle;throw Error(`Cannot use and() on system '${this._label}': not attached to ECSpresso or Bundle.`)}setOnDetach(j){return this.detachFunction=j,this}setOnInitialize(j){return this.initializeFunction=j,this}setEventHandlers(j){return this.eventHandlers=j,this}build(j){let G=this._createSystemObject();if(this._ecspresso)Q(G,this._ecspresso);if(j)Q(G,j);return this}}function Q(j,G){G._registerSystem(j)}function W(j,G){return new M(j,G)}function g(j,G){return new M(j,null,G)}var K="0.4.3";var z={};class A{static VERSION=K;_entityManager;_eventBus;_resourceManager;_systems=[];_sortedSystems=[];_installedBundles=new Set;constructor(){this._entityManager=new F,this._eventBus=new P,this._resourceManager=new U,this._sortedSystems=[]}static create(){return new V}addSystem(j){return W(j,this)}update(j){for(let G of this._sortedSystems){if(!G.process)continue;let H={},J=!1,X=!1;if(G.entityQueries)for(let Y in G.entityQueries){X=!0;let Z=G.entityQueries[Y];if(Z){if(H[Y]=this._entityManager.getEntitiesWithQuery(Z.with,Z.without||[]),H[Y].length)J=!0}}if(J)G.process(H,j,this);else if(!X)G.process(z,j,this)}}async initialize(){await this.initializeResources();for(let j of this._systems)await j.onInitialize?.(this)}async initializeResources(...j){await this._resourceManager.initializeResources(this,...j)}_sortSystems(){this._sortedSystems=[...this._systems].sort((j,G)=>{let H=j.priority??0;return(G.priority??0)-H})}updateSystemPriority(j,G){let H=this._systems.find((J)=>J.label===j);if(!H)return!1;return H.priority=G,this._sortSystems(),!0}removeSystem(j){let G=this._systems.findIndex((J)=>J.label===j);if(G===-1)return!1;let H=this._systems[G];if(!H)return!1;if(H.onDetach)H.onDetach(this);return this._systems.splice(G,1),this._sortSystems(),!0}_registerSystem(j){if(this._systems.push(j),this._sortSystems(),!j.eventHandlers)return;for(let G in j.eventHandlers){let H=j.eventHandlers[G]?.handler;if(H)this._eventBus.subscribe(G,(J)=>{H(J,this)})}}hasResource(j){return this._resourceManager.has(j)}getResource(j){let G=this._resourceManager.get(j,this);if(!G)throw Error(`Resource '${String(j)}' not found. Available resources: [${this.getResourceKeys().map((H)=>String(H)).join(", ")}]`);return G}addResource(j,G){return this._resourceManager.add(j,G),this}removeResource(j){return this._resourceManager.remove(j)}updateResource(j,G){let H=this.getResource(j),J=G(H);return this._resourceManager.add(j,J),this}getResourceKeys(){return this._resourceManager.getKeys()}resourceNeedsInitialization(j){return this._resourceManager.needsInitialization(j)}hasComponent(j,G){return this._entityManager.getComponent(j,G)!==null}spawn(j){let G=this._entityManager.createEntity();return this._entityManager.addComponents(G,j),G}getEntitiesWithQuery(j,G=[]){return this._entityManager.getEntitiesWithQuery(j,G)}get installedBundles(){return Array.from(this._installedBundles)}get entityManager(){return this._entityManager}get eventBus(){return this._eventBus}_installBundle(j){if(this._installedBundles.has(j.id))return this;this._installedBundles.add(j.id),j.registerSystemsWithEcspresso(this);let G=j.getResources();for(let[H,J]of G.entries())this._resourceManager.add(H,J);return this}}class V{ecspresso;constructor(){this.ecspresso=new A}withBundle(j){return this.ecspresso._installBundle(j),this}build(){return this.ecspresso}}function f(){return`bundle_${Date.now().toString(36)}_${Math.random().toString(36).substring(2,9)}`}class D{_systems=[];_resources=new Map;_id;constructor(j){this._id=j||f()}get id(){return this._id}set id(j){this._id=j}addSystem(j){if(typeof j==="string"){let G=g(j,this);return this._systems.push(G),G}else return this._systems.push(j),j}addResource(j,G){return this._resources.set(j,G),this}getSystems(){return this._systems.map((j)=>j.build())}registerSystemsWithEcspresso(j){for(let G of this._systems)G.build(j)}getResources(){return new Map(this._resources)}getResource(j){return this._resources.get(j)}getSystemBuilders(){return[...this._systems]}hasResource(j){return this._resources.has(j)}}function E(j,...G){if(G.length===0)return new D(j);let H=new D(j);for(let J of G){for(let X of J.getSystemBuilders())H.addSystem(X);for(let[X,Y]of J.getResources().entries())H.addResource(X,Y)}return H}function p(j){return j}var a=A;export{E as mergeBundles,a as default,p as createQueryDefinition,M as SystemBuilder,U as ResourceManager,P as EventBus,F as EntityManager,D as Bundle};
2
2
 
3
- //# debugId=186F2921EE95DCD564756E2164756E21
3
+ //# debugId=179E5389134FBDFC64756E2164756E21
4
4
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -2,16 +2,16 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/entity-manager.ts", "../src/event-bus.ts", "../src/resource-manager.ts", "../src/system-builder.ts", "../src/ecspresso.ts", "../src/bundle.ts", "../src/types.ts", "../src/index.ts"],
4
4
  "sourcesContent": [
5
- "import type { Entity, FilteredEntity } from \"./types\";\n\nexport default\nclass EntityManager<ComponentTypes> {\n\tprivate nextId: number = 1;\n\tprivate entities: Map<number, Entity<ComponentTypes>> = new Map();\n\tprivate componentIndices: Map<keyof ComponentTypes, Set<number>> = new Map();\n\t/**\n\t * Callbacks registered for component additions\n\t */\n\tprivate addedCallbacks: Map<keyof ComponentTypes, Set<(value: any, entity: Entity<ComponentTypes>) => void>> = new Map();\n\t/**\n\t * Callbacks registered for component removals\n\t */\n\tprivate removedCallbacks: Map<keyof ComponentTypes, Set<(oldValue: any, entity: Entity<ComponentTypes>) => void>> = new Map();\n\n\tcreateEntity(): Entity<ComponentTypes> {\n\t\tconst id = this.nextId++;\n\t\tconst entity: Entity<ComponentTypes> = { id, components: {} };\n\t\tthis.entities.set(id, entity);\n\t\treturn entity;\n\t}\n\n\t// TODO: Component object pooling if(/when) garbage collection is an issue...?\n\taddComponent<ComponentName extends keyof ComponentTypes>(\n\t\tentityOrId: number | Entity<ComponentTypes>,\n\t\tcomponentName: ComponentName,\n\t\tdata: ComponentTypes[ComponentName]\n\t) {\n\t\tconst entity = typeof entityOrId === 'number' ?\n\t\t\tthis.entities.get(entityOrId) :\n\t\t\tentityOrId;\n\n\t\tif (!entity) throw new Error(`Entity ${entityOrId} does not exist`);\n\n\t\tentity.components[componentName] = data;\n\n\t\t// Update component index\n\t\tif (!this.componentIndices.has(componentName)) {\n\t\t\tthis.componentIndices.set(componentName, new Set());\n\t\t}\n\t\tthis.componentIndices.get(componentName)?.add(entity.id);\n\t\t// Trigger added callbacks\n\t\tconst callbacks = this.addedCallbacks.get(componentName);\n\t\tif (callbacks) {\n\t\t\tfor (const cb of callbacks) {\n\t\t\t\tcb(data, entity);\n\t\t\t}\n\t\t}\n\t\treturn this;\n\t}\n\n\t/**\n\t * Add multiple components to an entity at once\n\t * @param entityOrId Entity or entity ID to add components to\n\t * @param components Object with component names as keys and component data as values\n\t */\n\taddComponents<\n\t\tT extends { [K in keyof ComponentTypes]?: ComponentTypes[K] }\n\t>(\n\t\tentityOrId: number | Entity<ComponentTypes>,\n\t\tcomponents: T & Record<Exclude<keyof T, keyof ComponentTypes>, never>\n\t) {\n\t\tconst entity = typeof entityOrId === 'number' ?\n\t\t\tthis.entities.get(entityOrId) :\n\t\t\tentityOrId;\n\n\t\tif (!entity) throw new Error(`Entity ${entityOrId} does not exist`);\n\n\t\tfor (const componentName in components) {\n\t\t\tthis.addComponent(\n\t\t\t\tentity,\n\t\t\t\tcomponentName as keyof ComponentTypes,\n\t\t\t\tcomponents[componentName as keyof T] as ComponentTypes[keyof ComponentTypes]\n\t\t\t);\n\t\t}\n\n\t\treturn this;\n\t}\n\n\tremoveComponent<ComponentName extends keyof ComponentTypes>(\n\t\tentityOrId: number | Entity<ComponentTypes>,\n\t\tcomponentName: ComponentName\n\t) {\n\t\tconst entity = typeof entityOrId === 'number' ?\n\t\t\tthis.entities.get(entityOrId) :\n\t\t\tentityOrId;\n\n\t\tif (!entity) throw new Error(`Entity ${entityOrId} does not exist`);\n\t\t// Get old value for callbacks\n\t\tconst oldValue = entity.components[componentName] as ComponentTypes[ComponentName] | undefined;\n\n\t\tdelete entity.components[componentName];\n\n\t\t// Trigger removed callbacks\n\t\tconst removeCbs = this.removedCallbacks.get(componentName);\n\t\tif (removeCbs && oldValue !== undefined) {\n\t\t\tfor (const cb of removeCbs) {\n\t\t\t\tcb(oldValue, entity);\n\t\t\t}\n\t\t}\n\n\t\t// Update component index\n\t\tthis.componentIndices.get(componentName)?.delete(entity.id);\n\n\t\treturn this;\n\t}\n\n\tgetComponent<ComponentName extends keyof ComponentTypes>(entityId: number, componentName: ComponentName): ComponentTypes[ComponentName] | null {\n\t\tconst entity = this.entities.get(entityId);\n\n\t\tif (!entity) throw new Error(`Entity ${entityId} does not exist`);\n\n\t\treturn entity.components[componentName] || null;\n\t}\n\n\tgetEntitiesWithQuery<\n\t\tWithComponents extends keyof ComponentTypes = never,\n\t\tWithoutComponents extends keyof ComponentTypes = never\n\t>(\n\t\trequired: ReadonlyArray<WithComponents> = [],\n\t\texcluded: ReadonlyArray<WithoutComponents> = [],\n\t): Array<FilteredEntity<ComponentTypes, WithComponents extends never ? never : WithComponents, WithoutComponents extends never ? never : WithoutComponents>> {\n\t\t// Use the smallest component set as base for better performance\n\t\tif (required.length === 0) {\n\t\t\tif (excluded.length === 0) {\n\t\t\t\treturn Array.from(this.entities.values()) as any;\n\t\t\t}\n\n\t\t\treturn Array\n\t\t\t\t.from(this.entities.values())\n\t\t\t\t.filter((entity) => {\n\t\t\t\t\treturn excluded.every(comp => !(comp in entity.components));\n\t\t\t\t}) as any;\n\t\t}\n\n\t\t// Find the component with the smallest entity set to start with\n\t\tconst smallestComponent = required.reduce((smallest, comp) => {\n\t\t\tconst set = this.componentIndices.get(comp);\n\t\t\tconst currentSize = set ? set.size : 0;\n\t\t\tconst smallestSize = this.componentIndices.get(smallest!)?.size ?? Infinity;\n\n\t\t\treturn currentSize < smallestSize ? comp : smallest;\n\t\t}, required[0])!;\n\n\t\t// Start with the entities from the smallest component set\n\t\tconst candidates = Array.from(this.componentIndices.get(smallestComponent) || []);\n\n\t\t// Return full entity objects, not just IDs\n\t\treturn candidates\n\t\t\t.filter(id => {\n\t\t\t\tconst entity = this.entities.get(id);\n\t\t\t\treturn (\n\t\t\t\t\tentity &&\n\t\t\t\t\trequired.every(comp => comp in entity.components) &&\n\t\t\t\t\texcluded.every(comp => !(comp in entity.components))\n\t\t\t\t);\n\t\t\t})\n\t\t\t.map(id => this.entities.get(id)!) as Array<FilteredEntity<ComponentTypes, WithComponents extends never ? never : WithComponents, WithoutComponents extends never ? never : WithoutComponents>>;\n\t}\n\n\tremoveEntity(entityOrId: number | Entity<ComponentTypes>): boolean {\n\t\tconst entity = typeof entityOrId === 'number' ?\n\t\t\tthis.entities.get(entityOrId) :\n\t\t\tentityOrId;\n\n\t\tif (!entity) return false;\n\n\t\t// Remove entity from all component indices\n\t\tfor (const componentName of Object.keys(entity.components) as Array<keyof ComponentTypes>) {\n\t\t\tthis.componentIndices.get(componentName)?.delete(entity.id);\n\t\t}\n\n\t\t// Remove the entity itself\n\t\treturn this.entities.delete(entity.id);\n\t}\n\n\tgetEntity(entityId: number): Entity<ComponentTypes> | undefined {\n\t\treturn this.entities.get(entityId);\n\t}\n\n\t/**\n\t * Register a callback when a specific component is added to any entity\n\t * @param componentName The component key\n\t * @param handler Function receiving the new component value and the entity\n\t */\n\tonComponentAdded<ComponentName extends keyof ComponentTypes>(\n\t\tcomponentName: ComponentName,\n\t\thandler: (value: ComponentTypes[ComponentName], entity: Entity<ComponentTypes>) => void\n\t): this {\n\t\tif (!this.addedCallbacks.has(componentName)) {\n\t\t\tthis.addedCallbacks.set(componentName, new Set());\n\t\t}\n\t\tthis.addedCallbacks.get(componentName)!.add(handler as any);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Register a callback when a specific component is removed from any entity\n\t * @param componentName The component key\n\t * @param handler Function receiving the old component value and the entity\n\t */\n\tonComponentRemoved<ComponentName extends keyof ComponentTypes>(\n\t\tcomponentName: ComponentName,\n\t\thandler: (oldValue: ComponentTypes[ComponentName], entity: Entity<ComponentTypes>) => void\n\t): this {\n\t\tif (!this.removedCallbacks.has(componentName)) {\n\t\t\tthis.removedCallbacks.set(componentName, new Set());\n\t\t}\n\t\tthis.removedCallbacks.get(componentName)!.add(handler as any);\n\t\treturn this;\n\t}\n}\n",
5
+ "import type { Entity, FilteredEntity } from \"./types\";\n\nexport default\nclass EntityManager<ComponentTypes> {\n\tprivate nextId: number = 1;\n\tprivate entities: Map<number, Entity<ComponentTypes>> = new Map();\n\tprivate componentIndices: Map<keyof ComponentTypes, Set<number>> = new Map();\n\t/**\n\t * Callbacks registered for component additions\n\t */\n\tprivate addedCallbacks: Map<keyof ComponentTypes, Set<(value: any, entity: Entity<ComponentTypes>) => void>> = new Map();\n\t/**\n\t * Callbacks registered for component removals\n\t */\n\tprivate removedCallbacks: Map<keyof ComponentTypes, Set<(oldValue: any, entity: Entity<ComponentTypes>) => void>> = new Map();\n\n\tcreateEntity(): Entity<ComponentTypes> {\n\t\tconst id = this.nextId++;\n\t\tconst entity: Entity<ComponentTypes> = { id, components: {} };\n\t\tthis.entities.set(id, entity);\n\t\treturn entity;\n\t}\n\n\t// TODO: Component object pooling if(/when) garbage collection is an issue...?\n\taddComponent<ComponentName extends keyof ComponentTypes>(\n\t\tentityOrId: number | Entity<ComponentTypes>,\n\t\tcomponentName: ComponentName,\n\t\tdata: ComponentTypes[ComponentName]\n\t) {\n\t\tconst entity = typeof entityOrId === 'number' ?\n\t\t\tthis.entities.get(entityOrId) :\n\t\t\tentityOrId;\n\n\t\tif (!entity) {\n\t\t\tconst id = typeof entityOrId === 'number' ? entityOrId : entityOrId.id;\n\t\t\tthrow new Error(`Cannot add component '${String(componentName)}': Entity with ID ${id} does not exist`);\n\t\t}\n\n\t\tentity.components[componentName] = data;\n\n\t\t// Update component index\n\t\tif (!this.componentIndices.has(componentName)) {\n\t\t\tthis.componentIndices.set(componentName, new Set());\n\t\t}\n\t\tthis.componentIndices.get(componentName)?.add(entity.id);\n\t\t// Trigger added callbacks\n\t\tconst callbacks = this.addedCallbacks.get(componentName);\n\t\tif (callbacks) {\n\t\t\tfor (const cb of callbacks) {\n\t\t\t\tcb(data, entity);\n\t\t\t}\n\t\t}\n\t\treturn this;\n\t}\n\n\t/**\n\t * Add multiple components to an entity at once\n\t * @param entityOrId Entity or entity ID to add components to\n\t * @param components Object with component names as keys and component data as values\n\t */\n\taddComponents<\n\t\tT extends { [K in keyof ComponentTypes]?: ComponentTypes[K] }\n\t>(\n\t\tentityOrId: number | Entity<ComponentTypes>,\n\t\tcomponents: T & Record<Exclude<keyof T, keyof ComponentTypes>, never>\n\t) {\n\t\tconst entity = typeof entityOrId === 'number' ?\n\t\t\tthis.entities.get(entityOrId) :\n\t\t\tentityOrId;\n\n\t\tif (!entity) {\n\t\t\tconst id = typeof entityOrId === 'number' ? entityOrId : entityOrId.id;\n\t\t\tthrow new Error(`Cannot add components: Entity with ID ${id} does not exist`);\n\t\t}\n\n\t\tfor (const componentName in components) {\n\t\t\tthis.addComponent(\n\t\t\t\tentity,\n\t\t\t\tcomponentName as keyof ComponentTypes,\n\t\t\t\tcomponents[componentName as keyof T] as ComponentTypes[keyof ComponentTypes]\n\t\t\t);\n\t\t}\n\n\t\treturn this;\n\t}\n\n\tremoveComponent<ComponentName extends keyof ComponentTypes>(\n\t\tentityOrId: number | Entity<ComponentTypes>,\n\t\tcomponentName: ComponentName\n\t) {\n\t\tconst entity = typeof entityOrId === 'number' ?\n\t\t\tthis.entities.get(entityOrId) :\n\t\t\tentityOrId;\n\n\t\tif (!entity) {\n\t\t\tconst id = typeof entityOrId === 'number' ? entityOrId : entityOrId.id;\n\t\t\tthrow new Error(`Cannot remove component '${String(componentName)}': Entity with ID ${id} does not exist`);\n\t\t}\n\t\t// Get old value for callbacks\n\t\tconst oldValue = entity.components[componentName] as ComponentTypes[ComponentName] | undefined;\n\n\t\tdelete entity.components[componentName];\n\n\t\t// Trigger removed callbacks\n\t\tconst removeCbs = this.removedCallbacks.get(componentName);\n\t\tif (removeCbs && oldValue !== undefined) {\n\t\t\tfor (const cb of removeCbs) {\n\t\t\t\tcb(oldValue, entity);\n\t\t\t}\n\t\t}\n\n\t\t// Update component index\n\t\tthis.componentIndices.get(componentName)?.delete(entity.id);\n\n\t\treturn this;\n\t}\n\n\tgetComponent<ComponentName extends keyof ComponentTypes>(entityId: number, componentName: ComponentName): ComponentTypes[ComponentName] | null {\n\t\tconst entity = this.entities.get(entityId);\n\n\t\tif (!entity) throw new Error(`Cannot get component '${String(componentName)}': Entity with ID ${entityId} does not exist`);\n\n\t\treturn entity.components[componentName] || null;\n\t}\n\n\tgetEntitiesWithQuery<\n\t\tWithComponents extends keyof ComponentTypes = never,\n\t\tWithoutComponents extends keyof ComponentTypes = never\n\t>(\n\t\trequired: ReadonlyArray<WithComponents> = [],\n\t\texcluded: ReadonlyArray<WithoutComponents> = [],\n\t): Array<FilteredEntity<ComponentTypes, WithComponents extends never ? never : WithComponents, WithoutComponents extends never ? never : WithoutComponents>> {\n\t\t// Use the smallest component set as base for better performance\n\t\tif (required.length === 0) {\n\t\t\tif (excluded.length === 0) {\n\t\t\t\treturn Array.from(this.entities.values()) as any;\n\t\t\t}\n\n\t\t\treturn Array\n\t\t\t\t.from(this.entities.values())\n\t\t\t\t.filter((entity) => {\n\t\t\t\t\treturn excluded.every(comp => !(comp in entity.components));\n\t\t\t\t}) as any;\n\t\t}\n\n\t\t// Find the component with the smallest entity set to start with\n\t\tconst smallestComponent = required.reduce((smallest, comp) => {\n\t\t\tconst currentSize = this.componentIndices.get(comp)?.size ?? 0;\n\t\t\tconst smallestSize = this.componentIndices.get(smallest!)?.size ?? Infinity;\n\t\t\treturn currentSize < smallestSize ? comp : smallest;\n\t\t}, required[0])!;\n\n\t\t// Start with the entities from the smallest component set\n\t\tconst candidateSet = this.componentIndices.get(smallestComponent);\n\t\tif (!candidateSet || candidateSet.size === 0) {\n\t\t\treturn [] as any;\n\t\t}\n\n\t\t// Return full entity objects, not just IDs\n\t\tconst result: Array<FilteredEntity<ComponentTypes, WithComponents extends never ? never : WithComponents, WithoutComponents extends never ? never : WithoutComponents>> = [];\n\t\tconst hasExclusions = excluded.length > 0;\n\t\t\n\t\tfor (const id of candidateSet) {\n\t\t\tconst entity = this.entities.get(id);\n\t\t\tif (\n\t\t\t\tentity &&\n\t\t\t\trequired.every(comp => comp in entity.components) &&\n\t\t\t\t(!hasExclusions || excluded.every(comp => !(comp in entity.components)))\n\t\t\t) {\n\t\t\t\tresult.push(entity as any);\n\t\t\t}\n\t\t}\n\t\t\n\t\treturn result;\n\t}\n\n\tremoveEntity(entityOrId: number | Entity<ComponentTypes>): boolean {\n\t\tconst entity = typeof entityOrId === 'number' ?\n\t\t\tthis.entities.get(entityOrId) :\n\t\t\tentityOrId;\n\n\t\tif (!entity) return false;\n\n\t\t// Trigger removal callbacks for each component before removing the entity\n\t\tfor (const componentName of Object.keys(entity.components) as Array<keyof ComponentTypes>) {\n\t\t\tconst oldValue = entity.components[componentName];\n\t\t\t\n\t\t\t// Trigger removed callbacks if the component exists\n\t\t\tif (oldValue !== undefined) {\n\t\t\t\tconst removeCbs = this.removedCallbacks.get(componentName);\n\t\t\t\tif (removeCbs) {\n\t\t\t\t\tfor (const cb of removeCbs) {\n\t\t\t\t\t\tcb(oldValue, entity);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Remove entity from component indices\n\t\t\tthis.componentIndices.get(componentName)?.delete(entity.id);\n\t\t}\n\n\t\t// Remove the entity itself\n\t\treturn this.entities.delete(entity.id);\n\t}\n\n\tgetEntity(entityId: number): Entity<ComponentTypes> | undefined {\n\t\treturn this.entities.get(entityId);\n\t}\n\n\t/**\n\t * Register a callback when a specific component is added to any entity\n\t * @param componentName The component key\n\t * @param handler Function receiving the new component value and the entity\n\t */\n\tonComponentAdded<ComponentName extends keyof ComponentTypes>(\n\t\tcomponentName: ComponentName,\n\t\thandler: (value: ComponentTypes[ComponentName], entity: Entity<ComponentTypes>) => void\n\t): this {\n\t\tif (!this.addedCallbacks.has(componentName)) {\n\t\t\tthis.addedCallbacks.set(componentName, new Set());\n\t\t}\n\t\tthis.addedCallbacks.get(componentName)!.add(handler as any);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Register a callback when a specific component is removed from any entity\n\t * @param componentName The component key\n\t * @param handler Function receiving the old component value and the entity\n\t */\n\tonComponentRemoved<ComponentName extends keyof ComponentTypes>(\n\t\tcomponentName: ComponentName,\n\t\thandler: (oldValue: ComponentTypes[ComponentName], entity: Entity<ComponentTypes>) => void\n\t): this {\n\t\tif (!this.removedCallbacks.has(componentName)) {\n\t\t\tthis.removedCallbacks.set(componentName, new Set());\n\t\t}\n\t\tthis.removedCallbacks.get(componentName)!.add(handler as any);\n\t\treturn this;\n\t}\n}\n",
6
6
  "import type { EventHandler } from \"./types\";\n\nexport default\nclass EventBus<EventTypes> {\n\tprivate handlers: Map<string, Array<EventHandler<any>>> = new Map();\n\n\t/**\n\t * Subscribe to an event\n\t */\n\tsubscribe<E extends keyof EventTypes>(\n\t\teventType: E,\n\t\tcallback: (data: EventTypes[E]) => void\n\t): () => void {\n\t\treturn this.addHandler(eventType, callback, false);\n\t}\n\n\t/**\n\t * Subscribe to an event once\n\t */\n\tonce<E extends keyof EventTypes>(\n\t\teventType: E,\n\t\tcallback: (data: EventTypes[E]) => void\n\t): () => void {\n\t\treturn this.addHandler(eventType, callback, true);\n\t}\n\n\t/**\n\t * Internal method to add an event handler\n\t */\n\tprivate addHandler<E extends keyof EventTypes>(\n\t\teventType: E,\n\t\tcallback: (data: EventTypes[E]) => void,\n\t\tonce: boolean\n\t): () => void {\n\t\tif (!this.handlers.has(eventType as string)) {\n\t\t\tthis.handlers.set(eventType as string, []);\n\t\t}\n\n\t\tconst handler: EventHandler<any> = {\n\t\t\tcallback,\n\t\t\tonce\n\t\t};\n\n\t\tthis.handlers.get(eventType as string)!.push(handler);\n\n\t\t// Return unsubscribe function\n\t\treturn () => {\n\t\t\tconst handlers = this.handlers.get(eventType as string);\n\t\t\tif (handlers) {\n\t\t\t\tconst index = handlers.indexOf(handler);\n\t\t\t\tif (index !== -1) {\n\t\t\t\t\thandlers.splice(index, 1);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n\n\tpublish<E extends keyof EventTypes>(\n\t\teventType: E,\n\t\tdata?: EventTypes[E]\n\t): void {\n\t\tconst handlers = this.handlers.get(eventType as string);\n\t\tif (!handlers) return;\n\n\t\t// Create a copy of handlers to avoid issues with handlers that modify the array\n\t\tconst handlersToCall = [...handlers];\n\n\t\t// Call all handlers and collect handlers to remove\n\t\tconst handlersToRemove: EventHandler<any>[] = [];\n\n\t\tfor (const handler of handlersToCall) {\n\t\t\thandler.callback(data as EventTypes[E]);\n\t\t\tif (handler.once) {\n\t\t\t\thandlersToRemove.push(handler);\n\t\t\t}\n\t\t}\n\n\t\tif (handlersToRemove.length > 0) {\n\t\t\tfor (const handler of handlersToRemove) {\n\t\t\t\tconst index = handlers.indexOf(handler);\n\t\t\t\tif (index !== -1) {\n\t\t\t\t\thandlers.splice(index, 1);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tclear(): void {\n\t\tthis.handlers.clear();\n\t}\n\n\tclearEvent<E extends keyof EventTypes>(eventType: E): void {\n\t\tthis.handlers.delete(eventType as string);\n\t}\n}\n",
7
- "export default\nclass ResourceManager<ResourceTypes extends Record<string, any> = Record<string, any>> {\n\tprivate resources: Map<string, any> = new Map();\n\tprivate resourceFactories: Map<string, (context?: any) => any | Promise<any>> = new Map();\n\tprivate initializedResourceKeys: Set<string> = new Set();\n\n\t/**\n\t * Add a resource to the manager\n\t * @param label The resource key\n\t * @param resource The resource value or a factory function that returns the resource\n\t * @returns The resource manager instance for chaining\n\t */\n\tadd<K extends keyof ResourceTypes>(\n\t\tlabel: K,\n\t\tresource: ResourceTypes[K] | ((context?: any) => ResourceTypes[K] | Promise<ResourceTypes[K]>),\n\t) {\n\t\tif (typeof resource === 'function' && !/^class\\s/.test(Function.prototype.toString.call(resource))) {\n\t\t\t// Likely a factory function\n\t\t\tthis.resourceFactories.set(label as string, resource as (context?: any) => any | Promise<any>);\n\t\t} else {\n\t\t\t// Direct resource value\n\t\t\tthis.resources.set(label as string, resource);\n\t\t\tthis.initializedResourceKeys.add(label as string);\n\t\t}\n\t\treturn this;\n\t}\n\n\t/**\n\t * Get a resource from the manager\n\t * @param label The resource key\n\t * @param context Optional context to pass to factory functions (usually the ECSpresso instance)\n\t * @returns The resource value\n\t * @throws Error if resource not found\n\t */\n\tget<K extends keyof ResourceTypes>(\n\t\tlabel: K,\n\t\tcontext?: any\n\t): ResourceTypes[K] {\n\t\t// Check if we already have the initialized resource\n\t\tconst resource = this.resources.get(label as string);\n\t\tif (resource !== undefined) {\n\t\t\treturn resource as any;\n\t\t}\n\n\t\t// Check if we have a factory for this resource\n\t\tconst factory = this.resourceFactories.get(label as string);\n\t\tif (factory === undefined) {\n\t\t\tthrow new Error(`Resource ${String(label)} not found`);\n\t\t}\n\n\t\t// Initialize the resource, passing the context\n\t\tconst initializedResource = factory(context);\n\n\t\t// If it's not a Promise, store it immediately\n\t\tif (!(initializedResource instanceof Promise)) {\n\t\t\tthis.resources.set(label as string, initializedResource);\n\t\t\tthis.initializedResourceKeys.add(label as string);\n\t\t}\n\n\t\treturn initializedResource as any;\n\t}\n\n\t/**\n\t * Check if a resource exists\n\t * @param label The resource key\n\t * @returns True if the resource exists\n\t */\n\thas<K extends keyof ResourceTypes>(label: K): boolean {\n\t\treturn this.resources.has(label as string) || this.resourceFactories.has(label as string);\n\t}\n\n\t/**\n\t * Remove a resource\n\t * @param label The resource key\n\t * @returns True if the resource was removed\n\t */\n\tremove<K extends keyof ResourceTypes>(label: K): boolean {\n\t\tconst resourceRemoved = this.resources.delete(label as string);\n\t\tconst factoryRemoved = this.resourceFactories.delete(label as string);\n\t\tif (this.initializedResourceKeys.has(label as string)) {\n\t\t\tthis.initializedResourceKeys.delete(label as string);\n\t\t}\n\t\treturn resourceRemoved || factoryRemoved;\n\t}\n\n\t/**\n\t * Get all resource keys\n\t * @returns Array of resource keys\n\t */\n\tgetKeys(): Array<string> {\n\t\tconst keys = new Set([\n\t\t\t...this.resources.keys(),\n\t\t\t...this.resourceFactories.keys()\n\t\t]);\n\t\treturn Array.from(keys);\n\t}\n\n\t/**\n\t * Check if a resource needs to be initialized\n\t * @param label The resource key\n\t * @returns True if the resource needs initialization\n\t */\n\tneedsInitialization<K extends keyof ResourceTypes>(label: K): boolean {\n\t\treturn this.resourceFactories.has(label as string) && !this.initializedResourceKeys.has(label as string);\n\t}\n\n\t/**\n\t * Get all resource keys that need to be initialized\n\t * @returns Array of resource keys that need initialization\n\t */\n\tgetPendingInitializationKeys(): Array<string> {\n\t\treturn Array\n\t\t\t.from(this.resourceFactories.keys())\n\t\t\t.filter(key => !this.initializedResourceKeys.has(key));\n\t}\n\n\t/**\n\t * Initialize a specific resource if it's a factory function\n\t * @param label The resource key\n\t * @param context Optional context to pass to factory functions\n\t * @returns Promise that resolves when the resource is initialized\n\t */\n\tasync initializeResource<K extends keyof ResourceTypes>(\n\t\tlabel: K,\n\t\tcontext?: any\n\t): Promise<void> {\n\t\tif (!this.resourceFactories.has(label as string) || this.initializedResourceKeys.has(label as string)) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst factory = this.resourceFactories.get(label as string)!;\n\t\tconst initializedResource = await factory(context);\n\t\tthis.resources.set(label as string, initializedResource);\n\t\tthis.initializedResourceKeys.add(label as string);\n\t\tthis.resourceFactories.delete(label as string);\n\t}\n\n\t/**\n\t * Initialize specific resources or all resources that haven't been initialized yet\n\t * @param keys Optional array of resource keys to initialize or optional context to pass to factory functions\n\t * @returns Promise that resolves when the specified resources are initialized\n\t */\n\tasync initializeResources<K extends keyof ResourceTypes>(\n\t\tcontext?: any,\n\t\t...keys: K[]\n\t): Promise<void> {\n\t\t// If no keys provided, initialize all pending resources\n\t\tif (keys.length === 0) {\n\t\t\tconst pendingKeys = this.getPendingInitializationKeys();\n\t\t\tawait Promise.all(pendingKeys.map(key => this.initializeResource(key, context)));\n\t\t\treturn;\n\t\t}\n\n\t\t// Otherwise, initialize only the specified resources\n\t\tawait Promise.all(\n\t\t\tkeys.map(key => this.initializeResource(key, context))\n\t\t);\n\t}\n}\n",
8
- "import Bundle from \"./bundle\";\nimport ECSpresso from \"./ecspresso\";\nimport type { FilteredEntity, System } from \"./types\";\n\n/**\n * Builder class for creating type-safe ECS Systems with proper query inference\n */\nexport class SystemBuilder<\n\tComponentTypes extends Record<string, any> = Record<string, any>,\n\tEventTypes extends Record<string, any> = Record<string, any>,\n\tResourceTypes extends Record<string, any> = Record<string, any>,\n\tQueries extends Record<string, QueryDefinition<ComponentTypes>> = {},\n> {\n\tprivate queries: Queries = {} as Queries;\n\tprivate processFunction?: ProcessFunction<ComponentTypes, EventTypes, ResourceTypes, Queries>;\n\tprivate detachFunction?: LifecycleFunction<ComponentTypes, EventTypes, ResourceTypes>;\n\tprivate initializeFunction?: LifecycleFunction<ComponentTypes, EventTypes, ResourceTypes>;\n\tprivate eventHandlers?: {\n\t\t[EventName in keyof EventTypes]?: {\n\t\t\thandler(\n\t\t\t\tdata: EventTypes[EventName],\n\t\t\t\tecs: ECSpresso<\n\t\t\t\t\tComponentTypes,\n\t\t\t\t\tEventTypes,\n\t\t\t\t\tResourceTypes\n\t\t\t\t>,\n\t\t\t): void;\n\t\t};\n\t};\n\tprivate _priority = 0; // Default priority is 0\n\tprivate _isRegistered = false; // Track if system has been auto-registered\n\n\tconstructor(\n\t\tprivate _label: string,\n\t\tprivate _ecspresso: ECSpresso<ComponentTypes, EventTypes, ResourceTypes> | null = null,\n\t\tprivate _bundle: Bundle<ComponentTypes, EventTypes, ResourceTypes> | null = null,\n\t) {}\n\n\tget label() {\n\t\treturn this._label;\n\t}\n\n\t/**\n\t * Returns the associated bundle if one was provided in the constructor\n\t */\n\tget bundle() {\n\t\treturn this._bundle;\n\t}\n\n\t/**\n\t * Returns the associated ECSpresso instance if one was provided in the constructor\n\t */\n\tget ecspresso() {\n\t\treturn this._ecspresso;\n\t}\n\n\t/**\n\t * Auto-register this system with its ECSpresso instance if not already registered\n\t * @private\n\t */\n\tprivate _autoRegister(): void {\n\t\tif (this._isRegistered || !this._ecspresso) return;\n\t\t\n\t\tconst system = this._buildSystemObject();\n\t\tregisterSystemWithEcspresso(system, this._ecspresso);\n\t\tthis._isRegistered = true;\n\t}\n\n\t/**\n\t * Create the system object without registering it\n\t * @private\n\t */\n\tprivate _buildSystemObject(): System<ComponentTypes, any, any, EventTypes, ResourceTypes> {\n\t\tconst system: System<ComponentTypes, any, any, EventTypes, ResourceTypes> = {\n\t\t\tlabel: this._label,\n\t\t\tentityQueries: this.queries,\n\t\t\tpriority: this._priority,\n\t\t};\n\n\t\tif (this.processFunction) {\n\t\t\tsystem.process = this.processFunction;\n\t\t}\n\n\t\tif (this.detachFunction) {\n\t\t\tsystem.onDetach = this.detachFunction;\n\t\t}\n\n\t\tif (this.initializeFunction) {\n\t\t\tsystem.onInitialize = this.initializeFunction;\n\t\t}\n\n\t\tif (this.eventHandlers) {\n\t\t\tsystem.eventHandlers = this.eventHandlers;\n\t\t}\n\n\t\treturn system;\n\t}\n\n\t// TODO: Should this be a setter?\n\t/**\n\t * Set the priority of this system. Systems with higher priority values\n\t * execute before those with lower values. Systems with the same priority\n\t * execute in the order they were registered.\n\t * @param priority The priority value (default: 0)\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tsetPriority(priority: number): this {\n\t\tthis._priority = priority;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Add a query definition to the system\n\t */\n\taddQuery<\n\t\tQueryName extends string,\n\t\tWithComponents extends keyof ComponentTypes,\n\t\tWithoutComponents extends keyof ComponentTypes = never,\n\t\tNewQueries extends Queries & Record<QueryName, QueryDefinition<ComponentTypes, WithComponents, WithoutComponents>> =\n\t\t\tQueries & Record<QueryName, QueryDefinition<ComponentTypes, WithComponents, WithoutComponents>>\n\t>(\n\t\tname: QueryName,\n\t\tdefinition: {\n\t\t\twith: ReadonlyArray<WithComponents>;\n\t\t\twithout?: ReadonlyArray<WithoutComponents>;\n\t\t}\n\t): this extends SystemBuilderWithEcspresso<ComponentTypes, EventTypes, ResourceTypes, Queries>\n\t\t? SystemBuilderWithEcspresso<ComponentTypes, EventTypes, ResourceTypes, NewQueries>\n\t\t: this extends SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, Queries>\n\t\t\t? SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, NewQueries>\n\t\t\t: SystemBuilder<ComponentTypes, EventTypes, ResourceTypes, NewQueries> {\n\t\t// Cast is needed because TypeScript can't preserve the type information\n\t\t// when modifying an object property\n\t\tconst newBuilder = this as any;\n\t\tnewBuilder.queries = {\n\t\t\t...this.queries,\n\t\t\t[name]: definition,\n\t\t};\n\t\treturn newBuilder;\n\t}\n\n\t/**\n\t * Set the system's process function that runs each update\n\t * @param process Function to process entities matching the system's queries each update\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tsetProcess(\n\t\tprocess: ProcessFunction<ComponentTypes, EventTypes, ResourceTypes, Queries>\n\t): this {\n\t\tthis.processFunction = process;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Register this system with its ECSpresso instance and return the ECSpresso for chaining\n\t * This enables seamless method chaining: .registerAndContinue().addSystem(...)\n\t * @returns ECSpresso instance if attached to one, otherwise throws an error\n\t */\n\tregisterAndContinue(): ECSpresso<ComponentTypes, EventTypes, ResourceTypes> {\n\t\tif (!this._ecspresso) {\n\t\t\tthrow new Error('Cannot register system: no ECSpresso instance attached');\n\t\t}\n\t\t\n\t\tthis._autoRegister();\n\t\treturn this._ecspresso;\n\t}\n\n\t/**\n\t * Complete this system and return ECSpresso for seamless chaining\n\t * Automatically registers the system when called\n\t * This method is primarily for SystemBuilderWithEcspresso instances\n\t */\n\tand(): ECSpresso<ComponentTypes, EventTypes, ResourceTypes> {\n\t\tif (!this._ecspresso) {\n\t\t\tthrow new Error('Cannot use and() method: no ECSpresso instance attached');\n\t\t}\n\t\t\n\t\tthis._autoRegister();\n\t\treturn this._ecspresso;\n\t}\n\n\t/**\n\t * Set the onDetach lifecycle hook\n\t * Called when the system is removed from the ECS\n\t * @param onDetach Function to run when this system is detached from the ECS\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tsetOnDetach(\n\t\tonDetach: LifecycleFunction<ComponentTypes, EventTypes, ResourceTypes>\n\t): this {\n\t\tthis.detachFunction = onDetach;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Set the onInitialize lifecycle hook\n\t * Called when the system is initialized via ECSpresso.initialize() method\n\t * @param onInitialize Function to run when this system is initialized\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tsetOnInitialize(\n\t\tonInitialize: LifecycleFunction<ComponentTypes, EventTypes, ResourceTypes>\n\t): this {\n\t\tthis.initializeFunction = onInitialize;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Set event handlers for the system\n\t * These handlers will be automatically subscribed when the system is attached\n\t * @param handlers Object mapping event names to handler functions\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tsetEventHandlers(\n\t\thandlers: {\n\t\t\t[EventName in keyof EventTypes]?: {\n\t\t\t\thandler(\n\t\t\t\t\tdata: EventTypes[EventName],\n\t\t\t\t\tecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>\n\t\t\t\t): void;\n\t\t\t};\n\t\t}\n\t): this {\n\t\tthis.eventHandlers = handlers;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Build the final system object\n\t */\n\tbuild(ecspresso?: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>) {\n\t\tconst system: System<ComponentTypes, any, any, EventTypes, ResourceTypes> = {\n\t\t\tlabel: this._label,\n\t\t\tentityQueries: this.queries,\n\t\t\tpriority: this._priority,\n\t\t};\n\n\t\tif (this.processFunction) {\n\t\t\tsystem.process = this.processFunction;\n\t\t}\n\n\t\tif (this.detachFunction) {\n\t\t\tsystem.onDetach = this.detachFunction;\n\t\t}\n\n\t\tif (this.initializeFunction) {\n\t\t\tsystem.onInitialize = this.initializeFunction;\n\t\t}\n\n\t\tif (this.eventHandlers) {\n\t\t\tsystem.eventHandlers = this.eventHandlers;\n\t\t}\n\n\t\tif (this._ecspresso) {\n\t\t\tregisterSystemWithEcspresso(system, this._ecspresso);\n\t\t}\n\n\t\tif(ecspresso) {\n\t\t\tregisterSystemWithEcspresso(system, ecspresso);\n\t\t}\n\n\t\treturn this;\n\t}\n}\n\n/**\n * Helper function to register a system with an ECSpresso instance\n * This handles attaching the system and setting up event handlers\n * @internal Used by SystemBuilder and Bundle\n */\nexport function registerSystemWithEcspresso<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>\n>(\n\tsystem: System<ComponentTypes, any, any, EventTypes, ResourceTypes>,\n\tecspresso: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>\n) {\n\t// TODO: Remove the index notation workaround hack for private property access\n\t// Add system to ECSpresso's system list\n\tecspresso[\"_systems\"].push(system);\n\n\t// Trigger sorting of systems by priority\n\tecspresso[\"_sortSystems\"]();\n\n\tif(!system.eventHandlers) return;\n\n\tfor (const eventName in system.eventHandlers) {\n\t\tconst handler = system.eventHandlers[eventName]?.handler;\n\n\t\thandler && ecspresso.eventBus.subscribe(eventName, (data) => {\n\t\t\thandler(data, ecspresso);\n\t\t});\n\t}\n}\n\n// Helper type definitions\ntype QueryDefinition<\n\tComponentTypes,\n\tWithComponents extends keyof ComponentTypes = any,\n\tWithoutComponents extends keyof ComponentTypes = any,\n> = {\n\twith: ReadonlyArray<WithComponents>;\n\twithout?: ReadonlyArray<WithoutComponents>;\n};\n\ntype QueryResults<\n\tComponentTypes,\n\tQueries extends Record<string, QueryDefinition<ComponentTypes>>,\n> = {\n\t[QueryName in keyof Queries]: QueryName extends string\n\t\t? FilteredEntity<\n\t\t\tComponentTypes,\n\t\t\tQueries[QueryName] extends QueryDefinition<ComponentTypes, infer W, any> ? W : never,\n\t\t\tQueries[QueryName] extends QueryDefinition<ComponentTypes, any, infer WO> ? WO : never\n\t\t>[]\n\t\t: never;\n};\n\n/**\n * Function signature for system process methods\n * @param queries Results of entity queries defined by the system\n * @param deltaTime Time elapsed since last update in seconds\n * @param ecs The ECSpresso instance providing access to all ECS functionality\n */\ntype ProcessFunction<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>,\n\tQueries extends Record<string, QueryDefinition<ComponentTypes>>,\n> = (\n\tqueries: QueryResults<ComponentTypes, Queries>,\n\tdeltaTime: number,\n\tecs: ECSpresso<\n\t\tComponentTypes,\n\t\tEventTypes,\n\t\tResourceTypes\n\t>\n) => void;\n\n/**\n * Type for system initialization functions\n * These can be asynchronous\n */\ntype LifecycleFunction<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>,\n> = (\n\tecs: ECSpresso<\n\t\tComponentTypes,\n\t\tEventTypes,\n\t\tResourceTypes\n\t>,\n) => void | Promise<void>;\n\n/**\n * Create a SystemBuilder attached to an ECSpresso instance\n * Helper function used by ECSpresso.addSystem\n */\nexport function createEcspressoSystemBuilder<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>\n>(\n\tlabel: string,\n\tecspresso: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>\n): SystemBuilderWithEcspresso<ComponentTypes, EventTypes, ResourceTypes> {\n\treturn new SystemBuilder<ComponentTypes, EventTypes, ResourceTypes>(\n\t\tlabel,\n\t\tecspresso\n\t) as SystemBuilderWithEcspresso<ComponentTypes, EventTypes, ResourceTypes>;\n}\n\n/**\n * Create a SystemBuilder attached to a Bundle\n * Helper function used by Bundle.addSystem\n */\nexport function createBundleSystemBuilder<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>\n>(\n\tlabel: string,\n\tbundle: Bundle<ComponentTypes, EventTypes, ResourceTypes>\n): SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes> {\n\treturn new SystemBuilder<ComponentTypes, EventTypes, ResourceTypes>(\n\t\tlabel,\n\t\tnull,\n\t\tbundle\n\t) as SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes>;\n}\n\n// Type interfaces for specialized SystemBuilders\n\n/**\n * SystemBuilder with a guaranteed non-null reference to an ECSpresso instance\n */\nexport interface SystemBuilderWithEcspresso<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>,\n\tQueries extends Record<string, QueryDefinition<ComponentTypes>> = {}\n> extends SystemBuilder<ComponentTypes, EventTypes, ResourceTypes, Queries> {\n\treadonly ecspresso: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>;\n\t\n\t/**\n\t * Complete this system and return ECSpresso for seamless chaining\n\t * Automatically registers the system when called\n\t */\n\tand(): ECSpresso<ComponentTypes, EventTypes, ResourceTypes>;\n}\n\n/**\n * SystemBuilder with a guaranteed non-null reference to a Bundle\n */\nexport interface SystemBuilderWithBundle<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>,\n\tQueries extends Record<string, QueryDefinition<ComponentTypes>> = {}\n> extends SystemBuilder<ComponentTypes, EventTypes, ResourceTypes, Queries> {\n\treadonly bundle: Bundle<ComponentTypes, EventTypes, ResourceTypes>;\n}\n",
9
- "import EntityManager from \"./entity-manager\";\nimport EventBus from \"./event-bus\";\nimport ResourceManager from \"./resource-manager\";\nimport type { System, FilteredEntity, Entity } from \"./types\";\nimport type Bundle from \"./bundle\";\nimport { createEcspressoSystemBuilder } from \"./system-builder\";\nimport { version } from \"../package.json\";\n\n/**\n\t* Type helper to detect conflicting types between two record types.\n\t* Returns a union of keys that exist in both T and U but have incompatible types.\n*/\ntype GetConflictingKeys<T, U> = {\n\t[K in keyof T & keyof U]: T[K] extends U[K]\n\t\t? U[K] extends T[K]\n\t\t\t? never\n\t\t\t: K\n\t\t: K\n}[keyof T & keyof U];\n\n/**\n\t* Simplified type helper to check bundle type compatibility.\n\t* Returns true if bundles can be merged without type conflicts.\n*/\ntype BundlesAreCompatible<\n\tC1 extends Record<string, any>,\n\tC2 extends Record<string, any>,\n\tE1 extends Record<string, any>,\n\tE2 extends Record<string, any>,\n\tR1 extends Record<string, any>,\n\tR2 extends Record<string, any>\n> =\n\t// If all base types are empty, any bundle is compatible\n\t[keyof C1] extends [never]\n\t\t? [keyof E1] extends [never]\n\t\t\t? [keyof R1] extends [never]\n\t\t\t\t? true\n\t\t\t\t: GetConflictingKeys<R1, R2> extends never ? true : false\n\t\t\t: GetConflictingKeys<E1, E2> extends never\n\t\t\t\t? GetConflictingKeys<R1, R2> extends never ? true : false\n\t\t\t\t: false\n\t\t: GetConflictingKeys<C1, C2> extends never\n\t\t\t? GetConflictingKeys<E1, E2> extends never\n\t\t\t\t? GetConflictingKeys<R1, R2> extends never\n\t\t\t\t\t? true\n\t\t\t\t\t: false\n\t\t\t\t: false\n\t\t\t: false;\n\n/**\n\t* Interface declaration for ECSpresso constructor to ensure type augmentation works properly.\n\t* This merges with the class declaration below.\n*/\nexport default interface ECSpresso<\n\tComponentTypes extends Record<string, any> = {},\n\tEventTypes extends Record<string, any> = {},\n\tResourceTypes extends Record<string, any> = {},\n> {\n\t/**\n\t\t* Default constructor\n\t*/\n\tnew(): ECSpresso<ComponentTypes, EventTypes, ResourceTypes>;\n}\n\nconst EmptyQueryResults = {};\n\n/**\n\t* ECSpresso is the central ECS framework class that connects all features.\n\t* It handles creation and management of entities, components, and systems, and provides lifecycle hooks.\n*/\nexport default class ECSpresso<\n\tComponentTypes extends Record<string, any> = {},\n\tEventTypes extends Record<string, any> = {},\n\tResourceTypes extends Record<string, any> = {},\n> {\n\t/** Library version*/\n\tpublic static readonly VERSION = version;\n\n\t/** Access/modify stored components and entities*/\n\tprivate _entityManager: EntityManager<ComponentTypes>;\n\t/** Publish/subscribe to events*/\n\tprivate _eventBus: EventBus<EventTypes>;\n\t/** Access/modify registered resources*/\n\tprivate _resourceManager: ResourceManager<ResourceTypes>;\n\n\t/** Registered systems that will be updated in order*/\n\tprivate _systems: Array<System<ComponentTypes, any, any, EventTypes, ResourceTypes>> = [];\n\t/** Cached sorted systems for efficient updates */\n\tprivate _sortedSystems: Array<System<ComponentTypes, any, any, EventTypes, ResourceTypes>> = [];\n\t/** Track installed bundles to prevent duplicates*/\n\tprivate _installedBundles: Set<string> = new Set();\n\n\t/**\n\t\t* Creates a new ECSpresso instance.\n\t*/\n\tconstructor() {\n\t\tthis._entityManager = new EntityManager<ComponentTypes>();\n\t\tthis._eventBus = new EventBus<EventTypes>();\n\t\tthis._resourceManager = new ResourceManager<ResourceTypes>();\n\t\tthis._sortedSystems = []; // Initialize the sorted systems array\n\t}\n\n\t/**\n\t\t* Creates a new ECSpresso builder for type-safe bundle installation.\n\t\t* This is the preferred way to create an ECSpresso instance with bundles.\n\t *\n\t\t* @returns A builder instance for fluent method chaining\n\t *\n\t\t* @example\n\t\t* ```typescript\n\t\t* const ecs = ECSpresso.create<BaseComponents, BaseEvents, BaseResources>()\n\t *\t .withBundle(bundle1)\n\t *\t .withBundle(bundle2)\n\t *\t .build();\n\t\t* ```\n\t*/\n\tstatic create<\n\t\tC extends Record<string, any> = {},\n\t\tE extends Record<string, any> = {},\n\t\tR extends Record<string, any> = {},\n\t>(): ECSpressoBuilder<C, E, R> {\n\t\treturn new ECSpressoBuilder<C, E, R>();\n\t}\n\n\t/**\n\t\t* Adds a system directly to this ECSpresso instance\n\t\t* @param label Unique name to identify the system\n\t\t* @returns A SystemBuilder instance for method chaining\n\t*/\n\taddSystem(label: string) {\n\t\treturn createEcspressoSystemBuilder<\n\t\t\tComponentTypes,\n\t\t\tEventTypes,\n\t\t\tResourceTypes\n\t\t>(label, this);\n\t}\n\n\t/**\n\t\t* Update all systems, passing deltaTime and query results to each system's process function\n\t\t* @param deltaTime Time elapsed since the last update (in seconds)\n\t*/\n\tupdate(deltaTime: number) {\n\t\t// Use the cached sorted systems array instead of re-sorting on every update\n\t\tfor (const system of this._sortedSystems) {\n\t\t\tif (!system.process) continue;\n\n\t\t\t// Prepare query results for each defined query in the system\n\t\t\tconst queryResults: Record<string, any> = {};\n\t\t\tlet hasResults = false;\n\t\t\tlet hasQueries = false;\n\n\t\t\tif (system.entityQueries) {\n\t\t\t\tfor (const queryName in system.entityQueries) {\n\t\t\t\t\thasQueries = true;\n\n\t\t\t\t\tconst query = system.entityQueries[queryName];\n\n\t\t\t\t\tif (query) {\n\t\t\t\t\t\tqueryResults[queryName] = this._entityManager.getEntitiesWithQuery(\n\t\t\t\t\t\t\tquery.with,\n\t\t\t\t\t\t\tquery.without || []\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tif(queryResults[queryName].length) {\n\t\t\t\t\t\t\thasResults = true; // At least one query has results\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Call the system's process function only if there are results or there is no query.\n\t\t\tif (hasResults) {\n\t\t\t\tsystem.process(queryResults, deltaTime, this);\n\t\t\t} else if(!hasQueries) {\n\t\t\t\tsystem.process(EmptyQueryResults, deltaTime, this);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Initialize all resources and systems\n\t * This method:\n\t * 1. Initializes all resources that were added as factory functions\n\t * 2. Calls the onInitialize lifecycle hook on all systems\n\t *\n\t * This is useful for game startup to ensure all resources are ready\n\t * and systems are properly initialized before the game loop begins.\n\t *\n\t * @param resourceKeys Optional array of specific resource keys to initialize\n\t * @returns Promise that resolves when everything is initialized\n\t */\n\tasync initialize(): Promise<void> {\n\t\tawait this.initializeResources();\n\n\t\tfor (const system of this._systems) {\n\t\t\tawait system.onInitialize?.(this);\n\t\t}\n\t}\n\n\t/**\n\t * Initialize specific resources or all resources that were added as factory functions but haven't been initialized yet.\n\t * This is useful when you need to ensure resources are ready before proceeding.\n\t * @param keys Optional array of resource keys to initialize. If not provided, all pending resources will be initialized.\n\t * @returns Promise that resolves when the specified resources are initialized\n\t */\n\tasync initializeResources<K extends keyof ResourceTypes>(...keys: K[]): Promise<void> {\n\t\tawait this._resourceManager.initializeResources(this, ...keys);\n\t}\n\n\t/**\n\t\t* Sort the systems array by priority (higher priority first)\n\t\t* Called internally when system list changes\n\t\t* @private\n\t*/\n\tprivate _sortSystems(): void {\n\t\tthis._sortedSystems = [...this._systems].sort((a, b) => {\n\t\t\tconst priorityA = a.priority ?? 0;\n\t\t\tconst priorityB = b.priority ?? 0;\n\t\t\treturn priorityB - priorityA; // Higher priority executes first\n\t\t});\n\t}\n\n\t/**\n\t\t* Update the priority of a system\n\t\t* @param label The unique label of the system to update\n\t\t* @param priority The new priority value (higher values execute first)\n\t\t* @returns true if the system was found and updated, false otherwise\n\t*/\n\tupdateSystemPriority(label: string, priority: number): boolean {\n\t\tconst system = this._systems.find(system => system.label === label);\n\t\tif (!system) return false;\n\n\t\t// Set the new priority\n\t\tsystem.priority = priority;\n\n\t\t// Re-sort the systems array\n\t\tthis._sortSystems();\n\n\t\treturn true;\n\t}\n\n\t/**\n\t\t* Remove a system by its label\n\t\t* Calls the system's onDetach method with this ECSpresso instance if defined\n\t\t* @param label The unique label of the system to remove\n\t\t* @returns true if the system was found and removed, false otherwise\n\t*/\n\tremoveSystem(label: string): boolean {\n\t\tconst index = this._systems.findIndex(system => system.label === label);\n\t\tif (index === -1) return false;\n\n\t\tconst system = this._systems[index];\n\t\t// This should never happen since we just found the system by index\n\t\tif (!system) return false;\n\n\t\t// Call the onDetach lifecycle hook if defined\n\t\tif (system.onDetach) {\n\t\t\tsystem.onDetach(this);\n\t\t}\n\n\t\t// Remove system\n\t\tthis._systems.splice(index, 1);\n\n\t\t// Re-sort systems\n\t\tthis._sortSystems();\n\n\t\treturn true;\n\t}\n\n\t/**\n\t\t* Check if a resource exists\n\t*/\n\thasResource<K extends keyof ResourceTypes>(key: K): boolean {\n\t\treturn this._resourceManager.has(key);\n\t}\n\n\t/**\n\t\t* Get a resource if it exists, or undefined if not\n\t*/\n\tgetResource<K extends keyof ResourceTypes>(key: K): ResourceTypes[K] {\n\t\tconst resource = this._resourceManager.get(key, this);\n\n\t\tif (!resource) throw new Error(`Resource \"${key.toString()}\" not found`);\n\n\t\treturn resource;\n\t}\n\n\t/**\n\t\t* Add a resource to the ECS instance\n\t*/\n\taddResource<K extends keyof ResourceTypes>(\n\t\tkey: K,\n\t\tresource: ResourceTypes[K] | ((ecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>) => ResourceTypes[K] | Promise<ResourceTypes[K]>)\n\t): this {\n\t\tthis._resourceManager.add(key, resource);\n\t\treturn this;\n\t}\n\n\t/**\n\t\t* Remove a resource from the ECS instance\n\t\t* @param key The resource key to remove\n\t\t* @returns True if the resource was removed, false if it didn't exist\n\t*/\n\tremoveResource<K extends keyof ResourceTypes>(key: K): boolean {\n\t\treturn this._resourceManager.remove(key);\n\t}\n\n\t/**\n\t\t* Update an existing resource using an updater function\n\t\t* @param key The resource key to update\n\t\t* @param updater Function that receives the current resource value and returns the new value\n\t\t* @returns This ECSpresso instance for chaining\n\t\t* @throws Error if the resource doesn't exist\n\t*/\n\tupdateResource<K extends keyof ResourceTypes>(\n\t\tkey: K,\n\t\tupdater: (current: ResourceTypes[K]) => ResourceTypes[K]\n\t): this {\n\t\tconst currentResource = this.getResource(key);\n\t\tconst updatedResource = updater(currentResource);\n\t\tthis._resourceManager.add(key, updatedResource);\n\t\treturn this;\n\t}\n\n\t/**\n\t\t* Get all resource keys that are currently registered\n\t\t* @returns Array of resource keys\n\t*/\n\tgetResourceKeys(): Array<keyof ResourceTypes> {\n\t\treturn this._resourceManager.getKeys() as Array<keyof ResourceTypes>;\n\t}\n\n\t/**\n\t\t* Check if a resource needs initialization (was added as a factory function)\n\t\t* @param key The resource key to check\n\t\t* @returns True if the resource needs initialization\n\t*/\n\tresourceNeedsInitialization<K extends keyof ResourceTypes>(key: K): boolean {\n\t\treturn this._resourceManager.needsInitialization(key);\n\t}\n\n\t/**\n\t\t* Check if an entity has a component\n\t*/\n\thasComponent<K extends keyof ComponentTypes>(\n\t\tentityId: number,\n\t\tcomponentName: K\n\t): boolean {\n\t\tconst component = this._entityManager.getComponent(entityId, componentName);\n\t\treturn component !== null;\n\t}\n\n\t/**\n\t\t* Create an entity and add components to it in one call\n\t\t* @param components Object with component names as keys and component data as values\n\t\t* @returns The created entity with all components added\n\t\t*/\n\tspawn<T extends { [K in keyof ComponentTypes]?: ComponentTypes[K] }>(\n\t\tcomponents: T & Record<Exclude<keyof T, keyof ComponentTypes>, never>\n\t): Entity<ComponentTypes> {\n\t\tconst entity = this._entityManager.createEntity();\n\t\tthis._entityManager.addComponents(entity, components);\n\t\treturn entity;\n\t}\n\n\t/**\n\t\t* Get all entities with specific components\n\t*/\n\tgetEntitiesWithQuery<\n\t\tWithComponents extends keyof ComponentTypes,\n\t\tWithoutComponents extends keyof ComponentTypes = never\n\t>(\n\t\twithComponents: ReadonlyArray<WithComponents>,\n\t\twithoutComponents: ReadonlyArray<WithoutComponents> = []\n\t): Array<FilteredEntity<ComponentTypes, WithComponents, WithoutComponents>> {\n\t\treturn this._entityManager.getEntitiesWithQuery(\n\t\t\twithComponents,\n\t\t\twithoutComponents\n\t\t);\n\t}\n\n\t/**\n\t\t* Get all installed bundle IDs\n\t*/\n\tget installedBundles(): string[] {\n\t\treturn Array.from(this._installedBundles);\n\t}\n\n\t// Getters for the internal managers\n\tget entityManager() {\n\t\treturn this._entityManager;\n\t}\n\n\tget eventBus() {\n\t\treturn this._eventBus;\n\t}\n\n\t/**\n\t\t* Internal method to install a bundle into this ECSpresso instance.\n\t\t* Called by the ECSpressoBuilder during the build process.\n\t\t* The type safety is guaranteed by the builder's type system.\n\t*/\n\t_installBundle<\n\t\tC extends Record<string, any>,\n\t\tE extends Record<string, any>,\n\t\tR extends Record<string, any>\n\t>(bundle: Bundle<C, E, R>): this {\n\t\t// Prevent duplicate installation of the same bundle\n\t\tif (this._installedBundles.has(bundle.id)) {\n\t\t\treturn this;\n\t\t}\n\n\t\t// Mark this bundle as installed\n\t\tthis._installedBundles.add(bundle.id);\n\n\t\t// Register systems from the bundle\n\t\t// The type compatibility is ensured by the builder's withBundle method\n\t\t// We need this cast due to TypeScript's limitations with generics\n\t\ttype BundleEcspresso = ECSpresso<C, E, R>;\n\t\tbundle.registerSystemsWithEcspresso(this as unknown as BundleEcspresso);\n\n\t\t// Register resources from the bundle\n\t\tconst resources = bundle.getResources();\n\t\tfor (const [key, value] of resources.entries()) {\n\t\t\t// Instead of casting, use the add method's flexibility\n\t\t\tthis._resourceManager.add(key as string, value);\n\t\t}\n\n\t\treturn this;\n\t}\n}\n\n/**\n\t* Builder class for ECSpresso that provides fluent type-safe bundle installation.\n\t* Handles type checking during build process to ensure type safety.\n*/\nexport class ECSpressoBuilder<\n\tC extends Record<string, any> = {},\n\tE extends Record<string, any> = {},\n\tR extends Record<string, any> = {}\n> {\n\t/** The ECSpresso instance being built*/\n\tprivate ecspresso: ECSpresso<C, E, R>;\n\n\tconstructor() {\n\t\tthis.ecspresso = new ECSpresso<C, E, R>();\n\t}\n\n\t/**\n\t\t* Add the first bundle when starting with empty types.\n\t\t* This overload allows any bundle to be added to an empty ECSpresso instance.\n\t*/\n\twithBundle<\n\t\tBC extends Record<string, any>,\n\t\tBE extends Record<string, any>,\n\t\tBR extends Record<string, any>\n\t>(\n\t\tthis: ECSpressoBuilder<{}, {}, {}>,\n\t\tbundle: Bundle<BC, BE, BR>\n\t): ECSpressoBuilder<BC, BE, BR>;\n\n\t/**\n\t\t* Add a subsequent bundle with type checking.\n\t\t* This overload enforces bundle type compatibility.\n\t*/\n\twithBundle<\n\t\tBC extends Record<string, any>,\n\t\tBE extends Record<string, any>,\n\t\tBR extends Record<string, any>\n\t>(\n\t\tbundle: BundlesAreCompatible<C, BC, E, BE, R, BR> extends true\n\t\t\t? Bundle<BC, BE, BR>\n\t\t\t: never\n\t): ECSpressoBuilder<C & BC, E & BE, R & BR>;\n\n\t/**\n\t\t* Implementation of both overloads.\n\t\t* Since the type compatibility is checked in the method signature,\n\t\t* we can safely assume the bundle is compatible here.\n\t*/\n\twithBundle<\n\t\tBC extends Record<string, any>,\n\t\tBE extends Record<string, any>,\n\t\tBR extends Record<string, any>\n\t>(\n\t\tbundle: Bundle<BC, BE, BR>\n\t): ECSpressoBuilder<C & BC, E & BE, R & BR> {\n\t\t// Install the bundle\n\t\t// Type compatibility is guaranteed by method overloads\n\t\tthis.ecspresso._installBundle(bundle);\n\n\t\t// Return a builder with the updated type parameters\n\t\treturn this as unknown as ECSpressoBuilder<C & BC, E & BE, R & BR>;\n\t}\n\n\t/**\n\t\t* Complete the build process and return the built ECSpresso instance\n\t*/\n\tbuild(): ECSpresso<C, E, R> {\n\t\treturn this.ecspresso;\n\t}\n}\n",
10
- "import { createBundleSystemBuilder, SystemBuilderWithBundle } from './system-builder';\nimport type ECSpresso from './ecspresso';\n\n/**\n * Generates a unique ID for a bundle\n */\nfunction generateBundleId(): string {\n\treturn `bundle_${Date.now().toString(36)}_${Math.random().toString(36).substring(2, 9)}`;\n}\n\n/**\n * Bundle class that encapsulates a set of components, resources, events, and systems\n * that can be merged into a ECSpresso instance\n */\nexport default class Bundle<\n\tComponentTypes extends Record<string, any> = {},\n\tEventTypes extends Record<string, any> = {},\n\tResourceTypes extends Record<string, any> = {},\n> {\n\tprivate _systems: SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, any>[] = [];\n\tprivate _resources: Map<keyof ResourceTypes, ResourceTypes[keyof ResourceTypes]> = new Map();\n\tprivate _id: string;\n\n\tconstructor(id?: string) {\n\t\tthis._id = id || generateBundleId();\n\t}\n\n\t/**\n\t * Get the unique ID of this bundle\n\t */\n\tget id(): string {\n\t\treturn this._id;\n\t}\n\n\t/**\n\t * Set the ID of this bundle\n\t * @internal Used by combineBundles\n\t */\n\tset id(value: string) {\n\t\tthis._id = value;\n\t}\n\n\t/**\n\t * Add a system to this bundle, by label (creating a new builder) or by reusing an existing one\n\t */\n\taddSystem<Q extends Record<string, any>>(builder: SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, Q>): SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, Q>;\n\taddSystem(label: string): SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, {}>;\n\taddSystem(builderOrLabel: string | SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, any>) {\n\t\tif (typeof builderOrLabel === 'string') {\n\t\t\tconst system = createBundleSystemBuilder<ComponentTypes, EventTypes, ResourceTypes>(builderOrLabel, this);\n\t\t\tthis._systems.push(system);\n\t\t\treturn system;\n\t\t} else {\n\t\t\tthis._systems.push(builderOrLabel);\n\t\t\treturn builderOrLabel;\n\t\t}\n\t}\n\n\t/**\n\t * Add a resource to this bundle\n\t * @param label The resource key\n\t * @param resource The resource value or a factory function that returns the resource\n\t */\n\taddResource<K extends keyof ResourceTypes>(\n\t\tlabel: K,\n\t\tresource: ResourceTypes[K] | ((ecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>) => ResourceTypes[K] | Promise<ResourceTypes[K]>)\n\t) {\n\t\t// We need this cast because TypeScript doesn't recognize that a value of type\n\t\t// ResourceTypes[K] | (() => ResourceTypes[K] | Promise<ResourceTypes[K]>)\n\t\t// can be properly assigned to Map<keyof ResourceTypes, ResourceTypes[keyof ResourceTypes]>\n\t\tthis._resources.set(label, resource as unknown as ResourceTypes[K]);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Get all systems defined in this bundle\n\t * Returns built System objects instead of SystemBuilders\n\t */\n\tgetSystems() {\n\t\treturn this._systems.map(system => system.build());\n\t}\n\n\t/**\n\t * Register all systems in this bundle with an ECSpresso instance\n\t * @internal Used by ECSpresso when adding a bundle\n\t */\n\tregisterSystemsWithEcspresso(ecspresso: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>) {\n\t\tfor (const systemBuilder of this._systems) {\n\t\t\tsystemBuilder.build(ecspresso);\n\t\t}\n\t}\n\n\t/**\n\t * Get all resources defined in this bundle\n\t */\n\tgetResources(): Map<keyof ResourceTypes, ResourceTypes[keyof ResourceTypes]> {\n\t\treturn new Map(this._resources);\n\t}\n\n\t/**\n\t * Get a specific resource by key\n\t * @param key The resource key\n\t * @returns The resource value or undefined if not found\n\t */\n\tgetResource<K extends keyof ResourceTypes>(key: K): ResourceTypes[K] {\n\t\treturn this._resources.get(key) as ResourceTypes[K];\n\t}\n\n\t/**\n\t * Get all system builders in this bundle\n\t */\n\tgetSystemBuilders(): SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, any>[] {\n\t\treturn [...this._systems];\n\t}\n\n\t/**\n\t * Check if this bundle has a specific resource\n\t * @param key The resource key to check\n\t * @returns True if the resource exists\n\t */\n\thasResource<K extends keyof ResourceTypes>(key: K): boolean {\n\t\treturn this._resources.has(key);\n\t}\n}\n\n// Check if object has exactly the same type\ntype Exactly<T, U> =\n\tT extends U\n\t\t? U extends T\n\t\t\t? true\n\t\t\t: false\n\t\t: false;\n\n// Create a type error for incompatible types\ntype IncompatibleBundles<\n\tC1 extends Record<string, any>,\n\tC2 extends Record<string, any>,\n\tE1 extends Record<string, any>,\n\tE2 extends Record<string, any>,\n\tR1 extends Record<string, any>,\n\tR2 extends Record<string, any>\n> = {\n\t[K in keyof C1 & keyof C2]: Exactly<C1[K], C2[K]> extends false ? never : unknown;\n} & {\n\t[K in keyof E1 & keyof E2]: Exactly<E1[K], E2[K]> extends false ? never : unknown;\n} & {\n\t[K in keyof R1 & keyof R2]: Exactly<R1[K], R2[K]> extends false ? never : unknown;\n};\n\n/**\n * Function that merges multiple bundles into a single bundle\n */\nexport function mergeBundles<\n\tC1 extends Record<string, any>,\n\tE1 extends Record<string, any>,\n\tR1 extends Record<string, any>,\n\tC2 extends Record<string, any>,\n\tE2 extends Record<string, any>,\n\tR2 extends Record<string, any>\n>(\n\tid: string,\n\tbundle1: Bundle<C1, E1, R1>,\n\tbundle2: Bundle<C2, E2, R2> & IncompatibleBundles<C1, C2, E1, E2, R1, R2>\n): Bundle<C1 & C2, E1 & E2, R1 & R2>;\n\nexport function mergeBundles<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>\n>(\n\tid: string,\n\t...bundles: Array<Bundle<ComponentTypes, EventTypes, ResourceTypes>>\n): Bundle<ComponentTypes, EventTypes, ResourceTypes>;\n\nexport function mergeBundles(\n\tid: string,\n\t...bundles: Array<Bundle>\n): Bundle {\n\tif (bundles.length === 0) {\n\t\treturn new Bundle(id);\n\t}\n\n\tconst combined = new Bundle(id);\n\n\tfor (const bundle of bundles) {\n\t\tfor (const system of bundle.getSystemBuilders()) {\n\t\t\t// reuse the full builder so we carry over queries, hooks, and handlers\n\t\t\tcombined.addSystem(system);\n\t\t}\n\n\t\t// Add resources from this bundle\n\t\tfor (const [label, resource] of bundle.getResources().entries()) {\n\t\t\tcombined.addResource(label, resource);\n\t\t}\n\t}\n\n\treturn combined;\n}\n",
11
- "import ECSpresso from \"./ecspresso\";\n\nexport\ninterface Entity<ComponentTypes> {\n\tid: number;\n\tcomponents: Partial<ComponentTypes>;\n}\n\nexport\ninterface EventHandler<T> {\n\tcallback: (data: T) => void;\n\tonce: boolean;\n}\n\nexport\ninterface FilteredEntity<\n\tComponentTypes,\n\tWithComponents extends keyof ComponentTypes = never,\n\tWithoutComponents extends keyof ComponentTypes = never,\n> {\n\tid: number;\n\t// components: Pick<ComponentTypes, WithComponents> & Omit<Partial<ComponentTypes>, WithComponents | WithoutComponents>;\n\tcomponents: Omit<Partial<ComponentTypes>, WithoutComponents> & {\n\t\t[ComponentName in WithComponents]: ComponentTypes[ComponentName]\n\t};\n\n}\n\nexport\ninterface QueryConfig<\n\tComponentTypes,\n\tWithComponents extends keyof ComponentTypes,\n\tWithoutComponents extends keyof ComponentTypes,\n> {\n\twith: ReadonlyArray<WithComponents>;\n\twithout?: ReadonlyArray<WithoutComponents>;\n}\n\n/**\n * Utility type to derive the entity type that would result from a query definition.\n * This is useful for creating helper functions that operate on query results.\n *\n * @example\n * ```typescript\n * const queryDef = {\n * with: ['position', 'sprite'],\n * without: ['dead']\n * };\n *\n * type EntityType = QueryResultEntity<Components, typeof queryDef>;\n *\n * function updateSpritePosition(entity: EntityType) {\n * entity.components.sprite.position.set(\n * entity.components.position.x,\n * entity.components.position.y\n * );\n * }\n * ```\n */\nexport type QueryResultEntity<\n\tComponentTypes extends Record<string, any>,\n\tQueryDef extends {\n\t\twith: ReadonlyArray<keyof ComponentTypes>;\n\t\twithout?: ReadonlyArray<keyof ComponentTypes>;\n\t}\n> = FilteredEntity<\n\tComponentTypes,\n\tQueryDef['with'][number],\n\tQueryDef['without'] extends ReadonlyArray<any> ? QueryDef['without'][number] : never\n>;\n\n/**\n * Utility type to create a query definition with proper type inference.\n * This enables you to create reusable query definitions and extract their result types.\n *\n * @example\n * ```typescript\n * const movingEntitiesQuery = createQueryDefinition({\n * with: ['position', 'velocity'],\n * without: ['dead']\n * });\n *\n * type MovingEntity = QueryResultEntity<Components, typeof movingEntitiesQuery>;\n * ```\n */\nexport type QueryDefinition<\n\tComponentTypes extends Record<string, any>,\n\tWithComponents extends keyof ComponentTypes = any,\n\tWithoutComponents extends keyof ComponentTypes = any,\n> = {\n\twith: ReadonlyArray<WithComponents>;\n\twithout?: ReadonlyArray<WithoutComponents>;\n};\n\n/**\n * Helper function to create a query definition with proper type inference.\n * This enables better TypeScript inference when creating reusable queries.\n *\n * @example\n * ```typescript\n * const movingEntitiesQuery = createQueryDefinition({\n * with: ['position', 'velocity'],\n * without: ['dead']\n * });\n *\n * type MovingEntity = QueryResultEntity<Components, typeof movingEntitiesQuery>;\n *\n * function updatePosition(entity: MovingEntity) {\n * entity.components.position.x += entity.components.velocity.x;\n * entity.components.position.y += entity.components.velocity.y;\n * }\n *\n * world.addSystem('movement')\n * .addQuery('entities', movingEntitiesQuery)\n * .setProcess((queries) => {\n * for (const entity of queries.entities) {\n * updatePosition(entity);\n * }\n * });\n * ```\n */\nexport function createQueryDefinition<\n\tComponentTypes extends Record<string, any>,\n\tconst QueryDef extends {\n\t\twith: ReadonlyArray<keyof ComponentTypes>;\n\t\twithout?: ReadonlyArray<keyof ComponentTypes>;\n\t}\n>(queryDef: QueryDef): QueryDef {\n\treturn queryDef;\n}\n\nexport\ninterface System<\n\tComponentTypes extends Record<string, any> = {},\n\tWithComponents extends keyof ComponentTypes = never,\n\tWithoutComponents extends keyof ComponentTypes = never,\n\tEventTypes extends Record<string, any> = {},\n\tResourceTypes extends Record<string, any> = {},\n> {\n\tlabel: string;\n\t/**\n\t * System priority - higher values execute first (default: 0)\n\t * When systems have the same priority, they execute in registration order\n\t */\n\tpriority?: number;\n\tentityQueries?: {\n\t\t[queryName: string]: QueryConfig<ComponentTypes, WithComponents, WithoutComponents>;\n\t};\n\t/**\n\t * Process method that runs during each update cycle\n\t * @param queries The entity queries results based on system's entityQueries definition\n\t * @param deltaTime Time elapsed since the last update in seconds\n\t * @param ecs The ECSpresso instance providing access to all ECS functionality\n\t */\n\tprocess?(\n\t\tqueries: {\n\t\t\t[queryName: string]: Array<FilteredEntity<ComponentTypes, WithComponents, WithoutComponents>>;\n\t\t} | Array<FilteredEntity<ComponentTypes, WithComponents, WithoutComponents>>,\n\t\tdeltaTime: number,\n\t\tecs: ECSpresso<\n\t\t\tComponentTypes,\n\t\t\tEventTypes,\n\t\t\tResourceTypes\n\t\t>\n\t): void;\n\n\t/**\n\t * Lifecycle hook called when the system is initialized\n\t * This is called when ECSpresso.initialize() is invoked, after resources are initialized\n\t * Use this for one-time initialization that depends on resources\n\t * @param ecs The ECSpresso instance providing access to all ECS functionality\n\t */\n\tonInitialize?(\n\t\tecs: ECSpresso<\n\t\t\tComponentTypes,\n\t\t\tEventTypes,\n\t\t\tResourceTypes\n\t\t>\n\t): void | Promise<void>;\n\n\t/**\n\t * Lifecycle hook called when the system is detached from the ECS\n\t * @param ecs The ECSpresso instance providing access to all ECS functionality\n\t */\n\tonDetach?(\n\t\tecs: import(\"./ecspresso\").default<\n\t\t\tComponentTypes,\n\t\t\tEventTypes,\n\t\t\tResourceTypes\n\t\t>\n\t): void;\n\n\t/**\n\t * Event handlers for specific event types\n\t */\n\teventHandlers?: {\n\t\t[EventName in keyof EventTypes]?: {\n\t\t\t/**\n\t\t\t * Event handler function\n\t\t\t * @param data The event data specific to this event type\n\t\t\t * @param ecs The ECSpresso instance providing access to all ECS functionality\n\t\t\t */\n\t\t\thandler(\n\t\t\t\tdata: EventTypes[EventName],\n\t\t\t\tecs: ECSpresso<\n\t\t\t\t\tComponentTypes,\n\t\t\t\t\tEventTypes,\n\t\t\t\t\tResourceTypes\n\t\t\t\t>\n\t\t\t): void;\n\t\t};\n\t};\n}\n\n/**\n * Utility type for merging two types\n */\n// This sets props with the same name but different type to \"never\". Maybe we want this?\nexport type Merge<T1, T2> = T1 & T2;\n// This makes the later prop types override the earlier ones. Maybe we want this instead?\n// export type Merge<T1, T2> = Omit<T1, keyof T2> & T2;\n// Or maybe this, which sets props with the same name to a union of the two types\n// export type Merge<T1, T2> = {\n// \t[K in keyof T1 | keyof T2]: K extends keyof T1 & keyof T2\n// \t\t? T1[K] | T2[K]\n// \t\t: K extends keyof T1\n// \t\t\t? T1[K]\n// \t\t\t: K extends keyof T2\n// \t\t\t\t? T2[K]\n// \t\t\t\t: never;\n// };\n\nexport type MergeAll<T extends any[]> = T extends [infer First, ...infer Rest] ?\n\tRest extends [] ?\n\t\tFirst: Merge<First, MergeAll<Rest>>:\n\t{};\n",
7
+ "export default\nclass ResourceManager<ResourceTypes extends Record<string, any> = Record<string, any>> {\n\tprivate resources: Map<string, any> = new Map();\n\tprivate resourceFactories: Map<string, (context?: any) => any | Promise<any>> = new Map();\n\tprivate initializedResourceKeys: Set<string> = new Set();\n\n\t/**\n\t * Add a resource to the manager\n\t * @param label The resource key\n\t * @param resource The resource value or a factory function that returns the resource\n\t * @returns The resource manager instance for chaining\n\t */\n\tadd<K extends keyof ResourceTypes>(\n\t\tlabel: K,\n\t\tresource: ResourceTypes[K] | ((context?: any) => ResourceTypes[K] | Promise<ResourceTypes[K]>),\n\t) {\n\t\tif (this._isFactoryFunction(resource)) {\n\t\t\t// Factory function\n\t\t\tthis.resourceFactories.set(label as string, resource as (context?: any) => any | Promise<any>);\n\t\t} else {\n\t\t\t// Direct resource value\n\t\t\tthis.resources.set(label as string, resource);\n\t\t\tthis.initializedResourceKeys.add(label as string);\n\t\t}\n\t\treturn this;\n\t}\n\n\t/**\n\t * Improved detection of factory functions vs direct values/classes\n\t * @private\n\t */\n\tprivate _isFactoryFunction(value: any): boolean {\n\t\tif (typeof value !== 'function') {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Get the function as string for analysis\n\t\tconst funcStr = value.toString();\n\n\t\t// Check for explicit class syntax\n\t\tif (funcStr.startsWith('class ')) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Check for native functions/constructors\n\t\tif (funcStr.includes('[native code]')) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Check if it's a constructor function (has prototype properties beyond constructor)\n\t\t// This is a more nuanced check than just checking for prototype existence\n\t\tif (value.prototype) {\n\t\t\tconst prototypeKeys = Object.getOwnPropertyNames(value.prototype);\n\t\t\t// Constructor functions typically have additional prototype properties\n\t\t\t// Regular functions only have 'constructor'\n\t\t\tif (prototypeKeys.length > 1 || (prototypeKeys.length === 1 && prototypeKeys[0] !== 'constructor')) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\t// Additional heuristics for constructor functions\n\t\t// Constructor functions typically start with capital letter\n\t\tif (value.name && value.name[0] === value.name[0].toUpperCase() && value.name.length > 1) {\n\t\t\t// But this alone isn't enough - many factory functions also start with capitals\n\t\t\t// Only treat as constructor if it also has other constructor-like characteristics\n\t\t\tif (funcStr.includes('this.') || funcStr.includes('new ')) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\t// If it passes all checks, treat as factory function\n\t\treturn true;\n\t}\n\n\t/**\n\t * Get a resource from the manager\n\t * @param label The resource key\n\t * @param context Optional context to pass to factory functions (usually the ECSpresso instance)\n\t * @returns The resource value\n\t * @throws Error if resource not found\n\t */\n\tget<K extends keyof ResourceTypes>(\n\t\tlabel: K,\n\t\tcontext?: any\n\t): ResourceTypes[K] {\n\t\t// Check if we already have the initialized resource\n\t\tconst resource = this.resources.get(label as string);\n\t\tif (resource !== undefined) {\n\t\t\treturn resource as any;\n\t\t}\n\n\t\t// Check if we have a factory for this resource\n\t\tconst factory = this.resourceFactories.get(label as string);\n\t\tif (factory === undefined) {\n\t\t\tthrow new Error(`Resource ${String(label)} not found`);\n\t\t}\n\n\t\t// Initialize the resource, passing the context\n\t\tconst initializedResource = factory(context);\n\n\t\t// If it's not a Promise, store it immediately\n\t\tif (!(initializedResource instanceof Promise)) {\n\t\t\tthis.resources.set(label as string, initializedResource);\n\t\t\tthis.initializedResourceKeys.add(label as string);\n\t\t}\n\n\t\treturn initializedResource as any;\n\t}\n\n\t/**\n\t * Check if a resource exists\n\t * @param label The resource key\n\t * @returns True if the resource exists\n\t */\n\thas<K extends keyof ResourceTypes>(label: K): boolean {\n\t\treturn this.resources.has(label as string) || this.resourceFactories.has(label as string);\n\t}\n\n\t/**\n\t * Remove a resource\n\t * @param label The resource key\n\t * @returns True if the resource was removed\n\t */\n\tremove<K extends keyof ResourceTypes>(label: K): boolean {\n\t\tconst resourceRemoved = this.resources.delete(label as string);\n\t\tconst factoryRemoved = this.resourceFactories.delete(label as string);\n\t\tif (this.initializedResourceKeys.has(label as string)) {\n\t\t\tthis.initializedResourceKeys.delete(label as string);\n\t\t}\n\t\treturn resourceRemoved || factoryRemoved;\n\t}\n\n\t/**\n\t * Get all resource keys\n\t * @returns Array of resource keys\n\t */\n\tgetKeys(): Array<string> {\n\t\tconst keys = new Set([\n\t\t\t...this.resources.keys(),\n\t\t\t...this.resourceFactories.keys()\n\t\t]);\n\t\treturn Array.from(keys);\n\t}\n\n\t/**\n\t * Check if a resource needs to be initialized\n\t * @param label The resource key\n\t * @returns True if the resource needs initialization\n\t */\n\tneedsInitialization<K extends keyof ResourceTypes>(label: K): boolean {\n\t\treturn this.resourceFactories.has(label as string) && !this.initializedResourceKeys.has(label as string);\n\t}\n\n\t/**\n\t * Get all resource keys that need to be initialized\n\t * @returns Array of resource keys that need initialization\n\t */\n\tgetPendingInitializationKeys(): Array<string> {\n\t\treturn Array\n\t\t\t.from(this.resourceFactories.keys())\n\t\t\t.filter(key => !this.initializedResourceKeys.has(key));\n\t}\n\n\t/**\n\t * Initialize a specific resource if it's a factory function\n\t * @param label The resource key\n\t * @param context Optional context to pass to factory functions\n\t * @returns Promise that resolves when the resource is initialized\n\t */\n\tasync initializeResource<K extends keyof ResourceTypes>(\n\t\tlabel: K,\n\t\tcontext?: any\n\t): Promise<void> {\n\t\tif (!this.resourceFactories.has(label as string) || this.initializedResourceKeys.has(label as string)) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst factory = this.resourceFactories.get(label as string)!;\n\t\tconst initializedResource = await factory(context);\n\t\tthis.resources.set(label as string, initializedResource);\n\t\tthis.initializedResourceKeys.add(label as string);\n\t\tthis.resourceFactories.delete(label as string);\n\t}\n\n\t/**\n\t * Initialize specific resources or all resources that haven't been initialized yet\n\t * @param keys Optional array of resource keys to initialize or optional context to pass to factory functions\n\t * @returns Promise that resolves when the specified resources are initialized\n\t */\n\tasync initializeResources<K extends keyof ResourceTypes>(\n\t\tcontext?: any,\n\t\t...keys: K[]\n\t): Promise<void> {\n\t\t// If no keys provided, initialize all pending resources\n\t\tif (keys.length === 0) {\n\t\t\tconst pendingKeys = this.getPendingInitializationKeys();\n\t\t\tawait Promise.all(pendingKeys.map(key => this.initializeResource(key, context)));\n\t\t\treturn;\n\t\t}\n\n\t\t// Otherwise, initialize only the specified resources\n\t\tawait Promise.all(\n\t\t\tkeys.map(key => this.initializeResource(key, context))\n\t\t);\n\t}\n}\n",
8
+ "import Bundle from \"./bundle\";\nimport ECSpresso from \"./ecspresso\";\nimport type { FilteredEntity, System } from \"./types\";\n\n/**\n * Builder class for creating type-safe ECS Systems with proper query inference\n */\nexport class SystemBuilder<\n\tComponentTypes extends Record<string, any> = Record<string, any>,\n\tEventTypes extends Record<string, any> = Record<string, any>,\n\tResourceTypes extends Record<string, any> = Record<string, any>,\n\tQueries extends Record<string, QueryDefinition<ComponentTypes>> = {},\n> {\n\tprivate queries: Queries = {} as Queries;\n\tprivate processFunction?: ProcessFunction<ComponentTypes, EventTypes, ResourceTypes, Queries>;\n\tprivate detachFunction?: LifecycleFunction<ComponentTypes, EventTypes, ResourceTypes>;\n\tprivate initializeFunction?: LifecycleFunction<ComponentTypes, EventTypes, ResourceTypes>;\n\tprivate eventHandlers?: {\n\t\t[EventName in keyof EventTypes]?: {\n\t\t\thandler(\n\t\t\t\tdata: EventTypes[EventName],\n\t\t\t\tecs: ECSpresso<\n\t\t\t\t\tComponentTypes,\n\t\t\t\t\tEventTypes,\n\t\t\t\t\tResourceTypes\n\t\t\t\t>,\n\t\t\t): void;\n\t\t};\n\t};\n\tprivate _priority = 0; // Default priority is 0\n\tprivate _isRegistered = false; // Track if system has been auto-registered\n\n\tconstructor(\n\t\tprivate _label: string,\n\t\tprivate _ecspresso: ECSpresso<ComponentTypes, EventTypes, ResourceTypes> | null = null,\n\t\tprivate _bundle: Bundle<ComponentTypes, EventTypes, ResourceTypes> | null = null,\n\t) {}\n\n\tget label() {\n\t\treturn this._label;\n\t}\n\n\t/**\n\t * Returns the associated bundle if one was provided in the constructor\n\t */\n\tget bundle() {\n\t\treturn this._bundle;\n\t}\n\n\t/**\n\t * Returns the associated ECSpresso instance if one was provided in the constructor\n\t */\n\tget ecspresso() {\n\t\treturn this._ecspresso;\n\t}\n\n\t/**\n\t * Auto-register this system with its ECSpresso instance if not already registered\n\t * @private\n\t */\n\tprivate _autoRegister(): void {\n\t\tif (this._isRegistered || !this._ecspresso) return;\n\t\t\n\t\tconst system = this._buildSystemObject();\n\t\tregisterSystemWithEcspresso(system, this._ecspresso);\n\t\tthis._isRegistered = true;\n\t}\n\n\t/**\n\t * Create the system object without registering it\n\t * @private\n\t */\n\tprivate _buildSystemObject(): System<ComponentTypes, any, any, EventTypes, ResourceTypes> {\n\t\treturn this._createSystemObject();\n\t}\n\n\t/**\n\t * Create a system object with all configured properties\n\t * @private\n\t */\n\tprivate _createSystemObject(): System<ComponentTypes, any, any, EventTypes, ResourceTypes> {\n\t\tconst system: System<ComponentTypes, any, any, EventTypes, ResourceTypes> = {\n\t\t\tlabel: this._label,\n\t\t\tentityQueries: this.queries,\n\t\t\tpriority: this._priority,\n\t\t};\n\n\t\tif (this.processFunction) {\n\t\t\tsystem.process = this.processFunction;\n\t\t}\n\n\t\tif (this.detachFunction) {\n\t\t\tsystem.onDetach = this.detachFunction;\n\t\t}\n\n\t\tif (this.initializeFunction) {\n\t\t\tsystem.onInitialize = this.initializeFunction;\n\t\t}\n\n\t\tif (this.eventHandlers) {\n\t\t\tsystem.eventHandlers = this.eventHandlers;\n\t\t}\n\n\t\treturn system;\n\t}\n\n\t// TODO: Should this be a setter?\n\t/**\n\t * Set the priority of this system. Systems with higher priority values\n\t * execute before those with lower values. Systems with the same priority\n\t * execute in the order they were registered.\n\t * @param priority The priority value (default: 0)\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tsetPriority(priority: number): this {\n\t\tthis._priority = priority;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Add a query definition to the system\n\t */\n\taddQuery<\n\t\tQueryName extends string,\n\t\tWithComponents extends keyof ComponentTypes,\n\t\tWithoutComponents extends keyof ComponentTypes = never,\n\t\tNewQueries extends Queries & Record<QueryName, QueryDefinition<ComponentTypes, WithComponents, WithoutComponents>> =\n\t\t\tQueries & Record<QueryName, QueryDefinition<ComponentTypes, WithComponents, WithoutComponents>>\n\t>(\n\t\tname: QueryName,\n\t\tdefinition: {\n\t\t\twith: ReadonlyArray<WithComponents>;\n\t\t\twithout?: ReadonlyArray<WithoutComponents>;\n\t\t}\n\t): this extends SystemBuilderWithEcspresso<ComponentTypes, EventTypes, ResourceTypes, Queries>\n\t\t? SystemBuilderWithEcspresso<ComponentTypes, EventTypes, ResourceTypes, NewQueries>\n\t\t: this extends SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, Queries>\n\t\t\t? SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, NewQueries>\n\t\t\t: SystemBuilder<ComponentTypes, EventTypes, ResourceTypes, NewQueries> {\n\t\t// Cast is needed because TypeScript can't preserve the type information\n\t\t// when modifying an object property\n\t\tconst newBuilder = this as any;\n\t\tnewBuilder.queries = {\n\t\t\t...this.queries,\n\t\t\t[name]: definition,\n\t\t};\n\t\treturn newBuilder;\n\t}\n\n\t/**\n\t * Set the system's process function that runs each update\n\t * @param process Function to process entities matching the system's queries each update\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tsetProcess(\n\t\tprocess: ProcessFunction<ComponentTypes, EventTypes, ResourceTypes, Queries>\n\t): this {\n\t\tthis.processFunction = process;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Register this system with its ECSpresso instance and return the ECSpresso for chaining\n\t * This enables seamless method chaining: .registerAndContinue().addSystem(...)\n\t * @returns ECSpresso instance if attached to one, otherwise throws an error\n\t */\n\tregisterAndContinue(): ECSpresso<ComponentTypes, EventTypes, ResourceTypes> {\n\t\tif (!this._ecspresso) {\n\t\t\tthrow new Error(`Cannot register system '${this._label}': SystemBuilder is not attached to an ECSpresso instance. Use Bundle.addSystem() or ECSpresso.addSystem() instead.`);\n\t\t}\n\t\t\n\t\tthis._autoRegister();\n\t\treturn this._ecspresso;\n\t}\n\n\t/**\n\t * Complete this system and return the parent container for seamless chaining\n\t * - For ECSpresso-attached builders: registers the system and returns ECSpresso\n\t * - For Bundle-attached builders: returns the Bundle\n\t * This method is typed via the specialized interfaces (SystemBuilderWithEcspresso, SystemBuilderWithBundle)\n\t */\n\tand(): ECSpresso<ComponentTypes, EventTypes, ResourceTypes> | Bundle<ComponentTypes, EventTypes, ResourceTypes> {\n\t\tif (this._ecspresso) {\n\t\t\tthis._autoRegister();\n\t\t\treturn this._ecspresso;\n\t\t}\n\n\t\tif (this._bundle) {\n\t\t\treturn this._bundle;\n\t\t}\n\n\t\tthrow new Error(`Cannot use and() on system '${this._label}': not attached to ECSpresso or Bundle.`);\n\t}\n\n\t/**\n\t * Set the onDetach lifecycle hook\n\t * Called when the system is removed from the ECS\n\t * @param onDetach Function to run when this system is detached from the ECS\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tsetOnDetach(\n\t\tonDetach: LifecycleFunction<ComponentTypes, EventTypes, ResourceTypes>\n\t): this {\n\t\tthis.detachFunction = onDetach;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Set the onInitialize lifecycle hook\n\t * Called when the system is initialized via ECSpresso.initialize() method\n\t * @param onInitialize Function to run when this system is initialized\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tsetOnInitialize(\n\t\tonInitialize: LifecycleFunction<ComponentTypes, EventTypes, ResourceTypes>\n\t): this {\n\t\tthis.initializeFunction = onInitialize;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Set event handlers for the system\n\t * These handlers will be automatically subscribed when the system is attached\n\t * @param handlers Object mapping event names to handler functions\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tsetEventHandlers(\n\t\thandlers: {\n\t\t\t[EventName in keyof EventTypes]?: {\n\t\t\t\thandler(\n\t\t\t\t\tdata: EventTypes[EventName],\n\t\t\t\t\tecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>\n\t\t\t\t): void;\n\t\t\t};\n\t\t}\n\t): this {\n\t\tthis.eventHandlers = handlers;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Build the final system object\n\t */\n\tbuild(ecspresso?: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>) {\n\t\tconst system = this._createSystemObject();\n\n\t\tif (this._ecspresso) {\n\t\t\tregisterSystemWithEcspresso(system, this._ecspresso);\n\t\t}\n\n\t\tif(ecspresso) {\n\t\t\tregisterSystemWithEcspresso(system, ecspresso);\n\t\t}\n\n\t\treturn this;\n\t}\n}\n\n/**\n * Helper function to register a system with an ECSpresso instance\n * This handles attaching the system and setting up event handlers\n * @internal Used by SystemBuilder and Bundle\n */\nexport function registerSystemWithEcspresso<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>\n>(\n\tsystem: System<ComponentTypes, any, any, EventTypes, ResourceTypes>,\n\tecspresso: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>\n) {\n\t// Use the new internal registration method instead of direct property access\n\tecspresso._registerSystem(system);\n}\n\n// Helper type definitions\ntype QueryDefinition<\n\tComponentTypes,\n\tWithComponents extends keyof ComponentTypes = any,\n\tWithoutComponents extends keyof ComponentTypes = any,\n> = {\n\twith: ReadonlyArray<WithComponents>;\n\twithout?: ReadonlyArray<WithoutComponents>;\n};\n\ntype QueryResults<\n\tComponentTypes,\n\tQueries extends Record<string, QueryDefinition<ComponentTypes>>,\n> = {\n\t[QueryName in keyof Queries]: QueryName extends string\n\t\t? FilteredEntity<\n\t\t\tComponentTypes,\n\t\t\tQueries[QueryName] extends QueryDefinition<ComponentTypes, infer W, any> ? W : never,\n\t\t\tQueries[QueryName] extends QueryDefinition<ComponentTypes, any, infer WO> ? WO : never\n\t\t>[]\n\t\t: never;\n};\n\n/**\n * Function signature for system process methods\n * @param queries Results of entity queries defined by the system\n * @param deltaTime Time elapsed since last update in seconds\n * @param ecs The ECSpresso instance providing access to all ECS functionality\n */\ntype ProcessFunction<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>,\n\tQueries extends Record<string, QueryDefinition<ComponentTypes>>,\n> = (\n\tqueries: QueryResults<ComponentTypes, Queries>,\n\tdeltaTime: number,\n\tecs: ECSpresso<\n\t\tComponentTypes,\n\t\tEventTypes,\n\t\tResourceTypes\n\t>\n) => void;\n\n/**\n * Type for system initialization functions\n * These can be asynchronous\n */\ntype LifecycleFunction<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>,\n> = (\n\tecs: ECSpresso<\n\t\tComponentTypes,\n\t\tEventTypes,\n\t\tResourceTypes\n\t>,\n) => void | Promise<void>;\n\n/**\n * Create a SystemBuilder attached to an ECSpresso instance\n * Helper function used by ECSpresso.addSystem\n */\nexport function createEcspressoSystemBuilder<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>\n>(\n\tlabel: string,\n\tecspresso: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>\n): SystemBuilderWithEcspresso<ComponentTypes, EventTypes, ResourceTypes> {\n\treturn new SystemBuilder<ComponentTypes, EventTypes, ResourceTypes>(\n\t\tlabel,\n\t\tecspresso\n\t) as SystemBuilderWithEcspresso<ComponentTypes, EventTypes, ResourceTypes>;\n}\n\n/**\n * Create a SystemBuilder attached to a Bundle\n * Helper function used by Bundle.addSystem\n */\nexport function createBundleSystemBuilder<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>\n>(\n\tlabel: string,\n\tbundle: Bundle<ComponentTypes, EventTypes, ResourceTypes>\n): SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes> {\n\treturn new SystemBuilder<ComponentTypes, EventTypes, ResourceTypes>(\n\t\tlabel,\n\t\tnull,\n\t\tbundle\n\t) as SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes>;\n}\n\n// Type interfaces for specialized SystemBuilders\n\n/**\n * SystemBuilder with a guaranteed non-null reference to an ECSpresso instance\n */\nexport interface SystemBuilderWithEcspresso<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>,\n\tQueries extends Record<string, QueryDefinition<ComponentTypes>> = {}\n> extends SystemBuilder<ComponentTypes, EventTypes, ResourceTypes, Queries> {\n\treadonly ecspresso: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>;\n\t\n\t/**\n\t * Complete this system and return ECSpresso for seamless chaining\n\t * Automatically registers the system when called\n\t */\n\tand(): ECSpresso<ComponentTypes, EventTypes, ResourceTypes>;\n}\n\n/**\n * SystemBuilder with a guaranteed non-null reference to a Bundle\n */\nexport interface SystemBuilderWithBundle<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>,\n\tQueries extends Record<string, QueryDefinition<ComponentTypes>> = {}\n> extends SystemBuilder<ComponentTypes, EventTypes, ResourceTypes, Queries> {\n\treadonly bundle: Bundle<ComponentTypes, EventTypes, ResourceTypes>;\n\n\t/**\n\t * Complete this system and return the Bundle for chaining\n\t * Enables fluent API: bundle.addSystem(...).and().addSystem(...)\n\t */\n\tand(): Bundle<ComponentTypes, EventTypes, ResourceTypes>;\n}\n",
9
+ "import EntityManager from \"./entity-manager\";\nimport EventBus from \"./event-bus\";\nimport ResourceManager from \"./resource-manager\";\nimport type { System, FilteredEntity, Entity } from \"./types\";\nimport type Bundle from \"./bundle\";\nimport { createEcspressoSystemBuilder } from \"./system-builder\";\nimport { version } from \"../package.json\";\nimport type { BundlesAreCompatible } from \"./type-utils\";\n\n/**\n\t* Interface declaration for ECSpresso constructor to ensure type augmentation works properly.\n\t* This merges with the class declaration below.\n*/\nexport default interface ECSpresso<\n\tComponentTypes extends Record<string, any> = {},\n\tEventTypes extends Record<string, any> = {},\n\tResourceTypes extends Record<string, any> = {},\n> {\n\t/**\n\t\t* Default constructor\n\t*/\n\tnew(): ECSpresso<ComponentTypes, EventTypes, ResourceTypes>;\n}\n\nconst EmptyQueryResults = {};\n\n/**\n\t* ECSpresso is the central ECS framework class that connects all features.\n\t* It handles creation and management of entities, components, and systems, and provides lifecycle hooks.\n*/\nexport default class ECSpresso<\n\tComponentTypes extends Record<string, any> = {},\n\tEventTypes extends Record<string, any> = {},\n\tResourceTypes extends Record<string, any> = {},\n> {\n\t/** Library version*/\n\tpublic static readonly VERSION = version;\n\n\t/** Access/modify stored components and entities*/\n\tprivate _entityManager: EntityManager<ComponentTypes>;\n\t/** Publish/subscribe to events*/\n\tprivate _eventBus: EventBus<EventTypes>;\n\t/** Access/modify registered resources*/\n\tprivate _resourceManager: ResourceManager<ResourceTypes>;\n\n\t/** Registered systems that will be updated in order*/\n\tprivate _systems: Array<System<ComponentTypes, any, any, EventTypes, ResourceTypes>> = [];\n\t/** Cached sorted systems for efficient updates */\n\tprivate _sortedSystems: Array<System<ComponentTypes, any, any, EventTypes, ResourceTypes>> = [];\n\t/** Track installed bundles to prevent duplicates*/\n\tprivate _installedBundles: Set<string> = new Set();\n\n\t/**\n\t\t* Creates a new ECSpresso instance.\n\t*/\n\tconstructor() {\n\t\tthis._entityManager = new EntityManager<ComponentTypes>();\n\t\tthis._eventBus = new EventBus<EventTypes>();\n\t\tthis._resourceManager = new ResourceManager<ResourceTypes>();\n\t\tthis._sortedSystems = []; // Initialize the sorted systems array\n\t}\n\n\t/**\n\t\t* Creates a new ECSpresso builder for type-safe bundle installation.\n\t\t* This is the preferred way to create an ECSpresso instance with bundles.\n\t *\n\t\t* @returns A builder instance for fluent method chaining\n\t *\n\t\t* @example\n\t\t* ```typescript\n\t\t* const ecs = ECSpresso.create<BaseComponents, BaseEvents, BaseResources>()\n\t *\t .withBundle(bundle1)\n\t *\t .withBundle(bundle2)\n\t *\t .build();\n\t\t* ```\n\t*/\n\tstatic create<\n\t\tC extends Record<string, any> = {},\n\t\tE extends Record<string, any> = {},\n\t\tR extends Record<string, any> = {},\n\t>(): ECSpressoBuilder<C, E, R> {\n\t\treturn new ECSpressoBuilder<C, E, R>();\n\t}\n\n\t/**\n\t\t* Adds a system directly to this ECSpresso instance\n\t\t* @param label Unique name to identify the system\n\t\t* @returns A SystemBuilder instance for method chaining\n\t*/\n\taddSystem(label: string) {\n\t\treturn createEcspressoSystemBuilder<\n\t\t\tComponentTypes,\n\t\t\tEventTypes,\n\t\t\tResourceTypes\n\t\t>(label, this);\n\t}\n\n\t/**\n\t\t* Update all systems, passing deltaTime and query results to each system's process function\n\t\t* @param deltaTime Time elapsed since the last update (in seconds)\n\t*/\n\tupdate(deltaTime: number) {\n\t\t// Use the cached sorted systems array instead of re-sorting on every update\n\t\tfor (const system of this._sortedSystems) {\n\t\t\tif (!system.process) continue;\n\n\t\t\t// Prepare query results for each defined query in the system\n\t\t\tconst queryResults: Record<string, any> = {};\n\t\t\tlet hasResults = false;\n\t\t\tlet hasQueries = false;\n\n\t\t\tif (system.entityQueries) {\n\t\t\t\tfor (const queryName in system.entityQueries) {\n\t\t\t\t\thasQueries = true;\n\n\t\t\t\t\tconst query = system.entityQueries[queryName];\n\n\t\t\t\t\tif (query) {\n\t\t\t\t\t\tqueryResults[queryName] = this._entityManager.getEntitiesWithQuery(\n\t\t\t\t\t\t\tquery.with,\n\t\t\t\t\t\t\tquery.without || []\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tif(queryResults[queryName].length) {\n\t\t\t\t\t\t\thasResults = true; // At least one query has results\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Call the system's process function only if there are results or there is no query.\n\t\t\tif (hasResults) {\n\t\t\t\tsystem.process(queryResults, deltaTime, this);\n\t\t\t} else if(!hasQueries) {\n\t\t\t\tsystem.process(EmptyQueryResults, deltaTime, this);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Initialize all resources and systems\n\t * This method:\n\t * 1. Initializes all resources that were added as factory functions\n\t * 2. Calls the onInitialize lifecycle hook on all systems\n\t *\n\t * This is useful for game startup to ensure all resources are ready\n\t * and systems are properly initialized before the game loop begins.\n\t *\n\t * @param resourceKeys Optional array of specific resource keys to initialize\n\t * @returns Promise that resolves when everything is initialized\n\t */\n\tasync initialize(): Promise<void> {\n\t\tawait this.initializeResources();\n\n\t\tfor (const system of this._systems) {\n\t\t\tawait system.onInitialize?.(this);\n\t\t}\n\t}\n\n\t/**\n\t * Initialize specific resources or all resources that were added as factory functions but haven't been initialized yet.\n\t * This is useful when you need to ensure resources are ready before proceeding.\n\t * @param keys Optional array of resource keys to initialize. If not provided, all pending resources will be initialized.\n\t * @returns Promise that resolves when the specified resources are initialized\n\t */\n\tasync initializeResources<K extends keyof ResourceTypes>(...keys: K[]): Promise<void> {\n\t\tawait this._resourceManager.initializeResources(this, ...keys);\n\t}\n\n\t/**\n\t\t* Sort the systems array by priority (higher priority first)\n\t\t* Called internally when system list changes\n\t\t* @private\n\t*/\n\tprivate _sortSystems(): void {\n\t\tthis._sortedSystems = [...this._systems].sort((a, b) => {\n\t\t\tconst priorityA = a.priority ?? 0;\n\t\t\tconst priorityB = b.priority ?? 0;\n\t\t\treturn priorityB - priorityA; // Higher priority executes first\n\t\t});\n\t}\n\n\t/**\n\t\t* Update the priority of a system\n\t\t* @param label The unique label of the system to update\n\t\t* @param priority The new priority value (higher values execute first)\n\t\t* @returns true if the system was found and updated, false otherwise\n\t*/\n\tupdateSystemPriority(label: string, priority: number): boolean {\n\t\tconst system = this._systems.find(system => system.label === label);\n\t\tif (!system) return false;\n\n\t\t// Set the new priority\n\t\tsystem.priority = priority;\n\n\t\t// Re-sort the systems array\n\t\tthis._sortSystems();\n\n\t\treturn true;\n\t}\n\n\t/**\n\t\t* Remove a system by its label\n\t\t* Calls the system's onDetach method with this ECSpresso instance if defined\n\t\t* @param label The unique label of the system to remove\n\t\t* @returns true if the system was found and removed, false otherwise\n\t*/\n\tremoveSystem(label: string): boolean {\n\t\tconst index = this._systems.findIndex(system => system.label === label);\n\t\tif (index === -1) return false;\n\n\t\tconst system = this._systems[index];\n\t\t// This should never happen since we just found the system by index\n\t\tif (!system) return false;\n\n\t\t// Call the onDetach lifecycle hook if defined\n\t\tif (system.onDetach) {\n\t\t\tsystem.onDetach(this);\n\t\t}\n\n\t\t// Remove system\n\t\tthis._systems.splice(index, 1);\n\n\t\t// Re-sort systems\n\t\tthis._sortSystems();\n\n\t\treturn true;\n\t}\n\n\t/**\n\t\t* Internal method to register a system with this ECSpresso instance\n\t\t* @internal Used by SystemBuilder - replaces direct private property access\n\t*/\n\t_registerSystem(system: System<ComponentTypes, any, any, EventTypes, ResourceTypes>): void {\n\t\tthis._systems.push(system);\n\t\tthis._sortSystems();\n\n\t\t// Set up event handlers if they exist\n\t\tif (!system.eventHandlers) return;\n\n\t\tfor (const eventName in system.eventHandlers) {\n\t\t\tconst handler = system.eventHandlers[eventName]?.handler;\n\t\t\tif (handler) {\n\t\t\t\tthis._eventBus.subscribe(eventName, (data) => {\n\t\t\t\t\thandler(data, this);\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t\t* Check if a resource exists\n\t*/\n\thasResource<K extends keyof ResourceTypes>(key: K): boolean {\n\t\treturn this._resourceManager.has(key);\n\t}\n\n\t/**\n\t\t* Get a resource if it exists, or undefined if not\n\t*/\n\tgetResource<K extends keyof ResourceTypes>(key: K): ResourceTypes[K] {\n\t\tconst resource = this._resourceManager.get(key, this);\n\n\t\tif (!resource) throw new Error(`Resource '${String(key)}' not found. Available resources: [${this.getResourceKeys().map(k => String(k)).join(', ')}]`);\n\n\t\treturn resource;\n\t}\n\n\t/**\n\t\t* Add a resource to the ECS instance\n\t*/\n\taddResource<K extends keyof ResourceTypes>(\n\t\tkey: K,\n\t\tresource: ResourceTypes[K] | ((ecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>) => ResourceTypes[K] | Promise<ResourceTypes[K]>)\n\t): this {\n\t\tthis._resourceManager.add(key, resource);\n\t\treturn this;\n\t}\n\n\t/**\n\t\t* Remove a resource from the ECS instance\n\t\t* @param key The resource key to remove\n\t\t* @returns True if the resource was removed, false if it didn't exist\n\t*/\n\tremoveResource<K extends keyof ResourceTypes>(key: K): boolean {\n\t\treturn this._resourceManager.remove(key);\n\t}\n\n\t/**\n\t\t* Update an existing resource using an updater function\n\t\t* @param key The resource key to update\n\t\t* @param updater Function that receives the current resource value and returns the new value\n\t\t* @returns This ECSpresso instance for chaining\n\t\t* @throws Error if the resource doesn't exist\n\t*/\n\tupdateResource<K extends keyof ResourceTypes>(\n\t\tkey: K,\n\t\tupdater: (current: ResourceTypes[K]) => ResourceTypes[K]\n\t): this {\n\t\tconst currentResource = this.getResource(key);\n\t\tconst updatedResource = updater(currentResource);\n\t\tthis._resourceManager.add(key, updatedResource);\n\t\treturn this;\n\t}\n\n\t/**\n\t\t* Get all resource keys that are currently registered\n\t\t* @returns Array of resource keys\n\t*/\n\tgetResourceKeys(): Array<keyof ResourceTypes> {\n\t\treturn this._resourceManager.getKeys() as Array<keyof ResourceTypes>;\n\t}\n\n\t/**\n\t\t* Check if a resource needs initialization (was added as a factory function)\n\t\t* @param key The resource key to check\n\t\t* @returns True if the resource needs initialization\n\t*/\n\tresourceNeedsInitialization<K extends keyof ResourceTypes>(key: K): boolean {\n\t\treturn this._resourceManager.needsInitialization(key);\n\t}\n\n\t/**\n\t\t* Check if an entity has a component\n\t*/\n\thasComponent<K extends keyof ComponentTypes>(\n\t\tentityId: number,\n\t\tcomponentName: K\n\t): boolean {\n\t\tconst component = this._entityManager.getComponent(entityId, componentName);\n\t\treturn component !== null;\n\t}\n\n\t/**\n\t\t* Create an entity and add components to it in one call\n\t\t* @param components Object with component names as keys and component data as values\n\t\t* @returns The created entity with all components added\n\t\t*/\n\tspawn<T extends { [K in keyof ComponentTypes]?: ComponentTypes[K] }>(\n\t\tcomponents: T & Record<Exclude<keyof T, keyof ComponentTypes>, never>\n\t): Entity<ComponentTypes> {\n\t\tconst entity = this._entityManager.createEntity();\n\t\tthis._entityManager.addComponents(entity, components);\n\t\treturn entity;\n\t}\n\n\t/**\n\t\t* Get all entities with specific components\n\t*/\n\tgetEntitiesWithQuery<\n\t\tWithComponents extends keyof ComponentTypes,\n\t\tWithoutComponents extends keyof ComponentTypes = never\n\t>(\n\t\twithComponents: ReadonlyArray<WithComponents>,\n\t\twithoutComponents: ReadonlyArray<WithoutComponents> = []\n\t): Array<FilteredEntity<ComponentTypes, WithComponents, WithoutComponents>> {\n\t\treturn this._entityManager.getEntitiesWithQuery(\n\t\t\twithComponents,\n\t\t\twithoutComponents\n\t\t);\n\t}\n\n\t/**\n\t\t* Get all installed bundle IDs\n\t*/\n\tget installedBundles(): string[] {\n\t\treturn Array.from(this._installedBundles);\n\t}\n\n\t// Getters for the internal managers\n\tget entityManager() {\n\t\treturn this._entityManager;\n\t}\n\n\tget eventBus() {\n\t\treturn this._eventBus;\n\t}\n\n\t/**\n\t\t* Internal method to install a bundle into this ECSpresso instance.\n\t\t* Called by the ECSpressoBuilder during the build process.\n\t\t* The type safety is guaranteed by the builder's type system.\n\t*/\n\t_installBundle<\n\t\tC extends Record<string, any>,\n\t\tE extends Record<string, any>,\n\t\tR extends Record<string, any>\n\t>(bundle: Bundle<C, E, R>): this {\n\t\t// Prevent duplicate installation of the same bundle\n\t\tif (this._installedBundles.has(bundle.id)) {\n\t\t\treturn this;\n\t\t}\n\n\t\t// Mark this bundle as installed\n\t\tthis._installedBundles.add(bundle.id);\n\n\t\t// Register systems from the bundle\n\t\t// The type compatibility is ensured by the builder's withBundle method\n\t\t// We need this cast due to TypeScript's limitations with generics\n\t\ttype BundleEcspresso = ECSpresso<C, E, R>;\n\t\tbundle.registerSystemsWithEcspresso(this as unknown as BundleEcspresso);\n\n\t\t// Register resources from the bundle\n\t\tconst resources = bundle.getResources();\n\t\tfor (const [key, value] of resources.entries()) {\n\t\t\t// Instead of casting, use the add method's flexibility\n\t\t\tthis._resourceManager.add(key as string, value);\n\t\t}\n\n\t\treturn this;\n\t}\n}\n\n/**\n\t* Builder class for ECSpresso that provides fluent type-safe bundle installation.\n\t* Handles type checking during build process to ensure type safety.\n*/\nexport class ECSpressoBuilder<\n\tC extends Record<string, any> = {},\n\tE extends Record<string, any> = {},\n\tR extends Record<string, any> = {}\n> {\n\t/** The ECSpresso instance being built*/\n\tprivate ecspresso: ECSpresso<C, E, R>;\n\n\tconstructor() {\n\t\tthis.ecspresso = new ECSpresso<C, E, R>();\n\t}\n\n\t/**\n\t\t* Add the first bundle when starting with empty types.\n\t\t* This overload allows any bundle to be added to an empty ECSpresso instance.\n\t*/\n\twithBundle<\n\t\tBC extends Record<string, any>,\n\t\tBE extends Record<string, any>,\n\t\tBR extends Record<string, any>\n\t>(\n\t\tthis: ECSpressoBuilder<{}, {}, {}>,\n\t\tbundle: Bundle<BC, BE, BR>\n\t): ECSpressoBuilder<BC, BE, BR>;\n\n\t/**\n\t\t* Add a subsequent bundle with type checking.\n\t\t* This overload enforces bundle type compatibility.\n\t*/\n\twithBundle<\n\t\tBC extends Record<string, any>,\n\t\tBE extends Record<string, any>,\n\t\tBR extends Record<string, any>\n\t>(\n\t\tbundle: BundlesAreCompatible<C, BC, E, BE, R, BR> extends true\n\t\t\t? Bundle<BC, BE, BR>\n\t\t\t: never\n\t): ECSpressoBuilder<C & BC, E & BE, R & BR>;\n\n\t/**\n\t\t* Implementation of both overloads.\n\t\t* Since the type compatibility is checked in the method signature,\n\t\t* we can safely assume the bundle is compatible here.\n\t*/\n\twithBundle<\n\t\tBC extends Record<string, any>,\n\t\tBE extends Record<string, any>,\n\t\tBR extends Record<string, any>\n\t>(\n\t\tbundle: Bundle<BC, BE, BR>\n\t): ECSpressoBuilder<C & BC, E & BE, R & BR> {\n\t\t// Install the bundle\n\t\t// Type compatibility is guaranteed by method overloads\n\t\tthis.ecspresso._installBundle(bundle);\n\n\t\t// Return a builder with the updated type parameters\n\t\treturn this as unknown as ECSpressoBuilder<C & BC, E & BE, R & BR>;\n\t}\n\n\t/**\n\t\t* Complete the build process and return the built ECSpresso instance\n\t*/\n\tbuild(): ECSpresso<C, E, R> {\n\t\treturn this.ecspresso;\n\t}\n}\n",
10
+ "import { createBundleSystemBuilder, SystemBuilderWithBundle } from './system-builder';\nimport type ECSpresso from './ecspresso';\n\n/**\n * Generates a unique ID for a bundle\n */\nfunction generateBundleId(): string {\n\treturn `bundle_${Date.now().toString(36)}_${Math.random().toString(36).substring(2, 9)}`;\n}\n\n/**\n * Bundle class that encapsulates a set of components, resources, events, and systems\n * that can be merged into a ECSpresso instance\n */\nexport default class Bundle<\n\tComponentTypes extends Record<string, any> = {},\n\tEventTypes extends Record<string, any> = {},\n\tResourceTypes extends Record<string, any> = {},\n> {\n\tprivate _systems: SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, any>[] = [];\n\tprivate _resources: Map<keyof ResourceTypes, ResourceTypes[keyof ResourceTypes]> = new Map();\n\tprivate _id: string;\n\n\tconstructor(id?: string) {\n\t\tthis._id = id || generateBundleId();\n\t}\n\n\t/**\n\t * Get the unique ID of this bundle\n\t */\n\tget id(): string {\n\t\treturn this._id;\n\t}\n\n\t/**\n\t * Set the ID of this bundle\n\t * @internal Used by combineBundles\n\t */\n\tset id(value: string) {\n\t\tthis._id = value;\n\t}\n\n\t/**\n\t * Add a system to this bundle, by label (creating a new builder) or by reusing an existing one\n\t */\n\taddSystem<Q extends Record<string, any>>(builder: SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, Q>): SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, Q>;\n\taddSystem(label: string): SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, {}>;\n\taddSystem(builderOrLabel: string | SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, any>) {\n\t\tif (typeof builderOrLabel === 'string') {\n\t\t\tconst system = createBundleSystemBuilder<ComponentTypes, EventTypes, ResourceTypes>(builderOrLabel, this);\n\t\t\tthis._systems.push(system);\n\t\t\treturn system;\n\t\t} else {\n\t\t\tthis._systems.push(builderOrLabel);\n\t\t\treturn builderOrLabel;\n\t\t}\n\t}\n\n\t/**\n\t * Add a resource to this bundle\n\t * @param label The resource key\n\t * @param resource The resource value or a factory function that returns the resource\n\t */\n\taddResource<K extends keyof ResourceTypes>(\n\t\tlabel: K,\n\t\tresource: ResourceTypes[K] | ((ecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>) => ResourceTypes[K] | Promise<ResourceTypes[K]>)\n\t) {\n\t\t// We need this cast because TypeScript doesn't recognize that a value of type\n\t\t// ResourceTypes[K] | (() => ResourceTypes[K] | Promise<ResourceTypes[K]>)\n\t\t// can be properly assigned to Map<keyof ResourceTypes, ResourceTypes[keyof ResourceTypes]>\n\t\tthis._resources.set(label, resource as unknown as ResourceTypes[K]);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Get all systems defined in this bundle\n\t * Returns built System objects instead of SystemBuilders\n\t */\n\tgetSystems() {\n\t\treturn this._systems.map(system => system.build());\n\t}\n\n\t/**\n\t * Register all systems in this bundle with an ECSpresso instance\n\t * @internal Used by ECSpresso when adding a bundle\n\t */\n\tregisterSystemsWithEcspresso(ecspresso: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>) {\n\t\tfor (const systemBuilder of this._systems) {\n\t\t\tsystemBuilder.build(ecspresso);\n\t\t}\n\t}\n\n\t/**\n\t * Get all resources defined in this bundle\n\t */\n\tgetResources(): Map<keyof ResourceTypes, ResourceTypes[keyof ResourceTypes]> {\n\t\treturn new Map(this._resources);\n\t}\n\n\t/**\n\t * Get a specific resource by key\n\t * @param key The resource key\n\t * @returns The resource value or undefined if not found\n\t */\n\tgetResource<K extends keyof ResourceTypes>(key: K): ResourceTypes[K] {\n\t\treturn this._resources.get(key) as ResourceTypes[K];\n\t}\n\n\t/**\n\t * Get all system builders in this bundle\n\t */\n\tgetSystemBuilders(): SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, any>[] {\n\t\treturn [...this._systems];\n\t}\n\n\t/**\n\t * Check if this bundle has a specific resource\n\t * @param key The resource key to check\n\t * @returns True if the resource exists\n\t */\n\thasResource<K extends keyof ResourceTypes>(key: K): boolean {\n\t\treturn this._resources.has(key);\n\t}\n}\n\n/**\n * Utility type to check if two types are exactly the same\n */\ntype Exactly<T, U> = T extends U ? U extends T ? true : false : false;\n\n/**\n * Simplified type constraint for bundle compatibility\n * Ensures that overlapping keys have exactly the same types\n */\ntype CompatibleBundles<\n\tC1 extends Record<string, any>,\n\tC2 extends Record<string, any>,\n\tE1 extends Record<string, any>,\n\tE2 extends Record<string, any>,\n\tR1 extends Record<string, any>,\n\tR2 extends Record<string, any>\n> = {\n\t[K in keyof C1 & keyof C2]: Exactly<C1[K], C2[K]> extends true ? C1[K] : never;\n} & {\n\t[K in keyof E1 & keyof E2]: Exactly<E1[K], E2[K]> extends true ? E1[K] : never;\n} & {\n\t[K in keyof R1 & keyof R2]: Exactly<R1[K], R2[K]> extends true ? R1[K] : never;\n};\n\n/**\n * Function that merges multiple bundles into a single bundle\n */\nexport function mergeBundles<\n\tC1 extends Record<string, any>,\n\tE1 extends Record<string, any>,\n\tR1 extends Record<string, any>,\n\tC2 extends Record<string, any>,\n\tE2 extends Record<string, any>,\n\tR2 extends Record<string, any>\n>(\n\tid: string,\n\tbundle1: Bundle<C1, E1, R1>,\n\tbundle2: Bundle<C2, E2, R2> & CompatibleBundles<C1, C2, E1, E2, R1, R2>\n): Bundle<C1 & C2, E1 & E2, R1 & R2>;\n\nexport function mergeBundles<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>\n>(\n\tid: string,\n\t...bundles: Array<Bundle<ComponentTypes, EventTypes, ResourceTypes>>\n): Bundle<ComponentTypes, EventTypes, ResourceTypes>;\n\nexport function mergeBundles(\n\tid: string,\n\t...bundles: Array<Bundle>\n): Bundle {\n\tif (bundles.length === 0) {\n\t\treturn new Bundle(id);\n\t}\n\n\tconst combined = new Bundle(id);\n\n\tfor (const bundle of bundles) {\n\t\tfor (const system of bundle.getSystemBuilders()) {\n\t\t\t// reuse the full builder so we carry over queries, hooks, and handlers\n\t\t\tcombined.addSystem(system);\n\t\t}\n\n\t\t// Add resources from this bundle\n\t\tfor (const [label, resource] of bundle.getResources().entries()) {\n\t\t\tcombined.addResource(label, resource);\n\t\t}\n\t}\n\n\treturn combined;\n}\n",
11
+ "import ECSpresso from \"./ecspresso\";\n\nexport\ninterface Entity<ComponentTypes> {\n\tid: number;\n\tcomponents: Partial<ComponentTypes>;\n}\n\nexport\ninterface EventHandler<T> {\n\tcallback: (data: T) => void;\n\tonce: boolean;\n}\n\nexport\ninterface FilteredEntity<\n\tComponentTypes,\n\tWithComponents extends keyof ComponentTypes = never,\n\tWithoutComponents extends keyof ComponentTypes = never,\n> {\n\tid: number;\n\tcomponents: Omit<Partial<ComponentTypes>, WithoutComponents> & {\n\t\t[K in WithComponents]: ComponentTypes[K]\n\t};\n}\n\nexport\ninterface QueryConfig<\n\tComponentTypes,\n\tWithComponents extends keyof ComponentTypes,\n\tWithoutComponents extends keyof ComponentTypes,\n> {\n\twith: ReadonlyArray<WithComponents>;\n\twithout?: ReadonlyArray<WithoutComponents>;\n}\n\n/**\n * Utility type to derive the entity type that would result from a query definition.\n * This is useful for creating helper functions that operate on query results.\n *\n * @example\n * ```typescript\n * const queryDef = {\n * with: ['position', 'sprite'],\n * without: ['dead']\n * };\n *\n * type EntityType = QueryResultEntity<Components, typeof queryDef>;\n *\n * function updateSpritePosition(entity: EntityType) {\n * entity.components.sprite.position.set(\n * entity.components.position.x,\n * entity.components.position.y\n * );\n * }\n * ```\n */\nexport type QueryResultEntity<\n\tComponentTypes extends Record<string, any>,\n\tQueryDef extends {\n\t\twith: ReadonlyArray<keyof ComponentTypes>;\n\t\twithout?: ReadonlyArray<keyof ComponentTypes>;\n\t}\n> = FilteredEntity<\n\tComponentTypes,\n\tQueryDef['with'][number],\n\tQueryDef['without'] extends ReadonlyArray<any> ? QueryDef['without'][number] : never\n>;\n\n/**\n * Simplified query definition type for creating reusable queries\n */\nexport type QueryDefinition<\n\tComponentTypes extends Record<string, any>,\n\tWithComponents extends keyof ComponentTypes = keyof ComponentTypes,\n\tWithoutComponents extends keyof ComponentTypes = keyof ComponentTypes,\n> = {\n\twith: ReadonlyArray<WithComponents>;\n\twithout?: ReadonlyArray<WithoutComponents>;\n};\n\n/**\n * Helper function to create a query definition with proper type inference.\n * This enables better TypeScript inference when creating reusable queries.\n *\n * @example\n * ```typescript\n * const movingEntitiesQuery = createQueryDefinition({\n * with: ['position', 'velocity'],\n * without: ['dead']\n * });\n *\n * type MovingEntity = QueryResultEntity<Components, typeof movingEntitiesQuery>;\n *\n * function updatePosition(entity: MovingEntity) {\n * entity.components.position.x += entity.components.velocity.x;\n * entity.components.position.y += entity.components.velocity.y;\n * }\n *\n * world.addSystem('movement')\n * .addQuery('entities', movingEntitiesQuery)\n * .setProcess((queries) => {\n * for (const entity of queries.entities) {\n * updatePosition(entity);\n * }\n * });\n * ```\n */\nexport function createQueryDefinition<\n\tComponentTypes extends Record<string, any>,\n\tconst QueryDef extends {\n\t\twith: ReadonlyArray<keyof ComponentTypes>;\n\t\twithout?: ReadonlyArray<keyof ComponentTypes>;\n\t}\n>(queryDef: QueryDef): QueryDef {\n\treturn queryDef;\n}\n\nexport\ninterface System<\n\tComponentTypes extends Record<string, any> = {},\n\tWithComponents extends keyof ComponentTypes = never,\n\tWithoutComponents extends keyof ComponentTypes = never,\n\tEventTypes extends Record<string, any> = {},\n\tResourceTypes extends Record<string, any> = {},\n> {\n\tlabel: string;\n\t/**\n\t * System priority - higher values execute first (default: 0)\n\t * When systems have the same priority, they execute in registration order\n\t */\n\tpriority?: number;\n\tentityQueries?: {\n\t\t[queryName: string]: QueryConfig<ComponentTypes, WithComponents, WithoutComponents>;\n\t};\n\t/**\n\t * Process method that runs during each update cycle\n\t * @param queries The entity queries results based on system's entityQueries definition\n\t * @param deltaTime Time elapsed since the last update in seconds\n\t * @param ecs The ECSpresso instance providing access to all ECS functionality\n\t */\n\tprocess?(\n\t\tqueries: {\n\t\t\t[queryName: string]: Array<FilteredEntity<ComponentTypes, WithComponents, WithoutComponents>>;\n\t\t} | Array<FilteredEntity<ComponentTypes, WithComponents, WithoutComponents>>,\n\t\tdeltaTime: number,\n\t\tecs: ECSpresso<\n\t\t\tComponentTypes,\n\t\t\tEventTypes,\n\t\t\tResourceTypes\n\t\t>\n\t): void;\n\n\t/**\n\t * Lifecycle hook called when the system is initialized\n\t * This is called when ECSpresso.initialize() is invoked, after resources are initialized\n\t * Use this for one-time initialization that depends on resources\n\t * @param ecs The ECSpresso instance providing access to all ECS functionality\n\t */\n\tonInitialize?(\n\t\tecs: ECSpresso<\n\t\t\tComponentTypes,\n\t\t\tEventTypes,\n\t\t\tResourceTypes\n\t\t>\n\t): void | Promise<void>;\n\n\t/**\n\t * Lifecycle hook called when the system is detached from the ECS\n\t * @param ecs The ECSpresso instance providing access to all ECS functionality\n\t */\n\tonDetach?(\n\t\tecs: import(\"./ecspresso\").default<\n\t\t\tComponentTypes,\n\t\t\tEventTypes,\n\t\t\tResourceTypes\n\t\t>\n\t): void;\n\n\t/**\n\t * Event handlers for specific event types\n\t */\n\teventHandlers?: {\n\t\t[EventName in keyof EventTypes]?: {\n\t\t\t/**\n\t\t\t * Event handler function\n\t\t\t * @param data The event data specific to this event type\n\t\t\t * @param ecs The ECSpresso instance providing access to all ECS functionality\n\t\t\t */\n\t\t\thandler(\n\t\t\t\tdata: EventTypes[EventName],\n\t\t\t\tecs: ECSpresso<\n\t\t\t\t\tComponentTypes,\n\t\t\t\t\tEventTypes,\n\t\t\t\t\tResourceTypes\n\t\t\t\t>\n\t\t\t): void;\n\t\t};\n\t};\n}\n\n// Re-export utility types from type-utils to maintain backward compatibility\nexport type { Merge, MergeAll } from './type-utils';\n",
12
12
  "import ECSpresso from './ecspresso';\nimport { SystemBuilder } from './system-builder';\nimport Bundle, { mergeBundles } from './bundle';\n\nexport * from './types';\nexport { default as EntityManager } from './entity-manager';\nexport { default as EventBus } from './event-bus';\n/**\n * @internal ResourceManager is exported for testing purposes only.\n * Use ECSpresso resource methods instead: getResource(), addResource(), removeResource(), updateResource(), hasResource()\n */\nexport { default as ResourceManager } from './resource-manager';\nexport { SystemBuilder };\nexport { Bundle, mergeBundles };\nexport default ECSpresso;\n"
13
13
  ],
14
- "mappings": "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",
15
- "debugId": "186F2921EE95DCD564756E2164756E21",
14
+ "mappings": "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",
15
+ "debugId": "179E5389134FBDFC64756E2164756E21",
16
16
  "names": []
17
17
  }
@@ -9,6 +9,11 @@ export default class ResourceManager<ResourceTypes extends Record<string, any> =
9
9
  * @returns The resource manager instance for chaining
10
10
  */
11
11
  add<K extends keyof ResourceTypes>(label: K, resource: ResourceTypes[K] | ((context?: any) => ResourceTypes[K] | Promise<ResourceTypes[K]>)): this;
12
+ /**
13
+ * Improved detection of factory functions vs direct values/classes
14
+ * @private
15
+ */
16
+ private _isFactoryFunction;
12
17
  /**
13
18
  * Get a resource from the manager
14
19
  * @param label The resource key
@@ -35,6 +35,11 @@ export declare class SystemBuilder<ComponentTypes extends Record<string, any> =
35
35
  * @private
36
36
  */
37
37
  private _buildSystemObject;
38
+ /**
39
+ * Create a system object with all configured properties
40
+ * @private
41
+ */
42
+ private _createSystemObject;
38
43
  /**
39
44
  * Set the priority of this system. Systems with higher priority values
40
45
  * execute before those with lower values. Systems with the same priority
@@ -63,11 +68,12 @@ export declare class SystemBuilder<ComponentTypes extends Record<string, any> =
63
68
  */
64
69
  registerAndContinue(): ECSpresso<ComponentTypes, EventTypes, ResourceTypes>;
65
70
  /**
66
- * Complete this system and return ECSpresso for seamless chaining
67
- * Automatically registers the system when called
68
- * This method is primarily for SystemBuilderWithEcspresso instances
71
+ * Complete this system and return the parent container for seamless chaining
72
+ * - For ECSpresso-attached builders: registers the system and returns ECSpresso
73
+ * - For Bundle-attached builders: returns the Bundle
74
+ * This method is typed via the specialized interfaces (SystemBuilderWithEcspresso, SystemBuilderWithBundle)
69
75
  */
70
- and(): ECSpresso<ComponentTypes, EventTypes, ResourceTypes>;
76
+ and(): ECSpresso<ComponentTypes, EventTypes, ResourceTypes> | Bundle<ComponentTypes, EventTypes, ResourceTypes>;
71
77
  /**
72
78
  * Set the onDetach lifecycle hook
73
79
  * Called when the system is removed from the ECS
@@ -149,5 +155,10 @@ export interface SystemBuilderWithEcspresso<ComponentTypes extends Record<string
149
155
  */
150
156
  export interface SystemBuilderWithBundle<ComponentTypes extends Record<string, any>, EventTypes extends Record<string, any>, ResourceTypes extends Record<string, any>, Queries extends Record<string, QueryDefinition<ComponentTypes>> = {}> extends SystemBuilder<ComponentTypes, EventTypes, ResourceTypes, Queries> {
151
157
  readonly bundle: Bundle<ComponentTypes, EventTypes, ResourceTypes>;
158
+ /**
159
+ * Complete this system and return the Bundle for chaining
160
+ * Enables fluent API: bundle.addSystem(...).and().addSystem(...)
161
+ */
162
+ and(): Bundle<ComponentTypes, EventTypes, ResourceTypes>;
152
163
  }
153
164
  export {};
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Utility types for ECSpresso ECS framework
3
+ * This file contains reusable type helpers used across the codebase
4
+ */
5
+ /**
6
+ * Check if two types are exactly the same for overlapping keys
7
+ */
8
+ type ExactlyCompatible<T, U> = T extends U ? U extends T ? true : false : false;
9
+ /**
10
+ * Check if two record types are compatible (no conflicting keys)
11
+ */
12
+ export type TypesAreCompatible<T extends Record<string, any>, U extends Record<string, any>> = {
13
+ [K in keyof T & keyof U]: ExactlyCompatible<T[K], U[K]>;
14
+ }[keyof T & keyof U] extends false ? false : true;
15
+ /**
16
+ * Simplified bundle compatibility checker
17
+ * Returns true if bundles can be merged without type conflicts
18
+ * More lenient - allows bundles without shared keys to be merged
19
+ */
20
+ export type BundlesAreCompatible<C1 extends Record<string, any>, C2 extends Record<string, any>, E1 extends Record<string, any>, E2 extends Record<string, any>, R1 extends Record<string, any>, R2 extends Record<string, any>> = keyof C1 & keyof C2 extends never ? keyof E1 & keyof E2 extends never ? keyof R1 & keyof R2 extends never ? true : TypesAreCompatible<R1, R2> : TypesAreCompatible<E1, E2> : TypesAreCompatible<C1, C2>;
21
+ /**
22
+ * Utility type for merging two types
23
+ */
24
+ export type Merge<T1, T2> = T1 & T2;
25
+ /**
26
+ * Utility type for merging an array of types
27
+ */
28
+ export type MergeAll<T extends any[]> = T extends [infer First, ...infer Rest] ? Rest extends [] ? First : Merge<First, MergeAll<Rest>> : {};
29
+ export {};
30
+ /**
31
+ * Basic utility types that can be used independently
32
+ */
package/dist/types.d.ts CHANGED
@@ -10,7 +10,7 @@ export interface EventHandler<T> {
10
10
  export interface FilteredEntity<ComponentTypes, WithComponents extends keyof ComponentTypes = never, WithoutComponents extends keyof ComponentTypes = never> {
11
11
  id: number;
12
12
  components: Omit<Partial<ComponentTypes>, WithoutComponents> & {
13
- [ComponentName in WithComponents]: ComponentTypes[ComponentName];
13
+ [K in WithComponents]: ComponentTypes[K];
14
14
  };
15
15
  }
16
16
  export interface QueryConfig<ComponentTypes, WithComponents extends keyof ComponentTypes, WithoutComponents extends keyof ComponentTypes> {
@@ -43,20 +43,9 @@ export type QueryResultEntity<ComponentTypes extends Record<string, any>, QueryD
43
43
  without?: ReadonlyArray<keyof ComponentTypes>;
44
44
  }> = FilteredEntity<ComponentTypes, QueryDef['with'][number], QueryDef['without'] extends ReadonlyArray<any> ? QueryDef['without'][number] : never>;
45
45
  /**
46
- * Utility type to create a query definition with proper type inference.
47
- * This enables you to create reusable query definitions and extract their result types.
48
- *
49
- * @example
50
- * ```typescript
51
- * const movingEntitiesQuery = createQueryDefinition({
52
- * with: ['position', 'velocity'],
53
- * without: ['dead']
54
- * });
55
- *
56
- * type MovingEntity = QueryResultEntity<Components, typeof movingEntitiesQuery>;
57
- * ```
46
+ * Simplified query definition type for creating reusable queries
58
47
  */
59
- export type QueryDefinition<ComponentTypes extends Record<string, any>, WithComponents extends keyof ComponentTypes = any, WithoutComponents extends keyof ComponentTypes = any> = {
48
+ export type QueryDefinition<ComponentTypes extends Record<string, any>, WithComponents extends keyof ComponentTypes = keyof ComponentTypes, WithoutComponents extends keyof ComponentTypes = keyof ComponentTypes> = {
60
49
  with: ReadonlyArray<WithComponents>;
61
50
  without?: ReadonlyArray<WithoutComponents>;
62
51
  };
@@ -136,8 +125,4 @@ export interface System<ComponentTypes extends Record<string, any> = {}, WithCom
136
125
  };
137
126
  };
138
127
  }
139
- /**
140
- * Utility type for merging two types
141
- */
142
- export type Merge<T1, T2> = T1 & T2;
143
- export type MergeAll<T extends any[]> = T extends [infer First, ...infer Rest] ? Rest extends [] ? First : Merge<First, MergeAll<Rest>> : {};
128
+ export type { Merge, MergeAll } from './type-utils';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ecspresso",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -25,12 +25,12 @@
25
25
  ],
26
26
  "devDependencies": {
27
27
  "@types/bun": "latest",
28
- "@types/three": "^0.176.0",
29
- "pixi.js": "^8.9.2",
30
- "three": "^0.176.0"
28
+ "@types/three": "^0.180.0",
29
+ "pixi.js": "^8.15.0",
30
+ "three": "^0.180.0"
31
31
  },
32
32
  "peerDependencies": {
33
- "typescript": "^5.8.3"
33
+ "typescript": "^5.9.2"
34
34
  },
35
35
  "files": [
36
36
  "dist"