/** * Generic entity store using Svelte 5's reactive SvelteMap * * Provides O(1) lookups by ID and granular reactivity for entity collections. * Ideal for managing collections of objects with unique identifiers. * * @example * ```ts * interface User extends Entity { * id: string; * name: string; * } * * const store = createEntityStore([ * { id: '1', name: 'Alice' }, * { id: '2', name: 'Bob' } * ]); * * // Access is reactive in Svelte components * const allUsers = store.all; * const alice = store.getById('1'); * ``` */ import { SvelteMap } from 'svelte/reactivity'; /** * Base entity interface requiring an ID field */ export interface Entity { /** * Unique identifier for the entity */ id: string; } /** * Reactive entity store with O(1) lookups * * Uses SvelteMap internally for reactive state that automatically * triggers updates when entities are added, removed, or modified. */ export class EntityStore { /** * Reactive map of entities keyed by ID */ #entities = new SvelteMap(); /** * Creates a new entity store with optional initial data * @param initialEntities - Initial entities to populate the store */ constructor(initialEntities: T[] = []) { this.setAll(initialEntities); } /** * Get all entities as an array * @returns Array of all entities in the store */ get all() { return Array.from(this.#entities.values()); } /** * Get a single entity by ID * @param id - Entity ID to look up * @returns The entity if found, undefined otherwise */ getById(id: string) { return this.#entities.get(id); } /** * Get multiple entities by their IDs * @param ids - Array of entity IDs to look up * @returns Array of found entities (undefined IDs are filtered out) */ getByIds(ids: string[]) { return ids.map(id => this.#entities.get(id)).filter((e): e is T => !!e); } /** * Add a single entity to the store * @param entity - Entity to add (updates if ID already exists) */ addOne(entity: T) { this.#entities.set(entity.id, entity); } /** * Add multiple entities to the store * @param entities - Array of entities to add */ addMany(entities: T[]) { entities.forEach(e => this.addOne(e)); } /** * Update an existing entity by merging changes * @param id - ID of entity to update * @param changes - Partial changes to merge into existing entity */ updateOne(id: string, changes: Partial) { const entity = this.#entities.get(id); if (entity) { this.#entities.set(id, { ...entity, ...changes }); } } /** * Remove a single entity by ID * @param id - ID of entity to remove */ removeOne(id: string) { this.#entities.delete(id); } /** * Remove multiple entities by their IDs * @param ids - Array of entity IDs to remove */ removeMany(ids: string[]) { ids.forEach(id => this.#entities.delete(id)); } /** * Replace all entities in the store * Clears existing entities and adds new ones * @param entities - New entities to populate the store with */ setAll(entities: T[]) { this.#entities.clear(); this.addMany(entities); } /** * Check if an entity exists in the store * @param id - Entity ID to check * @returns true if entity exists, false otherwise */ has(id: string) { return this.#entities.has(id); } /** * Remove all entities from the store */ clear() { this.#entities.clear(); } } /** * Creates a new entity store instance * @param initialEntities - Initial entities to populate the store with * @returns A new EntityStore instance * * @example * ```ts * const store = createEntityStore([ * { id: '1', name: 'Item 1' }, * { id: '2', name: 'Item 2' } * ]); * ``` */ export function createEntityStore(initialEntities: T[] = []) { return new EntityStore(initialEntities); }