refactor(helpers): modernize reactive helpers and add tests
This commit is contained in:
@@ -1,48 +1,102 @@
|
||||
/**
|
||||
* 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 {
|
||||
/** Unique identifier for the entity */
|
||||
id: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Svelte 5 Entity Store
|
||||
* Uses SvelteMap for O(1) lookups and granular reactivity.
|
||||
* 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> {
|
||||
// SvelteMap is a reactive version of the native Map
|
||||
/** 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);
|
||||
}
|
||||
|
||||
// --- Selectors (Equivalent to Selectors) ---
|
||||
|
||||
/** Get all entities as an array */
|
||||
/**
|
||||
* Get all entities as an array
|
||||
* @returns Array of all entities in the store
|
||||
*/
|
||||
get all() {
|
||||
return Array.from(this.#entities.values());
|
||||
}
|
||||
|
||||
/** Select a single entity by ID */
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/** Select multiple entities by IDs */
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
// --- Actions (CRUD) ---
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
@@ -50,32 +104,61 @@ export class EntityStore<T extends Entity> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 EntityStore instance with the given initial entities.
|
||||
* @param initialEntities The initial entities to populate the store with.
|
||||
* @returns - A new EntityStore instance.
|
||||
* 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);
|
||||
|
||||
Reference in New Issue
Block a user