@doeixd/machine 0.0.4 → 0.0.6
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 +952 -13
- package/dist/cjs/development/index.js +691 -0
- package/dist/cjs/development/index.js.map +4 -4
- package/dist/cjs/production/index.js +5 -1
- package/dist/esm/development/index.js +698 -0
- package/dist/esm/development/index.js.map +4 -4
- package/dist/esm/production/index.js +5 -1
- package/dist/types/extract.d.ts +71 -0
- package/dist/types/extract.d.ts.map +1 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/multi.d.ts +838 -0
- package/dist/types/multi.d.ts.map +1 -0
- package/dist/types/primitives.d.ts +202 -0
- package/dist/types/primitives.d.ts.map +1 -0
- package/dist/types/runtime-extract.d.ts +53 -0
- package/dist/types/runtime-extract.d.ts.map +1 -0
- package/package.json +6 -2
- package/src/extract.ts +452 -67
- package/src/index.ts +49 -0
- package/src/multi.ts +1145 -0
- package/src/primitives.ts +135 -0
- package/src/react.ts +349 -28
- package/src/runtime-extract.ts +141 -0
- package/src/solid.ts +8 -8
|
@@ -0,0 +1,838 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file multi.ts - Advanced operational patterns for state machine orchestration.
|
|
3
|
+
* @description
|
|
4
|
+
* This module provides optional, higher-level abstractions for managing machines.
|
|
5
|
+
* They solve common ergonomic and integration challenges without compromising the
|
|
6
|
+
* immutable core of the library.
|
|
7
|
+
*
|
|
8
|
+
* It introduces three patterns:
|
|
9
|
+
*
|
|
10
|
+
* 1. **Runner (`createRunner`):** A stateful controller for ergonomic control
|
|
11
|
+
* of a single, immutable machine. Solves state reassignment.
|
|
12
|
+
*
|
|
13
|
+
* 2. **Ensemble (`createEnsemble`):** A functional pattern for orchestrating logic
|
|
14
|
+
* over an external, framework-agnostic state store.
|
|
15
|
+
*
|
|
16
|
+
* 3. **MultiMachine (`createMultiMachine`):** A class-based alternative to the
|
|
17
|
+
* Ensemble for OOP-style orchestration.
|
|
18
|
+
*/
|
|
19
|
+
import { Machine, Context, TransitionArgs, TransitionNames } from './index';
|
|
20
|
+
/**
|
|
21
|
+
* A mapped type that creates a new object type with the same transition methods
|
|
22
|
+
* as the machine `M`, but pre-bound to update a Runner's internal state.
|
|
23
|
+
*
|
|
24
|
+
* When you call a method on `BoundTransitions`, it automatically transitions the
|
|
25
|
+
* runner's state and returns the new machine instance. This is the key mechanism
|
|
26
|
+
* that eliminates the need for manual state reassignment in imperative code.
|
|
27
|
+
*
|
|
28
|
+
* @template M - The machine type, which can be a union of multiple machine states.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* // If your machine has these transitions:
|
|
32
|
+
* // increment: () => Machine
|
|
33
|
+
* // add: (n: number) => Machine
|
|
34
|
+
* // Then BoundTransitions<typeof machine> provides:
|
|
35
|
+
* // increment: () => Machine (auto-updates runner state)
|
|
36
|
+
* // add: (n: number) => Machine (auto-updates runner state)
|
|
37
|
+
*/
|
|
38
|
+
export type BoundTransitions<M extends Machine<any>> = {
|
|
39
|
+
[K in TransitionNames<M>]: (...args: TransitionArgs<M, K>) => M[K] extends (...args: any[]) => infer R ? R : never;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* A stateful controller that wraps an immutable machine instance, providing a
|
|
43
|
+
* stable API for imperative state transitions without manual reassignment.
|
|
44
|
+
*
|
|
45
|
+
* The Runner holds the "current" machine state internally and updates it whenever
|
|
46
|
+
* an action is called. This solves the ergonomic problem of having to write:
|
|
47
|
+
* `machine = machine.transition()` over and over. Instead, you just call
|
|
48
|
+
* `runner.actions.transition()` and the runner manages the state for you.
|
|
49
|
+
*
|
|
50
|
+
* **Use Runner for:**
|
|
51
|
+
* - Complex local component state (React, Vue, Svelte components)
|
|
52
|
+
* - Scripts that need clean imperative state management
|
|
53
|
+
* - Situations where you have a single, self-contained state machine
|
|
54
|
+
*
|
|
55
|
+
* **Don't use Runner for:**
|
|
56
|
+
* - Global application state (use Ensemble instead)
|
|
57
|
+
* - Multiple interconnected machines
|
|
58
|
+
*
|
|
59
|
+
* @template M - The machine type (can be a union of states for Type-State patterns).
|
|
60
|
+
*/
|
|
61
|
+
export type Runner<M extends Machine<any>> = {
|
|
62
|
+
/**
|
|
63
|
+
* The current, raw machine instance. This property is essential for
|
|
64
|
+
* type-narrowing in Type-State Programming patterns.
|
|
65
|
+
*
|
|
66
|
+
* Since machines can be unions of different state types, you can narrow
|
|
67
|
+
* the type by checking `runner.state.context` properties, and TypeScript
|
|
68
|
+
* will automatically narrow which transitions are available.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* if (runner.state.context.status === 'loggedIn') {
|
|
72
|
+
* // runner.state is now typed as LoggedInMachine
|
|
73
|
+
* console.log(runner.state.context.username);
|
|
74
|
+
* runner.actions.logout(); // Only available when logged in
|
|
75
|
+
* }
|
|
76
|
+
*/
|
|
77
|
+
readonly state: M;
|
|
78
|
+
/**
|
|
79
|
+
* A direct, readonly accessor to the context of the current machine state.
|
|
80
|
+
* This is a convenience property equivalent to `runner.state.context`.
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* console.log(runner.context.count); // Same as runner.state.context.count
|
|
84
|
+
*/
|
|
85
|
+
readonly context: Context<M>;
|
|
86
|
+
/**
|
|
87
|
+
* A stable object containing all available transition methods, pre-bound to
|
|
88
|
+
* update the runner's state. This is the primary way to trigger transitions.
|
|
89
|
+
*
|
|
90
|
+
* When you call `runner.actions.someTransition()`, the runner automatically:
|
|
91
|
+
* 1. Calls the transition on the current machine
|
|
92
|
+
* 2. Updates `runner.state` with the new machine instance
|
|
93
|
+
* 3. Fires the `onChange` callback (if provided to createRunner)
|
|
94
|
+
* 4. Returns the new machine instance
|
|
95
|
+
*
|
|
96
|
+
* Note: For union-type machines, you must first narrow the type of `runner.state`
|
|
97
|
+
* to ensure a given action is available at compile time.
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* runner.actions.increment(); // Automatically updates runner.state
|
|
101
|
+
* runner.actions.add(5); // Returns new machine instance
|
|
102
|
+
*/
|
|
103
|
+
readonly actions: BoundTransitions<M>;
|
|
104
|
+
/**
|
|
105
|
+
* Manually sets the runner to a new machine state. Useful for resetting state
|
|
106
|
+
* or synchronizing with external events.
|
|
107
|
+
*
|
|
108
|
+
* This method bypasses the normal transition path and directly updates the
|
|
109
|
+
* runner's internal state. The `onChange` callback will be called.
|
|
110
|
+
*
|
|
111
|
+
* @param newState - The new machine instance to set.
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* const reset = createCounterMachine({ count: 0 });
|
|
115
|
+
* runner.setState(reset); // Jump back to initial state
|
|
116
|
+
*/
|
|
117
|
+
setState(newState: M): void;
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* Creates a Managed State Runner by wrapping a pure, immutable machine instance
|
|
121
|
+
* in a stateful controller. This eliminates the need for `machine = machine.transition()`
|
|
122
|
+
* reassignment, providing a more ergonomic, imperative API for complex local state.
|
|
123
|
+
*
|
|
124
|
+
* **How it works:**
|
|
125
|
+
* 1. The runner holds a reference to the current machine internally
|
|
126
|
+
* 2. When you call `runner.actions.transition()`, it calls the transition on the
|
|
127
|
+
* current machine and automatically updates the runner's internal state
|
|
128
|
+
* 3. The runner exposes a stable `actions` object that always reflects what
|
|
129
|
+
* transitions are available on the *current* machine (important for Type-State)
|
|
130
|
+
* 4. The `onChange` callback is invoked after every state change
|
|
131
|
+
*
|
|
132
|
+
* **Key difference from just calling transitions directly:**
|
|
133
|
+
* Instead of: `let machine = createMachine(...); machine = machine.increment();`
|
|
134
|
+
* You write: `const runner = createRunner(machine); runner.actions.increment();`
|
|
135
|
+
*
|
|
136
|
+
* The runner *is* the state holder, so you never need to reassign variables.
|
|
137
|
+
*
|
|
138
|
+
* @template M - The machine type.
|
|
139
|
+
* @param initialMachine - The starting machine instance.
|
|
140
|
+
* @param onChange - Optional callback fired after every state transition. Receives
|
|
141
|
+
* the new machine state, allowing you to react to changes (e.g., update a UI,
|
|
142
|
+
* log state changes, or trigger side effects).
|
|
143
|
+
* @returns A `Runner` instance with `state`, `context`, `actions`, and `setState()`.
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* // Simple counter example
|
|
147
|
+
* const counterMachine = createCounterMachine({ count: 0 });
|
|
148
|
+
* const runner = createRunner(counterMachine, (newState) => {
|
|
149
|
+
* console.log('Count is now:', newState.context.count);
|
|
150
|
+
* });
|
|
151
|
+
*
|
|
152
|
+
* runner.actions.increment(); // Logs: "Count is now: 1"
|
|
153
|
+
* runner.actions.add(5); // Logs: "Count is now: 6"
|
|
154
|
+
* console.log(runner.context.count); // 6
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* // Type-State example with conditional narrowing
|
|
158
|
+
* type AuthMachine = LoggedOutState | LoggedInState;
|
|
159
|
+
*
|
|
160
|
+
* const runner = createRunner(createLoggedOutMachine());
|
|
161
|
+
*
|
|
162
|
+
* // Narrow the type to access login
|
|
163
|
+
* if (runner.state.context.status === 'loggedOut') {
|
|
164
|
+
* runner.actions.login('alice'); // Only works in loggedOut state
|
|
165
|
+
* }
|
|
166
|
+
*
|
|
167
|
+
* // Now it's logged in, so we can call logout
|
|
168
|
+
* if (runner.state.context.status === 'loggedIn') {
|
|
169
|
+
* runner.actions.logout();
|
|
170
|
+
* }
|
|
171
|
+
*/
|
|
172
|
+
export declare function createRunner<M extends Machine<any>>(initialMachine: M, onChange?: (newState: M) => void): Runner<M>;
|
|
173
|
+
/**
|
|
174
|
+
* Defines the contract for an external, user-provided state store. The Ensemble
|
|
175
|
+
* uses this interface to read and write the machine's context, allowing it to
|
|
176
|
+
* plug into any state management solution (React, Solid, Zustand, etc.).
|
|
177
|
+
*
|
|
178
|
+
* **The power of this abstraction:**
|
|
179
|
+
* Your machine logic is completely decoupled from how or where the state is stored.
|
|
180
|
+
* The same machine factories can work with React's `useState`, Solid's `createSignal`,
|
|
181
|
+
* a plain object, or any custom store implementation.
|
|
182
|
+
*
|
|
183
|
+
* **Implementation examples:**
|
|
184
|
+
* - React: `{ getContext: () => state, setContext: setState }`
|
|
185
|
+
* - Solid: `{ getContext: () => store, setContext: (newCtx) => Object.assign(store, newCtx) }`
|
|
186
|
+
* - Plain object: `{ getContext: () => context, setContext: (ctx) => Object.assign(context, ctx) }`
|
|
187
|
+
*
|
|
188
|
+
* @template C - The shared context object type.
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* // Implement a simple in-memory store
|
|
192
|
+
* let sharedContext = { status: 'idle' };
|
|
193
|
+
* const store: StateStore<typeof sharedContext> = {
|
|
194
|
+
* getContext: () => sharedContext,
|
|
195
|
+
* setContext: (newCtx) => { sharedContext = newCtx; }
|
|
196
|
+
* };
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* // Implement a React-based store
|
|
200
|
+
* function useAppStore() {
|
|
201
|
+
* const [state, setState] = useState({ status: 'idle' });
|
|
202
|
+
* return {
|
|
203
|
+
* getContext: () => state,
|
|
204
|
+
* setContext: setState
|
|
205
|
+
* };
|
|
206
|
+
* }
|
|
207
|
+
*/
|
|
208
|
+
export interface StateStore<C extends object> {
|
|
209
|
+
/**
|
|
210
|
+
* A function that returns the current, up-to-date context from the external store.
|
|
211
|
+
* Called whenever the Ensemble needs the latest state.
|
|
212
|
+
*/
|
|
213
|
+
getContext: () => C;
|
|
214
|
+
/**
|
|
215
|
+
* A function that takes a new context and updates the external store.
|
|
216
|
+
* Called by transitions to persist state changes.
|
|
217
|
+
*
|
|
218
|
+
* @param newContext - The new context object to persist.
|
|
219
|
+
*/
|
|
220
|
+
setContext: (newContext: C) => void;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* A mapped type that finds all unique transition names across a union of machine types.
|
|
224
|
+
*
|
|
225
|
+
* This type extracts the union of all methods from all possible machine states,
|
|
226
|
+
* excluding the `context` property. This is used to create the `actions` object
|
|
227
|
+
* on an Ensemble, which can have methods from any of the machine states.
|
|
228
|
+
*
|
|
229
|
+
* At runtime, the Ensemble validates that an action is valid for the current state
|
|
230
|
+
* before executing it.
|
|
231
|
+
*
|
|
232
|
+
* @template AllMachines - A union of all possible machine types in an Ensemble.
|
|
233
|
+
*
|
|
234
|
+
* @example
|
|
235
|
+
* type IdleState = Machine<{ status: 'idle' }> & { fetch: () => LoadingState };
|
|
236
|
+
* type LoadingState = Machine<{ status: 'loading' }> & { cancel: () => IdleState };
|
|
237
|
+
* type AllStates = IdleState | LoadingState;
|
|
238
|
+
*
|
|
239
|
+
* // AllTransitions<AllStates> = { fetch: (...) => ..., cancel: (...) => ... }
|
|
240
|
+
* // (Both fetch and cancel are available, but each is only valid in its state)
|
|
241
|
+
*/
|
|
242
|
+
type AllTransitions<AllMachines extends Machine<any>> = Omit<{
|
|
243
|
+
[K in keyof AllMachines]: AllMachines[K];
|
|
244
|
+
}[keyof AllMachines], 'context'>;
|
|
245
|
+
/**
|
|
246
|
+
* The Ensemble object. It provides a stable, unified API for orchestrating a
|
|
247
|
+
* state machine whose context is managed by an external store.
|
|
248
|
+
*
|
|
249
|
+
* The Ensemble acts as the "director," determining which machine "actor" is
|
|
250
|
+
* currently active based on the state of the shared context. Unlike a Runner,
|
|
251
|
+
* which manages local state, an Ensemble plugs into external state management
|
|
252
|
+
* (like React's useState, Solid's signal, or a global store).
|
|
253
|
+
*
|
|
254
|
+
* **Key characteristics:**
|
|
255
|
+
* - Dynamically reconstructs the current machine based on context
|
|
256
|
+
* - Validates transitions at runtime for the current state
|
|
257
|
+
* - Integrates seamlessly with framework state managers
|
|
258
|
+
* - Same factories can be reused across different frameworks
|
|
259
|
+
*
|
|
260
|
+
* **Use Ensemble for:**
|
|
261
|
+
* - Global application state
|
|
262
|
+
* - Framework integration (React, Solid, Vue, etc.)
|
|
263
|
+
* - Complex workflows that span multiple components
|
|
264
|
+
* - Decoupling business logic from UI framework
|
|
265
|
+
*
|
|
266
|
+
* @template AllMachines - A union type of all possible machine states.
|
|
267
|
+
* @template C - The shared context type.
|
|
268
|
+
*/
|
|
269
|
+
export type Ensemble<AllMachines extends Machine<any>, C extends object> = {
|
|
270
|
+
/**
|
|
271
|
+
* A direct, readonly accessor to the context from the provided `StateStore`.
|
|
272
|
+
* This is always up-to-date with the external store.
|
|
273
|
+
*/
|
|
274
|
+
readonly context: C;
|
|
275
|
+
/**
|
|
276
|
+
* The current, fully-typed machine instance. This is dynamically created on-demand
|
|
277
|
+
* based on the context state. Use this for type-narrowing with Type-State patterns.
|
|
278
|
+
*
|
|
279
|
+
* The machine is reconstructed on every access, so it always reflects the
|
|
280
|
+
* current state of the context.
|
|
281
|
+
*/
|
|
282
|
+
readonly state: AllMachines;
|
|
283
|
+
/**
|
|
284
|
+
* A stable object containing all possible actions from all machine states.
|
|
285
|
+
* The Ensemble performs a runtime check to ensure an action is valid for the
|
|
286
|
+
* current state before executing it.
|
|
287
|
+
*
|
|
288
|
+
* The `actions` object itself is stable (doesn't change), but the methods
|
|
289
|
+
* available on it dynamically change based on the current state.
|
|
290
|
+
*/
|
|
291
|
+
readonly actions: AllTransitions<AllMachines>;
|
|
292
|
+
};
|
|
293
|
+
/**
|
|
294
|
+
* Creates an Ensemble to orchestrate a state machine over an external state store.
|
|
295
|
+
* This is the primary tool for framework integration, as it decouples pure state
|
|
296
|
+
* logic (defined in factories) from an application's state management solution
|
|
297
|
+
* (defined in store).
|
|
298
|
+
*
|
|
299
|
+
* **How it works:**
|
|
300
|
+
* 1. You provide a `StateStore` that can read and write your application's state
|
|
301
|
+
* 2. You define factory functions that create machines for each state
|
|
302
|
+
* 3. You provide a `getDiscriminant` accessor that tells the Ensemble which
|
|
303
|
+
* factory to use based on the current context
|
|
304
|
+
* 4. The Ensemble dynamically constructs the right machine and provides a stable
|
|
305
|
+
* `actions` object to call transitions
|
|
306
|
+
*
|
|
307
|
+
* **Why this pattern?**
|
|
308
|
+
* Your business logic (machines) is completely separated from your state management
|
|
309
|
+
* (React, Solid, Zustand). You can change state managers without rewriting machines,
|
|
310
|
+
* and you can test machines in isolation without framework dependencies.
|
|
311
|
+
*
|
|
312
|
+
* @template C - The shared context type.
|
|
313
|
+
* @template F - An object of functions that create machine instances for each state.
|
|
314
|
+
* Each factory receives the context and returns a Machine instance for that state.
|
|
315
|
+
* @param store - The user-provided `StateStore` that reads/writes the context.
|
|
316
|
+
* @param factories - An object mapping state discriminant keys to factory functions.
|
|
317
|
+
* Each factory receives the context and returns a machine instance.
|
|
318
|
+
* @param getDiscriminant - An accessor function that takes the context and returns
|
|
319
|
+
* the key of the current state in the `factories` object. This provides full
|
|
320
|
+
* refactoring safety—if you rename a property in your context, TypeScript will
|
|
321
|
+
* catch it at the accessor function.
|
|
322
|
+
* @returns An `Ensemble` instance with `context`, `state`, and `actions`.
|
|
323
|
+
*
|
|
324
|
+
* @example
|
|
325
|
+
* // Using a simple in-memory store
|
|
326
|
+
* let sharedContext = { status: 'idle' as const, data: null };
|
|
327
|
+
* const store = {
|
|
328
|
+
* getContext: () => sharedContext,
|
|
329
|
+
* setContext: (newCtx) => { sharedContext = newCtx; }
|
|
330
|
+
* };
|
|
331
|
+
*
|
|
332
|
+
* // Define factories for each state
|
|
333
|
+
* const factories = {
|
|
334
|
+
* idle: (ctx) => createMachine(ctx, {
|
|
335
|
+
* fetch: () => store.setContext({ ...ctx, status: 'loading' })
|
|
336
|
+
* }),
|
|
337
|
+
* loading: (ctx) => createMachine(ctx, {
|
|
338
|
+
* succeed: (data: any) => store.setContext({ status: 'success', data }),
|
|
339
|
+
* fail: (error: string) => store.setContext({ status: 'error', error })
|
|
340
|
+
* }),
|
|
341
|
+
* success: (ctx) => createMachine(ctx, {
|
|
342
|
+
* retry: () => store.setContext({ status: 'loading', data: null })
|
|
343
|
+
* }),
|
|
344
|
+
* error: (ctx) => createMachine(ctx, {
|
|
345
|
+
* retry: () => store.setContext({ status: 'loading', data: null })
|
|
346
|
+
* })
|
|
347
|
+
* };
|
|
348
|
+
*
|
|
349
|
+
* // Create the ensemble with a discriminant accessor
|
|
350
|
+
* const ensemble = createEnsemble(store, factories, (ctx) => ctx.status);
|
|
351
|
+
*
|
|
352
|
+
* // Use the ensemble
|
|
353
|
+
* ensemble.actions.fetch();
|
|
354
|
+
* console.log(ensemble.context.status); // 'loading'
|
|
355
|
+
*
|
|
356
|
+
* @example
|
|
357
|
+
* // React integration example
|
|
358
|
+
* function useAppEnsemble() {
|
|
359
|
+
* const [context, setContext] = useState({ status: 'idle' as const, data: null });
|
|
360
|
+
*
|
|
361
|
+
* const store: StateStore<typeof context> = {
|
|
362
|
+
* getContext: () => context,
|
|
363
|
+
* setContext: (newCtx) => setContext(newCtx)
|
|
364
|
+
* };
|
|
365
|
+
*
|
|
366
|
+
* const ensemble = useMemo(() =>
|
|
367
|
+
* createEnsemble(store, factories, (ctx) => ctx.status),
|
|
368
|
+
* [context] // Re-create ensemble if context changes
|
|
369
|
+
* );
|
|
370
|
+
*
|
|
371
|
+
* return ensemble;
|
|
372
|
+
* }
|
|
373
|
+
*
|
|
374
|
+
* // In your component:
|
|
375
|
+
* function MyComponent() {
|
|
376
|
+
* const ensemble = useAppEnsemble();
|
|
377
|
+
* return (
|
|
378
|
+
* <>
|
|
379
|
+
* <p>Status: {ensemble.context.status}</p>
|
|
380
|
+
* <button onClick={() => ensemble.actions.fetch()}>
|
|
381
|
+
* Fetch Data
|
|
382
|
+
* </button>
|
|
383
|
+
* </>
|
|
384
|
+
* );
|
|
385
|
+
* }
|
|
386
|
+
*/
|
|
387
|
+
export declare function createEnsemble<C extends object, F extends Record<string, (context: C) => Machine<C>>>(store: StateStore<C>, factories: F, getDiscriminant: (context: C) => keyof F): Ensemble<ReturnType<F[keyof F]>, C>;
|
|
388
|
+
/**
|
|
389
|
+
* Executes a generator-based workflow using a Managed State Runner.
|
|
390
|
+
*
|
|
391
|
+
* This provides the cleanest syntax for multi-step imperative workflows, as the
|
|
392
|
+
* `yield` keyword is only used for control flow, not state passing. Unlike the
|
|
393
|
+
* basic `run()` function from the core library, this works directly with a Runner,
|
|
394
|
+
* making it perfect for complex local state orchestration.
|
|
395
|
+
*
|
|
396
|
+
* **Syntax benefits:**
|
|
397
|
+
* - No need to manually thread state through a chain of transitions
|
|
398
|
+
* - `yield` is purely for control flow, not for passing state
|
|
399
|
+
* - Can use regular `if`/`for` statements without helpers
|
|
400
|
+
* - Generator return value is automatically your final result
|
|
401
|
+
*
|
|
402
|
+
* @param flow - A generator function that receives the `Runner` instance. The
|
|
403
|
+
* generator can yield values (returned by transitions) and use them for control
|
|
404
|
+
* flow, or just yield for side effects.
|
|
405
|
+
* @param initialMachine - The machine to start the flow with. A runner will be
|
|
406
|
+
* created from this automatically.
|
|
407
|
+
* @returns The final value returned by the generator (the `return` statement).
|
|
408
|
+
*
|
|
409
|
+
* @example
|
|
410
|
+
* // Simple sequential transitions
|
|
411
|
+
* const result = runWithRunner(function* (runner) {
|
|
412
|
+
* yield runner.actions.increment();
|
|
413
|
+
* yield runner.actions.add(10);
|
|
414
|
+
* if (runner.context.count > 5) {
|
|
415
|
+
* yield runner.actions.reset();
|
|
416
|
+
* }
|
|
417
|
+
* return runner.context;
|
|
418
|
+
* }, createCounterMachine());
|
|
419
|
+
* console.log(result); // { count: 0 }
|
|
420
|
+
*
|
|
421
|
+
* @example
|
|
422
|
+
* // Complex workflow with Type-State narrowing
|
|
423
|
+
* const result = runWithRunner(function* (runner) {
|
|
424
|
+
* // Start logged out
|
|
425
|
+
* if (runner.state.context.status === 'loggedOut') {
|
|
426
|
+
* yield runner.actions.login('alice');
|
|
427
|
+
* }
|
|
428
|
+
*
|
|
429
|
+
* // Now logged in, fetch profile
|
|
430
|
+
* if (runner.state.context.status === 'loggedIn') {
|
|
431
|
+
* yield runner.actions.fetchProfile();
|
|
432
|
+
* }
|
|
433
|
+
*
|
|
434
|
+
* // Return final context
|
|
435
|
+
* return runner.context;
|
|
436
|
+
* }, createAuthMachine());
|
|
437
|
+
*/
|
|
438
|
+
export declare function runWithRunner<M extends Machine<any>, T>(flow: (runner: Runner<M>) => Generator<any, T, any>, initialMachine: M): T;
|
|
439
|
+
/**
|
|
440
|
+
* Executes a generator-based workflow using an Ensemble.
|
|
441
|
+
*
|
|
442
|
+
* This pattern is ideal for orchestrating complex sagas or workflows that
|
|
443
|
+
* interact with a global, framework-managed state. Like `runWithRunner`,
|
|
444
|
+
* it provides clean imperative syntax for multi-step workflows, but operates
|
|
445
|
+
* on an Ensemble's external store rather than internal state.
|
|
446
|
+
*
|
|
447
|
+
* **Key differences from runWithRunner:**
|
|
448
|
+
* - Works with external state stores (React, Solid, etc.)
|
|
449
|
+
* - Useful for global workflows and sagas
|
|
450
|
+
* - State changes automatically propagate to the framework
|
|
451
|
+
* - Great for testing framework-agnostic state logic
|
|
452
|
+
*
|
|
453
|
+
* @param flow - A generator function that receives the `Ensemble` instance.
|
|
454
|
+
* The generator can read `ensemble.context` and call `ensemble.actions`.
|
|
455
|
+
* @param ensemble - The `Ensemble` to run the workflow against. Its context
|
|
456
|
+
* is shared across the entire workflow.
|
|
457
|
+
* @returns The final value returned by the generator (the `return` statement).
|
|
458
|
+
*
|
|
459
|
+
* @example
|
|
460
|
+
* // Multi-step workflow with an ensemble
|
|
461
|
+
* const result = runWithEnsemble(function* (ensemble) {
|
|
462
|
+
* // Fetch initial data
|
|
463
|
+
* if (ensemble.context.status === 'idle') {
|
|
464
|
+
* yield ensemble.actions.fetch();
|
|
465
|
+
* }
|
|
466
|
+
*
|
|
467
|
+
* // Process the data
|
|
468
|
+
* if (ensemble.context.status === 'success') {
|
|
469
|
+
* yield ensemble.actions.process(ensemble.context.data);
|
|
470
|
+
* }
|
|
471
|
+
*
|
|
472
|
+
* return ensemble.context;
|
|
473
|
+
* }, ensemble);
|
|
474
|
+
*
|
|
475
|
+
* @example
|
|
476
|
+
* // Testing a workflow without a UI framework
|
|
477
|
+
* const store: StateStore<AppContext> = {
|
|
478
|
+
* getContext: () => context,
|
|
479
|
+
* setContext: (newCtx) => Object.assign(context, newCtx)
|
|
480
|
+
* };
|
|
481
|
+
*
|
|
482
|
+
* const ensemble = createEnsemble(store, factories, (ctx) => ctx.status);
|
|
483
|
+
*
|
|
484
|
+
* // Run a complex workflow and assert the result
|
|
485
|
+
* const result = runWithEnsemble(function* (e) {
|
|
486
|
+
* yield e.actions.login('alice');
|
|
487
|
+
* yield e.actions.fetchProfile();
|
|
488
|
+
* yield e.actions.updateEmail('alice@example.com');
|
|
489
|
+
* return e.context;
|
|
490
|
+
* }, ensemble);
|
|
491
|
+
*
|
|
492
|
+
* expect(result.userEmail).toBe('alice@example.com');
|
|
493
|
+
*/
|
|
494
|
+
export declare function runWithEnsemble<AllMachines extends Machine<any>, C extends object, T>(flow: (ensemble: Ensemble<AllMachines, C>) => Generator<any, T, any>, ensemble: Ensemble<AllMachines, C>): T;
|
|
495
|
+
/**
|
|
496
|
+
* The base class for creating a class-based state machine (MultiMachine).
|
|
497
|
+
* Extend this class to define your state machine's logic using instance methods
|
|
498
|
+
* as transitions.
|
|
499
|
+
*
|
|
500
|
+
* This approach is ideal for developers who prefer class-based architectures
|
|
501
|
+
* and want to manage a shared context directly through an external StateStore.
|
|
502
|
+
* It provides a familiar OOP interface while maintaining the decoupling benefits
|
|
503
|
+
* of the StateStore pattern.
|
|
504
|
+
*
|
|
505
|
+
* **Key features:**
|
|
506
|
+
* - Extend this class and define transition methods as instance methods
|
|
507
|
+
* - Protected `context` getter provides access to the current state
|
|
508
|
+
* - Protected `setContext()` method updates the external store
|
|
509
|
+
* - Works seamlessly with `createMultiMachine()`
|
|
510
|
+
*
|
|
511
|
+
* @template C - The shared context type. Should typically contain a discriminant
|
|
512
|
+
* property (like `status`) that identifies the current state.
|
|
513
|
+
*
|
|
514
|
+
* @example
|
|
515
|
+
* // Define your context type
|
|
516
|
+
* type AppContext = { status: 'idle' | 'loading' | 'error'; data?: any; error?: string };
|
|
517
|
+
*
|
|
518
|
+
* // Extend MultiMachineBase and define transitions as methods
|
|
519
|
+
* class AppMachine extends MultiMachineBase<AppContext> {
|
|
520
|
+
* async fetch(url: string) {
|
|
521
|
+
* // Notify subscribers we're loading
|
|
522
|
+
* this.setContext({ ...this.context, status: 'loading' });
|
|
523
|
+
*
|
|
524
|
+
* try {
|
|
525
|
+
* const data = await fetch(url).then(r => r.json());
|
|
526
|
+
* // Update state when done
|
|
527
|
+
* this.setContext({ ...this.context, status: 'idle', data });
|
|
528
|
+
* } catch (error) {
|
|
529
|
+
* // Handle errors
|
|
530
|
+
* this.setContext({
|
|
531
|
+
* ...this.context,
|
|
532
|
+
* status: 'error',
|
|
533
|
+
* error: error.message
|
|
534
|
+
* });
|
|
535
|
+
* }
|
|
536
|
+
* }
|
|
537
|
+
*
|
|
538
|
+
* reset() {
|
|
539
|
+
* this.setContext({ status: 'idle' });
|
|
540
|
+
* }
|
|
541
|
+
* }
|
|
542
|
+
*/
|
|
543
|
+
export declare abstract class MultiMachineBase<C extends object> {
|
|
544
|
+
/**
|
|
545
|
+
* The external state store that manages the machine's context.
|
|
546
|
+
* @protected
|
|
547
|
+
*/
|
|
548
|
+
protected store: StateStore<C>;
|
|
549
|
+
/**
|
|
550
|
+
* @param store - The StateStore that will manage this machine's context.
|
|
551
|
+
*/
|
|
552
|
+
constructor(store: StateStore<C>);
|
|
553
|
+
/**
|
|
554
|
+
* Read-only access to the current context from the external store.
|
|
555
|
+
* This getter always returns the latest context from the store.
|
|
556
|
+
*
|
|
557
|
+
* @protected
|
|
558
|
+
*
|
|
559
|
+
* @example
|
|
560
|
+
* const currentStatus = this.context.status;
|
|
561
|
+
* const currentData = this.context.data;
|
|
562
|
+
*/
|
|
563
|
+
protected get context(): C;
|
|
564
|
+
/**
|
|
565
|
+
* Update the shared context in the external store.
|
|
566
|
+
* Call this method in your transition methods to update the state.
|
|
567
|
+
*
|
|
568
|
+
* @protected
|
|
569
|
+
* @param newContext - The new context object. Should typically be a shallow
|
|
570
|
+
* copy with only the properties you're changing, merged with the current
|
|
571
|
+
* context using spread operators.
|
|
572
|
+
*
|
|
573
|
+
* @example
|
|
574
|
+
* // In a transition method:
|
|
575
|
+
* this.setContext({ ...this.context, status: 'loading' });
|
|
576
|
+
*
|
|
577
|
+
* @example
|
|
578
|
+
* // Updating nested properties:
|
|
579
|
+
* this.setContext({
|
|
580
|
+
* ...this.context,
|
|
581
|
+
* user: { ...this.context.user, name: 'Alice' }
|
|
582
|
+
* });
|
|
583
|
+
*/
|
|
584
|
+
protected setContext(newContext: C): void;
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Creates a live, type-safe instance of a class-based state machine (MultiMachine).
|
|
588
|
+
*
|
|
589
|
+
* This is the class-based alternative to the functional `createEnsemble` pattern,
|
|
590
|
+
* designed for developers who prefer an OOP-style architecture. This function takes
|
|
591
|
+
* your MultiMachine class blueprint and an external state store, and wires them
|
|
592
|
+
* together. The returned object is a Proxy that dynamically exposes both context
|
|
593
|
+
* properties and the available transition methods from your class.
|
|
594
|
+
*
|
|
595
|
+
* **Key features:**
|
|
596
|
+
* - Directly access context properties as if they were on the machine object
|
|
597
|
+
* - Call transition methods to update state through the store
|
|
598
|
+
* - Type-safe integration with TypeScript
|
|
599
|
+
* - Seamless Proxy-based API (no special method names or API quirks)
|
|
600
|
+
*
|
|
601
|
+
* **How it works:**
|
|
602
|
+
* The returned Proxy intercepts property access. For context properties, it returns
|
|
603
|
+
* values from the store. For methods, it calls them on the MultiMachine instance.
|
|
604
|
+
* This creates the illusion of a single object that is both data and behavior.
|
|
605
|
+
*
|
|
606
|
+
* @template C - The shared context type.
|
|
607
|
+
* @template T - The MultiMachine class type.
|
|
608
|
+
*
|
|
609
|
+
* @param MachineClass - The class you defined that extends `MultiMachineBase<C>`.
|
|
610
|
+
* @param store - The `StateStore` that will manage the machine's context.
|
|
611
|
+
* @returns A Proxy that merges context properties with class methods, allowing
|
|
612
|
+
* direct access to both via a unified object interface.
|
|
613
|
+
*
|
|
614
|
+
* @example
|
|
615
|
+
* // Define your context type
|
|
616
|
+
* type CounterContext = { count: number };
|
|
617
|
+
*
|
|
618
|
+
* // Define your machine class
|
|
619
|
+
* class CounterMachine extends MultiMachineBase<CounterContext> {
|
|
620
|
+
* increment() {
|
|
621
|
+
* this.setContext({ count: this.context.count + 1 });
|
|
622
|
+
* }
|
|
623
|
+
*
|
|
624
|
+
* add(n: number) {
|
|
625
|
+
* this.setContext({ count: this.context.count + n });
|
|
626
|
+
* }
|
|
627
|
+
*
|
|
628
|
+
* reset() {
|
|
629
|
+
* this.setContext({ count: 0 });
|
|
630
|
+
* }
|
|
631
|
+
* }
|
|
632
|
+
*
|
|
633
|
+
* // Create a store
|
|
634
|
+
* let sharedContext = { count: 0 };
|
|
635
|
+
* const store = {
|
|
636
|
+
* getContext: () => sharedContext,
|
|
637
|
+
* setContext: (ctx) => { sharedContext = ctx; }
|
|
638
|
+
* };
|
|
639
|
+
*
|
|
640
|
+
* // Create the machine instance
|
|
641
|
+
* const machine = createMultiMachine(CounterMachine, store);
|
|
642
|
+
*
|
|
643
|
+
* // Use it naturally - properties and methods seamlessly integrated
|
|
644
|
+
* console.log(machine.count); // 0
|
|
645
|
+
* machine.increment();
|
|
646
|
+
* console.log(machine.count); // 1
|
|
647
|
+
* machine.add(5);
|
|
648
|
+
* console.log(machine.count); // 6
|
|
649
|
+
* machine.reset();
|
|
650
|
+
* console.log(machine.count); // 0
|
|
651
|
+
*
|
|
652
|
+
* @example
|
|
653
|
+
* // Status-based state machine with type discrimination
|
|
654
|
+
* type AppContext = {
|
|
655
|
+
* status: 'idle' | 'loading' | 'success' | 'error';
|
|
656
|
+
* data?: any;
|
|
657
|
+
* error?: string;
|
|
658
|
+
* };
|
|
659
|
+
*
|
|
660
|
+
* class AppMachine extends MultiMachineBase<AppContext> {
|
|
661
|
+
* async fetch() {
|
|
662
|
+
* this.setContext({ ...this.context, status: 'loading' });
|
|
663
|
+
* try {
|
|
664
|
+
* const data = await fetch('/api/data').then(r => r.json());
|
|
665
|
+
* this.setContext({ status: 'success', data });
|
|
666
|
+
* } catch (error) {
|
|
667
|
+
* this.setContext({
|
|
668
|
+
* status: 'error',
|
|
669
|
+
* error: error instanceof Error ? error.message : 'Unknown error'
|
|
670
|
+
* });
|
|
671
|
+
* }
|
|
672
|
+
* }
|
|
673
|
+
*
|
|
674
|
+
* reset() {
|
|
675
|
+
* this.setContext({ status: 'idle' });
|
|
676
|
+
* }
|
|
677
|
+
* }
|
|
678
|
+
*
|
|
679
|
+
* // Set up
|
|
680
|
+
* let context: AppContext = { status: 'idle' };
|
|
681
|
+
* const store = {
|
|
682
|
+
* getContext: () => context,
|
|
683
|
+
* setContext: (ctx) => { context = ctx; }
|
|
684
|
+
* };
|
|
685
|
+
*
|
|
686
|
+
* const app = createMultiMachine(AppMachine, store);
|
|
687
|
+
*
|
|
688
|
+
* // Use naturally with type discrimination
|
|
689
|
+
* console.log(app.status); // 'idle'
|
|
690
|
+
*
|
|
691
|
+
* if (app.status === 'idle') {
|
|
692
|
+
* app.fetch(); // Transition to loading
|
|
693
|
+
* }
|
|
694
|
+
*
|
|
695
|
+
* // Later: app.status === 'success'
|
|
696
|
+
* // console.log(app.data); // Access the data
|
|
697
|
+
*/
|
|
698
|
+
export declare function createMultiMachine<C extends object, T extends MultiMachineBase<C>>(MachineClass: new (store: StateStore<C>) => T, store: StateStore<C>): C & T;
|
|
699
|
+
/**
|
|
700
|
+
* A mapped type that defines the shape of a Mutable Machine: an intersection
|
|
701
|
+
* of the context `C` and all possible transitions.
|
|
702
|
+
*/
|
|
703
|
+
type MutableMachine<C extends object, AllMachines extends Machine<any>> = C & AllTransitions<AllMachines>;
|
|
704
|
+
/**
|
|
705
|
+
* Creates a Mutable Machine that uses a shared, mutable context. This primitive
|
|
706
|
+
* provides a stable object reference whose properties are mutated in place,
|
|
707
|
+
* offering a direct, imperative API.
|
|
708
|
+
*
|
|
709
|
+
* ---
|
|
710
|
+
*
|
|
711
|
+
* ### Key Characteristics & Trade-offs
|
|
712
|
+
*
|
|
713
|
+
* - **Stable Object Reference**: The machine is a single object. You can pass this
|
|
714
|
+
* reference around, and it will always reflect the current state.
|
|
715
|
+
* - **Direct Imperative API**: Transitions are called like methods directly on the
|
|
716
|
+
* object (`machine.login('user')`), and the object's properties update immediately.
|
|
717
|
+
* - **No State History**: Since the context is mutated, the history of previous
|
|
718
|
+
* states is not preserved, which makes patterns like time-travel debugging impossible.
|
|
719
|
+
* - **Not for Reactive UIs**: Most UI frameworks (React, Solid, Vue) rely on
|
|
720
|
+
* immutable state changes to trigger updates. Mutating the context directly
|
|
721
|
+
* will not cause components to re-render. Use the `Ensemble` primitive for UI integration.
|
|
722
|
+
*
|
|
723
|
+
* ---
|
|
724
|
+
*
|
|
725
|
+
* ### Best Suited For
|
|
726
|
+
*
|
|
727
|
+
* - **Backend Services & Game Logic**: Ideal for managing state in server-side
|
|
728
|
+
* processes, game loops, or other non-UI environments where performance and a
|
|
729
|
+
* stable state object are priorities.
|
|
730
|
+
* - **Complex Synchronous Scripts**: Useful for orchestrating data processing
|
|
731
|
+
* pipelines, command-line tools, or any script where state needs to be managed
|
|
732
|
+
* imperatively without passing it through a function chain.
|
|
733
|
+
*
|
|
734
|
+
* @template C - The shared context type.
|
|
735
|
+
* @template F - An object of functions that create machine instances for each state.
|
|
736
|
+
* **Crucially, transitions inside these machines must be pure functions that
|
|
737
|
+
* return the *next context object*, not a new machine instance.**
|
|
738
|
+
* @param sharedContext - The initial context object. This object will be mutated.
|
|
739
|
+
* @param factories - An object mapping state names to functions that create machine instances.
|
|
740
|
+
* @param getDiscriminant - An accessor function that takes the context and returns the key
|
|
741
|
+
* of the current state in the `factories` object. Provides refactoring safety.
|
|
742
|
+
* @returns A Proxy that acts as a stable, mutable machine instance.
|
|
743
|
+
*
|
|
744
|
+
* @example
|
|
745
|
+
* // ===== 1. Basic Authentication Example =====
|
|
746
|
+
*
|
|
747
|
+
* type AuthContext =
|
|
748
|
+
* | { status: 'loggedOut'; error?: string }
|
|
749
|
+
* | { status: 'loggedIn'; username: string };
|
|
750
|
+
*
|
|
751
|
+
* const authFactories = {
|
|
752
|
+
* loggedOut: (ctx: AuthContext) => ({
|
|
753
|
+
* context: ctx,
|
|
754
|
+
* // This transition is a PURE function that returns the NEXT CONTEXT
|
|
755
|
+
* login: (username: string) => ({ status: 'loggedIn', username }),
|
|
756
|
+
* }),
|
|
757
|
+
* loggedIn: (ctx: AuthContext) => ({
|
|
758
|
+
* context: ctx,
|
|
759
|
+
* logout: () => ({ status: 'loggedOut' }),
|
|
760
|
+
* }),
|
|
761
|
+
* };
|
|
762
|
+
*
|
|
763
|
+
* const authUser = createMutableMachine(
|
|
764
|
+
* { status: 'loggedOut' } as AuthContext,
|
|
765
|
+
* authFactories,
|
|
766
|
+
* 'status'
|
|
767
|
+
* );
|
|
768
|
+
*
|
|
769
|
+
* const userReference = authUser; // Store a reference to the object
|
|
770
|
+
*
|
|
771
|
+
* console.log(authUser.status); // 'loggedOut'
|
|
772
|
+
*
|
|
773
|
+
* authUser.login('alice'); // Mutates the object in place
|
|
774
|
+
*
|
|
775
|
+
* console.log(authUser.status); // 'loggedIn'
|
|
776
|
+
* console.log(authUser.username); // 'alice'
|
|
777
|
+
*
|
|
778
|
+
* // The original reference points to the same, mutated object
|
|
779
|
+
* console.log(userReference.status); // 'loggedIn'
|
|
780
|
+
* console.log(userReference === authUser); // true
|
|
781
|
+
*
|
|
782
|
+
* // --- Type-safe transitions ---
|
|
783
|
+
* // `authUser.login('bob')` would now throw a runtime error because `login`
|
|
784
|
+
* // is not a valid action in the 'loggedIn' state.
|
|
785
|
+
*
|
|
786
|
+
* if (authUser.status === 'loggedIn') {
|
|
787
|
+
* // TypeScript correctly narrows the type here, allowing a safe call.
|
|
788
|
+
* authUser.logout();
|
|
789
|
+
* }
|
|
790
|
+
* console.log(authUser.status); // 'loggedOut'
|
|
791
|
+
*
|
|
792
|
+
* @example
|
|
793
|
+
* // ===== 2. Game State Loop Example =====
|
|
794
|
+
*
|
|
795
|
+
* type PlayerContext = {
|
|
796
|
+
* state: 'idle' | 'walking' | 'attacking';
|
|
797
|
+
* hp: number;
|
|
798
|
+
* position: { x: number; y: number };
|
|
799
|
+
* };
|
|
800
|
+
*
|
|
801
|
+
* const playerFactories = {
|
|
802
|
+
* idle: (ctx: PlayerContext) => ({
|
|
803
|
+
* context: ctx,
|
|
804
|
+
* walk: (dx: number, dy: number) => ({ ...ctx, state: 'walking', position: { x: ctx.position.x + dx, y: ctx.position.y + dy } }),
|
|
805
|
+
* attack: () => ({ ...ctx, state: 'attacking' }),
|
|
806
|
+
* }),
|
|
807
|
+
* walking: (ctx: PlayerContext) => ({
|
|
808
|
+
* context: ctx,
|
|
809
|
+
* stop: () => ({ ...ctx, state: 'idle' }),
|
|
810
|
+
* }),
|
|
811
|
+
* attacking: (ctx: PlayerContext) => ({
|
|
812
|
+
* context: ctx,
|
|
813
|
+
* finishAttack: () => ({ ...ctx, state: 'idle' }),
|
|
814
|
+
* }),
|
|
815
|
+
* };
|
|
816
|
+
*
|
|
817
|
+
* const player = createMutableMachine(
|
|
818
|
+
* { state: 'idle', hp: 100, position: { x: 0, y: 0 } },
|
|
819
|
+
* playerFactories,
|
|
820
|
+
* (ctx) => ctx.state
|
|
821
|
+
* );
|
|
822
|
+
*
|
|
823
|
+
* // Simulate a game loop
|
|
824
|
+
* function processInput(input: 'move_right' | 'attack') {
|
|
825
|
+
* if (player.state === 'idle') {
|
|
826
|
+
* if (input === 'move_right') player.walk(1, 0);
|
|
827
|
+
* if (input === 'attack') player.attack();
|
|
828
|
+
* }
|
|
829
|
+
* console.log(`State: ${player.state}, Position: (${player.position.x}, ${player.position.y})`);
|
|
830
|
+
* }
|
|
831
|
+
*
|
|
832
|
+
* processInput('move_right'); // State: walking, Position: (1, 0)
|
|
833
|
+
* player.stop();
|
|
834
|
+
* processInput('attack'); // State: attacking, Position: (1, 0)
|
|
835
|
+
*/
|
|
836
|
+
export declare function createMutableMachine<C extends object, F extends Record<string, (context: C) => Machine<C>>>(sharedContext: C, factories: F, getDiscriminant: (context: C) => keyof F): MutableMachine<C, ReturnType<F[keyof F]>>;
|
|
837
|
+
export {};
|
|
838
|
+
//# sourceMappingURL=multi.d.ts.map
|