@doeixd/machine 0.0.4
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/LICENSE +7 -0
- package/README.md +1070 -0
- package/dist/cjs/development/index.js +198 -0
- package/dist/cjs/development/index.js.map +7 -0
- package/dist/cjs/production/index.js +1 -0
- package/dist/esm/development/index.js +175 -0
- package/dist/esm/development/index.js.map +7 -0
- package/dist/esm/production/index.js +1 -0
- package/dist/types/generators.d.ts +314 -0
- package/dist/types/generators.d.ts.map +1 -0
- package/dist/types/index.d.ts +339 -0
- package/dist/types/index.d.ts.map +1 -0
- package/package.json +110 -0
- package/src/devtools.ts +74 -0
- package/src/extract.ts +190 -0
- package/src/generators.ts +421 -0
- package/src/index.ts +528 -0
- package/src/primitives.ts +191 -0
- package/src/react.ts +44 -0
- package/src/solid.ts +502 -0
- package/src/test.ts +207 -0
- package/src/utils.ts +167 -0
package/src/utils.ts
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file A collection of high-level, type-safe utility functions for @doeixd/machine.
|
|
3
|
+
* @description These helpers provide ergonomic improvements for common patterns like
|
|
4
|
+
* state checking, event creation, debugging, and composing transitions.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
Machine,
|
|
9
|
+
AsyncMachine,
|
|
10
|
+
MaybePromise,
|
|
11
|
+
Context,
|
|
12
|
+
Event,
|
|
13
|
+
Transitions,
|
|
14
|
+
TransitionArgs,
|
|
15
|
+
setContext,
|
|
16
|
+
} from './index'; // Assuming index.ts is in the same directory
|
|
17
|
+
|
|
18
|
+
// =============================================================================
|
|
19
|
+
// SECTION: STATE & TYPE GUARDS
|
|
20
|
+
// =============================================================================
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A type representing a Class Constructor, used for type guards.
|
|
24
|
+
*/
|
|
25
|
+
type ClassConstructor = new (...args: any[]) => any;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A type-safe way to check if a machine is in a specific state, acting as a Type Guard.
|
|
29
|
+
* This is the preferred way to do state checking when using class-based machines.
|
|
30
|
+
*
|
|
31
|
+
* @template T - The class constructor type to check against.
|
|
32
|
+
* @param machine - The machine instance to check.
|
|
33
|
+
* @param machineClass - The class constructor representing the state.
|
|
34
|
+
* @returns {boolean} `true` if the machine is an instance of the class, narrowing its type.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* declare const machine: LoggedInMachine | LoggedOutMachine;
|
|
38
|
+
*
|
|
39
|
+
* if (isState(machine, LoggedInMachine)) {
|
|
40
|
+
* // `machine` is now correctly typed as LoggedInMachine
|
|
41
|
+
* machine.logout();
|
|
42
|
+
* }
|
|
43
|
+
*/
|
|
44
|
+
export function isState<T extends ClassConstructor>(
|
|
45
|
+
machine: any,
|
|
46
|
+
machineClass: T
|
|
47
|
+
): machine is InstanceType<T> {
|
|
48
|
+
return machine instanceof machineClass;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
// =============================================================================
|
|
53
|
+
// SECTION: EVENT & DISPATCH HELPERS
|
|
54
|
+
// =============================================================================
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* A type-safe factory function for creating event objects for `runMachine`.
|
|
58
|
+
* This provides full autocompletion and type checking for event names and their arguments.
|
|
59
|
+
*
|
|
60
|
+
* @template M - The machine type the event belongs to.
|
|
61
|
+
* @template K - The specific event name (transition method name).
|
|
62
|
+
* @param type - The name of the event (e.g., "increment").
|
|
63
|
+
* @param args - The arguments for that event, correctly typed.
|
|
64
|
+
* @returns A type-safe event object ready to be passed to `dispatch`.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* // Given: type MyMachine = Machine<{...}> & { add: (n: number) => any }
|
|
68
|
+
* const event = createEvent<MyMachine, 'add'>('add', 5);
|
|
69
|
+
* // `event` is correctly typed as { type: "add"; args: [number] }
|
|
70
|
+
*
|
|
71
|
+
* await runner.dispatch(event);
|
|
72
|
+
*/
|
|
73
|
+
export function createEvent<
|
|
74
|
+
M extends Machine<any>,
|
|
75
|
+
K extends keyof Transitions<M> & string
|
|
76
|
+
>(type: K, ...args: TransitionArgs<M, K>): Event<M> {
|
|
77
|
+
return { type, args } as unknown as Event<M>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
// =============================================================================
|
|
82
|
+
// SECTION: CONTEXT & STATE MANIPULATION
|
|
83
|
+
// =============================================================================
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Creates a new machine instance by shallowly merging a partial context into the
|
|
87
|
+
* current context, preserving all original transitions.
|
|
88
|
+
*
|
|
89
|
+
* @template M - The machine type.
|
|
90
|
+
* @param machine - The original machine instance.
|
|
91
|
+
* @param partialContext - An object with a subset of context properties to update.
|
|
92
|
+
* @returns A new machine instance of the same type with the merged context.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* const user = new User({ name: 'Alex', age: 30, status: 'active' });
|
|
96
|
+
* const updatedUser = mergeContext(user, { status: 'inactive' });
|
|
97
|
+
* // updatedUser.context is { name: 'Alex', age: 30, status: 'inactive' }
|
|
98
|
+
*/
|
|
99
|
+
export function mergeContext<M extends Machine<any>>(
|
|
100
|
+
machine: M,
|
|
101
|
+
partialContext: Partial<Context<M>>
|
|
102
|
+
): M {
|
|
103
|
+
return setContext(machine, (ctx) => ({ ...ctx, ...partialContext }));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
// =============================================================================
|
|
108
|
+
// SECTION: COMPOSITION & DEBUGGING
|
|
109
|
+
// =============================================================================
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Sequentially applies a series of transitions to a machine.
|
|
113
|
+
* This function correctly handles both synchronous and asynchronous transitions,
|
|
114
|
+
* always returning a Promise with the final machine state.
|
|
115
|
+
*
|
|
116
|
+
* @template M - The machine type, must be compatible with AsyncMachine.
|
|
117
|
+
* @param initialMachine - The starting machine state.
|
|
118
|
+
* @param transitions - An array of functions, each taking a machine and returning the next.
|
|
119
|
+
* @returns A `Promise` that resolves to the final machine state after all transitions complete.
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* const finalState = await pipeTransitions(
|
|
123
|
+
* new Counter({ count: 0 }),
|
|
124
|
+
* (m) => m.increment(), // sync
|
|
125
|
+
* (m) => m.addAsync(5), // async
|
|
126
|
+
* (m) => m.increment() // sync
|
|
127
|
+
* );
|
|
128
|
+
* // finalState.context.count will be 6
|
|
129
|
+
*/
|
|
130
|
+
export async function pipeTransitions<M extends AsyncMachine<any>>(
|
|
131
|
+
initialMachine: M,
|
|
132
|
+
...transitions: ((m: M) => MaybePromise<M>)[]
|
|
133
|
+
): Promise<M> {
|
|
134
|
+
let current: M = initialMachine;
|
|
135
|
+
for (const transitionFn of transitions) {
|
|
136
|
+
current = await transitionFn(current);
|
|
137
|
+
}
|
|
138
|
+
return current;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* A "tap" utility for logging a machine's context without interrupting a chain of operations.
|
|
143
|
+
* It prints the context to the console and returns the machine instance unchanged.
|
|
144
|
+
*
|
|
145
|
+
* @template M - The machine type.
|
|
146
|
+
* @param machine - The machine instance to log.
|
|
147
|
+
* @param label - An optional label to print before the context object.
|
|
148
|
+
* @returns The original, unmodified machine instance.
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* import { logState as tap } from './utils';
|
|
152
|
+
*
|
|
153
|
+
* await pipeTransitions(
|
|
154
|
+
* new Counter({ count: 0 }),
|
|
155
|
+
* tap, // Logs: { count: 0 }
|
|
156
|
+
* (m) => m.increment(),
|
|
157
|
+
* (m) => tap(m, 'After increment:') // Logs: After increment: { count: 1 }
|
|
158
|
+
* );
|
|
159
|
+
*/
|
|
160
|
+
export function logState<M extends Machine<any>>(machine: M, label?: string): M {
|
|
161
|
+
if (label) {
|
|
162
|
+
console.log(label, machine.context);
|
|
163
|
+
} else {
|
|
164
|
+
console.log(machine.context);
|
|
165
|
+
}
|
|
166
|
+
return machine;
|
|
167
|
+
}
|