@statewalker/fsm 0.29.0 → 0.30.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +170 -13
- package/dist/index.js +267 -92
- package/package.json +8 -9
- package/src/FsmBaseClass.ts +13 -6
- package/src/FsmProcess.ts +7 -3
- package/src/FsmState.ts +7 -7
- package/src/FsmStateDescriptor.ts +8 -5
- package/src/index.ts +2 -2
- package/src/newFsmProcess.ts +46 -17
- package/src/orchestrator/ProcessConfigManager.ts +70 -0
- package/src/orchestrator/constants.ts +130 -0
- package/src/orchestrator/index.ts +6 -0
- package/src/orchestrator/isGenerator.ts +12 -0
- package/src/orchestrator/launcher.ts +72 -0
- package/src/orchestrator/startFsmProcess.ts +73 -0
- package/src/orchestrator/types.ts +34 -0
- package/src/utils/handlers.ts +9 -4
- package/src/utils/printer.ts +1 -1
- package/src/startFsmProcess copy 2.ts +0 -74
- package/src/startFsmProcess copy.ts +0 -77
- package/src/startFsmProcess.ts +0 -78
- package/src/utils/newFsmStateHandler.ts +0 -195
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import type { FsmStateConfig } from "./FsmStateConfig.js";
|
|
2
|
-
import { type Module, newFsmProcess } from "./newFsmProcess.js";
|
|
3
|
-
|
|
4
|
-
export type ModuleOrTrigger<C = unknown> =
|
|
5
|
-
| Module<C>
|
|
6
|
-
| AsyncGenerator<string, void, unknown>
|
|
7
|
-
| Generator<string, void, unknown>;
|
|
8
|
-
export function startFsmProcess<C>(
|
|
9
|
-
context: C,
|
|
10
|
-
config: FsmStateConfig,
|
|
11
|
-
load: (
|
|
12
|
-
state: string,
|
|
13
|
-
event: undefined | string,
|
|
14
|
-
) =>
|
|
15
|
-
| undefined
|
|
16
|
-
| ModuleOrTrigger<C>
|
|
17
|
-
| ModuleOrTrigger<C>[]
|
|
18
|
-
| Promise<undefined | ModuleOrTrigger<C> | ModuleOrTrigger<C>[]>,
|
|
19
|
-
startEvent = "",
|
|
20
|
-
) {
|
|
21
|
-
function validateModule(
|
|
22
|
-
module: ModuleOrTrigger<C> | undefined,
|
|
23
|
-
): Module<C> | undefined {
|
|
24
|
-
if (module === undefined) {
|
|
25
|
-
return undefined;
|
|
26
|
-
}
|
|
27
|
-
if (isGeneratorFunction<string, [C]>(module)) {
|
|
28
|
-
const generator = module as (context: C) => AsyncGenerator<string>;
|
|
29
|
-
return async (context: C) => {
|
|
30
|
-
const iterator = generator(context);
|
|
31
|
-
console.warn('INIT', module, iterator);
|
|
32
|
-
(async () => {
|
|
33
|
-
for await (const event of iterator) {
|
|
34
|
-
await dispatch(event);
|
|
35
|
-
}
|
|
36
|
-
})();
|
|
37
|
-
return () => {
|
|
38
|
-
console.warn('DONE', module, iterator);
|
|
39
|
-
iterator.return?.(void 0);
|
|
40
|
-
};
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
return module as Module<C>;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const [dispatch, terminate] = newFsmProcess(
|
|
47
|
-
context,
|
|
48
|
-
config,
|
|
49
|
-
async (state, event) => {
|
|
50
|
-
const modules = (await load(state, event)) ?? [];
|
|
51
|
-
const modulesList = Array.isArray(modules) ? modules : [modules];
|
|
52
|
-
return modulesList
|
|
53
|
-
.map(validateModule)
|
|
54
|
-
.flat()
|
|
55
|
-
.filter(Boolean) as Module<C>[];
|
|
56
|
-
},
|
|
57
|
-
);
|
|
58
|
-
dispatch(startEvent);
|
|
59
|
-
return terminate;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const AsyncGeneratorFunction = async function* () {}.constructor;
|
|
63
|
-
const GeneratorFunction = function* () {}.constructor;
|
|
64
|
-
|
|
65
|
-
function isGeneratorFunction<T, P extends unknown[] = []>(
|
|
66
|
-
value: unknown,
|
|
67
|
-
): value is
|
|
68
|
-
| ((...args: P) => AsyncGenerator<T>)
|
|
69
|
-
| ((...args: P) => Generator<T>) {
|
|
70
|
-
return (
|
|
71
|
-
value instanceof AsyncGeneratorFunction ||
|
|
72
|
-
value instanceof GeneratorFunction
|
|
73
|
-
);
|
|
74
|
-
}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { FsmProcess } from "./FsmProcess.js";
|
|
2
|
-
import type { FsmStateConfig } from "./FsmStateConfig.js";
|
|
3
|
-
import { isStateTransitionEnabled } from "./utils/transitions.js";
|
|
4
|
-
|
|
5
|
-
export type Module<C = unknown> = (
|
|
6
|
-
context: C,
|
|
7
|
-
) =>
|
|
8
|
-
| void
|
|
9
|
-
| (() => void | Promise<void>)
|
|
10
|
-
| Promise<void | (() => void | Promise<void>)>
|
|
11
|
-
| AsyncGenerator<string, void, unknown>
|
|
12
|
-
| Generator<string, void, unknown>;
|
|
13
|
-
|
|
14
|
-
export function startFsmProcess<C>(
|
|
15
|
-
context: C,
|
|
16
|
-
config: FsmStateConfig,
|
|
17
|
-
load: (
|
|
18
|
-
state: string,
|
|
19
|
-
event: undefined | string,
|
|
20
|
-
) =>
|
|
21
|
-
| undefined
|
|
22
|
-
| Module<C>
|
|
23
|
-
| Module<C>[]
|
|
24
|
-
| Promise<undefined | Module<C> | Module<C>[]>,
|
|
25
|
-
startEvent = ""
|
|
26
|
-
): () => Promise<void> {
|
|
27
|
-
let started = false;
|
|
28
|
-
let terminated = false;
|
|
29
|
-
const process = new FsmProcess(config);
|
|
30
|
-
process.onStateCreate((state) => {
|
|
31
|
-
started = true;
|
|
32
|
-
state.onEnter(async () => {
|
|
33
|
-
const modules = (await load(state.key, process.event)) ?? [];
|
|
34
|
-
for (const module of Array.isArray(modules) ? modules : [modules]) {
|
|
35
|
-
const result = await module(context);
|
|
36
|
-
if (isGenerator(result)) {
|
|
37
|
-
state.onExit(() => {
|
|
38
|
-
result.return?.(void 0);
|
|
39
|
-
});
|
|
40
|
-
(async () => {
|
|
41
|
-
for await (const event of result) {
|
|
42
|
-
await dispatch(event);
|
|
43
|
-
}
|
|
44
|
-
})();
|
|
45
|
-
} else if (typeof result === "function") {
|
|
46
|
-
state.onExit(result);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
function isGenerator(
|
|
53
|
-
value: unknown,
|
|
54
|
-
): value is
|
|
55
|
-
| Generator<string, void, unknown>
|
|
56
|
-
| AsyncGenerator<string, void, unknown> {
|
|
57
|
-
return (
|
|
58
|
-
typeof value === "object" &&
|
|
59
|
-
value !== null &&
|
|
60
|
-
"next" in value &&
|
|
61
|
-
typeof (value as Generator<string, void, unknown>).next === "function"
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
async function dispatch(event: string): Promise<boolean> {
|
|
65
|
-
return terminated ||
|
|
66
|
-
(event !== undefined &&
|
|
67
|
-
(!started || isStateTransitionEnabled(process, event)))
|
|
68
|
-
? process.dispatch(event)
|
|
69
|
-
: false;
|
|
70
|
-
}
|
|
71
|
-
async function shutdown(): Promise<void> {
|
|
72
|
-
await process.shutdown();
|
|
73
|
-
terminated = true;
|
|
74
|
-
}
|
|
75
|
-
dispatch(startEvent);
|
|
76
|
-
return shutdown;
|
|
77
|
-
}
|
package/src/startFsmProcess.ts
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { FsmProcess } from "./FsmProcess.js";
|
|
2
|
-
import type { FsmStateConfig } from "./FsmStateConfig.js";
|
|
3
|
-
import { isStateTransitionEnabled } from "./utils/transitions.js";
|
|
4
|
-
|
|
5
|
-
export type ModuleOrTrigger<C = unknown> = (
|
|
6
|
-
context: C,
|
|
7
|
-
) =>
|
|
8
|
-
| void
|
|
9
|
-
| (() => void | Promise<void>)
|
|
10
|
-
| Promise<void | (() => void | Promise<void>)>
|
|
11
|
-
| AsyncGenerator<string, void, unknown>
|
|
12
|
-
| Generator<string, void, unknown>;
|
|
13
|
-
|
|
14
|
-
export function startFsmProcess<C>(
|
|
15
|
-
context: C,
|
|
16
|
-
config: FsmStateConfig,
|
|
17
|
-
load: (
|
|
18
|
-
state: string,
|
|
19
|
-
event: undefined | string,
|
|
20
|
-
) =>
|
|
21
|
-
| undefined
|
|
22
|
-
| ModuleOrTrigger<C>
|
|
23
|
-
| ModuleOrTrigger<C>[]
|
|
24
|
-
| Promise<undefined | ModuleOrTrigger<C> | ModuleOrTrigger<C>[]>,
|
|
25
|
-
startEvent = "",
|
|
26
|
-
): () => Promise<void> {
|
|
27
|
-
let started = false;
|
|
28
|
-
let terminated = false;
|
|
29
|
-
const process = new FsmProcess(config);
|
|
30
|
-
process.onStateCreate((state) => {
|
|
31
|
-
started = true;
|
|
32
|
-
state.onEnter(async () => {
|
|
33
|
-
const modules = (await load(state.key, process.event)) ?? [];
|
|
34
|
-
for (const module of Array.isArray(modules) ? modules : [modules]) {
|
|
35
|
-
const result = await module?.(context);
|
|
36
|
-
if (isGenerator(result)) {
|
|
37
|
-
state.onExit(() => {
|
|
38
|
-
result.return?.(void 0);
|
|
39
|
-
});
|
|
40
|
-
(async () => {
|
|
41
|
-
for await (const event of result) {
|
|
42
|
-
if (terminated) {
|
|
43
|
-
break;
|
|
44
|
-
}
|
|
45
|
-
if (
|
|
46
|
-
event !== undefined &&
|
|
47
|
-
isStateTransitionEnabled(process, event)
|
|
48
|
-
) {
|
|
49
|
-
await process.dispatch(event);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
})();
|
|
53
|
-
} else if (typeof result === "function") {
|
|
54
|
-
state.onExit(result);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
function isGenerator(
|
|
61
|
-
value: unknown,
|
|
62
|
-
): value is
|
|
63
|
-
| Generator<string, void, unknown>
|
|
64
|
-
| AsyncGenerator<string, void, unknown> {
|
|
65
|
-
return (
|
|
66
|
-
typeof value === "object" &&
|
|
67
|
-
value !== null &&
|
|
68
|
-
"next" in value &&
|
|
69
|
-
typeof (value as Generator<string, void, unknown>).next === "function"
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
async function shutdown(): Promise<void> {
|
|
73
|
-
await process.shutdown();
|
|
74
|
-
terminated = true;
|
|
75
|
-
}
|
|
76
|
-
process.dispatch(startEvent);
|
|
77
|
-
return shutdown;
|
|
78
|
-
}
|
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
import { FsmState } from "../FsmState";
|
|
2
|
-
import { isStateTransitionEnabled } from "./transitions";
|
|
3
|
-
|
|
4
|
-
export function newModuleLoader<T>(baseUrl: string) {
|
|
5
|
-
const cache: Record<string, Promise<T>> = {};
|
|
6
|
-
return async (path: string) => {
|
|
7
|
-
return (cache[path] =
|
|
8
|
-
cache[path] ||
|
|
9
|
-
(async () => {
|
|
10
|
-
const suffixes = ["/", "", ".js", "/index.js"];
|
|
11
|
-
for (const suffix of suffixes) {
|
|
12
|
-
const url = new URL(path + suffix, baseUrl).pathname;
|
|
13
|
-
try {
|
|
14
|
-
return (await import(url)) as T;
|
|
15
|
-
} catch (error) {
|
|
16
|
-
continue;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
})());
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export async function newHandlerLoader(
|
|
24
|
-
baseUrl: string,
|
|
25
|
-
loader: (
|
|
26
|
-
path: string,
|
|
27
|
-
) => Promise<undefined | FsmStateModule> = newModuleLoader(baseUrl),
|
|
28
|
-
): Promise<(state: FsmState) => Promise<FsmStateModule[]>> {
|
|
29
|
-
return async (state: FsmState) => {
|
|
30
|
-
const stack: string[] = [];
|
|
31
|
-
for (let s: FsmState | undefined = state; s; s = s.parent) {
|
|
32
|
-
stack.unshift(s.key);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const handlers: FsmStateModule[] = [];
|
|
36
|
-
let topName: undefined | string;
|
|
37
|
-
while (stack.length > 0) {
|
|
38
|
-
const lastSegment = stack.pop();
|
|
39
|
-
if (!topName) {
|
|
40
|
-
topName = lastSegment;
|
|
41
|
-
if (topName === undefined) break;
|
|
42
|
-
}
|
|
43
|
-
const path = [...stack, topName].join("/");
|
|
44
|
-
const handler = await loader(path);
|
|
45
|
-
handler && handlers.push(handler);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return handlers;
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/*
|
|
53
|
-
* StateContext:
|
|
54
|
-
- Each context is a function or a class
|
|
55
|
-
- It recieves the stack of state objects
|
|
56
|
-
- The returned value is used to pass to the handler function
|
|
57
|
-
* Trigger function
|
|
58
|
-
- recieves the context instance
|
|
59
|
-
- notifies about changes using the given callback
|
|
60
|
-
* Handler
|
|
61
|
-
- recieves the context
|
|
62
|
-
- starts activities on call
|
|
63
|
-
- optionally returns a cleanup function
|
|
64
|
-
- could return a promise with optional cleanup function; the state is not
|
|
65
|
-
activated until all handlers return the control
|
|
66
|
-
*/
|
|
67
|
-
export type FsmStateModule<
|
|
68
|
-
FsmStateContext = Record<string, unknown>,
|
|
69
|
-
FsmStateStore = Record<string, unknown>,
|
|
70
|
-
> = {
|
|
71
|
-
context?:
|
|
72
|
-
| ((...stack: FsmStateStore[]) => FsmStateContext)
|
|
73
|
-
| (new (...stack: FsmStateStore[]) => FsmStateContext);
|
|
74
|
-
// trigger?: (context: FsmStateContext) => AsyncGenerator<string>;
|
|
75
|
-
trigger?: (context: FsmStateContext) => AsyncIterator<string>;
|
|
76
|
-
handler?: (
|
|
77
|
-
context: FsmStateContext,
|
|
78
|
-
) => void | (() => void) | Promise<void | (() => void)>;
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
export function newModuleContext<
|
|
82
|
-
C = Record<string, unknown>,
|
|
83
|
-
S = Record<string, unknown>,
|
|
84
|
-
>(module: FsmStateModule<C, S>, ...stack: S[]): C {
|
|
85
|
-
if (isClass<S[], C>(module.context)) {
|
|
86
|
-
return new module.context(...stack) as unknown as C;
|
|
87
|
-
} else if (isFunction<S[], C>(module.context)) {
|
|
88
|
-
return module.context(...stack);
|
|
89
|
-
} else {
|
|
90
|
-
return stack[0] as unknown as C;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
function isFunction<T extends Array<unknown>, R = unknown>(
|
|
94
|
-
value: unknown,
|
|
95
|
-
): value is (...args: T) => R {
|
|
96
|
-
return typeof value === "function";
|
|
97
|
-
}
|
|
98
|
-
function isClass<T extends Array<unknown>, R = unknown>(
|
|
99
|
-
value: unknown,
|
|
100
|
-
): value is new (...args: T) => R {
|
|
101
|
-
if (!isFunction(value)) return false;
|
|
102
|
-
return value?.prototype?.constructor?.toString().match(/^class/);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export function newStateHandlers<
|
|
106
|
-
FsmStateContext = Record<string, unknown>,
|
|
107
|
-
FsmStateStore = Record<string, unknown>,
|
|
108
|
-
>(
|
|
109
|
-
loader: (
|
|
110
|
-
state: FsmState,
|
|
111
|
-
) => Promise<FsmStateModule<FsmStateContext, FsmStateStore>[]>,
|
|
112
|
-
newContext: (state: FsmState, ...stack: FsmStateStore[]) => FsmStateStore = (
|
|
113
|
-
state,
|
|
114
|
-
) =>
|
|
115
|
-
({
|
|
116
|
-
state: state.key,
|
|
117
|
-
event: state.process.event,
|
|
118
|
-
}) as unknown as FsmStateStore,
|
|
119
|
-
contextCache: Map<unknown, FsmStateContext> = new Map<
|
|
120
|
-
unknown,
|
|
121
|
-
FsmStateContext
|
|
122
|
-
>(),
|
|
123
|
-
) {
|
|
124
|
-
const triggers = new Map<unknown, () => void>();
|
|
125
|
-
return (state: FsmState) => {
|
|
126
|
-
const stack: FsmStateStore[] = [];
|
|
127
|
-
for (let s: FsmState | undefined = state.parent; !!s; s = s.parent) {
|
|
128
|
-
const store = s.getData<FsmStateStore>("context");
|
|
129
|
-
store && stack.push(store);
|
|
130
|
-
}
|
|
131
|
-
const store = newContext(state, ...stack);
|
|
132
|
-
stack.unshift(store);
|
|
133
|
-
state.setData("context", store);
|
|
134
|
-
|
|
135
|
-
let registrations: (void | (() => void) | Promise<void | (() => void)>)[] =
|
|
136
|
-
[];
|
|
137
|
-
state.onExit(async () => {
|
|
138
|
-
for (const r of registrations) {
|
|
139
|
-
const registration = await r;
|
|
140
|
-
registration?.();
|
|
141
|
-
}
|
|
142
|
-
});
|
|
143
|
-
state.onEnter(async () => {
|
|
144
|
-
const modules = await loader(state);
|
|
145
|
-
if (!modules?.length) return;
|
|
146
|
-
for (const module of modules) {
|
|
147
|
-
const contextKey: unknown = module.context;
|
|
148
|
-
// Create state context or get it from the local cache
|
|
149
|
-
let stateContext = contextCache.get(contextKey);
|
|
150
|
-
if (!stateContext) {
|
|
151
|
-
stateContext = newModuleContext(module, ...stack);
|
|
152
|
-
contextCache.set(contextKey, stateContext);
|
|
153
|
-
registrations.push(() => {
|
|
154
|
-
contextCache.delete(contextKey);
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Initialize triggers
|
|
159
|
-
if (module.trigger && !triggers.has(module.trigger)) {
|
|
160
|
-
const process = state.process;
|
|
161
|
-
const iterator = module.trigger(stateContext);
|
|
162
|
-
let finalize: undefined | (() => void);
|
|
163
|
-
const end = new Promise<{ done: boolean; value?: string }>(
|
|
164
|
-
(r) => (finalize = () => r({ done: true })),
|
|
165
|
-
);
|
|
166
|
-
registrations.push(() => triggers.delete(module.trigger));
|
|
167
|
-
registrations.push(finalize);
|
|
168
|
-
registrations.push(() => iterator?.return?.(void 0));
|
|
169
|
-
(async () => {
|
|
170
|
-
try {
|
|
171
|
-
while (true) {
|
|
172
|
-
const { done, value: event } = await Promise.race([
|
|
173
|
-
iterator.next(),
|
|
174
|
-
end,
|
|
175
|
-
]);
|
|
176
|
-
if (done) break;
|
|
177
|
-
if (event && isStateTransitionEnabled(process, event)) {
|
|
178
|
-
process.dispatch(event);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
} finally {
|
|
182
|
-
finalize?.();
|
|
183
|
-
}
|
|
184
|
-
})();
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Run actions associated with this event
|
|
188
|
-
if (module.handler) {
|
|
189
|
-
const cleanup = module.handler(stateContext);
|
|
190
|
-
registrations.push(cleanup);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
});
|
|
194
|
-
};
|
|
195
|
-
}
|