@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.
Files changed (53) hide show
  1. package/README.md +101 -65
  2. package/dist/cjs/development/core.js +56 -57
  3. package/dist/cjs/development/core.js.map +4 -4
  4. package/dist/cjs/development/index.js +99 -58
  5. package/dist/cjs/development/index.js.map +4 -4
  6. package/dist/cjs/development/react.js +56 -58
  7. package/dist/cjs/development/react.js.map +4 -4
  8. package/dist/cjs/production/core.js +1 -1
  9. package/dist/cjs/production/index.js +3 -3
  10. package/dist/cjs/production/react.js +1 -1
  11. package/dist/esm/development/core.js +56 -57
  12. package/dist/esm/development/core.js.map +4 -4
  13. package/dist/esm/development/index.js +99 -58
  14. package/dist/esm/development/index.js.map +4 -4
  15. package/dist/esm/development/react.js +56 -58
  16. package/dist/esm/development/react.js.map +4 -4
  17. package/dist/esm/production/core.js +1 -1
  18. package/dist/esm/production/index.js +3 -3
  19. package/dist/esm/production/react.js +1 -1
  20. package/dist/types/actor.d.ts +4 -4
  21. package/dist/types/actor.d.ts.map +1 -1
  22. package/dist/types/context-bound.d.ts +94 -0
  23. package/dist/types/context-bound.d.ts.map +1 -0
  24. package/dist/types/entry-react.d.ts +1 -1
  25. package/dist/types/entry-react.d.ts.map +1 -1
  26. package/dist/types/functional-combinators.d.ts +5 -5
  27. package/dist/types/generators.d.ts +2 -2
  28. package/dist/types/index.d.ts +14 -34
  29. package/dist/types/index.d.ts.map +1 -1
  30. package/dist/types/internal-transitions.d.ts +5 -0
  31. package/dist/types/internal-transitions.d.ts.map +1 -0
  32. package/dist/types/primitives.d.ts +25 -5
  33. package/dist/types/primitives.d.ts.map +1 -1
  34. package/dist/types/react.d.ts.map +1 -1
  35. package/dist/types/utils.d.ts +22 -22
  36. package/dist/types/utils.d.ts.map +1 -1
  37. package/package.json +1 -1
  38. package/src/actor.ts +1 -1
  39. package/src/context-bound.ts +160 -0
  40. package/src/entry-react.ts +9 -2
  41. package/src/functional-combinators.ts +5 -5
  42. package/src/generators.ts +2 -2
  43. package/src/higher-order.ts +2 -2
  44. package/src/index.ts +47 -80
  45. package/src/internal-transitions.ts +32 -0
  46. package/src/middleware/time-travel.ts +2 -2
  47. package/src/middleware.ts +2 -2
  48. package/src/multi.ts +4 -4
  49. package/src/primitives.ts +34 -14
  50. package/src/prototype_functional.ts +2 -2
  51. package/src/react.ts +1 -2
  52. package/src/test.ts +7 -7
  53. 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
- * return createMachine({ balance: this.balance - amount }, this);
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 context
407
- const contextForTransition = isMachine ? (this as Machine<C>).context : (this as C);
408
- return transition.apply(contextForTransition, args);
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
- * return createMachine({ balance: this.balance - amount }, this);
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 context
512
- const contextForTransition = isMachine ? (this as Machine<C>).context : (this as C);
513
- return transition.apply(contextForTransition, args);
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 context
610
- const contextForTransition = isMachine ? (this as Machine<C>).context : (this as C);
611
- return transition.apply(contextForTransition, args);
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.context, event.args)
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.context);
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` context.
249
- * Useful for invoking transition methods with proper context binding.
248
+ * Calls a transition function with an explicit `this` binding.
249
+ * Useful for invoking transition methods with proper machine binding.
250
250
  *
251
- * @template C - The context type that the function expects as `this`.
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 context - The context object to bind as `this`.
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 context and arguments.
257
+ * @returns The result of calling the function with the given machine and arguments.
258
258
  *
259
259
  * @example
260
- * type MyContext = { count: number };
261
- * const increment = function(this: MyContext) { return this.count + 1; };
262
- * const result = call(increment, { count: 5 }); // Returns 6
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.context));
266
+ * const nextMachine = yield* step(call(m.increment, m));
267
267
  */
268
- export function call<C, F extends (this: C, ...args: any[]) => any>(
268
+ export function call<M extends Machine<any>, F extends (this: M, ...args: any[]) => any>(
269
269
  fn: F,
270
- context: C,
270
+ machine: M,
271
271
  ...args: Parameters<F> extends [any, ...infer Rest] ? Rest : never
272
272
  ): ReturnType<F> {
273
- return fn.apply(context, args);
273
+ return fn.apply(machine, args);
274
274
  }
275
275
 
276
276
  /**
277
- * Binds all transition methods of a machine to its context automatically.
278
- * Returns a Proxy that intercepts method calls and binds them to `machine.context`.
279
- * This eliminates the need to use `.call(m.context, ...)` for every transition.
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's context.
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 CounterContext = { count: number };
289
+ * type CounterMachine = Machine<{ count: number }>;
290
290
  * const counter = bindTransitions(createMachine({ count: 0 }, {
291
- * increment(this: CounterContext) { return createCounter(this.count + 1); }
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 `machine.context` before invocation. Returned machines are automatically
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 context
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.context, args);
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 context.
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 CounterContext = { count: number };
341
+ * type CounterMachine = Machine<{ count: number }>;
342
342
  * const counter = createMachine({ count: 0 }, {
343
- * increment(this: CounterContext) { return createCounter(this.count + 1); }
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 context
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 context
386
+ // Bind transition methods to machine
387
387
  if (typeof value === 'function') {
388
388
  return (...args: any[]) => {
389
- const result = value.apply(this.wrappedMachine.context, args);
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
+ }