import * as plugins from './smartstate.plugins.js'; import { StateAction, type IActionDef } from './smartstate.classes.stateaction.js'; export class StatePart { public name: TStatePartName; public state = new plugins.smartrx.rxjs.Subject(); public stateStore: TStatePayload; private cumulativeDeferred = plugins.smartpromise.cumulativeDefer(); private webStoreOptions: plugins.webstore.IWebStoreOptions; private webStore: plugins.webstore.WebStore | null = null; // Add WebStore instance constructor(nameArg: TStatePartName, webStoreOptionsArg?: plugins.webstore.IWebStoreOptions) { this.name = nameArg; // Initialize WebStore if webStoreOptions are provided if (webStoreOptionsArg) { this.webStoreOptions = webStoreOptionsArg; } } /** * initializes the webstore */ public async init() { if (this.webStoreOptions) { this.webStore = new plugins.webstore.WebStore(this.webStoreOptions); await this.webStore.init(); const storedState = await this.webStore.get(String(this.name)); if (storedState) { this.stateStore = storedState; this.notifyChange(); } } } /** * gets the state from the state store */ public getState(): TStatePayload { return this.stateStore; } /** * sets the stateStore to the new state * @param newStateArg */ public async setState(newStateArg: TStatePayload) { this.stateStore = newStateArg; this.notifyChange(); // Save state to WebStore if initialized if (this.webStore) { await this.webStore.set(String(this.name), newStateArg); } return this.stateStore; } /** * notifies of a change on the state */ public notifyChange() { const createStateHash = (stateArg: any) => { return plugins.isohash.sha256FromString(plugins.smartjson.stringify(stateArg)); }; if ( this.stateStore && this.lastStateNotificationPayloadHash && createStateHash(this.stateStore) === this.lastStateNotificationPayloadHash ) { return; } else { this.lastStateNotificationPayloadHash = this.stateStore; } this.state.next(this.stateStore); } private lastStateNotificationPayloadHash: any; /** * creates a cumulative notification by adding a change notification at the end of the call stack; */ public notifyChangeCumulative() { // TODO: check viability setTimeout(() => this.state.next(this.stateStore), 0); } /** * selects a state or a substate */ public select( selectorFn?: (state: TStatePayload) => T ): plugins.smartrx.rxjs.Observable { if (!selectorFn) { selectorFn = (state: TStatePayload) => (state); } const mapped = this.state.pipe( plugins.smartrx.rxjs.ops.startWith(this.getState()), plugins.smartrx.rxjs.ops.map((stateArg) => { try { return selectorFn(stateArg); } catch (e) { // Nothing here } }) ); return mapped; } /** * creates an action capable of modifying the state */ public createAction( actionDef: IActionDef ): StateAction { return new StateAction(this, actionDef); } /** * dispatches an action on the statepart level */ public async dispatchAction(stateAction: StateAction, actionPayload: T): Promise { await this.cumulativeDeferred.promise; const newState = await stateAction.actionDef(this, actionPayload); await this.setState(newState); return this.getState(); } /** * waits until a certain part of the state becomes available * @param selectorFn */ public async waitUntilPresent( selectorFn?: (state: TStatePayload) => T ): Promise { const done = plugins.smartpromise.defer(); const selectedObservable = this.select(selectorFn); const subscription = selectedObservable.subscribe(async (value) => { if (value) { done.resolve(value); } }); const result = await done.promise; subscription.unsubscribe(); return result; } /** * is executed */ public async stateSetup( funcArg: (statePartArg?: StatePart) => Promise ) { const resultPromise = funcArg(this); this.cumulativeDeferred.addPromise(resultPromise); this.setState(await resultPromise); } }