ecspresso 0.4.2 → 0.5.0

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,9 +2,7 @@
2
2
 
3
3
  *(pronounced "ex-presso")*
4
4
 
5
- l__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
 
@@ -13,6 +11,9 @@ A type-safe, modular, and extensible Entity Component System (ECS) framework for
13
11
  - **Developer-Friendly**: Clean, fluent API with method chaining
14
12
  - **Event-Driven**: Integrated event system for decoupled communication
15
13
  - **Resource Management**: Global state management with lazy loading
14
+ - **Asset Management**: Eager/lazy asset loading with groups and progress tracking
15
+ - **Screen Management**: Game state/screen transitions with overlay support
16
+ - **Entity Hierarchy**: Parent-child relationships with traversal and cascade deletion
16
17
  - **Query System**: Powerful entity filtering with helper type utilities
17
18
 
18
19
  ## Installation
@@ -135,21 +136,32 @@ world.addSystem('scoring')
135
136
 
136
137
  ### Method Chaining
137
138
 
138
- Chain multiple systems for cleaner code:
139
+ Chain multiple systems using `.and()` for cleaner code. The `.and()` method returns the parent container (ECSpresso or Bundle), enabling fluent chaining:
139
140
 
140
141
  ```typescript
142
+ // Chaining systems on ECSpresso
141
143
  world.addSystem('physics')
142
144
  .addQuery('moving', { with: ['position', 'velocity'] })
143
145
  .setProcess((queries, deltaTime) => {
144
146
  // Physics logic
145
147
  })
146
- .and() // Complete this system and continue chaining
148
+ .and() // Returns ECSpresso for continued chaining
147
149
  .addSystem('rendering')
148
150
  .addQuery('visible', { with: ['position', 'sprite'] })
149
151
  .setProcess((queries) => {
150
152
  // Rendering logic
151
153
  })
152
154
  .build();
155
+
156
+ // Chaining systems in a Bundle
157
+ const bundle = new Bundle<Components>()
158
+ .addSystem('movement')
159
+ .setProcess(() => { /* ... */ })
160
+ .and() // Returns Bundle for continued chaining
161
+ .addSystem('collision')
162
+ .setProcess(() => { /* ... */ })
163
+ .and()
164
+ .addResource('config', { speed: 10 });
153
165
  ```
154
166
 
155
167
  ### Query Type Utilities
@@ -209,35 +221,49 @@ Organize related systems and resources into reusable bundles:
209
221
  ```typescript
210
222
  import { Bundle } from 'ecspresso';
211
223
 
212
- // Define bundle interfaces
213
- interface InputComponents {
224
+ interface GameComponents {
214
225
  position: { x: number; y: number };
215
226
  velocity: { x: number; y: number };
216
- player: { id: number };
227
+ sprite: { texture: string };
217
228
  }
218
229
 
219
- interface RenderComponents {
220
- position: { x: number; y: number };
221
- sprite: any; // Your sprite type
230
+ interface GameResources {
231
+ gravity: { value: number };
222
232
  }
223
233
 
224
- const inputBundle = new Bundle<InputComponents>('input')
225
- .addSystem('playerInput')
226
- .addQuery('players', { with: ['position', 'velocity', 'player'] })
234
+ // Create a bundle with multiple systems using .and() for chaining
235
+ const physicsBundle = new Bundle<GameComponents, {}, GameResources>('physics')
236
+ .addSystem('applyVelocity')
237
+ .addQuery('moving', { with: ['position', 'velocity'] })
238
+ .setProcess((queries, deltaTime) => {
239
+ for (const entity of queries.moving) {
240
+ entity.components.position.x += entity.components.velocity.x * deltaTime;
241
+ entity.components.position.y += entity.components.velocity.y * deltaTime;
242
+ }
243
+ })
244
+ .and() // Returns the bundle for continued chaining
245
+ .addSystem('applyGravity')
246
+ .addQuery('falling', { with: ['velocity'] })
227
247
  .setProcess((queries, deltaTime, ecs) => {
228
- // Handle input
229
- });
248
+ const gravity = ecs.getResource('gravity');
249
+ for (const entity of queries.falling) {
250
+ entity.components.velocity.y += gravity.value * deltaTime;
251
+ }
252
+ })
253
+ .and()
254
+ .addResource('gravity', { value: 9.8 });
230
255
 
231
- const renderBundle = new Bundle<RenderComponents>('render')
256
+ const renderBundle = new Bundle<GameComponents>('render')
232
257
  .addSystem('renderer')
233
258
  .addQuery('sprites', { with: ['position', 'sprite'] })
234
259
  .setProcess((queries) => {
235
260
  // Render sprites
236
- });
261
+ })
262
+ .and();
237
263
 
238
264
  // Create world with bundles
239
- const game = ECSpresso.create()
240
- .withBundle(inputBundle)
265
+ const game = ECSpresso.create<GameComponents, {}, GameResources>()
266
+ .withBundle(physicsBundle)
241
267
  .withBundle(renderBundle)
242
268
  .build();
243
269
  ```
