@doeixd/machine 0.0.23 → 1.0.1

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 (50) hide show
  1. package/README.md +101 -65
  2. package/dist/cjs/development/core.js +19 -45
  3. package/dist/cjs/development/core.js.map +3 -3
  4. package/dist/cjs/development/index.js +51 -46
  5. package/dist/cjs/development/index.js.map +4 -4
  6. package/dist/cjs/development/react.js +19 -46
  7. package/dist/cjs/development/react.js.map +3 -3
  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 +19 -45
  12. package/dist/esm/development/core.js.map +3 -3
  13. package/dist/esm/development/index.js +51 -46
  14. package/dist/esm/development/index.js.map +4 -4
  15. package/dist/esm/development/react.js +19 -46
  16. package/dist/esm/development/react.js.map +3 -3
  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 -29
  29. package/dist/types/index.d.ts.map +1 -1
  30. package/dist/types/primitives.d.ts +25 -5
  31. package/dist/types/primitives.d.ts.map +1 -1
  32. package/dist/types/react.d.ts.map +1 -1
  33. package/dist/types/utils.d.ts +22 -22
  34. package/dist/types/utils.d.ts.map +1 -1
  35. package/package.json +1 -1
  36. package/src/actor.ts +1 -1
  37. package/src/context-bound.ts +147 -0
  38. package/src/entry-react.ts +9 -2
  39. package/src/functional-combinators.ts +5 -5
  40. package/src/generators.ts +2 -2
  41. package/src/higher-order.ts +2 -2
  42. package/src/index.ts +31 -68
  43. package/src/middleware/time-travel.ts +2 -2
  44. package/src/middleware.ts +2 -2
  45. package/src/multi.ts +4 -4
  46. package/src/primitives.ts +34 -14
  47. package/src/prototype_functional.ts +2 -2
  48. package/src/react.ts +1 -2
  49. package/src/test.ts +7 -7
  50. package/src/utils.ts +31 -31
package/README.md CHANGED
@@ -76,12 +76,12 @@ import { createMachine } from "@doeixd/machine";
76
76
  const counter = createMachine(
77
77
  { count: 0 }, // Initial state (s₀)
78
78
  (next) => ({
79
- // Transitions (δ) - `this` is automatically typed
79
+ // Transitions (δ) - access state via this.context
80
80
  increment() {
81
- return next({ count: this.count + 1 });
81
+ return next({ count: this.context.count + 1 });
82
82
  },
83
83
  add(n: number) {
84
- return next({ count: this.count + n });
84
+ return next({ count: this.context.count + n });
85
85
  }
86
86
  })
87
87
  );
@@ -104,10 +104,10 @@ console.log(counter.context.count); // 0
104
104
  ```typescript
105
105
  const transitions = {
106
106
  increment: function() {
107
- return createMachine({ count: this.count + 1 }, transitions);
107
+ return createMachine({ count: this.context.count + 1 }, transitions);
108
108
  },
109
109
  add: function(n: number) {
110
- return createMachine({ count: this.count + n }, transitions);
110
+ return createMachine({ count: this.context.count + n }, transitions);
111
111
  }
112
112
  };
113
113
  const counter = createMachine({ count: 0 }, transitions);
@@ -161,49 +161,84 @@ This shows the **flexibility** of the library: immutability is the default patte
161
161
  The most powerful pattern: different machine types represent different states.
162
162
 
163
163
  ```typescript
164
- import { createMachine, Machine } from "@doeixd/machine";
164
+ import { MachineBase } from "@doeixd/machine";
165
165
 
166
- // Define distinct machine types for each state
167
- type LoggedOut = Machine<{ status: "loggedOut" }, {
168
- login: (username: string) => LoggedIn;
169
- };
166
+ /**
167
+ * Type-State Programming with classes:
168
+ * - Each distinct class *is* a distinct state.
169
+ * - The methods on that class are the only valid transitions from that state.
170
+ * - Returning a different class type moves you to a different state (at compile time).
171
+ */
170
172
 
171
- type LoggedIn = Machine<{ status: "loggedIn"; username: string }, {
172
- logout: () => LoggedOut;
173
- viewProfile: () => LoggedIn;
174
- };
173
+ /** "LoggedOut" state: only transitions available are methods on this class. */
174
+ class LoggedOut extends MachineBase<{ status: "loggedOut" }> {
175
+ constructor() {
176
+ // MachineBase stores state data in `this.context`
177
+ super({ status: "loggedOut" });
178
+ }
175
179
 
176
- // Create factory functions
177
- const createLoggedOut = (): LoggedOut => {
178
- return createMachine({ status: "loggedOut" }, {
179
- login: function(username: string): LoggedIn {
180
- return createLoggedIn(username);
181
- }
182
- });
183
- };
180
+ /**
181
+ * Transition: LoggedOut -> LoggedIn
182
+ * Notice: there's no `logout()` method here, so you literally cannot call it.
183
+ */
184
+ login(username: string): LoggedIn {
185
+ return new LoggedIn(username);
186
+ }
187
+ }
184
188
 
