import * as plugins from './smartstate.plugins.js'; import { StatePart } from './smartstate.classes.statepart.js'; export type TInitMode = 'soft' | 'mandatory' | 'force' | 'persistent'; /** * Smartstate takes care of providing state */ export class Smartstate { public statePartMap: { [key in StatePartNameType]?: StatePart } = {}; private pendingStatePartCreation: Map>> = new Map(); constructor() {} /** * Allows getting and initializing a new statepart * initMode === 'soft' (default) - returns existing statepart if exists, creates new if not * initMode === 'mandatory' - requires statepart to not exist, fails if it does * initMode === 'force' - always creates new statepart, overwriting any existing * initMode === 'persistent' - like 'soft' but with webstore persistence * @param statePartNameArg * @param initialArg * @param initMode */ public async getStatePart( statePartNameArg: StatePartNameType, initialArg?: PayloadType, initMode: TInitMode = 'soft' ): Promise> { // Return pending creation if one exists to prevent duplicate state parts const pending = this.pendingStatePartCreation.get(statePartNameArg); if (pending) { return pending as Promise>; } const existingStatePart = this.statePartMap[statePartNameArg]; if (existingStatePart) { switch (initMode) { case 'mandatory': throw new Error( `State part '${statePartNameArg}' already exists, but initMode is 'mandatory'` ); case 'force': // Force mode: create new state part break; // Fall through to creation case 'soft': case 'persistent': default: // Return existing state part return existingStatePart as StatePart; } } else { // State part doesn't exist if (!initialArg) { throw new Error( `State part '${statePartNameArg}' does not exist and no initial state provided` ); } } const creationPromise = this.createStatePart(statePartNameArg, initialArg, initMode); this.pendingStatePartCreation.set(statePartNameArg, creationPromise); try { const result = await creationPromise; return result; } finally { this.pendingStatePartCreation.delete(statePartNameArg); } } /** * Creates a statepart * @param statePartName * @param initialPayloadArg * @param initMode */ private async createStatePart( statePartName: StatePartNameType, initialPayloadArg: PayloadType, initMode: TInitMode = 'soft' ): Promise> { const newState = new StatePart( statePartName, initMode === 'persistent' ? { dbName: 'smartstate', storeName: statePartName, } : null ); await newState.init(); const currentState = newState.getState(); if (initMode === 'persistent' && currentState !== undefined) { // Persisted state exists - merge with defaults, persisted values take precedence await newState.setState({ ...initialPayloadArg, ...currentState, }); } else { // No persisted state or non-persistent mode await newState.setState(initialPayloadArg); } this.statePartMap[statePartName] = newState; return newState; } }