@@ -254,6 +280,19 @@ interface Events {
254
280
 
255
281
  const world = new ECSpresso<Components, Events>();
256
282
 
283
+ // Subscribe to events with on() - returns unsubscribe function
284
+ const unsubscribe = world.on('playerDied', (data) => {
285
+ console.log(`Player ${data.playerId} died`);
286
+ });
287
+
288
+ // Unsubscribe when done
289
+ unsubscribe();
290
+
291
+ // Or unsubscribe by callback reference with off()
292
+ const handler = (data) => console.log(`Level complete! Score: ${data.score}`);
293
+ world.on('levelComplete', handler);
294
+ world.off('levelComplete', handler);
295
+
257
296
  // Handle events in systems
258
297
  world.addSystem('gameLogic')
259
298
  .setEventHandlers({
@@ -318,6 +357,369 @@ world.addSystem('gameSystem')
318
357
  await world.initialize();
319
358
  ```
320
359
 
360
+ ### Post-Update Hooks
361
+
362
+ Register callbacks that run after all systems have processed during `update()`:
363
+
364
+ ```typescript
365
+ // Register a post-update hook - returns unsubscribe function
366
+ const unsubscribe = world.onPostUpdate((ecs, deltaTime) => {
367
+ // Runs after all systems in update()
368
+ // Useful for cleanup, state sync, or debug logging
369
+ console.log(`Frame completed in ${deltaTime}s`);
370
+ });
371
+
372
+ // Multiple hooks run in registration order
373
+ world.onPostUpdate((ecs) => {
374
+ // First hook
375
+ });
376
+ world.onPostUpdate((ecs) => {
377
+ // Second hook
378
+ });
379
+
380
+ // Unsubscribe when no longer needed
381
+ unsubscribe();
382
+ ```
383
+
384
+ ### Entity Hierarchy
385
+
386
+ Create parent-child relationships between entities for scene graphs, UI trees, or skeletal hierarchies:
387
+
388
+ ```typescript
389
+ const world = new ECSpresso<Components>();
390
+
391
+ // Create a parent entity
392
+ const player = world.spawn({
393
+ position: { x: 0, y: 0 }
394
+ });
395
+
396
+ // Create a child entity using spawnChild
397
+ const weapon = world.spawnChild(player.id, {
398
+ position: { x: 10, y: 0 } // Relative to parent
399
+ });
400
+
401
+ // Or set parent on existing entity
402
+ const shield = world.spawn({ position: { x: -10, y: 0 } });
403
+ world.setParent(shield.id, player.id);
404
+
405
+ // Query relationships
406
+ world.getParent(weapon.id); // player.id
407
+ world.getChildren(player.id); // [weapon.id, shield.id]
408
+
409
+ // Orphan an entity (remove from parent)
410
+ world.removeParent(shield.id);
411
+ world.getParent(shield.id); // null
412
+ ```
413
+
414
+ #### Traversal Methods
415
+
416
+ Navigate the hierarchy tree with traversal utilities:
417
+
418
+ ```typescript
419
+ // Build a hierarchy: root -> child -> grandchild
420
+ const root = world.spawn({ position: { x: 0, y: 0 } });
421
+ const child = world.spawnChild(root.id, { position: { x: 10, y: 0 } });
422
+ const grandchild = world.spawnChild(child.id, { position: { x: 20, y: 0 } });
423
+
424
+ // Ancestors (from entity up to root)
425
+ world.getAncestors(grandchild.id); // [child.id, root.id]
426
+
427
+ // Descendants (depth-first order)
428
+ world.getDescendants(root.id); // [child.id, grandchild.id]
429
+
430
+ // Get root of any entity
431
+ world.getRoot(grandchild.id); // root.id
432
+
433
+ // Siblings (other children of same parent)
434
+ const child2 = world.spawnChild(root.id, { position: { x: -10, y: 0 } });
435
+ world.getSiblings(child.id); // [child2.id]
436
+
437
+ // Relationship checks
438
+ world.isDescendantOf(grandchild.id, root.id); // true
439
+ world.isAncestorOf(root.id, grandchild.id); // true
440
+
441
+ // All root entities (entities with children but no parent)
442
+ world.getRootEntities(); // [root.id]
443
+
444
+ // Child ordering
445
+ world.getChildAt(root.id, 0); // child.id
446
+ world.getChildIndex(root.id, child2.id); // 1
447
+ ```
448
+
449
+ #### Cascade Deletion
450
+
451
+ When removing entities, descendants are automatically removed by default:
452
+
453
+ ```typescript
454
+ const parent = world.spawn({ position: { x: 0, y: 0 } });
455
+ const child = world.spawnChild(parent.id, { position: { x: 10, y: 0 } });
456
+ const grandchild = world.spawnChild(child.id, { position: { x: 20, y: 0 } });
457
+
458
+ // Remove parent - cascades to all descendants
459
+ world.removeEntity(parent.id);
460
+ world.entityManager.getEntity(child.id); // undefined
461
+ world.entityManager.getEntity(grandchild.id); // undefined
462
+
463
+ // To orphan children instead of deleting them:
464
+ world.removeEntity(parent.id, { cascade: false });
465
+ // Children still exist but have no parent
466
+ ```
467
+
468
+ #### Hierarchy Events
469
+
470
+ React to hierarchy changes with the `hierarchyChanged` event:
471
+
472
+ ```typescript
473
+ interface Events {
474
+ hierarchyChanged: {
475
+ entityId: number;
476
+ oldParent: number | null;
477
+ newParent: number | null;
478
+ };
479
+ }
480
+
481
+ const world = new ECSpresso<Components, Events>();
482
+
483
+ world.on('hierarchyChanged', (data) => {
484
+ if (data.newParent !== null) {
485
+ console.log(`Entity ${data.entityId} attached to ${data.newParent}`);
486
+ } else {
487
+ console.log(`Entity ${data.entityId} detached from ${data.oldParent}`);
488
+ }
489
+ });
490
+
491
+ // Events fire on setParent, removeParent, and spawnChild
492
+ world.setParent(child.id, parent.id); // Emits hierarchyChanged
493
+ ```
494
+
495
+ ### Asset Management
496
+
497
+ Manage game assets with eager/lazy loading, groups, and progress tracking:
498
+
499
+ ```typescript
500
+ // Define asset types
501
+ type Assets = {
502
+ playerTexture: { data: ImageBitmap };
503
+ enemyTexture: { data: ImageBitmap };
504
+ level1Music: { buffer: AudioBuffer };
505
+ level1Background: { data: ImageBitmap };
506
+ };
507
+
508
+ // Create world with assets using the builder pattern
509
+ const game = ECSpresso.create<Components, Events, Resources, Assets>()
510
+ .withAssets(assets => assets
511
+ // Eager assets - loaded automatically during initialize()
512
+ .add('playerTexture', async () => {
513
+ const img = await loadImage('player.png');
514
+ return { data: img };
515
+ })
516
+ .add('enemyTexture', async () => {
517
+ const img = await loadImage('enemy.png');
518
+ return { data: img };
519
+ })
520
+ // Lazy asset group - loaded on demand
521
+ .addGroup('level1', {
522
+ level1Music: async () => {
523
+ const buffer = await loadAudio('level1.mp3');
524
+ return { buffer };
525
+ },
526
+ level1Background: async () => {
527
+ const img = await loadImage('level1-bg.png');
528
+ return { data: img };
529
+ },
530
+ })
531
+ )
532
+ .build();
533
+
534
+ // Initialize loads eager assets automatically
535
+ await game.initialize();
536
+
537
+ // Access loaded assets
538
+ const player = game.getAsset('playerTexture');
539
+
540
+ // Check if asset is loaded
541
+ if (game.isAssetLoaded('enemyTexture')) {
542
+ const enemy = game.getAsset('enemyTexture');
543
+ }
544
+
545
+ // Load asset groups on demand (e.g., when entering a level)
546
+ await game.loadAssetGroup('level1');
547
+
548
+ // Track loading progress
549
+ const progress = game.getAssetGroupProgress('level1'); // 0-1
550
+
551
+ // Check if group is fully loaded
552
+ if (game.isAssetGroupLoaded('level1')) {
553
+ const music = game.getAsset('level1Music');
554
+ }
555
+ ```
556
+
557
+ #### Asset Events
558
+
559
+ React to asset loading with built-in events:
560
+
561
+ ```typescript
562
+ game.addSystem('loadingUI')
563
+ .setEventHandlers({
564
+ assetLoaded: {
565
+ handler: (data) => console.log(`Loaded: ${data.key}`)
566
+ },
567
+ assetFailed: {
568
+ handler: (data) => console.error(`Failed: ${data.key}`, data.error)
569
+ },
570
+ assetGroupProgress: {
571
+ handler: (data) => {
572
+ console.log(`${data.group}: ${data.loaded}/${data.total}`);
573
+ }
574
+ },
575
+ assetGroupLoaded: {
576
+ handler: (data) => console.log(`Group ready: ${data.group}`)
577
+ }
578
+ })
579
+ .build();
580
+ ```
581
+
582
+ #### Systems with Asset Requirements
583
+
584
+ Systems can declare required assets and will only run when those assets are loaded:
585
+
586
+ ```typescript
587
+ game.addSystem('gameplay')
588
+ .requiresAssets(['playerTexture', 'enemyTexture'])
589
+ .setProcess((queries, dt, ecs) => {
590
+ // This only runs when both assets are loaded
591
+ const player = ecs.getAsset('playerTexture');
592
+ })
593
+ .build();
594
+ ```
595
+
596
+ ### Screen Management
597
+
598
+ Manage game states/screens with transitions and overlay support:
599
+
600
+ ```typescript
601
+ import type { ScreenDefinition } from 'ecspresso';
602
+
603
+ // Define screen types with config and state
604
+ type Screens = {
605
+ menu: ScreenDefinition<
606
+ Record<string, never>, // Config (passed when entering)
607
+ { selectedOption: number } // State (mutable during screen)
608
+ >;
609
+ gameplay: ScreenDefinition<
610
+ { difficulty: string; level: number }, // Config
611
+ { score: number; isPaused: boolean } // State
612
+ >;
613
+ pause: ScreenDefinition<
614
+ Record<string, never>,
615
+ Record<string, never>
616
+ >;
617
+ };
618
+
619
+ // Create world with screens
620
+ const game = ECSpresso.create<Components, Events, Resources, {}, Screens>()
621
+ .withScreens(screens => screens
622
+ .add('menu', {
623
+ initialState: () => ({ selectedOption: 0 }),
624
+ onEnter: () => console.log('Entered menu'),
625
+ onExit: () => console.log('Left menu'),
626
+ })
627
+ .add('gameplay', {
628
+ initialState: () => ({ score: 0, isPaused: false }),
629
+ onEnter: (config) => console.log(`Starting level ${config.level}`),
630
+ onExit: () => console.log('Gameplay ended'),
631
+ // Require assets before screen can be entered
632
+ requiredAssetGroups: ['level1'],
633
+ })
634
+ .add('pause', {
635
+ initialState: () => ({}),
636
+ onEnter: () => console.log('Paused'),
637
+ onExit: () => console.log('Resumed'),
638
+ })
639
+ )
640
+ .build();
641
+
642
+ await game.initialize();
643
+
644
+ // Set initial screen
645
+ await game.setScreen('menu', {});
646
+
647
+ // Transition to gameplay (clears screen stack)
648
+ await game.setScreen('gameplay', { difficulty: 'hard', level: 1 });
649
+
650
+ // Push overlay screen (adds to stack, previous screen stays active)
651
+ await game.pushScreen('pause', {});
652
+
653
+ // Pop overlay (returns to previous screen)
654
+ await game.popScreen();
655
+
656
+ // Access current screen info
657
+ const current = game.getCurrentScreen(); // 'gameplay'
658
+ const config = game.getScreenConfig(); // { difficulty: 'hard', level: 1 }
659
+ const state = game.getScreenState(); // { score: 0, isPaused: false }
660
+
661
+ // Update screen state
662
+ game.updateScreenState({ score: 100 });
663
+ ```
664
+
665
+ #### Screen-Scoped Systems
666
+
667
+ Systems can be restricted to run only in specific screens:
668
+
669
+ ```typescript
670
+ // Only runs when 'menu' is the current screen
671
+ game.addSystem('menuUI')
672
+ .inScreens(['menu'])
673
+ .setProcess((queries, dt, ecs) => {
674
+ const state = ecs.getScreenState();
675
+ renderMenu(state.selectedOption);
676
+ })
677
+ .build();
678
+
679
+ // Only runs in 'gameplay' screen
680
+ game.addSystem('scoring')
681
+ .inScreens(['gameplay'])
682
+ .setProcess((queries, dt, ecs) => {
683
+ const state = ecs.getScreenState();
684
+ ecs.updateScreenState({ score: state.score + 1 });
685
+ })
686
+ .build();
687
+
688
+ // Runs in all screens EXCEPT 'pause'
689
+ game.addSystem('animations')
690
+ .excludeScreens(['pause'])
691
+ .setProcess(() => {
692
+ // Animations continue except when paused
693
+ })
694
+ .build();
695
+ ```
696
+
697
+ #### Screen Resource
698
+
699
+ Access screen state through the `$screen` resource:
700
+
701
+ ```typescript
702
+ game.addSystem('ui')
703
+ .setProcess((queries, dt, ecs) => {
704
+ const screen = ecs.getResource('$screen');
705
+
706
+ console.log(screen.current); // Current screen name
707
+ console.log(screen.config); // Current screen config
708
+ console.log(screen.state); // Current screen state (mutable)
709
+ console.log(screen.isOverlay); // true if screen was pushed
710
+ console.log(screen.stackDepth); // Number of screens in stack
711
+
712
+ // Check screen status
713
+ if (screen.isCurrent('gameplay')) {
714
+ // ...
715
+ }
716
+ if (screen.isActive('menu')) {
717
+ // true if menu is current OR in the stack
718
+ }
719
+ })
720
+ .build();
721
+ ```
722
+
321
723
  ## Type Safety
322
724
 
323
725
  ECSpresso provides comprehensive TypeScript support:
@@ -384,14 +786,23 @@ world.entityManager.onComponentRemoved('health', (oldValue, entity) => {
384
786
 
385
787
  ## Error Handling
386
788
 
387
- ECSpresso provides clear error messages for common issues:
789
+ ECSpresso provides clear, contextual error messages for common issues:
388
790
 
389
791
  ```typescript
390
- // Resource not found
792
+ // Resource not found with helpful context
391
793
  try {
392
794
  const missing = world.getResource('nonexistent');
393
795
  } catch (error) {
394
- console.error(error.message); // "Resource 'nonexistent' not found"
796
+ console.error(error.message);
797
+ // "Resource 'nonexistent' not found. Available resources: [config, score, settings]"
798
+ }
799
+
800
+ // Entity operations with detailed context
801
+ try {
802
+ world.entityManager.addComponent(999, 'position', { x: 0, y: 0 });
803
+ } catch (error) {
804
+ console.error(error.message);
805
+ // "Cannot add component 'position': Entity with ID 999 does not exist"
395
806
  }
396
807
 
397
808
  // Component not found returns null
@@ -408,3 +819,4 @@ if (component === null) {
408
819
  - Use system priorities to control execution order
409
820
  - Use resource factories for expensive initialization (textures, audio, etc.)
410
821
  - Consider component callbacks for immediate reactions to state changes
822
+ - Minimize the number of components in queries when possible to leverage indexing
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Asset management for ECSpresso ECS framework
3
+ */
4
+ import type EventBus from './event-bus';
5
+ import type { AssetStatus, AssetDefinition, AssetHandle, AssetsResource, AssetEvents, AssetConfigurator } from './asset-types';
6
+ /**
7
+ * Manages asset loading and access for ECSpresso
8
+ */
9
+ export default class AssetManager<AssetTypes extends Record<string, unknown> = Record<string, never>> {
10
+ private readonly assets;
11
+ private readonly groups;
12
+ private eventBus;
13
+ /**
14
+ * Set the event bus for asset events
15
+ * @internal
16
+ */
17
+ setEventBus(eventBus: EventBus<AssetEvents>): void;
18
+ /**
19
+ * Register an asset definition
20
+ */
21
+ register<K extends string, T>(key: K, definition: AssetDefinition<T>): void;
22
+ /**
23
+ * Load all assets marked as eager
24
+ */
25
+ loadEagerAssets(): Promise<void>;
26
+ /**
27
+ * Load a single asset by key
28
+ */
29
+ loadAsset<K extends keyof AssetTypes>(key: K): Promise<AssetTypes[K]>;
30
+ /**
31
+ * Load all assets in a group
32
+ */
33
+ loadAssetGroup(groupName: string): Promise<void>;
34
+ /**
35
+ * Get a loaded asset. Throws if not loaded.
36
+ */
37
+ get<K extends keyof AssetTypes>(key: K): AssetTypes[K];
38
+ /**
39
+ * Get a loaded asset or undefined
40
+ */
41
+ getOrUndefined<K extends keyof AssetTypes>(key: K): AssetTypes[K] | undefined;
42
+ /**
43
+ * Get a handle to an asset with status information
44
+ */
45
+ getHandle<K extends keyof AssetTypes>(key: K): AssetHandle<AssetTypes[K]>;
46
+ /**
47
+ * Get the status of an asset
48
+ */
49
+ getStatus<K extends keyof AssetTypes>(key: K): AssetStatus;
50
+ /**
51
+ * Check if an asset is loaded
52
+ */
53
+ isLoaded<K extends keyof AssetTypes>(key: K): boolean;
54
+ /**
55
+ * Check if all assets in a group are loaded
56
+ */
57
+ isGroupLoaded(groupName: string): boolean;
58
+ /**
59
+ * Get the loading progress of a group (0-1)
60
+ */
61
+ getGroupProgress(groupName: string): number;
62
+ /**
63
+ * Get detailed group progress
64
+ */
65
+ getGroupProgressDetails(groupName: string): {
66
+ loaded: number;
67
+ total: number;
68
+ progress: number;
69
+ };
70
+ /**
71
+ * Check group progress and emit events
72
+ */
73
+ private checkGroupProgress;
74
+ /**
75
+ * Create the $assets resource object
76
+ */
77
+ createResource(): AssetsResource<AssetTypes>;
78
+ /**
79
+ * Get all registered asset keys
80
+ */
81
+ getKeys(): Array<keyof AssetTypes>;
82
+ /**
83
+ * Get all group names
84
+ */
85
+ getGroupNames(): string[];
86
+ /**
87
+ * Get all asset keys in a group
88
+ */
89
+ getGroupKeys(groupName: string): Array<keyof AssetTypes>;
90
+ }
91
+ /**
92
+ * Implementation of AssetConfigurator for builder pattern
93
+ */
94
+ export declare class AssetConfiguratorImpl<A extends Record<string, unknown>> implements AssetConfigurator<A> {
95
+ private readonly manager;
96
+ constructor(manager: AssetManager<A>);
97
+ add<K extends string, T>(key: K, loader: () => Promise<T>): AssetConfigurator<A & Record<K, T>>;
98
+ addWithConfig<K extends string, T>(key: K, definition: AssetDefinition<T>): AssetConfigurator<A & Record<K, T>>;
99
+ addGroup<G extends string, T extends Record<string, () => Promise<unknown>>>(groupName: G, assets: T): AssetConfigurator<A & {
100
+ [K in keyof T]: Awaited<ReturnType<T[K]>>;
101
+ }>;
102
+ /**
103
+ * Get the underlying manager
104
+ * @internal
105
+ */
106
+ getManager(): AssetManager<A>;
107
+ }
108
+ /**
109
+ * Create a new AssetConfigurator for builder pattern usage
110
+ */
111
+ export declare function createAssetConfigurator<A extends Record<string, unknown> = Record<string, never>>(manager?: AssetManager<A>): AssetConfiguratorImpl<A>;