185
- const createLoggedIn = (username: string): LoggedIn => {
186
- return createMachine({ status: "loggedIn", username }, {
187
- logout: function(): LoggedOut {
188
- return createLoggedOut();
189
- },
190
- viewProfile: function(): LoggedIn {
191
- console.log(`Viewing ${this.username}'s profile`);
192
- return this;
193
- }
194
- });
195
- };
189
+ /** "LoggedIn" state: different data + different allowed transitions. */
190
+ class LoggedIn extends MachineBase<{ status: "loggedIn"; username: string }> {
191
+ constructor(username: string) {
192
+ // Context shape changes in this state (now includes `username`)
193
+ super({ status: "loggedIn", username });
194
+ }
195
+
196
+ /**
197
+ * Transition: LoggedIn -> LoggedOut
198
+ * This exists only on LoggedIn, so you cannot log out unless you're logged in.
199
+ */
200
+ logout(): LoggedOut {
201
+ return new LoggedOut();
202
+ }
196
203
 
197
- // Usage
198
- const machine = createLoggedOut();
204
+ /**
205
+ * Transition: LoggedIn -> LoggedIn (self-transition)
206
+ * Returning `this` means "stay in the same state".
207
+ */
208
+ viewProfile(): LoggedIn {
209
+ // With MachineBase, context lives under `this.context`
210
+ console.log(`Viewing ${this.context.username}'s profile`);
211
+ return this;
212
+ }
213
+ }
214
+
215
+ // -------------------- Usage --------------------
199
216
 
200
- // TypeScript prevents invalid transitions at compile time!
201
- // machine.logout(); // ❌ Error: Property 'logout' does not exist on type 'LoggedOut'
217
+ const machine = new LoggedOut();
218
+
219
+ /**
220
+ * ✅ Compiler-enforced validity:
221
+ * LoggedOut has only `.login()`, so calling `.logout()` is a type error.
222
+ */
223
+ // machine.logout(); // ❌ Property 'logout' does not exist on type 'LoggedOut'
202
224
 
203
225
  const loggedIn = machine.login("alice");
204
- // loggedIn.login("bob"); // ❌ Error: Property 'login' does not exist on type 'LoggedIn'
205
226
 
206
- const loggedOut = loggedIn.logout(); // ✅ Valid
227
+ /**
228
+ * LoggedIn has `.logout()` and `.viewProfile()`.
229
+ * It does NOT have `.login()`, so calling it is a compile-time error.
230
+ */
231
+ // loggedIn.login("bob"); // ❌ Property 'login' does not exist on type 'LoggedIn'
232
+
233
+ const stillLoggedIn = loggedIn.viewProfile(); // ✅ OK (self-transition)
234
+ const loggedOut = loggedIn.logout(); // ✅ OK (LoggedIn -> LoggedOut)
235
+
236
+ /**
237
+ * The key idea:
238
+ * - You never check `status` at runtime to know what you can do.
239
+ * - The *type* tells you what transitions are available.
240
+ * - Autocomplete only offers valid transitions for the current state.
241
+ */
207
242
  ```
208
243
 
209
244
  This pattern makes **illegal states unrepresentable** in your type system.
@@ -439,11 +474,11 @@ Creates a synchronous state machine using the **Functional Builder** pattern. Th
439
474
  ```typescript
440
475
  const machine = createMachine({ count: 0 }, (next) => ({
441
476
  increment() {
442
- // `this` is correctly inferred as Context
443
- return next({ count: this.count + 1 });
477
+ // `this` points at the machine; read state via this.context
478
+ return next({ count: this.context.count + 1 });
444
479
  },
445
480
  add(n: number) {
446
- return next({ count: this.count + n });
481
+ return next({ count: this.context.count + n });
447
482
  }
448
483
  }));
449
484
  ```
@@ -457,7 +492,7 @@ const machine = createMachine(
457
492
  { count: 0 }, // Context (state data)
458
493
  { // Transitions (state transformations)
459
494
  increment: function() {
460
- return createMachine({ count: this.count + 1 }, this);
495
+ return createMachine({ count: this.context.count + 1 }, this);
461
496
  }
462
497
  }
463
498
  );
@@ -470,11 +505,11 @@ Creates a synchronous state machine using the **Functional Builder** pattern. Th
470
505
  ```typescript
