@doeixd/machine 0.0.23 → 1.0.2
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 +101 -65
- package/dist/cjs/development/core.js +56 -57
- package/dist/cjs/development/core.js.map +4 -4
- package/dist/cjs/development/index.js +99 -58
- package/dist/cjs/development/index.js.map +4 -4
- package/dist/cjs/development/react.js +56 -58
- package/dist/cjs/development/react.js.map +4 -4
- package/dist/cjs/production/core.js +1 -1
- package/dist/cjs/production/index.js +3 -3
- package/dist/cjs/production/react.js +1 -1
- package/dist/esm/development/core.js +56 -57
- package/dist/esm/development/core.js.map +4 -4
- package/dist/esm/development/index.js +99 -58
- package/dist/esm/development/index.js.map +4 -4
- package/dist/esm/development/react.js +56 -58
- package/dist/esm/development/react.js.map +4 -4
- package/dist/esm/production/core.js +1 -1
- package/dist/esm/production/index.js +3 -3
- package/dist/esm/production/react.js +1 -1
- package/dist/types/actor.d.ts +4 -4
- package/dist/types/actor.d.ts.map +1 -1
- package/dist/types/context-bound.d.ts +94 -0
- package/dist/types/context-bound.d.ts.map +1 -0
- package/dist/types/entry-react.d.ts +1 -1
- package/dist/types/entry-react.d.ts.map +1 -1
- package/dist/types/functional-combinators.d.ts +5 -5
- package/dist/types/generators.d.ts +2 -2
- package/dist/types/index.d.ts +14 -34
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/internal-transitions.d.ts +5 -0
- package/dist/types/internal-transitions.d.ts.map +1 -0
- package/dist/types/primitives.d.ts +25 -5
- package/dist/types/primitives.d.ts.map +1 -1
- package/dist/types/react.d.ts.map +1 -1
- package/dist/types/utils.d.ts +22 -22
- package/dist/types/utils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/actor.ts +1 -1
- package/src/context-bound.ts +160 -0
- package/src/entry-react.ts +9 -2
- package/src/functional-combinators.ts +5 -5
- package/src/generators.ts +2 -2
- package/src/higher-order.ts +2 -2
- package/src/index.ts +47 -80
- package/src/internal-transitions.ts +32 -0
- package/src/middleware/time-travel.ts +2 -2
- package/src/middleware.ts +2 -2
- package/src/multi.ts +4 -4
- package/src/primitives.ts +34 -14
- package/src/prototype_functional.ts +2 -2
- package/src/react.ts +1 -2
- package/src/test.ts +7 -7
- package/src/utils.ts +31 -31
package/src/primitives.ts
CHANGED
|
@@ -354,6 +354,14 @@ export type GuardedTransition<
|
|
|
354
354
|
* Creates a synchronous runtime guard that checks conditions before executing transitions.
|
|
355
355
|
* This provides actual runtime protection with synchronous execution - use this for the majority of cases.
|
|
356
356
|
*
|
|
357
|
+
* **IMPORTANT - Context-Bound Limitation:**
|
|
358
|
+
* Guards accept calls with either `this === machine` or `this === context`, but when called
|
|
359
|
+
* with context-only binding, the guard normalizes to `{ context }` before passing to the transition.
|
|
360
|
+
* This means:
|
|
361
|
+
* - ✅ Transitions can access `this.context`
|
|
362
|
+
* - ❌ Transitions CANNOT call `this.otherTransition()` (no transitions property)
|
|
363
|
+
* - Recommended: Use guards only with machine-bound transitions for full composition support
|
|
364
|
+
*
|
|
357
365
|
* @template C - The context type
|
|
358
366
|
* @template TSuccess - The transition return type when condition passes
|
|
359
367
|
* @template TFailure - The fallback return type when condition fails (defaults to Machine<C>)
|
|
@@ -367,8 +375,10 @@ export type GuardedTransition<
|
|
|
367
375
|
* const machine = createMachine({ balance: 100 }, {
|
|
368
376
|
* withdraw: guard(
|
|
369
377
|
* (ctx, amount) => ctx.balance >= amount,
|
|
370
|
-
* function(amount: number) {
|
|
371
|
-
*
|
|
378
|
+
* function(this: Machine<{balance: number}>, amount: number) {
|
|
379
|
+
* // ✅ Can access this.context
|
|
380
|
+
* return createMachine({ balance: this.context.balance - amount }, this);
|
|
381
|
+
* // ❌ Cannot call this.otherTransition() if guard was called with context-only binding
|
|
372
382
|
* },
|
|
373
383
|
* { onFail: 'throw', errorMessage: 'Insufficient funds' }
|
|
374
384
|
* )
|
|
@@ -403,9 +413,9 @@ export function guard<
|
|
|
403
413
|
|
|
404
414
|
if (conditionResult) {
|
|
405
415
|
// Condition passed, execute the transition
|
|
406
|
-
// Transition functions expect 'this' to be the
|
|
407
|
-
const
|
|
408
|
-
return transition.apply(
|
|
416
|
+
// Transition functions expect 'this' to be the machine
|
|
417
|
+
const machineForTransition = isMachine ? (this as Machine<C>) : { context: this as C };
|
|
418
|
+
return transition.apply(machineForTransition, args);
|
|
409
419
|
} else {
|
|
410
420
|
// Condition failed, handle according to options
|
|
411
421
|
if (onFail === 'throw') {
|
|
@@ -453,6 +463,14 @@ export function guard<
|
|
|
453
463
|
* This provides actual runtime protection, unlike the `guarded` primitive which only adds metadata.
|
|
454
464
|
* Use this when your condition or transition logic is asynchronous.
|
|
455
465
|
*
|
|
466
|
+
* **IMPORTANT - Context-Bound Limitation:**
|
|
467
|
+
* Guards accept calls with either `this === machine` or `this === context`, but when called
|
|
468
|
+
* with context-only binding, the guard normalizes to `{ context }` before passing to the transition.
|
|
469
|
+
* This means:
|
|
470
|
+
* - ✅ Transitions can access `this.context`
|
|
471
|
+
* - ❌ Transitions CANNOT call `this.otherTransition()` (no transitions property)
|
|
472
|
+
* - Recommended: Use guards only with machine-bound transitions for full composition support
|
|
473
|
+
*
|
|
456
474
|
* @template C - The context type
|
|
457
475
|
* @template TSuccess - The transition return type when condition passes
|
|
458
476
|
* @template TFailure - The fallback return type when condition fails (defaults to Machine<C>)
|
|
@@ -470,10 +488,12 @@ export function guard<
|
|
|
470
488
|
* await new Promise(resolve => setTimeout(resolve, 100));
|
|
471
489
|
* return ctx.balance >= amount;
|
|
472
490
|
* },
|
|
473
|
-
* async function(amount: number) {
|
|
491
|
+
* async function(this: Machine<{balance: number}>, amount: number) {
|
|
474
492
|
* // Simulate API call to process withdrawal
|
|
475
493
|
* await new Promise(resolve => setTimeout(resolve, 100));
|
|
476
|
-
*
|
|
494
|
+
* // ✅ Can access this.context
|
|
495
|
+
* return createMachine({ balance: this.context.balance - amount }, this);
|
|
496
|
+
* // ❌ Cannot call this.otherTransition() if guard was called with context-only binding
|
|
477
497
|
* },
|
|
478
498
|
* { onFail: 'throw', errorMessage: 'Insufficient funds' }
|
|
479
499
|
* )
|
|
@@ -508,9 +528,9 @@ export function guardAsync<
|
|
|
508
528
|
|
|
509
529
|
if (conditionResult) {
|
|
510
530
|
// Condition passed, execute the transition
|
|
511
|
-
// Transition functions expect 'this' to be the
|
|
512
|
-
const
|
|
513
|
-
return transition.apply(
|
|
531
|
+
// Transition functions expect 'this' to be the machine
|
|
532
|
+
const machineForTransition = isMachine ? (this as Machine<C>) : { context: this as C };
|
|
533
|
+
return transition.apply(machineForTransition, args);
|
|
514
534
|
} else {
|
|
515
535
|
// Condition failed, handle according to options
|
|
516
536
|
if (onFail === 'throw') {
|
|
@@ -571,7 +591,7 @@ export function guardAsync<
|
|
|
571
591
|
* withdraw: guardSync(
|
|
572
592
|
* (ctx, amount) => ctx.balance >= amount,
|
|
573
593
|
* function(amount: number) {
|
|
574
|
-
* return createMachine({ balance: this.balance - amount }, this);
|
|
594
|
+
* return createMachine({ balance: this.context.balance - amount }, this);
|
|
575
595
|
* },
|
|
576
596
|
* { onFail: 'throw', errorMessage: 'Insufficient funds' }
|
|
577
597
|
* )
|
|
@@ -606,9 +626,9 @@ export function guardSync<
|
|
|
606
626
|
|
|
607
627
|
if (conditionResult) {
|
|
608
628
|
// Condition passed, execute the transition
|
|
609
|
-
// Transition functions expect 'this' to be the
|
|
610
|
-
const
|
|
611
|
-
return transition.apply(
|
|
629
|
+
// Transition functions expect 'this' to be the machine
|
|
630
|
+
const machineForTransition = isMachine ? (this as Machine<C>) : { context: this as C };
|
|
631
|
+
return transition.apply(machineForTransition, args);
|
|
612
632
|
} else {
|
|
613
633
|
// Condition failed, handle according to options
|
|
614
634
|
if (onFail === 'throw') {
|
|
@@ -33,10 +33,10 @@ type Context = { count: number };
|
|
|
33
33
|
const machine = createMachine({ count: 0 }, (transition) => ({
|
|
34
34
|
inc() {
|
|
35
35
|
// 'this' should be Context
|
|
36
|
-
return transition({ count: this.count + 1 });
|
|
36
|
+
return transition({ count: this.context.count + 1 });
|
|
37
37
|
},
|
|
38
38
|
add(n: number) {
|
|
39
|
-
return transition({ count: this.count + n });
|
|
39
|
+
return transition({ count: this.context.count + n });
|
|
40
40
|
}
|
|
41
41
|
}));
|
|
42
42
|
|
package/src/react.ts
CHANGED
|
@@ -302,7 +302,6 @@ export function useActorSelector<M extends BaseMachine<any>, T>(
|
|
|
302
302
|
selector: (state: M) => T,
|
|
303
303
|
isEqual: (a: T, b: T) => boolean = Object.is
|
|
304
304
|
): T {
|
|
305
|
-
const subscribe = useMemo(() => actor.subscribe.bind(actor), [actor]);
|
|
306
305
|
const getSnapshot = useMemo(() => actor.getSnapshot.bind(actor), [actor]);
|
|
307
306
|
|
|
308
307
|
const getSelection = () => selector(getSnapshot());
|
|
@@ -333,4 +332,4 @@ export function useActorSelector<M extends BaseMachine<any>, T>(
|
|
|
333
332
|
}, [actor, selector, isEqual]);
|
|
334
333
|
|
|
335
334
|
return selection;
|
|
336
|
-
}
|
|
335
|
+
}
|
package/src/test.ts
CHANGED
|
@@ -27,7 +27,7 @@ type AsyncFunctions<C extends object> =
|
|
|
27
27
|
* const machine: Machine<{ count: number }> = {
|
|
28
28
|
* context: { count: 0 },
|
|
29
29
|
* increment: function() {
|
|
30
|
-
* return createMachine({ count: this.count + 1 }, this)
|
|
30
|
+
* return createMachine({ count: this.context.count + 1 }, this)
|
|
31
31
|
* }
|
|
32
32
|
* }
|
|
33
33
|
*/
|
|
@@ -57,10 +57,10 @@ export type AsyncMachine<C extends object> = { context: C } & AsyncFunctions<C>
|
|
|
57
57
|
* { count: 0 },
|
|
58
58
|
* {
|
|
59
59
|
* increment: function() {
|
|
60
|
-
* return createMachine({ count: this.count + 1 }, this)
|
|
60
|
+
* return createMachine({ count: this.context.count + 1 }, this)
|
|
61
61
|
* },
|
|
62
62
|
* decrement: function() {
|
|
63
|
-
* return createMachine({ count: this.count - 1 }, this)
|
|
63
|
+
* return createMachine({ count: this.context.count - 1 }, this)
|
|
64
64
|
* }
|
|
65
65
|
* }
|
|
66
66
|
* )
|
|
@@ -147,7 +147,7 @@ export type Event<M> = {
|
|
|
147
147
|
* { count: 0 },
|
|
148
148
|
* {
|
|
149
149
|
* increment: async function() {
|
|
150
|
-
* return createAsyncMachine({ count: this.count + 1 }, this)
|
|
150
|
+
* return createAsyncMachine({ count: this.context.count + 1 }, this)
|
|
151
151
|
* }
|
|
152
152
|
* }
|
|
153
153
|
* )
|
|
@@ -164,7 +164,7 @@ export function runMachine<C extends object>(
|
|
|
164
164
|
async function dispatch<E extends Event<typeof current>>(event: E) {
|
|
165
165
|
const fn = current[event.type] as any
|
|
166
166
|
if (!fn) throw new Error(`Unknown event: ${event.type}`)
|
|
167
|
-
const next = await fn.apply(current
|
|
167
|
+
const next = await fn.apply(current as any, event.args)
|
|
168
168
|
current = next
|
|
169
169
|
onChange?.(current)
|
|
170
170
|
return current
|
|
@@ -203,5 +203,5 @@ const counter = createMachine(
|
|
|
203
203
|
);
|
|
204
204
|
|
|
205
205
|
// Test by calling with proper context binding
|
|
206
|
-
const result = counterFns.increment.call(counter
|
|
207
|
-
console.log('Result:', result.context.count);
|
|
206
|
+
const result = counterFns.increment.call(counter);
|
|
207
|
+
console.log('Result:', result.context.count);
|
package/src/utils.ts
CHANGED
|
@@ -231,7 +231,7 @@ export function createTransition<
|
|
|
231
231
|
C extends object,
|
|
232
232
|
TArgs extends any[]
|
|
233
233
|
>(
|
|
234
|
-
getTransitions: () => Record<string, (this: C, ...args: any[]) => any>,
|
|
234
|
+
getTransitions: () => Record<string, (this: Machine<C, any>, ...args: any[]) => any>,
|
|
235
235
|
transformer: (ctx: C, ...args: TArgs) => C
|
|
236
236
|
): (this: { context: C }, ...args: TArgs) => Machine<C> {
|
|
237
237
|
return function (this: { context: C }, ...args: TArgs): Machine<C> {
|
|
@@ -245,50 +245,50 @@ export function createTransition<
|
|
|
245
245
|
// =============================================================================
|
|
246
246
|
|
|
247
247
|
/**
|
|
248
|
-
* Calls a transition function with an explicit `this`
|
|
249
|
-
* Useful for invoking transition methods with proper
|
|
248
|
+
* Calls a transition function with an explicit `this` binding.
|
|
249
|
+
* Useful for invoking transition methods with proper machine binding.
|
|
250
250
|
*
|
|
251
|
-
* @template
|
|
251
|
+
* @template M - The machine type that the function expects as `this`.
|
|
252
252
|
* @template F - The function type with a `this` parameter.
|
|
253
253
|
* @template A - The argument types for the function.
|
|
254
254
|
* @param fn - The transition function to call.
|
|
255
|
-
* @param
|
|
255
|
+
* @param machine - The machine object to bind as `this`.
|
|
256
256
|
* @param args - Arguments to pass to the function.
|
|
257
|
-
* @returns The result of calling the function with the given
|
|
257
|
+
* @returns The result of calling the function with the given machine and arguments.
|
|
258
258
|
*
|
|
259
259
|
* @example
|
|
260
|
-
* type
|
|
261
|
-
* const increment = function(this:
|
|
262
|
-
* const result = call(increment,
|
|
260
|
+
* type MyMachine = Machine<{ count: number }>;
|
|
261
|
+
* const increment = function(this: MyMachine) { return createMachine({ count: this.context.count + 1 }, this); };
|
|
262
|
+
* const result = call(increment, machine); // Returns new machine
|
|
263
263
|
*
|
|
264
264
|
* // Particularly useful with machine transitions:
|
|
265
265
|
* import { call } from '@doeixd/machine/utils';
|
|
266
|
-
* const nextMachine = yield* step(call(m.increment, m
|
|
266
|
+
* const nextMachine = yield* step(call(m.increment, m));
|
|
267
267
|
*/
|
|
268
|
-
export function call<
|
|
268
|
+
export function call<M extends Machine<any>, F extends (this: M, ...args: any[]) => any>(
|
|
269
269
|
fn: F,
|
|
270
|
-
|
|
270
|
+
machine: M,
|
|
271
271
|
...args: Parameters<F> extends [any, ...infer Rest] ? Rest : never
|
|
272
272
|
): ReturnType<F> {
|
|
273
|
-
return fn.apply(
|
|
273
|
+
return fn.apply(machine, args);
|
|
274
274
|
}
|
|
275
275
|
|
|
276
276
|
/**
|
|
277
|
-
* Binds all transition methods of a machine to
|
|
278
|
-
* Returns a Proxy that intercepts method calls and binds them to
|
|
279
|
-
* This eliminates the need to use `.call(m
|
|
277
|
+
* Binds all transition methods of a machine to the machine itself automatically.
|
|
278
|
+
* Returns a Proxy that intercepts method calls and binds them to the full machine.
|
|
279
|
+
* This eliminates the need to use `.call(m, ...)` for every transition.
|
|
280
280
|
*
|
|
281
281
|
* Automatically recursively wraps returned machines, enabling seamless chaining
|
|
282
282
|
* in generator-based flows.
|
|
283
283
|
*
|
|
284
284
|
* @template M - The machine type with a `context` property and transition methods.
|
|
285
285
|
* @param machine - The machine instance to wrap.
|
|
286
|
-
* @returns A Proxy of the machine where all callable properties (transitions) are automatically bound to the machine
|
|
286
|
+
* @returns A Proxy of the machine where all callable properties (transitions) are automatically bound to the machine.
|
|
287
287
|
*
|
|
288
288
|
* @example
|
|
289
|
-
* type
|
|
289
|
+
* type CounterMachine = Machine<{ count: number }>;
|
|
290
290
|
* const counter = bindTransitions(createMachine({ count: 0 }, {
|
|
291
|
-
* increment(this:
|
|
291
|
+
* increment(this: CounterMachine) { return createMachine({ count: this.context.count + 1 }, this); }
|
|
292
292
|
* }));
|
|
293
293
|
*
|
|
294
294
|
* // Now you can call transitions directly without .call():
|
|
@@ -304,18 +304,18 @@ export function call<C, F extends (this: C, ...args: any[]) => any>(
|
|
|
304
304
|
* @remarks
|
|
305
305
|
* The Proxy preserves all original properties and methods. Non-callable properties
|
|
306
306
|
* are accessed directly from the machine. Callable properties are wrapped to bind
|
|
307
|
-
* them to
|
|
307
|
+
* them to the machine before invocation. Returned machines are automatically
|
|
308
308
|
* re-wrapped to maintain binding across transition chains.
|
|
309
309
|
*/
|
|
310
310
|
export function bindTransitions<M extends { context: any }>(machine: M): M {
|
|
311
311
|
return new Proxy(machine, {
|
|
312
312
|
get(target, prop) {
|
|
313
313
|
const value = target[prop as keyof M];
|
|
314
|
-
|
|
315
|
-
// If it's a callable property (transition method), bind it to
|
|
314
|
+
|
|
315
|
+
// If it's a callable property (transition method), bind it to machine
|
|
316
316
|
if (typeof value === 'function') {
|
|
317
317
|
return function(...args: any[]) {
|
|
318
|
-
const result = value.apply(target
|
|
318
|
+
const result = value.apply(target, args);
|
|
319
319
|
// Recursively wrap returned machines to maintain binding
|
|
320
320
|
if (result && typeof result === 'object' && 'context' in result) {
|
|
321
321
|
return bindTransitions(result);
|
|
@@ -323,7 +323,7 @@ export function bindTransitions<M extends { context: any }>(machine: M): M {
|
|
|
323
323
|
return result;
|
|
324
324
|
};
|
|
325
325
|
}
|
|
326
|
-
|
|
326
|
+
|
|
327
327
|
// Otherwise, return the value as-is
|
|
328
328
|
return value;
|
|
329
329
|
},
|
|
@@ -331,21 +331,21 @@ export function bindTransitions<M extends { context: any }>(machine: M): M {
|
|
|
331
331
|
}
|
|
332
332
|
|
|
333
333
|
/**
|
|
334
|
-
* A strongly-typed wrapper class for binding transitions to machine
|
|
334
|
+
* A strongly-typed wrapper class for binding transitions to the machine.
|
|
335
335
|
* Unlike the Proxy-based `bindTransitions`, this class preserves full type safety
|
|
336
336
|
* and provides better IDE support through explicit property forwarding.
|
|
337
337
|
*
|
|
338
338
|
* @template M - The machine type with a `context` property and transition methods.
|
|
339
339
|
*
|
|
340
340
|
* @example
|
|
341
|
-
* type
|
|
341
|
+
* type CounterMachine = Machine<{ count: number }>;
|
|
342
342
|
* const counter = createMachine({ count: 0 }, {
|
|
343
|
-
* increment(this:
|
|
343
|
+
* increment(this: CounterMachine) { return createMachine({ count: this.context.count + 1 }, this); }
|
|
344
344
|
* });
|
|
345
345
|
*
|
|
346
346
|
* const bound = new BoundMachine(counter);
|
|
347
347
|
*
|
|
348
|
-
* // All transitions are automatically bound to
|
|
348
|
+
* // All transitions are automatically bound to machine
|
|
349
349
|
* const result = run(function* (m) {
|
|
350
350
|
* m = yield* step(m.increment());
|
|
351
351
|
* m = yield* step(m.add(5));
|
|
@@ -383,10 +383,10 @@ export class BoundMachine<M extends { context: any }> {
|
|
|
383
383
|
|
|
384
384
|
const value = this.wrappedMachine[prop as keyof M];
|
|
385
385
|
|
|
386
|
-
// Bind transition methods to
|
|
386
|
+
// Bind transition methods to machine
|
|
387
387
|
if (typeof value === 'function') {
|
|
388
388
|
return (...args: any[]) => {
|
|
389
|
-
const result = value.apply(this.wrappedMachine
|
|
389
|
+
const result = value.apply(this.wrappedMachine, args);
|
|
390
390
|
// Recursively wrap returned machines
|
|
391
391
|
if (result && typeof result === 'object' && 'context' in result) {
|
|
392
392
|
return new BoundMachine(result);
|
|
@@ -619,4 +619,4 @@ export function sequence3<
|
|
|
619
619
|
isFinal: (machine: M1 | M2 | M3) => boolean
|
|
620
620
|
): M1 | M2 | M3 {
|
|
621
621
|
return sequence([machine1, machine2, machine3], isFinal);
|
|
622
|
-
}
|
|
622
|
+
}
|