@alwatr/fsm 9.25.0 → 9.28.0
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/README.md +372 -212
- package/dist/facade.d.ts +6 -7
- package/dist/facade.d.ts.map +1 -1
- package/dist/fsm-service.d.ts +26 -6
- package/dist/fsm-service.d.ts.map +1 -1
- package/dist/main.js +3 -3
- package/dist/main.js.map +4 -4
- package/dist/type.d.ts +40 -16
- package/dist/type.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/facade.ts +10 -9
- package/src/fsm-service.ts +141 -54
- package/src/type.ts +45 -27
package/src/fsm-service.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {SingleOrArray} from '@alwatr/type-helper';
|
|
2
2
|
import {createLogger, type AlwatrLogger} from '@alwatr/logger';
|
|
3
3
|
import {
|
|
4
4
|
createEventSignal,
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
type IReadonlySignal,
|
|
9
9
|
} from '@alwatr/signal';
|
|
10
10
|
|
|
11
|
-
import type {StateMachineConfig, MachineState, MachineEvent, Transition, Effect, Assigner} from './type.js';
|
|
11
|
+
import type {StateMachineConfig, MachineState, MachineEvent, Transition, Effect, Assigner, Actor} from './type.js';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* A generic, encapsulated service that creates, runs, and manages a finite state machine.
|
|
@@ -19,15 +19,22 @@ import type {StateMachineConfig, MachineState, MachineEvent, Transition, Effect,
|
|
|
19
19
|
* @template TEvent The union type of all possible events.
|
|
20
20
|
* @template TContext The type of the machine's context (extended state).
|
|
21
21
|
*/
|
|
22
|
-
export class FsmService<
|
|
22
|
+
export class FsmService<
|
|
23
|
+
TState extends string,
|
|
24
|
+
TEvent extends MachineEvent,
|
|
25
|
+
TContext extends Record<string, unknown> = Record<string, never>,
|
|
26
|
+
> {
|
|
23
27
|
protected readonly logger_: AlwatrLogger;
|
|
24
28
|
|
|
25
|
-
/** The event signal for sending events to the FSM. */
|
|
26
|
-
|
|
29
|
+
/** The private event signal for sending events to the FSM. */
|
|
30
|
+
private readonly eventSignal__: EventSignal<TEvent>;
|
|
27
31
|
|
|
28
32
|
/** The public, read-only state signal. Subscribe to react to state changes. */
|
|
29
33
|
public readonly stateSignal: IReadonlySignal<MachineState<TState, TContext>>;
|
|
30
34
|
|
|
35
|
+
/** The set of cleanup functions for currently active state actors. */
|
|
36
|
+
private readonly activeActorCleanups__ = new Set<() => void>();
|
|
37
|
+
|
|
31
38
|
constructor(
|
|
32
39
|
protected readonly config_: StateMachineConfig<TState, TEvent, TContext>,
|
|
33
40
|
private readonly stateSignal__:
|
|
@@ -38,19 +45,32 @@ export class FsmService<TState extends string, TEvent extends MachineEvent, TCon
|
|
|
38
45
|
this.logger_.logMethodArgs?.('constructor', config_);
|
|
39
46
|
|
|
40
47
|
this.stateSignal = this.stateSignal__.asReadonly();
|
|
41
|
-
this.
|
|
48
|
+
this.eventSignal__ = createEventSignal<TEvent>({
|
|
42
49
|
name: `fsm-event-${this.config_.name}`,
|
|
43
50
|
});
|
|
44
|
-
this.
|
|
51
|
+
this.eventSignal__.subscribe((event) => this.processTransition__(event), {receivePrevious: false});
|
|
52
|
+
|
|
53
|
+
// Execute initial state entry effects and actors.
|
|
54
|
+
this.start_();
|
|
45
55
|
}
|
|
46
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Dispatches an event to the FSM mailbox.
|
|
59
|
+
*
|
|
60
|
+
* @param event The event to process.
|
|
61
|
+
*/
|
|
62
|
+
public readonly dispatch = (event: TEvent): void => {
|
|
63
|
+
this.logger_.logMethodArgs?.('dispatch', {event});
|
|
64
|
+
this.eventSignal__.dispatch(event);
|
|
65
|
+
};
|
|
66
|
+
|
|
47
67
|
/**
|
|
48
68
|
* The core FSM logic that processes a single event and transitions the machine to a new state.
|
|
49
69
|
* This process is atomic and follows the Run-to-Completion (RTC) model.
|
|
50
70
|
*
|
|
51
71
|
* @param event The event to process.
|
|
52
72
|
*/
|
|
53
|
-
private
|
|
73
|
+
private processTransition__(event: TEvent): void {
|
|
54
74
|
const currentState = this.stateSignal__.get();
|
|
55
75
|
this.logger_.logMethodArgs?.('processTransition__', {state: currentState.name, event});
|
|
56
76
|
|
|
@@ -65,10 +85,12 @@ export class FsmService<TState extends string, TEvent extends MachineEvent, TCon
|
|
|
65
85
|
}
|
|
66
86
|
|
|
67
87
|
const targetStateName = transition.target ?? currentState.name;
|
|
88
|
+
const isExternalTransition = transition.target !== undefined;
|
|
68
89
|
|
|
69
|
-
// 1. Execute exit effects of the current state if
|
|
70
|
-
if (
|
|
71
|
-
|
|
90
|
+
// 1. Execute exit effects and cleanup actors of the current state if it's an external transition.
|
|
91
|
+
if (isExternalTransition) {
|
|
92
|
+
this.executeEffects__(event, currentState.context, this.config_.states[currentState.name]?.exit);
|
|
93
|
+
this.cleanupActors__();
|
|
72
94
|
}
|
|
73
95
|
|
|
74
96
|
// 2. Apply assigners to compute the next context. This is a pure function.
|
|
@@ -83,14 +105,15 @@ export class FsmService<TState extends string, TEvent extends MachineEvent, TCon
|
|
|
83
105
|
// 4. Set the new state, notifying all subscribers.
|
|
84
106
|
this.stateSignal__.set(nextState);
|
|
85
107
|
|
|
86
|
-
// 5. Execute entry effects of the new state if
|
|
87
|
-
if (
|
|
88
|
-
|
|
108
|
+
// 5. Execute entry effects and spawn actors of the new state if it's an external transition.
|
|
109
|
+
if (isExternalTransition) {
|
|
110
|
+
this.executeEffects__(event, nextState.context, this.config_.states[nextState.name]?.entry);
|
|
111
|
+
this.spawnActors__(event, nextState.context, this.config_.states[nextState.name]?.actors);
|
|
89
112
|
}
|
|
90
113
|
}
|
|
91
114
|
|
|
92
115
|
/**
|
|
93
|
-
* Finds the first valid transition for the given event and context by evaluating
|
|
116
|
+
* Finds the first valid transition for the given event and context by evaluating guards.
|
|
94
117
|
*
|
|
95
118
|
* @param event The triggering event.
|
|
96
119
|
* @param context The current machine context.
|
|
@@ -110,32 +133,32 @@ export class FsmService<TState extends string, TEvent extends MachineEvent, TCon
|
|
|
110
133
|
|
|
111
134
|
if (!transitions) return undefined;
|
|
112
135
|
|
|
113
|
-
// Normalize to an array to handle both single and multiple transitions uniformly.
|
|
114
136
|
const transitionsArray = Array.isArray(transitions) ? transitions : [transitions];
|
|
115
137
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
138
|
+
for (let index = 0; index < transitionsArray.length; index++) {
|
|
139
|
+
const transition = transitionsArray[index];
|
|
140
|
+
if (!transition.guard) return transition;
|
|
119
141
|
try {
|
|
120
|
-
const
|
|
121
|
-
this.logger_.logStep?.('findTransition__', '
|
|
142
|
+
const guardMet = transition.guard({event, context});
|
|
143
|
+
this.logger_.logStep?.('findTransition__', 'check_guard', {
|
|
122
144
|
state: currentStateName,
|
|
123
145
|
eventType: event.type,
|
|
124
146
|
transitionIndex: index,
|
|
125
|
-
|
|
126
|
-
result:
|
|
147
|
+
guard: transition.guard.name || 'anonymous',
|
|
148
|
+
result: guardMet,
|
|
127
149
|
});
|
|
128
|
-
return
|
|
150
|
+
if (guardMet) return transition;
|
|
129
151
|
} catch (error) {
|
|
130
|
-
this.logger_.error('findTransition__', '
|
|
152
|
+
this.logger_.error('findTransition__', 'guard_failed', error, {
|
|
131
153
|
state: currentStateName,
|
|
132
154
|
eventType: event.type,
|
|
133
155
|
transitionIndex: index,
|
|
134
|
-
|
|
156
|
+
guard: transition.guard.name || 'anonymous',
|
|
135
157
|
});
|
|
136
|
-
return false; // Treat a failing condition as not met.
|
|
137
158
|
}
|
|
138
|
-
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return undefined;
|
|
139
162
|
}
|
|
140
163
|
|
|
141
164
|
/**
|
|
@@ -146,30 +169,31 @@ export class FsmService<TState extends string, TEvent extends MachineEvent, TCon
|
|
|
146
169
|
* @param context The context at the time of execution.
|
|
147
170
|
* @param effects A single effect or an array of effects.
|
|
148
171
|
*/
|
|
149
|
-
private
|
|
172
|
+
private executeEffects__(
|
|
150
173
|
event: TEvent,
|
|
151
174
|
context: Readonly<TContext>,
|
|
152
175
|
effects?: SingleOrArray<Effect<TEvent, TContext>>,
|
|
153
|
-
):
|
|
176
|
+
): void {
|
|
154
177
|
if (!effects) {
|
|
155
178
|
this.logger_.logMethodArgs?.('executeEffects__//skipped', {count: 0});
|
|
156
179
|
return;
|
|
157
180
|
}
|
|
158
|
-
const effectsArray
|
|
181
|
+
const effectsArray = Array.isArray(effects) ? effects : [effects];
|
|
159
182
|
|
|
160
183
|
this.logger_.logMethodArgs?.('executeEffects__', {count: effectsArray.length});
|
|
161
184
|
|
|
162
185
|
for (const effect of effectsArray) {
|
|
163
186
|
try {
|
|
164
|
-
const result =
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
187
|
+
const result = effect({event, context});
|
|
188
|
+
if (result instanceof Promise) {
|
|
189
|
+
result.catch((error) => {
|
|
190
|
+
this.logger_.error('executeEffects__', 'effect_failed', error, {
|
|
191
|
+
effect: effect.name || 'anonymous',
|
|
192
|
+
state: this.stateSignal__.get().name,
|
|
193
|
+
event,
|
|
194
|
+
context,
|
|
195
|
+
});
|
|
171
196
|
});
|
|
172
|
-
this.eventSignal.dispatch(result);
|
|
173
197
|
}
|
|
174
198
|
} catch (error) {
|
|
175
199
|
this.logger_.error('executeEffects__', 'effect_failed', error, {
|
|
@@ -194,7 +218,7 @@ export class FsmService<TState extends string, TEvent extends MachineEvent, TCon
|
|
|
194
218
|
*/
|
|
195
219
|
private applyAssigners__(
|
|
196
220
|
event: TEvent,
|
|
197
|
-
context:
|
|
221
|
+
context: TContext,
|
|
198
222
|
assigners?: SingleOrArray<Assigner<TEvent, TContext>>,
|
|
199
223
|
): TContext {
|
|
200
224
|
if (!assigners) {
|
|
@@ -202,27 +226,24 @@ export class FsmService<TState extends string, TEvent extends MachineEvent, TCon
|
|
|
202
226
|
return context;
|
|
203
227
|
}
|
|
204
228
|
|
|
205
|
-
const assignersArray
|
|
229
|
+
const assignersArray = Array.isArray(assigners) ? assigners : [assigners];
|
|
206
230
|
|
|
207
231
|
this.logger_.logMethodArgs?.('applyAssigners__', {count: assignersArray.length});
|
|
208
232
|
|
|
209
233
|
try {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
const partialUpdate = assigner(event, accContext);
|
|
234
|
+
let accContext = context;
|
|
235
|
+
for (const assigner of assignersArray) {
|
|
236
|
+
const nextContext = assigner({event, context: accContext});
|
|
214
237
|
this.logger_.logMethodFull?.(
|
|
215
238
|
`event.${event.type}.action.${assigner.name || 'anonymous'}`,
|
|
216
239
|
{event, accContext},
|
|
217
|
-
|
|
240
|
+
nextContext,
|
|
218
241
|
);
|
|
219
|
-
if (
|
|
220
|
-
|
|
221
|
-
return {...accContext, ...partialUpdate};
|
|
242
|
+
if (nextContext !== undefined && nextContext !== null) {
|
|
243
|
+
accContext = nextContext;
|
|
222
244
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
}, context);
|
|
245
|
+
}
|
|
246
|
+
return accContext;
|
|
226
247
|
} catch (error) {
|
|
227
248
|
this.logger_.error('applyAssigners__', 'assigner_failed_atomic', error, {
|
|
228
249
|
event,
|
|
@@ -233,13 +254,79 @@ export class FsmService<TState extends string, TEvent extends MachineEvent, TCon
|
|
|
233
254
|
}
|
|
234
255
|
}
|
|
235
256
|
|
|
257
|
+
/**
|
|
258
|
+
* Starts the FSM by executing the entry effects and spawning the actors
|
|
259
|
+
* of the initial/current state.
|
|
260
|
+
*/
|
|
261
|
+
protected start_(): void {
|
|
262
|
+
if (this.eventSignal__.isDestroyed) return;
|
|
263
|
+
this.logger_.logMethod?.('start_');
|
|
264
|
+
const currentState = this.stateSignal__.get();
|
|
265
|
+
const initEvent = {type: '__init__'} as unknown as TEvent;
|
|
266
|
+
this.executeEffects__(initEvent, currentState.context, this.config_.states[currentState.name]?.entry);
|
|
267
|
+
this.spawnActors__(initEvent, currentState.context, this.config_.states[currentState.name]?.actors);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Spawns all configured actors for the entered state.
|
|
272
|
+
*/
|
|
273
|
+
private spawnActors__(
|
|
274
|
+
event: TEvent,
|
|
275
|
+
context: Readonly<TContext>,
|
|
276
|
+
actors?: SingleOrArray<Actor<TEvent, TContext>>,
|
|
277
|
+
): void {
|
|
278
|
+
if (!actors) {
|
|
279
|
+
this.logger_.logMethodArgs?.('spawnActors__//skipped', {count: 0});
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
const actorsArray = Array.isArray(actors) ? actors : [actors];
|
|
283
|
+
|
|
284
|
+
this.logger_.logMethodArgs?.('spawnActors__', {count: actorsArray.length});
|
|
285
|
+
|
|
286
|
+
for (const actor of actorsArray) {
|
|
287
|
+
try {
|
|
288
|
+
const cleanup = actor({
|
|
289
|
+
event,
|
|
290
|
+
context,
|
|
291
|
+
dispatch: this.dispatch,
|
|
292
|
+
});
|
|
293
|
+
if (typeof cleanup === 'function') {
|
|
294
|
+
this.activeActorCleanups__.add(cleanup);
|
|
295
|
+
}
|
|
296
|
+
} catch (error) {
|
|
297
|
+
this.logger_.error('spawnActors__', 'actor_failed', error, {
|
|
298
|
+
actor: actor.name || 'anonymous',
|
|
299
|
+
state: this.stateSignal__.get().name,
|
|
300
|
+
event,
|
|
301
|
+
context,
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Cleans up (destroys) all currently active state actors.
|
|
309
|
+
*/
|
|
310
|
+
private cleanupActors__(): void {
|
|
311
|
+
this.logger_.logMethodArgs?.('cleanupActors__', {count: this.activeActorCleanups__.size});
|
|
312
|
+
for (const cleanup of this.activeActorCleanups__) {
|
|
313
|
+
try {
|
|
314
|
+
cleanup();
|
|
315
|
+
} catch (error) {
|
|
316
|
+
this.logger_.error('cleanupActors__', 'cleanup_failed', error);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
this.activeActorCleanups__.clear();
|
|
320
|
+
}
|
|
321
|
+
|
|
236
322
|
/**
|
|
237
323
|
* Destroys the service, cleaning up all internal signals and subscriptions
|
|
238
324
|
* to prevent memory leaks.
|
|
239
325
|
*/
|
|
240
326
|
public destroy(): void {
|
|
241
327
|
this.logger_.logMethod?.('destroy');
|
|
242
|
-
this.
|
|
243
|
-
this.
|
|
328
|
+
this.cleanupActors__();
|
|
329
|
+
this.eventSignal__.destroy();
|
|
330
|
+
this.stateSignal__.destroy();
|
|
244
331
|
}
|
|
245
332
|
}
|
package/src/type.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {Awaitable,
|
|
1
|
+
import type {Awaitable, SingleOrArray} from '@alwatr/type-helper';
|
|
2
2
|
import type {SignalConfig} from '@alwatr/signal';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -6,9 +6,9 @@ import type {SignalConfig} from '@alwatr/signal';
|
|
|
6
6
|
* and its extended state (context).
|
|
7
7
|
*
|
|
8
8
|
* @template TState The union type of the finite state values.
|
|
9
|
-
* @template TContext The type of the context
|
|
9
|
+
* @template TContext The type of the machine's context (extended state).
|
|
10
10
|
*/
|
|
11
|
-
export type MachineState<TState extends string, TContext extends
|
|
11
|
+
export type MachineState<TState extends string, TContext extends Record<string, unknown>> = {
|
|
12
12
|
/** The current finite state value. */
|
|
13
13
|
readonly name: TState;
|
|
14
14
|
/** The context (extended state) of the machine, holding quantitative data. */
|
|
@@ -30,31 +30,29 @@ export interface MachineEvent<TEventType extends string = string> {
|
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* Defines an assigner (synchronous action) that updates the context during transitions.
|
|
33
|
-
* It
|
|
33
|
+
* It returns the complete new context object or void/undefined if no changes are made.
|
|
34
34
|
*
|
|
35
35
|
* @template TContext The type of the machine's context.
|
|
36
36
|
* @template TEvent The type of the event that triggered this assigner.
|
|
37
|
-
* @returns
|
|
37
|
+
* @returns The complete next context object or void.
|
|
38
38
|
*/
|
|
39
|
-
export type Assigner<TEvent extends MachineEvent, TContext extends
|
|
40
|
-
event: Readonly<TEvent
|
|
41
|
-
context:
|
|
42
|
-
|
|
43
|
-
) => Partial<TContext> | void;
|
|
39
|
+
export type Assigner<TEvent extends MachineEvent, TContext extends Record<string, unknown>> = (params: {
|
|
40
|
+
readonly event: Readonly<TEvent>;
|
|
41
|
+
readonly context: TContext;
|
|
42
|
+
}) => TContext | void;
|
|
44
43
|
|
|
45
44
|
/**
|
|
46
|
-
* Defines an effect (
|
|
47
|
-
* It can interact with the outside world
|
|
45
|
+
* Defines an effect (fire-and-forget side-effect action) executed on state entry/exit.
|
|
46
|
+
* It can interact with the outside world, but does not return new events to trigger transitions.
|
|
48
47
|
*
|
|
49
48
|
* @template TContext The type of the machine's context.
|
|
50
49
|
* @template TEvent The type of the event that triggered this effect.
|
|
51
50
|
* @returns void or a Promise<void>.
|
|
52
51
|
*/
|
|
53
|
-
export type Effect<TEvent extends MachineEvent, TContext extends
|
|
54
|
-
event: Readonly<TEvent
|
|
55
|
-
context: Readonly<TContext
|
|
56
|
-
|
|
57
|
-
) => Awaitable<TEvent | void>;
|
|
52
|
+
export type Effect<TEvent extends MachineEvent, TContext extends Record<string, unknown>> = (params: {
|
|
53
|
+
readonly event: Readonly<TEvent>;
|
|
54
|
+
readonly context: Readonly<TContext>;
|
|
55
|
+
}) => Awaitable<void>;
|
|
58
56
|
|
|
59
57
|
/**
|
|
60
58
|
* Defines a conditional guard function for a transition.
|
|
@@ -64,24 +62,42 @@ export type Effect<TEvent extends MachineEvent, TContext extends JsonObject> = (
|
|
|
64
62
|
* @template TEvent The type of the event.
|
|
65
63
|
* @returns `true` if the transition should be taken, `false` otherwise.
|
|
66
64
|
*/
|
|
67
|
-
export type
|
|
68
|
-
event: Readonly<TEvent
|
|
69
|
-
context: Readonly<TContext
|
|
70
|
-
) => boolean;
|
|
65
|
+
export type Guard<TEvent extends MachineEvent, TContext extends Record<string, unknown>> = (params: {
|
|
66
|
+
readonly event: Readonly<TEvent>;
|
|
67
|
+
readonly context: Readonly<TContext>;
|
|
68
|
+
}) => boolean;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Defines an actor (asynchronous lifecycle process) invoked on state entry.
|
|
72
|
+
* It starts an operation and can send events back to the parent FSM via `dispatch`.
|
|
73
|
+
* It can return a cleanup function to be called when exiting the state or destroying the machine.
|
|
74
|
+
*
|
|
75
|
+
* @template TEvent The union type of all events in the machine.
|
|
76
|
+
* @template TContext The type of the machine's context.
|
|
77
|
+
*/
|
|
78
|
+
export type Actor<TEvent extends MachineEvent, TContext extends Record<string, unknown>> = (params: {
|
|
79
|
+
readonly event: Readonly<TEvent>;
|
|
80
|
+
readonly context: Readonly<TContext>;
|
|
81
|
+
readonly dispatch: (event: TEvent) => void;
|
|
82
|
+
}) => (() => void) | void;
|
|
71
83
|
|
|
72
84
|
/**
|
|
73
85
|
* Defines a transition for a given state and event. It specifies the target state,
|
|
74
|
-
* actions, and an optional
|
|
86
|
+
* actions, and an optional guard.
|
|
75
87
|
*
|
|
76
88
|
* @template TState The type of the state.
|
|
77
89
|
* @template TEvent The type of the event.
|
|
78
90
|
* @template TContext The type of the machine's context.
|
|
79
91
|
*/
|
|
80
|
-
export interface Transition<
|
|
92
|
+
export interface Transition<
|
|
93
|
+
TState extends string,
|
|
94
|
+
TEvent extends MachineEvent,
|
|
95
|
+
TContext extends Record<string, unknown>,
|
|
96
|
+
> {
|
|
81
97
|
/** The target state to transition to. If undefined, it's an internal transition. */
|
|
82
98
|
readonly target?: TState;
|
|
83
|
-
/** A
|
|
84
|
-
readonly
|
|
99
|
+
/** A guard function that must return true for the transition to occur. */
|
|
100
|
+
readonly guard?: Guard<TEvent, TContext>;
|
|
85
101
|
/** An array of assigners to execute. These update context synchronously. */
|
|
86
102
|
readonly assigners?: SingleOrArray<Assigner<TEvent, TContext>>;
|
|
87
103
|
}
|
|
@@ -109,12 +125,12 @@ export interface FsmPersistenceConfig {
|
|
|
109
125
|
*
|
|
110
126
|
* @template TState The union type of all possible states.
|
|
111
127
|
* @template TEvent The union type of all possible events.
|
|
112
|
-
* @template TContext The type of the context
|
|
128
|
+
* @template TContext The type of the machine's context.
|
|
113
129
|
*/
|
|
114
130
|
export interface StateMachineConfig<
|
|
115
131
|
TState extends string,
|
|
116
132
|
TEvent extends MachineEvent,
|
|
117
|
-
TContext extends
|
|
133
|
+
TContext extends Record<string, unknown>,
|
|
118
134
|
> extends Pick<SignalConfig, 'name'> {
|
|
119
135
|
/** The initial finite state value. */
|
|
120
136
|
readonly initial: TState;
|
|
@@ -136,6 +152,8 @@ export interface StateMachineConfig<
|
|
|
136
152
|
readonly entry?: SingleOrArray<Effect<TEvent, TContext>>;
|
|
137
153
|
/** An array of side-effect effects to execute upon exiting this state. */
|
|
138
154
|
readonly exit?: SingleOrArray<Effect<TEvent, TContext>>;
|
|
155
|
+
/** An array of actors to spawn upon entering this state, cleaned up when leaving. */
|
|
156
|
+
readonly actors?: SingleOrArray<Actor<TEvent, TContext>>;
|
|
139
157
|
};
|
|
140
158
|
};
|
|
141
159
|
}
|