471
506
  const machine = createMachine({ count: 0 }, (transition) => ({
472
507
  increment() {
473
- // `this` is correctly inferred as Context
474
- return transition({ count: this.count + 1 });
508
+ // `this` points at the machine; read state via this.context
509
+ return transition({ count: this.context.count + 1 });
475
510
  },
476
511
  add(n: number) {
477
- return transition({ count: this.count + n });
512
+ return transition({ count: this.context.count + n });
478
513
  }
479
514
  }));
480
515
  ```
@@ -581,45 +616,46 @@ const updated = next(counter, (ctx) => ({ count: ctx.count + 1 }));
581
616
 
582
617
  ### Transition Binding Helpers
583
618
 
584
- These utilities eliminate the need for `.call(m.context, ...)` boilerplate when invoking transitions.
619
+ These utilities eliminate the need for `.call(m, ...)` boilerplate when invoking transitions.
585
620
 
586
- #### `call<C, F>(fn, context, ...args)`
621
+ #### `call<M, F>(fn, machine, ...args)`
587
622
 
588
- Explicitly binds a transition function to a context and invokes it. Useful when you need to call a transition with proper `this` binding.
623
+ Explicitly binds a transition function to a machine and invokes it. Useful when you need to call a transition with proper `this` binding.
589
624
 
590
625
  ```typescript
591
- import { call } from "@doeixd/machine";
626
+ import { call, Machine } from "@doeixd/machine";
592
627
 
