ecspresso
    Preparing search index...

    Core Concepts

    Entities are containers for components. Use spawn() to create entities with initial components:

    // Create entity with components
    const entity = world.spawn({
    position: { x: 10, y: 20 },
    health: { value: 100 }
    });

    // Look up an entity by ID
    const found = world.getEntity(entity.id); // Entity | undefined

    // Add components later
    world.addComponent(entity.id, 'velocity', { x: 5, y: 0 });

    // Get component data (returns undefined if not found)
    const position = world.getComponent(entity.id, 'position');

    // Remove components or entities
    world.removeComponent(entity.id, 'velocity');
    world.removeEntity(entity.id);

    React to component additions and removals. Both methods return an unsubscribe function:

    const unsubAdd = world.onComponentAdded('health', ({ value, entity }) => {
    console.log(`Health added to entity ${entity.id}:`, value);
    });

    const unsubRemove = world.onComponentRemoved('health', ({ value, entity }) => {
    console.log(`Health removed from entity ${entity.id}:`, value);
    });

    // Unsubscribe when done
    unsubAdd();
    unsubRemove();

    Also available on world.entityManager.onComponentAdded() / onComponentRemoved().

    Systems process entities that match specific component patterns:

    world.addSystem('combat')
    .addQuery('fighters', {
    with: ['position', 'health'],
    without: ['dead']
    })
    .addQuery('projectiles', {
    with: ['position', 'damage']
    })
    .setProcess(({ queries }) => {
    for (const fighter of queries.fighters) {
    for (const projectile of queries.projectiles) {
    // Combat logic here
    }
    }
    });

    For more on systems, see Systems.

    Resources provide global state accessible to all systems.

    interface Resources {
    score: { value: number };
    settings: { difficulty: 'easy' | 'hard' };
    }

    const world = ECSpresso.create()
    .withComponentTypes<Components>()
    .withResourceTypes<Resources>()
    .withResource('score', { value: 0 })
    .build();

    // Sync or async factories (lazy initialization)
    world.addResource('config', () => ({ difficulty: 'normal', soundEnabled: true }));
    world.addResource('database', async () => await connectToDatabase());

    // Factory with dependencies (initialized after dependencies are ready)
    world.addResource('cache', {
    dependsOn: ['database'],
    factory: (ecs) => ({ db: ecs.getResource('database') })
    });

    // Initialize all resources (respects dependency order, detects circular deps)
    await world.initializeResources();

    // Use in systems
    world.addSystem('scoring')
    .setProcess(({ ecs }) => {
    const score = ecs.getResource('score');
    score.value += 10;
    });
    // Get a resource (throws if not found)
    const score = world.getResource('score');

    // Get a resource (returns undefined if not found)
    const score = world.tryGetResource('score');

    // Set a resource to a plain value
    world.setResource('score', { value: 42 });

    // Update a resource using a function (when new value depends on old)
    world.updateResource('score', (prev) => ({ value: prev.value + 1 }));

    Subscribe to resource changes for reactive integrations (e.g., React's useSyncExternalStore). Returns an unsubscribe function. Notifications only fire when the value actually changes (Object.is comparison).

    const unsubscribe = world.onResourceChange('score', (newValue, oldValue) => {
    console.log(`Score changed from ${oldValue.value} to ${newValue.value}`);
    });

    // Stop listening
    unsubscribe();

    Resources also chain naturally with plugins in the builder:

    const world = ECSpresso.create()
    .withPlugin(physicsPlugin)
    .withResource('config', { debug: true, maxEntities: 1000 })
    .withResource('score', () => ({ value: 0 }))
    .withResource('cache', {
    dependsOn: ['database'],
    factory: (ecs) => createCache(ecs.getResource('database'))
    })
    .build();

    Resources can define cleanup logic with onDispose callbacks:

    world.addResource('keyboard', {
    factory: () => {
    const handler = (e: KeyboardEvent) => { /* ... */ };
    window.addEventListener('keydown', handler);
    return { handler };
    },
    onDispose: (resource) => {
    window.removeEventListener('keydown', resource.handler);
    }
    });

    await world.disposeResource('keyboard'); // Dispose a single resource
    await world.disposeResources(); // All, in reverse dependency order

    onDispose receives the resource value and the ECSpresso instance. Supports sync and async callbacks. Only initialized resources have their onDispose called. removeResource() still exists for removal without disposal.