import { EventEmitter } from 'events' export class Store { protected state: T protected events = 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.events.on } /** Liste once on event from the store */ public get once() { return this.events.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.events.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) this.events.emit(`${this.name}Added`, entity) } /** 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)) this.events.emit(`${this.name}Removed`, id) } /** Update one entity of the state */ public updateOne(id: string, update: Partial) { this.state.entities[id] = { ...this.state.entities[id], ...update } this.events.emit(`${this.name}Updated`, this.state.entities[id]) } /** Set the actives entities */ public setActive(ids: string[] | string) { this.state.actives = Array.isArray(ids) ? ids : [ids] this.events.emit(`${this.name}Activated`, this.actives) } /** 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.events.emit(`${this.name}Activated`, 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)) this.events.emit(`${this.name}Deactivated`, 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)) }) }) }