import { EventEmitter } from "events"; export class Store { protected state: T; protected event = new EventEmitter(); /** * Create a Store that hold the state of the component * @param name The name of the store * @param initialState The initial state of the store */ constructor(public name: string, private initialState: T) { this.state = this.initialState; } /** Listen on event from the store */ public get on() { return this.event.on; } /** Liste once on event from the store */ public get once() { return this.event.once; } /** Update one field of the state */ public update(state: Partial) { this.state = { ...this.state, ...state }; } /** Get one field of the state */ public get(key: Key): T[Key] { return this.state[key]; } /** Reset the state its initial value */ public reset() { this.state = this.initialState; } /** Dispatch an event with the new state */ public dispatch() { this.event.emit("newState", this.state); } } interface EntityState { ids: string[]; actives: string[]; entities: { [id: string]: T; }; [key: string]: any; } export class EntityStore extends Store> { /** * Create a entity Store that hold a map entity of the same model * @param name The name of the store * @param initialState The initial state of the store * @param keyId The value used as a key for the `entities` and inside `ids` */ constructor( name: string, initialState: EntityState, private keyId: keyof T ) { super(name, initialState); } //////////// // GETTER // //////////// /** Tne entities as a Map */ get entities() { return this.state.entities; } /** List of all the ids */ get ids() { return this.state.ids; } /** List of all active ID */ get actives() { return this.state.actives; } /** Return the length of the entity collection */ get length() { return this.state.ids.length; } ///////////// // SETTERS // ///////////// /** Add a new entity to the state */ public add(entity: T) { const id = entity[this.keyId]; if (typeof id !== "string") { throw new Error( `${id} should be of type 'string', but is of type ${typeof id}` ); } this.state.entities[id as string] = entity; this.state.ids.push(id); } /** Remove an entity from the state */ public remove(id: string) { delete this.state.entities[id]; this.state.ids.slice(this.state.ids.indexOf(id)); this.state.actives.slice(this.state.ids.indexOf(id)); } /** Update one entity of the state */ public updateOne(id: string, update: Partial) { this.state.entities[id] = { ...this.state.entities[id], ...update }; } /** Activate one or several entity from the state */ public activate(ids: string[] | string) { Array.isArray(ids) ? this.state.actives.push() : this.state.actives.concat(ids); this.event.emit("activate", ids); } /** Remove one or */ public deactivate(ids: string[] | string) { Array.isArray(ids) ? ids.forEach(id => this.state.actives.slice(this.state.ids.indexOf(id))) : this.state.actives.slice(this.state.ids.indexOf(ids)); } /////////// // QUERY // /////////// /** Get one entity */ getOne(id: string) { return this.state.entities[id]; } /** Get many entities as an array */ getMany(ids: string[]) { return ids.map(id => this.state.entities[id]); } /** Get all the entities as an array */ getAll() { return this.state.ids.map(id => this.state.entities[id]); } /** Get all active entities */ getActives() { return this.state.actives.map(id => this.state.entities[id]); } //////////////// // CONDITIONS // //////////////// /** Is the entity active */ public isActive(id: string) { return this.state.actives.includes(id); } /** Is this id inside the store */ public hasEntity(id: string) { return this.state.ids.includes(id); } /** Is the state empty */ public isEmpty() { return this.state.ids.length === 0; } } /** Store the state of the stores into LocalStorage */ function localState(stores: Store[]) { stores.forEach(store => { const name = store.name; store.on("newState", (state: any) => { localStorage.setItem(name, JSON.stringify(state)); }); }); }