UNPKG

@qiwi/cyclone

Version:

"State machine" for basic purposes

1 lines 12.9 kB
{"version":3,"file":"cyclone.modern.mjs","sources":["../../src/main/ts/error.ts","../../src/main/ts/generator.ts","../../src/main/ts/log.ts","../../src/main/ts/machine.ts","../../src/main/ts/registry.ts","../../src/main/ts/factory.ts"],"sourcesContent":["export const TRANSITION_VIOLATION = 'Transition violation'\nexport const INVALID_UNLOCK_KEY = 'Invalid unlock key'\nexport const LOCK_VIOLATION = 'Lock violation'\nexport const UNREACHABLE_STATE = 'Unreachable state'\n\nexport class MachineError extends Error {}\n","export const generateDate = (): Date => new Date()\nexport const generateId = (): string => Math.random().toString()\n","export const LOG_PREFIX = '[cyclone]'\n\nexport const debug = console.debug.bind(console, LOG_PREFIX)\nexport const info = console.info.bind(console, LOG_PREFIX)\nexport const error = console.error.bind(console, LOG_PREFIX)\nexport const warn = console.warn.bind(console, LOG_PREFIX)\n\nexport const log = {\n debug,\n info,\n error,\n warn\n}\n","import {\n INVALID_UNLOCK_KEY,\n LOCK_VIOLATION,\n MachineError,\n TRANSITION_VIOLATION,\n UNREACHABLE_STATE\n} from './error'\nimport {\n generateDate,\n generateId\n} from './generator'\nimport { log } from './log'\n\ntype IState = string\n\ntype IAny = any\n\ntype IHandler = (data: IAny, ...payload: Array<IAny>) => IAny\n\ntype ITransitions = {\n [key: string]: IHandler | null | boolean\n}\n\ntype IMachineOpts = {\n transitions: ITransitions,\n initialState?: IState,\n initialData?: IAny,\n immutable?: boolean,\n historySize?: number\n}\n\nexport const DELIMITER = '>'\nexport const DEFAULT_HANDLER: IHandler = (...last) => last.pop()\nexport const DEFAULT_HISTORY_SIZE = 10\nexport const DEFAULT_OPTS: IMachineOpts = {\n transitions: {},\n historySize: DEFAULT_HISTORY_SIZE,\n immutable: false\n}\n\ntype IKey = string | null\n\ntype IDigest = {\n state?: IState,\n data?: IAny\n}\n\ntype IHistoryItem = {\n state: IState,\n data: IAny,\n id: string,\n date: Date\n}\ntype IHistory = IHistoryItem[]\n\ninterface IMachine {\n next(state: IState, ...payload: Array<IAny>): IMachine,\n prev(state?: IState): IMachine,\n current(): IDigest,\n lock(key?: IKey): IMachine,\n unlock(key: IKey): IMachine,\n\n transitions: ITransitions,\n history: IHistory,\n opts: IMachineOpts,\n key: IKey,\n id: string,\n}\n\ntype IPredicate = (item: IHistoryItem) => boolean\n\nexport class Machine implements IMachine {\n\n /**\n * Machine options.\n * @property\n */\n public opts: IMachineOpts\n\n /**\n * State history.\n * @property\n */\n public history: IHistory\n\n /**\n * Lock key.\n * @property\n */\n public key: IKey\n\n /**\n * Unique machine id\n * @property\n */\n public id: string\n\n /**\n * Transition handler map\n * @property\n */\n public transitions: ITransitions\n\n constructor(opts: IMachineOpts) {\n this.opts = { ...DEFAULT_OPTS, ...opts }\n this.history = []\n this.key = null\n this.id = generateId()\n this.transitions = opts.transitions\n\n if (typeof opts.initialState === 'string') {\n this.history.push({\n state: opts.initialState,\n data: opts.initialData,\n id: generateId(),\n date: generateDate()\n })\n }\n\n return this\n }\n\n /**\n * Provides next state transition.\n * @param state Next state name.\n * @param payload Any data for handler.\n */\n public next(state: IState, ...payload: Array<IAny>): IMachine {\n if (this.key) {\n throw new MachineError(LOCK_VIOLATION)\n }\n\n const handler = Machine.getHandler(state, this.history, this.transitions)\n const current = this.current()\n const data = handler(current.data, ...payload)\n const id = generateId()\n const date = generateDate()\n\n this.history.push({\n state,\n data,\n id,\n date\n })\n\n if (this.history.length > Machine.getHistoryLimit(this.opts.historySize)) {\n log.debug('history limit reached')\n this.history.shift()\n }\n\n return this\n }\n\n /**\n * Returns the machine's digest: state name and stored data.\n */\n public current(): IHistoryItem {\n return { ...this.history[this.history.length - 1] }\n }\n\n /**\n * Returns the last state, that satisfies the condition\n */\n public last(condition?: string | IPredicate): IHistoryItem | void {\n if (condition === undefined) {\n return this.current()\n }\n\n const filter = typeof condition === 'string'\n ? ({ state }: IHistoryItem) => state === condition\n : condition\n\n return [...this.history].reverse().find(filter)\n }\n\n /**\n * Reverts current state to the previous.\n * @param state\n */\n public prev(state?: string | IPredicate): IMachine {\n if (this.key) {\n throw new MachineError(LOCK_VIOLATION)\n }\n\n if (this.history.length < 2) {\n throw new MachineError(UNREACHABLE_STATE)\n }\n\n if (state === undefined) {\n this.history.pop()\n return this\n }\n\n const last = this.last(state)\n if (!last) {\n throw new MachineError(UNREACHABLE_STATE)\n }\n\n this.history.length = this.history.indexOf(last) + 1\n\n return this\n }\n\n /**\n * Locks the machine. Any transitions are prohibited before unlocking.\n * @param key\n */\n public lock(key?: IKey): IMachine {\n this.key = key || `lock${generateId()}`\n\n return this\n }\n\n /**\n * Unlocks the machine.\n * @param key\n */\n public unlock(key: IKey): IMachine {\n if (this.key !== key) {\n throw new MachineError(INVALID_UNLOCK_KEY)\n }\n\n this.key = null\n\n return this\n }\n\n public static getHistoryLimit(historySize?: number): number {\n if (historySize === undefined) {\n return DEFAULT_HISTORY_SIZE\n }\n\n if (historySize === -1) {\n return Number.POSITIVE_INFINITY\n }\n\n return historySize\n }\n\n public static getHandler(next: IState, history: IHistory, transitions: ITransitions): IHandler {\n const targetTransition = this.getTargetTransition(next, history)\n const nextTransition = this.getTransition(targetTransition, transitions)\n\n if (!nextTransition) {\n throw new MachineError(TRANSITION_VIOLATION)\n }\n\n const handler = transitions[nextTransition]\n\n return typeof handler === 'function'\n ? handler\n : DEFAULT_HANDLER\n }\n\n public static getTransition(targetTransition: string, transitions: ITransitions): string | void {\n // TODO Support wildcards\n // TODO Support OR operator\n // TODO Generate patterns in constructor\n return Object.keys(transitions)\n .filter(transition => targetTransition.length > transition.length\n ? new RegExp(`.*${transition}$`).test(targetTransition)\n : targetTransition === transition\n )\n .sort((a, b) => b.length - a.length)[0]\n }\n\n public static getTargetTransition(next: IState, history: IHistory): string {\n return [...history.map(({ state }: IHistoryItem) => state), next]\n .join(DELIMITER)\n }\n\n /**\n * Returns the last passes argument as a result\n * @param {any} state\n * @param {any} [payload]\n * @return {any}\n */\n public DEFAULT_HANDLER = DEFAULT_HANDLER\n\n}\n\nexport {\n IMachine, IHistory, ITransitions, IHistoryItem, IHandler, IMachineOpts\n}\n","export class Registry {\n\n store: { [key: string]: any }\n\n constructor () {\n this.store = {}\n }\n\n get (key: string): void {\n return this.store[key]\n }\n\n add (key: string, value: any): void {\n this.store[key] = value\n }\n\n remove (key: string): void {\n delete this.store[key]\n }\n\n}\n","import {\n DEFAULT_OPTS as DEFAULT_MACHINE_OPTS,\n IMachine,\n IMachineOpts,\n Machine} from './machine'\nimport {\n Registry\n} from './registry'\n\nexport const DEFAULT_TEMPLATE = {}\nexport const DEFAULT_TEMPLATE_REGISTRY = new Registry()\nexport const DEFAULT_MACHINE_REGISTRY = new Registry()\n\ntype IFactoryOpts = {\n machine?: IMachineOpts\n template?: string | Object\n templateRegistry?: Registry\n machineRegistry?: Registry\n}\n\nexport const factory = (opts: IFactoryOpts): IMachine => {\n // NOTE flowgen throws error on typed args destruction\n const {\n machine,\n template,\n templateRegistry = DEFAULT_TEMPLATE_REGISTRY,\n machineRegistry = DEFAULT_MACHINE_REGISTRY\n } = opts\n const _template = getTemplate(template, templateRegistry)\n const machineOpts: IMachineOpts = { ...DEFAULT_MACHINE_OPTS, ..._template, ...machine }\n const instance = new Machine(machineOpts)\n\n machineRegistry.add(instance.id, instance)\n\n return instance\n}\n\nexport const getTemplate = (template: string | Object | void, templateRegistry: Registry): any => {\n return typeof template === 'string'\n ? templateRegistry.get(template)\n : template\n}\n\nexport {\n IFactoryOpts\n}\n"],"names":["TRANSITION_VIOLATION","INVALID_UNLOCK_KEY","LOCK_VIOLATION","UNREACHABLE_STATE","MachineError","Error","generateDate","Date","generateId","Math","random","toString","log","debug","console","bind","info","error","warn","DELIMITER","last","pop","DEFAULT_HISTORY_SIZE","DEFAULT_OPTS","transitions","historySize","immutable","Machine","constructor","opts","this","history","key","id","DEFAULT_HANDLER","_extends","initialState","push","state","data","initialData","date","next","payload","getHandler","handler","current","length","getHistoryLimit","shift","condition","undefined","filter","reverse","find","prev","indexOf","lock","unlock","static","POSITIVE_INFINITY","targetTransition","getTargetTransition","getTransition","nextTransition","Object","keys","transition","RegExp","test","sort","a","b","map","join","Registry","store","get","add","value","remove","DEFAULT_TEMPLATE","DEFAULT_TEMPLATE_REGISTRY","DEFAULT_MACHINE_REGISTRY","factory","machine","template","templateRegistry","machineRegistry","_template","getTemplate","machineOpts","DEFAULT_MACHINE_OPTS","instance"],"mappings":"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"}