@deepkit/workflow
Version:
Deepkit workflow engine / finite state machine
234 lines (231 loc) • 12.7 kB
JavaScript
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