@doeixd/machine 0.0.11 → 0.0.12
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/cjs/development/index.js +55 -0
- package/dist/cjs/development/index.js.map +4 -4
- package/dist/cjs/production/index.js +4 -4
- package/dist/esm/development/index.js +55 -0
- package/dist/esm/development/index.js.map +4 -4
- package/dist/esm/production/index.js +3 -3
- package/dist/types/functional-combinators.d.ts +116 -0
- package/dist/types/functional-combinators.d.ts.map +1 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/multi.d.ts +14 -0
- package/dist/types/multi.d.ts.map +1 -1
- package/dist/types/utils.d.ts +63 -0
- package/dist/types/utils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/functional-combinators.ts +267 -0
- package/src/index.ts +12 -1
- package/src/multi.ts +36 -0
- package/src/utils.ts +74 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { createMachine, Machine, extendTransitions } from './index';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates a factory for building type-safe transitions for a specific machine.
|
|
5
|
+
* This higher-order function captures the machine's `transitions` object in a closure,
|
|
6
|
+
* enabling a clean, functional pattern for defining state changes without directly
|
|
7
|
+
* manipulating the machine's context.
|
|
8
|
+
*
|
|
9
|
+
* This pattern promotes:
|
|
10
|
+
* - Pure functions for state transformations
|
|
11
|
+
* - Separation of transition logic from machine construction
|
|
12
|
+
* - Reusable transition factories across similar machines
|
|
13
|
+
* - Type safety through generic constraints
|
|
14
|
+
*
|
|
15
|
+
* @template C The context type of the machine
|
|
16
|
+
* @template C The context type of the machine
|
|
17
|
+
* @returns A `createTransition` function that can create transitions for machines with context type C
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* // Define your machine's transitions object
|
|
22
|
+
* const counterTransitions = {
|
|
23
|
+
* increment: function(amount: number) {
|
|
24
|
+
* return createMachine({ count: this.count + amount }, counterTransitions);
|
|
25
|
+
* }
|
|
26
|
+
* };
|
|
27
|
+
*
|
|
28
|
+
* // Create a transition factory
|
|
29
|
+
* const createCounterTransition = createTransitionFactory<{ count: number }>();
|
|
30
|
+
*
|
|
31
|
+
* // Use the factory to create pure, type-safe transitions
|
|
32
|
+
* const incrementBy = createCounterTransition(
|
|
33
|
+
* (ctx, amount: number) => ({ count: ctx.count + amount })
|
|
34
|
+
* );
|
|
35
|
+
*
|
|
36
|
+
* const counter = createMachine({ count: 0 }, counterTransitions);
|
|
37
|
+
* const newCounter = counter.increment(5); // Direct call
|
|
38
|
+
* // OR
|
|
39
|
+
* const incremented = incrementBy.call(counter, 5); // Using factory
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export function createTransitionFactory<C extends object>() {
|
|
43
|
+
/**
|
|
44
|
+
* Takes a pure context transformer function and returns a full, type-safe
|
|
45
|
+
* machine transition method that can be attached to a machine.
|
|
46
|
+
*
|
|
47
|
+
* The transformer function receives the current context as its first argument,
|
|
48
|
+
* followed by any additional arguments passed to the transition.
|
|
49
|
+
*
|
|
50
|
+
* @template TArgs The argument types for the transition (excluding context)
|
|
51
|
+
* @param transformer A pure function: `(context, ...args) => nextContext`
|
|
52
|
+
* @returns A machine transition method that can be called on a machine instance
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```typescript
|
|
56
|
+
* const createTodoTransition = createTransitionFactory<TodoContext>();
|
|
57
|
+
*
|
|
58
|
+
* const addTodo = createTodoTransition(
|
|
59
|
+
* (ctx, text: string) => ({
|
|
60
|
+
* ...ctx,
|
|
61
|
+
* todos: [...ctx.todos, { id: Date.now(), text, completed: false }]
|
|
62
|
+
* })
|
|
63
|
+
* );
|
|
64
|
+
*
|
|
65
|
+
* const updateTodo = createTodoTransition(
|
|
66
|
+
* (ctx, id: number, updates: Partial<Todo>) => ({
|
|
67
|
+
* ...ctx,
|
|
68
|
+
* todos: ctx.todos.map(todo =>
|
|
69
|
+
* todo.id === id ? { ...todo, ...updates } : todo
|
|
70
|
+
* )
|
|
71
|
+
* })
|
|
72
|
+
* );
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
return function createTransition<TArgs extends any[]>(
|
|
76
|
+
transformer: (ctx: C, ...args: TArgs) => C
|
|
77
|
+
) {
|
|
78
|
+
return function (this: Machine<C>, ...args: TArgs): Machine<C> {
|
|
79
|
+
const nextContext = transformer(this.context, ...args);
|
|
80
|
+
// Use `this` as the transitions object, which includes all current transitions
|
|
81
|
+
return createMachine(nextContext, this);
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Creates a factory for adding new, type-safe transitions to an existing machine instance.
|
|
88
|
+
* This enables a functional, compositional approach to building up a machine's capabilities
|
|
89
|
+
* incrementally, without modifying the original machine.
|
|
90
|
+
*
|
|
91
|
+
* This pattern supports:
|
|
92
|
+
* - Progressive enhancement of machine behavior
|
|
93
|
+
* - Plugin-like extension of existing machines
|
|
94
|
+
* - Immutable composition (original machine unchanged)
|
|
95
|
+
* - Type-safe addition of new transitions
|
|
96
|
+
*
|
|
97
|
+
* @template M The machine type being extended
|
|
98
|
+
* @param machine The machine instance to extend
|
|
99
|
+
* @returns An `addTransition` function pre-configured for this machine
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```typescript
|
|
103
|
+
* // Start with a basic counter machine
|
|
104
|
+
* const basicCounter = createMachine({ count: 0 }, {
|
|
105
|
+
* increment: function() {
|
|
106
|
+
* return createMachine({ count: this.count + 1 }, this);
|
|
107
|
+
* }
|
|
108
|
+
* });
|
|
109
|
+
*
|
|
110
|
+
* // Create an extender for this machine
|
|
111
|
+
* const extendCounter = createTransitionExtender(basicCounter);
|
|
112
|
+
*
|
|
113
|
+
* // Add new transitions functionally
|
|
114
|
+
* const extendedCounter = extendCounter('decrement',
|
|
115
|
+
* (ctx) => ({ count: ctx.count - 1 })
|
|
116
|
+
* ).addTransition('reset',
|
|
117
|
+
* (ctx) => ({ count: 0 })
|
|
118
|
+
* ).addTransition('add',
|
|
119
|
+
* (ctx, amount: number) => ({ count: ctx.count + amount })
|
|
120
|
+
* );
|
|
121
|
+
*
|
|
122
|
+
* // The original machine is unchanged
|
|
123
|
+
* console.log(basicCounter.count); // 0
|
|
124
|
+
*
|
|
125
|
+
* // The extended machine has all transitions
|
|
126
|
+
* const result = extendedCounter.increment().add(10).decrement();
|
|
127
|
+
* console.log(result.count); // 10
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
export function createTransitionExtender<M extends Machine<any>>(machine: M) {
|
|
131
|
+
type C = M['context'];
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Adds a new transition to the machine and returns a new extender for chaining.
|
|
135
|
+
* The new transition is created from a pure context transformer function.
|
|
136
|
+
*
|
|
137
|
+
* @template TName The name of the new transition
|
|
138
|
+
* @template TArgs The argument types for the new transition
|
|
139
|
+
* @param name The name of the new transition method
|
|
140
|
+
* @param transformer A pure function that defines how the context should change
|
|
141
|
+
* @returns A new transition extender with the added transition
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```typescript
|
|
145
|
+
* const userMachine = createMachine({ name: '', email: '' }, {});
|
|
146
|
+
* const extendUser = createTransitionExtender(userMachine);
|
|
147
|
+
*
|
|
148
|
+
* const withValidation = extendUser.addTransition('setName',
|
|
149
|
+
* (ctx, name: string) => {
|
|
150
|
+
* if (name.length < 2) throw new Error('Name too short');
|
|
151
|
+
* return { ...ctx, name };
|
|
152
|
+
* }
|
|
153
|
+
* ).addTransition('setEmail',
|
|
154
|
+
* (ctx, email: string) => {
|
|
155
|
+
* if (!email.includes('@')) throw new Error('Invalid email');
|
|
156
|
+
* return { ...ctx, email };
|
|
157
|
+
* }
|
|
158
|
+
* );
|
|
159
|
+
*
|
|
160
|
+
* const user = withValidation.machine.setName('John').setEmail('john@example.com');
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
return {
|
|
164
|
+
machine,
|
|
165
|
+
|
|
166
|
+
addTransition: function<
|
|
167
|
+
TName extends string,
|
|
168
|
+
TArgs extends any[],
|
|
169
|
+
>(
|
|
170
|
+
name: TName,
|
|
171
|
+
transformer: (ctx: C, ...args: TArgs) => C
|
|
172
|
+
) {
|
|
173
|
+
const transitionFn = function (this: Machine<C>, ...args: TArgs) {
|
|
174
|
+
const nextContext = transformer(this.context, ...args);
|
|
175
|
+
|
|
176
|
+
// Use `this` as the transitions object, which includes all current transitions
|
|
177
|
+
return createMachine(nextContext, this);
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// Create the extended machine
|
|
181
|
+
const newMachine = extendTransitions(machine, { [name]: transitionFn } as any);
|
|
182
|
+
|
|
183
|
+
// Return a new extender that includes this transition
|
|
184
|
+
return createTransitionExtender(newMachine);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* A mapped type that creates the final transition method signatures based on
|
|
191
|
+
* an object of pure context transformers. It infers argument types and sets
|
|
192
|
+
* the correct return type.
|
|
193
|
+
*/
|
|
194
|
+
type MachineTransitions<
|
|
195
|
+
T extends Record<string, (ctx: C, ...args: any[]) => C>,
|
|
196
|
+
C extends object
|
|
197
|
+
> = {
|
|
198
|
+
[K in keyof T]: T[K] extends (ctx: C, ...args: infer A) => C
|
|
199
|
+
? (this: { context: C } & T, ...args: A) => Machine<C>
|
|
200
|
+
: never;
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Creates a complete, type-safe, functional state machine using a curried, two-step
|
|
205
|
+
* approach that separates the initial data from the transition logic.
|
|
206
|
+
*
|
|
207
|
+
* This is a highly declarative and functional pattern for building single-state machines.
|
|
208
|
+
*
|
|
209
|
+
* @template C The context type of the machine.
|
|
210
|
+
* @param initialContext The starting context (data) for the machine.
|
|
211
|
+
* @returns A new function that takes an object of pure context-transformer
|
|
212
|
+
* functions and returns a fully-formed machine instance.
|
|
213
|
+
*/
|
|
214
|
+
export function createFunctionalMachine<C extends object>(initialContext: C) {
|
|
215
|
+
/**
|
|
216
|
+
* This returned function is pre-configured with the `initialContext`.
|
|
217
|
+
*
|
|
218
|
+
* @template T The type of the transformers object.
|
|
219
|
+
* @param transformers An object where each key is a transition name and each value
|
|
220
|
+
* is a pure function: `(context, ...args) => nextContext`.
|
|
221
|
+
* @returns A fully-formed, immutable, and type-safe machine instance.
|
|
222
|
+
*
|
|
223
|
+
* @example
|
|
224
|
+
* ```typescript
|
|
225
|
+
* const createCounter = createFunctionalMachine({ count: 0 });
|
|
226
|
+
*
|
|
227
|
+
* const counter = createCounter({
|
|
228
|
+
* increment: (ctx) => ({ count: ctx.count + 1 }),
|
|
229
|
+
* decrement: (ctx) => ({ count: ctx.count - 1 }),
|
|
230
|
+
* add: (ctx, amount: number) => ({ count: ctx.count + amount }),
|
|
231
|
+
* reset: (ctx) => ({ count: 0 })
|
|
232
|
+
* });
|
|
233
|
+
*
|
|
234
|
+
* // Use the machine
|
|
235
|
+
* const updated = counter.increment().add(5).decrement();
|
|
236
|
+
* console.log(updated.context.count); // 5
|
|
237
|
+
* ```
|
|
238
|
+
*/
|
|
239
|
+
return function withTransitions<
|
|
240
|
+
T extends Record<string, (ctx: C, ...args: any[]) => C>
|
|
241
|
+
>(
|
|
242
|
+
transformers: T
|
|
243
|
+
): Machine<C> & MachineTransitions<T, C> {
|
|
244
|
+
// 1. Create a placeholder object for the final transitions.
|
|
245
|
+
const transitions: any = {};
|
|
246
|
+
|
|
247
|
+
// 2. Map the pure transformers to full machine transition methods.
|
|
248
|
+
const machineTransitions = Object.fromEntries(
|
|
249
|
+
Object.entries(transformers).map(([key, transformer]) => [
|
|
250
|
+
key,
|
|
251
|
+
function (this: { context: C }, ...args: any[]) {
|
|
252
|
+
// Apply the pure data transformation.
|
|
253
|
+
const nextContext = transformer(this.context, ...args);
|
|
254
|
+
// Return a new machine, passing in the `transitions` object from the closure.
|
|
255
|
+
// At this point, `transitions` will be fully populated.
|
|
256
|
+
return createMachine(nextContext, transitions);
|
|
257
|
+
},
|
|
258
|
+
])
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
// 3. Populate the placeholder with the real transitions.
|
|
262
|
+
Object.assign(transitions, machineTransitions);
|
|
263
|
+
|
|
264
|
+
// 4. Create and return the initial machine instance using the provided context.
|
|
265
|
+
return createMachine(initialContext, transitions) as any;
|
|
266
|
+
};
|
|
267
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -778,10 +778,21 @@ export {
|
|
|
778
778
|
export {
|
|
779
779
|
isState,
|
|
780
780
|
createEvent,
|
|
781
|
+
createTransition,
|
|
781
782
|
mergeContext,
|
|
782
783
|
pipeTransitions,
|
|
783
784
|
logState,
|
|
784
785
|
call,
|
|
785
786
|
bindTransitions,
|
|
786
787
|
BoundMachine
|
|
787
|
-
} from './utils';
|
|
788
|
+
} from './utils';
|
|
789
|
+
|
|
790
|
+
// =============================================================================
|
|
791
|
+
// SECTION: FUNCTIONAL COMBINATORS
|
|
792
|
+
// =============================================================================
|
|
793
|
+
|
|
794
|
+
export {
|
|
795
|
+
createTransitionFactory,
|
|
796
|
+
createTransitionExtender,
|
|
797
|
+
createFunctionalMachine
|
|
798
|
+
} from './functional-combinators';
|
package/src/multi.ts
CHANGED
|
@@ -514,6 +514,42 @@ export function createEnsemble<
|
|
|
514
514
|
};
|
|
515
515
|
}
|
|
516
516
|
|
|
517
|
+
/**
|
|
518
|
+
* Creates a factory for building type-safe, framework-agnostic Ensembles.
|
|
519
|
+
* This is a higher-order function that captures the application's state store
|
|
520
|
+
* and state-discriminant logic in a closure.
|
|
521
|
+
*
|
|
522
|
+
* This allows you to define your application's state "environment" once and then
|
|
523
|
+
* easily create multiple, consistent ensembles by only providing the behavioral logic.
|
|
524
|
+
*
|
|
525
|
+
* @template C The shared context type for the application.
|
|
526
|
+
* @param store The application's state store (e.g., from React, Zustand, etc.).
|
|
527
|
+
* @param getDiscriminant An accessor function that determines the current state from the context.
|
|
528
|
+
* @returns A `withFactories` function that is pre-configured for your app's environment.
|
|
529
|
+
*/
|
|
530
|
+
export function createEnsembleFactory<C extends object>(
|
|
531
|
+
store: StateStore<C>,
|
|
532
|
+
getDiscriminant: (context: C) => keyof any
|
|
533
|
+
) {
|
|
534
|
+
/**
|
|
535
|
+
* This returned function is pre-configured with the `store` and `getDiscriminant` logic.
|
|
536
|
+
* It takes the machine factories (the behavioral logic) and returns a complete Ensemble.
|
|
537
|
+
*
|
|
538
|
+
* @template F The type of the factories object.
|
|
539
|
+
* @param factories An object where each key is a state name and each value is a
|
|
540
|
+
* function that creates a machine instance for that state.
|
|
541
|
+
* @returns A fully-formed, reactive, and type-safe Ensemble instance.
|
|
542
|
+
*/
|
|
543
|
+
return function withFactories<
|
|
544
|
+
F extends Record<string, (context: C) => Machine<C>>
|
|
545
|
+
>(
|
|
546
|
+
factories: F
|
|
547
|
+
): Ensemble<ReturnType<F[keyof F]>, C> {
|
|
548
|
+
// We simply call the original createEnsemble with the captured arguments.
|
|
549
|
+
return createEnsemble(store, factories, getDiscriminant as (context: C) => keyof F);
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
|
|
517
553
|
// =============================================================================
|
|
518
554
|
// SECTION 3: GENERATOR INTEGRATION
|
|
519
555
|
// =============================================================================
|
package/src/utils.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
Transitions,
|
|
14
14
|
TransitionArgs,
|
|
15
15
|
setContext,
|
|
16
|
+
createMachine,
|
|
16
17
|
} from './index'; // Assuming index.ts is in the same directory
|
|
17
18
|
|
|
18
19
|
// =============================================================================
|
|
@@ -166,6 +167,79 @@ export function logState<M extends Machine<any>>(machine: M, label?: string): M
|
|
|
166
167
|
return machine;
|
|
167
168
|
}
|
|
168
169
|
|
|
170
|
+
/**
|
|
171
|
+
* A generic combinator that creates transition functions from pure context transformers.
|
|
172
|
+
* This enables writing transitions as simple, testable functions that only transform context,
|
|
173
|
+
* while automatically handling the machine creation boilerplate.
|
|
174
|
+
*
|
|
175
|
+
* @template C - The context object type.
|
|
176
|
+
* @template TArgs - The argument types for the transition function.
|
|
177
|
+
* @param getTransitions - A function that returns the transition functions object to use for the new machine.
|
|
178
|
+
* @param transformer - A pure function that transforms the context based on the current state and arguments.
|
|
179
|
+
* @returns A transition function that can be used as a machine method.
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* ```typescript
|
|
183
|
+
* // Define transitions object with self-reference
|
|
184
|
+
* const counterTransitions = {
|
|
185
|
+
* increment: createTransition(
|
|
186
|
+
* () => counterTransitions,
|
|
187
|
+
* (ctx) => ({ count: ctx.count + 1 })
|
|
188
|
+
* ),
|
|
189
|
+
* add: createTransition(
|
|
190
|
+
* () => counterTransitions,
|
|
191
|
+
* (ctx, n: number) => ({ count: ctx.count + n })
|
|
192
|
+
* )
|
|
193
|
+
* };
|
|
194
|
+
*
|
|
195
|
+
* // Create machine
|
|
196
|
+
* const counter = createMachine({ count: 0 }, counterTransitions);
|
|
197
|
+
*
|
|
198
|
+
* // Use transitions
|
|
199
|
+
* const incremented = counter.increment(); // { count: 1 }
|
|
200
|
+
* const added = incremented.add(5); // { count: 6 }
|
|
201
|
+
* ```
|
|
202
|
+
*
|
|
203
|
+
* @example
|
|
204
|
+
* ```typescript
|
|
205
|
+
* // With class-based machines
|
|
206
|
+
* class Counter extends MachineBase<{ count: number }> {
|
|
207
|
+
* constructor(count = 0) {
|
|
208
|
+
* super({ count });
|
|
209
|
+
* }
|
|
210
|
+
*
|
|
211
|
+
* increment = createTransition(
|
|
212
|
+
* () => ({ increment: this.increment, add: this.add }),
|
|
213
|
+
* (ctx) => ({ count: ctx.count + 1 })
|
|
214
|
+
* );
|
|
215
|
+
*
|
|
216
|
+
* add = createTransition(
|
|
217
|
+
* () => ({ increment: this.increment, add: this.add }),
|
|
218
|
+
* (ctx, n: number) => ({ count: ctx.count + n })
|
|
219
|
+
* );
|
|
220
|
+
* }
|
|
221
|
+
* ```
|
|
222
|
+
*
|
|
223
|
+
* @remarks
|
|
224
|
+
* This function promotes the library's philosophy of pure, immutable transitions.
|
|
225
|
+
* The transformer function should be pure and only depend on its parameters.
|
|
226
|
+
* The returned transition function automatically creates a new machine instance,
|
|
227
|
+
* preserving all transitions while updating only the context.
|
|
228
|
+
* The getTransitions function is called lazily to avoid circular reference issues.
|
|
229
|
+
*/
|
|
230
|
+
export function createTransition<
|
|
231
|
+
C extends object,
|
|
232
|
+
TArgs extends any[]
|
|
233
|
+
>(
|
|
234
|
+
getTransitions: () => Record<string, (this: C, ...args: any[]) => any>,
|
|
235
|
+
transformer: (ctx: C, ...args: TArgs) => C
|
|
236
|
+
): (this: { context: C }, ...args: TArgs) => Machine<C> {
|
|
237
|
+
return function (this: { context: C }, ...args: TArgs): Machine<C> {
|
|
238
|
+
const nextContext = transformer(this.context, ...args);
|
|
239
|
+
return createMachine(nextContext, getTransitions());
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
169
243
|
// =============================================================================
|
|
170
244
|
// SECTION: TRANSITION BINDING HELPERS
|
|
171
245
|
// =============================================================================
|