Files
frontend-svelte/src/shared/lib/helpers/createEntityStore/createEntityStore.svelte.ts
T

170 lines
4.1 KiB
TypeScript
Raw Normal View History

/**
* 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<User>([
* { 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 {
2026-04-17 12:14:55 +03:00
/**
* 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<T extends Entity> {
2026-04-17 12:14:55 +03:00
/**
* Reactive map of entities keyed by ID
*/
#entities = new SvelteMap<string, T>();
/**
* 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<T>) {
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<T extends Entity>(initialEntities: T[] = []) {
return new EntityStore<T>(initialEntities);
}