593
- type MyContext = { count: number };
594
- const increment = function(this: MyContext) {
595
- return { count: this.count + 1 };
628
+ type MyMachine = Machine<{ count: number }>;
629
+ const increment = function(this: MyMachine) {
630
+ return { count: this.context.count + 1 };
596
631
  };
597
632
 
598
- const result = call(increment, { count: 5 }); // Returns { count: 6 }
633
+ const machine = { context: { count: 5 } } as MyMachine;
634
+ const result = call(increment, machine); // Returns { count: 6 }
599
635
 
600
636
  // Particularly useful with generator-based flows:
601
637
  const result = run(function* (m) {
602
- m = yield* step(call(m.increment, m.context));
603
- m = yield* step(call(m.add, m.context, 5));
638
+ m = yield* step(call(m.increment, m));
639
+ m = yield* step(call(m.add, m, 5));
604
640
  return m;
605
641
  }, counter);
606
642
  ```
607
643
 
608
644
  #### `bindTransitions<M>(machine)`
609
645
 
610
- Returns a Proxy that automatically binds all transition methods to the machine's context. Eliminates `.call(m.context, ...)` boilerplate entirely.
646
+ Returns a Proxy that automatically binds all transition methods to the machine. Eliminates `.call(m, ...)` boilerplate entirely.
611
647
 
612
648
  ```typescript
613
- import { bindTransitions } from "@doeixd/machine";
649
+ import { bindTransitions, Machine } from "@doeixd/machine";
614
650
 
615
651
  const counter = bindTransitions(createMachine(
616
652
  { count: 0 },
617
653
  {
618
- increment(this: { count: number }) {
619
- return createMachine({ count: this.count + 1 }, this);
654
+ increment(this: Machine<{ count: number }>) {
655
+ return createMachine({ count: this.context.count + 1 }, this);
620
656
  },
621
- add(this: { count: number }, n: number) {
622
- return createMachine({ count: this.count + n }, this);
657
+ add(this: Machine<{ count: number }>, n: number) {
658
+ return createMachine({ count: this.context.count + n }, this);
623
659
  }
624
660
  }
625
661
  ));
@@ -637,7 +673,7 @@ const result = run(function* (m) {
637
673
  ```
638
674
 
639
675
  **How it works:**
640
- The Proxy intercepts all property access on the machine. When a property is a function (transition method), it wraps it to automatically call `.apply(machine.context, args)` before invoking. Non-callable properties are returned as-is.
676
+ The Proxy intercepts all property access on the machine. When a property is a function (transition method), it wraps it to automatically call `.apply(machine, args)` before invoking. Non-callable properties are returned as-is.
641
677
 
642
678
  **Note:** The Proxy preserves type safety while providing ergonomic syntax. Use this when writing generator-based flows or any code that frequently calls transitions.
643
679
 
@@ -761,7 +797,7 @@ const mocked = overrideTransitions(counter, {
761
797
  // Decorate with logging
762
798
  const logged = overrideTransitions(counter, {
763
799
  increment: function() {
764
- console.log("Before:", this.count);
800
+ console.log("Before:", this.context.count);
765
801
  const next = counter.increment.call(this);
766
802
  console.log("After:", next.context.count);
767
803
  return next;
@@ -2104,7 +2140,7 @@ We avoid magic strings wherever possible. Instead, we use **typed object referen
2104
2140
  // ✅ Good: Typed method reference
2105
2141
  const counter = createMachine({ count: 0 }, {
2106
2142
  increment: function() {
2107
- return createMachine({ count: this.count + 1 }, this);
2143
+ return createMachine({ count: this.context.count + 1 }, this);
2108
2144
  }
2109
2145
  });
2110
2146
 
@@ -246,8 +246,8 @@ function guard(condition, transition, options = {}) {
246
246
  const ctx = isMachine ? this.context : this;
247
247
  const conditionResult = condition(ctx, ...args);
248
248
  if (conditionResult) {
249
- const contextForTransition = isMachine ? this.context : this;
250
- return transition.apply(contextForTransition, args);
249
+ const machineForTransition = isMachine ? this : { context: this };
250
+ return transition.apply(machineForTransition, args);
251
251
  } else {
252
252
  if (onFail === "throw") {
253
253
  const message = errorMessage || "Guard condition failed";
@@ -287,8 +287,8 @@ function guardAsync(condition, transition, options = {}) {
287
287
  const ctx = isMachine ? this.context : this;
288
288
  const conditionResult = await Promise.resolve(condition(ctx, ...args));
289
289
  if (conditionResult) {
290
- const contextForTransition = isMachine ? this.context : this;
291
- return transition.apply(contextForTransition, args);
290
+ const machineForTransition = isMachine ? this : { context: this };
291
+ return transition.apply(machineForTransition, args);
292
292
  } else {
293
293
  if (onFail === "throw") {
294
294
  const message = errorMessage || "Guard condition failed";
@@ -369,7 +369,7 @@ function createRunner(initialMachine, onChange) {
369
369
  return void 0;
370
370
  }
371
371
  return (...args) => {
372
- const nextState = transition.apply(currentMachine.context, args);
372
+ const nextState = transition.apply(currentMachine, args);
373
373
  const nextStateWithTransitions = Object.assign(
374
374
  { context: nextState.context },
375
375
  originalTransitions
@@ -412,7 +412,7 @@ function createEnsemble(store, factories, getDiscriminant) {
412
412
  );
413
413
  }
414
414
  return (...args) => {
415
- return action2.apply(currentMachine.context, args);
415
+ return action2.apply(currentMachine, args);
416
416
  };
417
417
  }
418
418
  });
@@ -563,7 +563,7 @@ function createMutableMachine(sharedContext, factories, getDiscriminant) {
563
563
  const transition = currentMachine[prop];
564
564
  if (typeof transition === "function") {
565
565
  return (...args) => {
566
- const nextContext = transition.apply(currentMachine.context, args);
566
+ const nextContext = transition.apply(currentMachine, args);
567
567
  if (typeof nextContext !== "object" || nextContext === null) {
568
568
  console.warn(`[MutableMachine] Transition "${String(prop)}" did not return a valid context object. State may be inconsistent.`);
569
569
  return;
@@ -685,14 +685,14 @@ function createParallelMachine(m1, m2) {
685
685
  for (const key in transitions1) {
686
686
  const transitionFn = transitions1[key];
687
687
  combinedTransitions[key] = (...args) => {
688
- const nextM1 = transitionFn.apply(m1.context, args);
688
+ const nextM1 = transitionFn.apply(m1, args);
689
689
  return createParallelMachine(nextM1, m2);
690
690
  };
691
691
  }
692
692
  for (const key in transitions2) {
693
693
  const transitionFn = transitions2[key];
694
694
  combinedTransitions[key] = (...args) => {
695
- const nextM2 = transitionFn.apply(m2.context, args);
695
+ const nextM2 = transitionFn.apply(m2, args);
696
696
  return createParallelMachine(m1, nextM2);
697
697
  };
698
698
  }
@@ -1163,7 +1163,7 @@ function withTimeTravel(machine, options = {}) {
1163
1163
  for (const entry of transitionsToReplay) {
1164
1164
  const transitionFn = replayedMachine[entry.transitionName];
1165
1165
  if (transitionFn) {
1166
- replayedMachine = transitionFn.apply(replayedMachine.context, entry.args);
1166
+ replayedMachine = transitionFn.apply(replayedMachine, entry.args);
1167
1167
  }
1168
1168
  }
1169
1169
  return replayedMachine;
@@ -1583,8 +1583,8 @@ function createTransition(getTransitions, transformer) {
1583
1583
  return createMachine(nextContext, getTransitions());
1584
1584
  };
1585
1585
  }
1586
- function call(fn, context, ...args) {
1587
- return fn.apply(context, args);
1586
+ function call(fn, machine, ...args) {
1587
+ return fn.apply(machine, args);
1588
1588
  }
1589
1589
  function bindTransitions(machine) {
1590
1590
  return new Proxy(machine, {
@@ -1592,7 +1592,7 @@ function bindTransitions(machine) {
1592
1592
  const value = target[prop];
1593
1593
  if (typeof value === "function") {
1594
1594
  return function(...args) {
1595
- const result = value.apply(target.context, args);
1595
+ const result = value.apply(target, args);
1596
1596
  if (result && typeof result === "object" && "context" in result) {
1597
1597
  return bindTransitions(result);
1598
1598
  }
@@ -1617,7 +1617,7 @@ var BoundMachine = class _BoundMachine {
1617
1617
  const value = this.wrappedMachine[prop];
1618
1618
  if (typeof value === "function") {
1619
1619
  return (...args) => {
1620
- const result = value.apply(this.wrappedMachine.context, args);
1620
+ const result = value.apply(this.wrappedMachine, args);
1621
1621
  if (result && typeof result === "object" && "context" in result) {
1622
1622
  return new _BoundMachine(result);
1623
1623
  }
@@ -1680,23 +1680,10 @@ function createMachine(context, fnsOrFactory) {
1680
1680
  if (typeof fnsOrFactory === "function") {
1681
1681
  let transitions2;
1682
1682
  const transition = (newContext) => {
1683
- const machine2 = createMachine(newContext, transitions2);
1684
- const boundTransitions2 = Object.fromEntries(
1685
- Object.entries(transitions2).map(([key, fn]) => [
1686
- key,
1687
- fn.bind(newContext)
1688
- ])
1689
- );
1690
- return Object.assign(machine2, boundTransitions2);
1683
+ return createMachine(newContext, transitions2);
1691
1684
  };
1692
1685
  transitions2 = fnsOrFactory(transition);
1693
- const boundTransitions = Object.fromEntries(
1694
- Object.entries(transitions2).map(([key, fn]) => [
1695
- key,
1696
- fn.bind(context)
1697
- ])
1698
- );
1699
- return Object.assign({ context }, boundTransitions);
1686
+ return Object.assign({ context }, transitions2);
1700
1687
  }
1701
1688
  const transitions = "context" in fnsOrFactory ? Object.fromEntries(
1702
1689
  Object.entries(fnsOrFactory).filter(([key]) => key !== "context")
@@ -1708,23 +1695,10 @@ function createAsyncMachine(context, fnsOrFactory) {
1708
1695
  if (typeof fnsOrFactory === "function") {
1709
1696
  let transitions2;
1710
1697
  const transition = (newContext) => {
1711
- const machine2 = createAsyncMachine(newContext, transitions2);
1712
- const boundTransitions2 = Object.fromEntries(
1713
- Object.entries(transitions2).map(([key, fn]) => [
1714
- key,
1715
- fn.bind(newContext)
1716
- ])
1717
- );
1718
- return Object.assign(machine2, boundTransitions2);
1698
+ return createAsyncMachine(newContext, transitions2);
1719
1699
  };
1720
1700
  transitions2 = fnsOrFactory(transition);
1721
- const boundTransitions = Object.fromEntries(
1722
- Object.entries(transitions2).map(([key, fn]) => [
1723
- key,
1724
- fn.bind(context)
1725
- ])
1726
- );
1727
- return Object.assign({ context }, boundTransitions);
1701
+ return Object.assign({ context }, transitions2);
1728
1702
  }
1729
1703
  const transitions = "context" in fnsOrFactory ? Object.fromEntries(
1730
1704
  Object.entries(fnsOrFactory).filter(([key]) => key !== "context")
@@ -1806,7 +1780,7 @@ function runMachine(initial, onChange) {
1806
1780
  const controller = new AbortController();
1807
1781
  activeController = controller;
1808
1782
  try {
1809
- const nextStatePromise = fn.apply(current.context, [...event.args, { signal: controller.signal }]);
1783
+ const nextStatePromise = fn.apply(current, [...event.args, { signal: controller.signal }]);
1810
1784
  const nextState = await nextStatePromise;
1811
1785
  if (controller.signal.aborted) {
1812
1786
  return current;