@statewalker/fsm 0.30.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@statewalker/fsm",
3
- "version": "0.30.0",
3
+ "version": "0.30.1",
4
4
  "description": "HFSM Implementation",
5
5
  "keywords": [],
6
6
  "homepage": "https://github.com/statewalker/statewalker-fsm",
@@ -1,199 +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 = ["/", "", ".ts", "/index.ts"];
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 (
74
- ...stack: FsmStateStore[]
75
- ) => FsmStateContext);
76
- // trigger?: (context: FsmStateContext) => AsyncGenerator<string>;
77
- trigger?: (context: FsmStateContext) => AsyncIterator<string>;
78
- handler?: (
79
- context: FsmStateContext,
80
- ) => void | (() => void) | Promise<void | (() => void)>;
81
- };
82
-
83
- export function newModuleContext<
84
- C = Record<string, unknown>,
85
- S = Record<string, unknown>,
86
- >(module: FsmStateModule<C, S>, ...stack: S[]): C {
87
- if (isClass<S[], C>(module.context)) {
88
- return new module.context(...stack) as unknown as C;
89
- } else if (isFunction<S[], C>(module.context)) {
90
- return module.context(...stack);
91
- } else {
92
- return stack[0] as unknown as C;
93
- }
94
- }
95
- function isFunction<T extends Array<unknown>, R = unknown>(
96
- value: unknown,
97
- ): value is (...args: T) => R {
98
- return typeof value === "function";
99
- }
100
- function isClass<T extends Array<unknown>, R = unknown>(
101
- value: unknown,
102
- ): value is new (
103
- ...args: T
104
- ) => R {
105
- if (!isFunction(value)) return false;
106
- return value?.prototype?.constructor?.toString().match(/^class/);
107
- }
108
-
109
- export function newStateHandlers<
110
- FsmStateContext = Record<string, unknown>,
111
- FsmStateStore = Record<string, unknown>,
112
- >(
113
- loader: (
114
- state: FsmState,
115
- ) => Promise<FsmStateModule<FsmStateContext, FsmStateStore>[]>,
116
- newContext: (state: FsmState, ...stack: FsmStateStore[]) => FsmStateStore = (
117
- state,
118
- ) =>
119
- ({
120
- state: state.key,
121
- event: state.process.event,
122
- }) as unknown as FsmStateStore,
123
- contextCache: Map<unknown, FsmStateContext> = new Map<
124
- unknown,
125
- FsmStateContext
126
- >(),
127
- ) {
128
- const triggers = new Map<unknown, () => void>();
129
- return (state: FsmState) => {
130
- const stack: FsmStateStore[] = [];
131
- for (let s: FsmState | undefined = state.parent; !!s; s = s.parent) {
132
- const store = s.getData<FsmStateStore>("context");
133
- store && stack.push(store);
134
- }
135
- const store = newContext(state, ...stack);
136
- stack.unshift(store);
137
- state.setData("context", store);
138
-
139
- let registrations: (void | (() => void) | Promise<void | (() => void)>)[] =
140
- [];
141
- state.onExit(async () => {
142
- for (const r of registrations) {
143
- const registration = await r;
144
- registration?.();
145
- }
146
- });
147
- state.onEnter(async () => {
148
- const modules = await loader(state);
149
- if (!modules?.length) return;
150
- for (const module of modules) {
151
- const contextKey: unknown = module.context;
152
- // Create state context or get it from the local cache
153
- let stateContext = contextCache.get(contextKey);
154
- if (!stateContext) {
155
- stateContext = newModuleContext(module, ...stack);
156
- contextCache.set(contextKey, stateContext);
157
- registrations.push(() => {
158
- contextCache.delete(contextKey);
159
- });
160
- }
161
-
162
- // Initialize triggers
163
- if (module.trigger && !triggers.has(module.trigger)) {
164
- const process = state.process;
165
- const iterator = module.trigger(stateContext);
166
- let finalize: undefined | (() => void);
167
- const end = new Promise<{ done: boolean; value?: string }>(
168
- (r) => (finalize = () => r({ done: true })),
169
- );
170
- registrations.push(() => triggers.delete(module.trigger));
171
- registrations.push(finalize);
172
- registrations.push(() => iterator?.return?.(void 0));
173
- (async () => {
174
- try {
175
- while (true) {
176
- const { done, value: event } = await Promise.race([
177
- iterator.next(),
178
- end,
179
- ]);
180
- if (done) break;
181
- if (event && isStateTransitionEnabled(process, event)) {
182
- process.dispatch(event);
183
- }
184
- }
185
- } finally {
186
- finalize?.();
187
- }
188
- })();
189
- }
190
-
191
- // Run actions associated with this event
192
- if (module.handler) {
193
- const cleanup = module.handler(stateContext);
194
- registrations.push(cleanup);
195
- }
196
- }
197
- });
198
- };
199
- }