UNPKG

@deepkit/workflow

Version:

Deepkit workflow engine / finite state machine

234 lines (231 loc) 12.7 kB
const __ΩOmit = ['T', 'K', () => __ΩPick, () => __ΩExclude, 'Omit', 'b!b"e!!e!!ge!"o$#o##w%y']; const __ΩCapitalize = ['S', 'Capitalize', 'b!!w"y']; const __ΩPick = ['T', 'K', 'Pick', 'l+e#!e"!fRb!b"Pde""N#!w#y']; const __ΩExclude = ['T', 'U', 'Exclude', 'l6!Re$!RPe#!e$"qk#%QRb!b"Pde"!p)w#y']; /*@ts-ignore*/ import { __ΩExtractClassType } from '@deepkit/core'; /*@ts-ignore*/ import { __ΩClassType } from '@deepkit/core'; function __assignType(fn, args) { fn.__type = args; return fn; } /* * Deepkit Framework * Copyright (C) 2021 Deepkit UG, Marc J. Schmidt * * This program is free software: you can redistribute it and/or modify * it under the terms of the MIT License. * * You should have received a copy of the MIT License along with this program. */ import { capitalize, CompilerContext, CustomError, getClassName, isArray, toFastProperties } from '@deepkit/core'; import { BaseEvent, EventDispatcher, EventToken, isEventListenerContainerEntryCallback, isEventListenerContainerEntryService } from '@deepkit/event'; import { injectedFunction, InjectorContext } from '@deepkit/injector'; import { FrameCategory, Stopwatch } from '@deepkit/stopwatch'; import { ReflectionClass } from '@deepkit/type'; const __ΩWorkflowTransition = ['T', 'from', 'to', 'label', 'WorkflowTransition', 'b!PPe#!g&K4"Pe#!g&K4#&4$8Mw%y']; export class WorkflowEvent extends BaseEvent { clearNext() { this.nextState = undefined; this.nextStateEvent = undefined; } /** * @see WorkflowNextEvent.next */ next(nextState, event) { this.nextState = nextState; this.nextStateEvent = event; } hasNext() { return this.nextState !== undefined; } } WorkflowEvent.__type = [() => BaseEvent, 'nextState', 'nextStateEvent', 'clearNext', 'event', 'next', 'hasNext', 'WorkflowEvent', 'P7!"3"8"3#8P"0$P&2""2%8"0&P)0\'5w(']; const __ΩWorkflowPlaces = [() => __ΩClassType, () => WorkflowEvent, 'WorkflowPlaces', 'P&P7"o!"LMw#y']; export { __ΩWorkflowPlaces as __ΩWorkflowPlaces }; const __ΩWorkflowNextEvent = ['T', 'nextState', () => __ΩExtractClassType, 'event', 'next', 'WorkflowNextEvent', 'b!PPe#!g&K4"8P"2"e#!"fo#"2$8$1%Mw&y']; export { __ΩWorkflowNextEvent as __ΩWorkflowNextEvent }; const __ΩWorkflowDefinitionEvents = ['T', () => BaseEvent, () => __ΩOmit, () => __ΩExtractClassType, "next", "nextState", () => __ΩWorkflowNextEvent, () => EventToken, 'on', () => __ΩCapitalize, 'WorkflowDefinitionEvents', 'lQPPPP7"e&!e%!fo$"P.%.&Jo##e&!o\'"K7(P.)e$!o*"SGRb!PdPe#!g&Kt#!w+y']; export { __ΩWorkflowDefinitionEvents as __ΩWorkflowDefinitionEvents }; export class WorkflowDefinition { constructor(name, places, transitions = {}) { this.name = name; this.places = places; this.transitions = []; this.tokens = {}; this.next = {}; this.symbol = Symbol('workflow'); for (const placeName in this.places) { if (!this.places.hasOwnProperty(placeName)) continue; const token = new EventToken(name + '.' + placeName, this.places[placeName]); this.tokens[placeName] = token; this['on' + capitalize(placeName)] = token; } for (const [i, value] of Object.entries(transitions)) { if (isArray(value)) { for (const v of value) this.addTransition(i, v); } else if (value !== undefined) { this.addTransition(i, value); } } toFastProperties(this.tokens); toFastProperties(this.next); } getEventToken(name) { if (!this.tokens[name]) throw new Error(`No event token found for ${String(name)}`); return this.tokens[name]; } addTransition(from, to, label) { this.transitions.push({ from, to, label }); if (!this.next[from]) this.next[from] = []; this.next[from].push(to); } create(state, eventDispatcher, injector, stopwatch) { return new Workflow(this, new WorkflowStateSubject(state), eventDispatcher, injector || eventDispatcher.injector, stopwatch); } getTransitionsFrom(state) { return this.next[state] || []; } buildApplier(eventDispatcher) { const compiler = new CompilerContext(); compiler.context.set('WorkflowError', WorkflowError); compiler.context.set('WorkflowEvent', WorkflowEvent); compiler.context.set('getClassName', getClassName); const lines = []; for (const [place, eventType] of Object.entries(this.places)) { const stateString = JSON.stringify(place); const eventTypeVar = compiler.reserveVariable('eventType', eventType); const allowedFrom = this.transitions.filter(__assignType(v => v.to === place, ['v', '', 'P"2!"/"'])); const allowedFromCondition = allowedFrom.map(__assignType(v => `currentState === ${JSON.stringify(v.from)}`, ['v', '', 'P"2!"/"'])).join(' || '); const checkFrom = `if (!(${allowedFromCondition})) throw new WorkflowError(\`Can not apply state change from \${currentState}->\${nextState}. There's no transition between them or it was blocked.\`);`; const eventToken = this.tokens[place]; const listeners = eventDispatcher.getListeners(eventToken); listeners.sort(__assignType((a, b) => { if (a.order > b.order) return +1; if (a.order < b.order) return -1; return 0; }, ['a', 'b', '', 'P"2!"2""/#'])); const listenerCode = []; for (const listener of listeners) { if (isEventListenerContainerEntryCallback(listener)) { const injector = listener.module ? eventDispatcher.injector.getInjector(listener.module) : eventDispatcher.injector.getRootInjector(); const fn = injectedFunction(listener.fn, injector, 1); const fnVar = compiler.reserveVariable('fn', fn); listenerCode.push(` if (!event.immediatePropagationStopped) { await ${fnVar}(scopedContext.scope, event); } `); } else if (isEventListenerContainerEntryService(listener)) { const injector = listener.module ? eventDispatcher.injector.getInjector(listener.module) : eventDispatcher.injector.getRootInjector(); const classTypeVar = compiler.reserveVariable('classType', listener.classType); const moduleVar = listener.module ? ', ' + compiler.reserveVariable('module', listener.module) : ''; const method = ReflectionClass.from(listener.classType).getMethod(listener.methodName); const resolvedVar = compiler.reserveVariable('resolved'); let call = `${resolvedVar}.${listener.methodName}(event)`; if (method.getParameters().length > 1) { const fn = injectedFunction(__assignType((event, classInstance, ...args) => { return classInstance[listener.methodName](event, ...args); }, ['event', 'classInstance', 'args', '', 'P"2!"2""@2#"/$']), injector, 2, method.type, 1); call = `${compiler.reserveVariable('fn', fn)}(scopedContext.scope, event, ${resolvedVar})`; } listenerCode.push(` //${getClassName(listener.classType)}.${listener.methodName} if (!event.immediatePropagationStopped) { if (!${resolvedVar}) ${resolvedVar} = scopedContext.get(${classTypeVar}${moduleVar}); await ${call}; } `); } } const stopWatchId = this.name + '/' + place; lines.push(` case ${stateString}: { ${allowedFrom.length ? checkFrom : ''} if (!(event instanceof ${eventTypeVar})) { throw new Error(\`State ${place} got the wrong event. Expected ${getClassName(eventType)}, got \${getClassName(event)}\`); } const frame = stopwatch && stopwatch.active ? stopwatch.start(${JSON.stringify(stopWatchId)}, ${FrameCategory.workflow}) : undefined; ${listenerCode.join('\n')} if (frame) frame.end(); state.set(${stateString}); break; } `); } return compiler.buildAsync(` while (true) { const currentState = state.get(); switch (nextState) { ${lines.join('\n')} } if (event.nextState) { nextState = event.nextState; event = event.nextStateEvent || new WorkflowEvent(); continue; } return; } `, 'scopedContext', 'state', 'nextState', 'event', 'stopwatch'); } } WorkflowDefinition.__type = ['T', () => __ΩWorkflowTransition, 'transitions', function () { return []; }, () => EventToken, 'tokens', function () { return {}; }, 'next', function () { return {}; }, 'symbol', function () { return Symbol('workflow'); }, 'name', 'places', () => __ΩWorkflowTransitions, () => ({}), 'constructor', () => __ΩExtractClassType, () => EventToken, 'getEventToken', 'from', 'to', 'label', 'addTransition', 'state', () => EventDispatcher, 'eventDispatcher', () => InjectorContext, 'injector', () => Stopwatch, 'stopwatch', () => Workflow, 'create', 'getTransitionsFrom', () => EventDispatcher, 'buildApplier', 'WorkflowDefinition', 'l1P"7%RPe$!g&KFRb!e!!o""F3#>$Pde"!gN#"3&>\'Pde"!gN("3(>)+3*>+P&2,:9e"!2-:9e"!o."2#>/"00P"2,Pe#!"fo1"7203PPe#!g&K24Pe#!g&K25&268"07PPe#!g&K28P792:P7;2<8P7=2>8Pe#!7?0@PPe#!g&K28Pe#!g&KF0APP7B2:"0C5wD']; const __ΩWorkflowTransitions = ['T', 'WorkflowTransitions', 'l5PPe%!g&KPe%!g&KFJRb!Pde"!gN#"w"y']; export function createWorkflow(name, definition, transitions = {}) { return new WorkflowDefinition(name, definition, transitions); } createWorkflow.__type = ['name', 'definition', () => __ΩWorkflowTransitions, 'transitions', () => ({}), () => WorkflowDefinition, () => __ΩWorkflowDefinitionEvents, 'createWorkflow', 'P&2!"2""o#"2$>%PP"7&"o\'"K/(']; const __ΩWorkflowState = ['T', 'get', 'v', 'set', 'WorkflowState', 'b!PPPe$!g&K1"PPe$!g&K2#$1$Mw%y']; export { __ΩWorkflowState as __ΩWorkflowState }; export class WorkflowStateSubject { constructor(value) { this.value = value; } get() { return this.value; } set(v) { this.value = v; } } WorkflowStateSubject.__type = ['T', 'value', 'constructor', 'get', 'v', 'set', () => __ΩWorkflowState, 'WorkflowStateSubject', 'b!PPe#!g&K2":"0#P"0$PPe#!g&K2%"0&5e!!o\'"x"w(']; export class WorkflowError extends CustomError { } WorkflowError.__type = [() => CustomError, 'WorkflowError', 'P7!5w"']; export class Workflow { constructor(definition, state, eventDispatcher, injector, stopwatch) { this.definition = definition; this.state = state; this.eventDispatcher = eventDispatcher; this.injector = injector; this.stopwatch = stopwatch; this.events = {}; } can(nextState) { return this.definition.getTransitionsFrom(this.state.get()).includes(nextState); } /** * @throws WorkflowError when next state is not possible to apply. */ apply(nextState, event) { let fn = this.eventDispatcher[this.definition.symbol]; if (!fn) { fn = this.eventDispatcher[this.definition.symbol] = this.definition.buildApplier(this.eventDispatcher); } return fn(this.injector, this.state, nextState, event || new WorkflowEvent(), this.stopwatch); } isDone() { return this.definition.getTransitionsFrom(this.state.get()).length === 0; } } Workflow.__type = ['T', () => Function, 'events', function () { return {}; }, () => WorkflowDefinition, 'definition', () => __ΩWorkflowState, 'state', () => EventDispatcher, 'eventDispatcher', () => InjectorContext, 'injector', () => Stopwatch, 'stopwatch', 'constructor', 'nextState', 'can', () => __ΩExtractClassType, 'event', 'apply', 'isDone', 'Workflow', 'l\'Pu"Rb!Pde"!gN#"3#<>$PPe#!7%2&:e"!o\'"2(:P7)2*;P7+2,;P7-2.8;"0/PPe#!g&K20)01P"20e"!"fo2"238$`04P)055w6']; //# sourceMappingURL=workflow.js.map