UNPKG

@deepkit/workflow

Version:

Deepkit workflow engine / finite state machine

243 lines (240 loc) 13.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Workflow = exports.WorkflowError = exports.WorkflowStateSubject = exports.__ΩWorkflowState = exports.WorkflowDefinition = exports.__ΩWorkflowDefinitionEvents = exports.__ΩWorkflowNextEvent = exports.__ΩWorkflowPlaces = exports.WorkflowEvent = void 0; exports.createWorkflow = createWorkflow; 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*/ const { __ΩExtractClassType } = require('@deepkit/core'); /*@ts-ignore*/ const { __ΩClassType } = require('@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. */ const core_1 = require("@deepkit/core"); const event_1 = require("@deepkit/event"); const injector_1 = require("@deepkit/injector"); const stopwatch_1 = require("@deepkit/stopwatch"); const type_1 = require("@deepkit/type"); const __ΩWorkflowTransition = ['T', 'from', 'to', 'label', 'WorkflowTransition', 'b!PPe#!g&K4"Pe#!g&K4#&4$8Mw%y']; class WorkflowEvent extends event_1.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; } } exports.WorkflowEvent = WorkflowEvent; WorkflowEvent.__type = [() => event_1.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']; exports.__ΩWorkflowPlaces = __ΩWorkflowPlaces; const __ΩWorkflowNextEvent = ['T', 'nextState', () => __ΩExtractClassType, 'event', 'next', 'WorkflowNextEvent', 'b!PPe#!g&K4"8P"2"e#!"fo#"2$8$1%Mw&y']; exports.__ΩWorkflowNextEvent = __ΩWorkflowNextEvent; const __ΩWorkflowDefinitionEvents = ['T', () => event_1.BaseEvent, () => __ΩOmit, () => __ΩExtractClassType, "next", "nextState", () => __ΩWorkflowNextEvent, () => event_1.EventToken, 'on', () => __ΩCapitalize, 'WorkflowDefinitionEvents', 'lQPPPP7"e&!e%!fo$"P.%.&Jo##e&!o\'"K7(P.)e$!o*"SGRb!PdPe#!g&Kt#!w+y']; exports.__ΩWorkflowDefinitionEvents = __ΩWorkflowDefinitionEvents; 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 event_1.EventToken(name + '.' + placeName, this.places[placeName]); this.tokens[placeName] = token; this['on' + (0, core_1.capitalize)(placeName)] = token; } for (const [i, value] of Object.entries(transitions)) { if ((0, core_1.isArray)(value)) { for (const v of value) this.addTransition(i, v); } else if (value !== undefined) { this.addTransition(i, value); } } (0, core_1.toFastProperties)(this.tokens); (0, core_1.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 core_1.CompilerContext(); compiler.context.set('WorkflowError', WorkflowError); compiler.context.set('WorkflowEvent', WorkflowEvent); compiler.context.set('getClassName', core_1.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 ((0, event_1.isEventListenerContainerEntryCallback)(listener)) { const injector = listener.module ? eventDispatcher.injector.getInjector(listener.module) : eventDispatcher.injector.getRootInjector(); const fn = (0, injector_1.injectedFunction)(listener.fn, injector, 1); const fnVar = compiler.reserveVariable('fn', fn); listenerCode.push(` if (!event.immediatePropagationStopped) { await ${fnVar}(scopedContext.scope, event); } `); } else if ((0, event_1.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 = type_1.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 = (0, injector_1.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(` //${(0, core_1.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 ${(0, core_1.getClassName)(eventType)}, got \${getClassName(event)}\`); } const frame = stopwatch && stopwatch.active ? stopwatch.start(${JSON.stringify(stopWatchId)}, ${stopwatch_1.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'); } } exports.WorkflowDefinition = WorkflowDefinition; WorkflowDefinition.__type = ['T', () => __ΩWorkflowTransition, 'transitions', function () { return []; }, () => event_1.EventToken, 'tokens', function () { return {}; }, 'next', function () { return {}; }, 'symbol', function () { return Symbol('workflow'); }, 'name', 'places', () => __ΩWorkflowTransitions, () => ({}), 'constructor', () => __ΩExtractClassType, () => event_1.EventToken, 'getEventToken', 'from', 'to', 'label', 'addTransition', 'state', () => event_1.EventDispatcher, 'eventDispatcher', () => injector_1.InjectorContext, 'injector', () => stopwatch_1.Stopwatch, 'stopwatch', () => Workflow, 'create', 'getTransitionsFrom', () => event_1.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']; 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']; exports.__ΩWorkflowState = __ΩWorkflowState; class WorkflowStateSubject { constructor(value) { this.value = value; } get() { return this.value; } set(v) { this.value = v; } } exports.WorkflowStateSubject = WorkflowStateSubject; 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(']; class WorkflowError extends core_1.CustomError { } exports.WorkflowError = WorkflowError; WorkflowError.__type = [() => core_1.CustomError, 'WorkflowError', 'P7!5w"']; 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; } } exports.Workflow = Workflow; Workflow.__type = ['T', () => Function, 'events', function () { return {}; }, () => WorkflowDefinition, 'definition', () => __ΩWorkflowState, 'state', () => event_1.EventDispatcher, 'eventDispatcher', () => injector_1.InjectorContext, 'injector', () => stopwatch_1.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