effect-machine 0.17.1 → 0.19.0

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 (86) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +97 -54
  3. package/dist/actor.d.ts +8 -45
  4. package/dist/actor.js +157 -231
  5. package/dist/atom.d.ts +31 -0
  6. package/dist/atom.js +29 -0
  7. package/dist/cluster/adapters/in-memory.d.ts +0 -1
  8. package/dist/cluster/adapters/in-memory.js +10 -5
  9. package/dist/cluster/entity-actor-ref.d.ts +2 -3
  10. package/dist/cluster/entity-actor-ref.js +14 -17
  11. package/dist/cluster/entity-machine.d.ts +2 -8
  12. package/dist/cluster/entity-machine.js +57 -34
  13. package/dist/cluster/index.js +1 -1
  14. package/dist/cluster/persistence.d.ts +0 -1
  15. package/dist/cluster/to-entity.d.ts +19 -21
  16. package/dist/cluster/to-entity.js +18 -19
  17. package/dist/errors.d.ts +11 -37
  18. package/dist/errors.js +12 -30
  19. package/dist/index.d.ts +4 -5
  20. package/dist/index.js +4 -5
  21. package/dist/inspection.d.ts +0 -1
  22. package/dist/inspection.js +24 -13
  23. package/dist/internal/brands.d.ts +0 -1
  24. package/dist/internal/event-advancement.d.ts +50 -0
  25. package/dist/internal/event-advancement.js +79 -0
  26. package/dist/internal/inspection.d.ts +5 -9
  27. package/dist/internal/inspection.js +31 -10
  28. package/dist/internal/machine-definition.d.ts +16 -0
  29. package/dist/internal/runtime.d.ts +1 -161
  30. package/dist/internal/runtime.js +191 -150
  31. package/dist/internal/transition.d.ts +4 -135
  32. package/dist/internal/transition.js +82 -157
  33. package/dist/internal/utils.d.ts +3 -40
  34. package/dist/internal/utils.js +2 -2
  35. package/dist/machine.d.ts +47 -137
  36. package/dist/machine.js +147 -215
  37. package/dist/schema.d.ts +42 -21
  38. package/dist/schema.js +28 -18
  39. package/dist/supervision.d.ts +1 -24
  40. package/dist/supervision.js +2 -3
  41. package/dist/testing.d.ts +15 -29
  42. package/dist/testing.js +76 -95
  43. package/package.json +41 -35
  44. package/dist/slot.d.ts +0 -159
  45. package/dist/slot.js +0 -165
  46. package/v3/dist/_virtual/_rolldown/runtime.js +0 -13
  47. package/v3/dist/actor.d.ts +0 -250
  48. package/v3/dist/actor.js +0 -577
  49. package/v3/dist/cluster/adapters/in-memory.d.ts +0 -15
  50. package/v3/dist/cluster/adapters/in-memory.js +0 -62
  51. package/v3/dist/cluster/entity-actor-ref.d.ts +0 -49
  52. package/v3/dist/cluster/entity-actor-ref.js +0 -19
  53. package/v3/dist/cluster/entity-machine.d.ts +0 -74
  54. package/v3/dist/cluster/entity-machine.js +0 -166
  55. package/v3/dist/cluster/index.d.ts +0 -6
  56. package/v3/dist/cluster/index.js +0 -6
  57. package/v3/dist/cluster/persistence.d.ts +0 -48
  58. package/v3/dist/cluster/persistence.js +0 -14
  59. package/v3/dist/cluster/to-entity.d.ts +0 -69
  60. package/v3/dist/cluster/to-entity.js +0 -59
  61. package/v3/dist/errors.d.ts +0 -95
  62. package/v3/dist/errors.js +0 -54
  63. package/v3/dist/index.d.ts +0 -11
  64. package/v3/dist/index.js +0 -9
  65. package/v3/dist/inspection.d.ts +0 -151
  66. package/v3/dist/inspection.js +0 -128
  67. package/v3/dist/internal/brands.d.ts +0 -50
  68. package/v3/dist/internal/inspection.d.ts +0 -11
  69. package/v3/dist/internal/inspection.js +0 -20
  70. package/v3/dist/internal/runtime.d.ts +0 -161
  71. package/v3/dist/internal/runtime.js +0 -360
  72. package/v3/dist/internal/transition.d.ts +0 -190
  73. package/v3/dist/internal/transition.js +0 -278
  74. package/v3/dist/internal/utils.d.ts +0 -101
  75. package/v3/dist/internal/utils.js +0 -75
  76. package/v3/dist/machine.d.ts +0 -398
  77. package/v3/dist/machine.js +0 -487
  78. package/v3/dist/schema.d.ts +0 -174
  79. package/v3/dist/schema.js +0 -206
  80. package/v3/dist/slot.d.ts +0 -158
  81. package/v3/dist/slot.js +0 -165
  82. package/v3/dist/supervision.d.ts +0 -97
  83. package/v3/dist/supervision.js +0 -42
  84. package/v3/dist/testing.d.ts +0 -151
  85. package/v3/dist/testing.js +0 -189
  86. /package/{v3/dist/internal/brands.js → dist/internal/machine-definition.js} +0 -0
@@ -1,151 +0,0 @@
1
- import { AssertionError } from "./errors.js";
2
- import { MachineContext, ProvideSlots, SlotsDef } from "./slot.js";
3
- import { Machine, MachineRef } from "./machine.js";
4
- import { Effect, SubscriptionRef } from "effect";
5
-
6
- //#region src/testing.d.ts
7
- type MachineInput<S, E, R, SD extends SlotsDef = Record<string, never>> = Machine<S, E, R, any, any, SD>;
8
- /**
9
- * Result of simulating events through a machine
10
- */
11
- interface SimulationResult<S> {
12
- readonly states: ReadonlyArray<S>;
13
- readonly finalState: S;
14
- }
15
- /**
16
- * Simulate a sequence of events through a machine without running an actor.
17
- * Useful for testing state transitions in isolation.
18
- * Does not run onEnter/spawn/background effects, but does run slots
19
- * within transition handlers.
20
- *
21
- * @example
22
- * ```ts
23
- * const result = yield* simulate(
24
- * fetcherMachine,
25
- * [
26
- * Event.Fetch({ url: "https://example.com" }),
27
- * Event._Done({ data: { foo: "bar" } })
28
- * ]
29
- * )
30
- *
31
- * expect(result.finalState._tag).toBe("Success")
32
- * expect(result.states).toHaveLength(3) // Idle -> Loading -> Success
33
- * ```
34
- */
35
- declare const simulate: <S extends {
36
- readonly _tag: string;
37
- }, E extends {
38
- readonly _tag: string;
39
- }, R, SD extends SlotsDef = Record<string, never>>(input: MachineInput<S, E, R, SD>, events: readonly E[], options?: {
40
- slots?: ProvideSlots<SD, any>;
41
- } | undefined) => Effect.Effect<{
42
- states: S[];
43
- finalState: S;
44
- }, never, Exclude<R, MachineContext<S, E, MachineRef<E>>>>;
45
- /**
46
- * Assert that a machine can reach a specific state given a sequence of events
47
- */
48
- declare const assertReaches: <S extends {
49
- readonly _tag: string;
50
- }, E extends {
51
- readonly _tag: string;
52
- }, R, SD extends SlotsDef = Record<string, never>>(input: MachineInput<S, E, R, SD>, events: readonly E[], expectedTag: string, options?: {
53
- slots?: ProvideSlots<SD, any>;
54
- } | undefined) => Effect.Effect<S, AssertionError, Exclude<R, MachineContext<S, E, MachineRef<E>>>>;
55
- /**
56
- * Assert that a machine follows a specific path of state tags
57
- *
58
- * @example
59
- * ```ts
60
- * yield* assertPath(
61
- * machine,
62
- * [Event.Start(), Event.Increment(), Event.Stop()],
63
- * ["Idle", "Counting", "Counting", "Done"]
64
- * )
65
- * ```
66
- */
67
- declare const assertPath: <S extends {
68
- readonly _tag: string;
69
- }, E extends {
70
- readonly _tag: string;
71
- }, R, SD extends SlotsDef = Record<string, never>>(input: MachineInput<S, E, R, SD>, events: readonly E[], expectedPath: readonly string[], options?: {
72
- slots?: ProvideSlots<SD, any>;
73
- } | undefined) => Effect.Effect<{
74
- states: S[];
75
- finalState: S;
76
- }, AssertionError, Exclude<R, MachineContext<S, E, MachineRef<E>>>>;
77
- /**
78
- * Assert that a machine never reaches a specific state given a sequence of events
79
- *
80
- * @example
81
- * ```ts
82
- * // Verify error handling doesn't reach crash state
83
- * yield* assertNeverReaches(
84
- * machine,
85
- * [Event.Error(), Event.Retry(), Event.Success()],
86
- * "Crashed"
87
- * )
88
- * ```
89
- */
90
- declare const assertNeverReaches: <S extends {
91
- readonly _tag: string;
92
- }, E extends {
93
- readonly _tag: string;
94
- }, R, SD extends SlotsDef = Record<string, never>>(input: MachineInput<S, E, R, SD>, events: readonly E[], forbiddenTag: string, options?: {
95
- slots?: ProvideSlots<SD, any>;
96
- } | undefined) => Effect.Effect<{
97
- states: S[];
98
- finalState: S;
99
- }, AssertionError, Exclude<R, MachineContext<S, E, MachineRef<E>>>>;
100
- /**
101
- * Create a controllable test harness for a machine
102
- */
103
- interface TestHarness<S, E, R> {
104
- readonly state: SubscriptionRef.SubscriptionRef<S>;
105
- readonly send: (event: E) => Effect.Effect<S, never, R>;
106
- readonly getState: Effect.Effect<S>;
107
- }
108
- /**
109
- * Options for creating a test harness
110
- */
111
- interface TestHarnessOptions<S, E, SD extends SlotsDef = Record<string, never>> {
112
- /**
113
- * Called after each transition with the previous state, event, and new state.
114
- * Useful for logging or spying on transitions.
115
- */
116
- readonly onTransition?: (from: S, event: E, to: S) => void;
117
- /** Slot handler implementations. */
118
- readonly slots?: ProvideSlots<SD, any>;
119
- }
120
- /**
121
- * Create a test harness for step-by-step testing.
122
- * Does not run onEnter/spawn/background effects, but does run slots
123
- * within transition handlers.
124
- *
125
- * @example Basic usage
126
- * ```ts
127
- * const harness = yield* createTestHarness(machine)
128
- * yield* harness.send(Event.Start())
129
- * const state = yield* harness.getState
130
- * ```
131
- *
132
- * @example With transition observer
133
- * ```ts
134
- * const transitions: Array<{ from: string; event: string; to: string }> = []
135
- * const harness = yield* createTestHarness(machine, {
136
- * onTransition: (from, event, to) =>
137
- * transitions.push({ from: from._tag, event: event._tag, to: to._tag })
138
- * })
139
- * ```
140
- */
141
- declare const createTestHarness: <S extends {
142
- readonly _tag: string;
143
- }, E extends {
144
- readonly _tag: string;
145
- }, R, SD extends SlotsDef = Record<string, never>>(input: MachineInput<S, E, R, SD>, options?: TestHarnessOptions<S, E, SD> | undefined) => Effect.Effect<{
146
- state: SubscriptionRef.SubscriptionRef<S>;
147
- send: (event: E) => Effect.Effect<S, never, Exclude<R, MachineContext<S, E, MachineRef<E>>>>;
148
- getState: Effect.Effect<S, never, never>;
149
- }, never, never>;
150
- //#endregion
151
- export { AssertionError, SimulationResult, TestHarness, TestHarnessOptions, assertNeverReaches, assertPath, assertReaches, createTestHarness, simulate };
@@ -1,189 +0,0 @@
1
- import { stubSystem } from "./internal/utils.js";
2
- import { AssertionError } from "./errors.js";
3
- import { executeTransition, shouldPostpone } from "./internal/transition.js";
4
- import { materializeMachine } from "./machine.js";
5
- import { Effect, SubscriptionRef } from "effect";
6
- //#region src/testing.ts
7
- const makeDummySelf = (label) => {
8
- const dummySend = Effect.fn(label)((_event) => Effect.void);
9
- return {
10
- send: dummySend,
11
- cast: dummySend,
12
- spawn: () => Effect.die(`spawn not supported in ${label}`),
13
- reply: () => Effect.succeed(false)
14
- };
15
- };
16
- /**
17
- * Simulate a sequence of events through a machine without running an actor.
18
- * Useful for testing state transitions in isolation.
19
- * Does not run onEnter/spawn/background effects, but does run slots
20
- * within transition handlers.
21
- *
22
- * @example
23
- * ```ts
24
- * const result = yield* simulate(
25
- * fetcherMachine,
26
- * [
27
- * Event.Fetch({ url: "https://example.com" }),
28
- * Event._Done({ data: { foo: "bar" } })
29
- * ]
30
- * )
31
- *
32
- * expect(result.finalState._tag).toBe("Success")
33
- * expect(result.states).toHaveLength(3) // Idle -> Loading -> Success
34
- * ```
35
- */
36
- const simulate = Effect.fn("effect-machine.simulate")(function* (input, events, options) {
37
- const machine = materializeMachine(input, options?.slots);
38
- const dummySelf = makeDummySelf("effect-machine.testing.simulate");
39
- let currentState = machine.initial;
40
- const states = [currentState];
41
- const hasPostponeRules = machine.postponeRules.length > 0;
42
- const postponed = [];
43
- for (const event of events) {
44
- if (hasPostponeRules && shouldPostpone(machine, currentState._tag, event._tag)) {
45
- postponed.push(event);
46
- continue;
47
- }
48
- const result = yield* executeTransition(machine, currentState, event, dummySelf, stubSystem, "simulation");
49
- if (!result.transitioned) continue;
50
- const prevTag = currentState._tag;
51
- currentState = result.newState;
52
- states.push(currentState);
53
- if (machine.finalStates.has(currentState._tag)) break;
54
- let drainTag = prevTag;
55
- while (currentState._tag !== drainTag && postponed.length > 0) {
56
- drainTag = currentState._tag;
57
- const drained = postponed.splice(0);
58
- for (const postponedEvent of drained) {
59
- if (shouldPostpone(machine, currentState._tag, postponedEvent._tag)) {
60
- postponed.push(postponedEvent);
61
- continue;
62
- }
63
- const drainResult = yield* executeTransition(machine, currentState, postponedEvent, dummySelf, stubSystem, "simulation");
64
- if (drainResult.transitioned) {
65
- currentState = drainResult.newState;
66
- states.push(currentState);
67
- if (machine.finalStates.has(currentState._tag)) break;
68
- }
69
- }
70
- }
71
- }
72
- return {
73
- states,
74
- finalState: currentState
75
- };
76
- });
77
- /**
78
- * Assert that a machine can reach a specific state given a sequence of events
79
- */
80
- const assertReaches = Effect.fn("effect-machine.assertReaches")(function* (input, events, expectedTag, options) {
81
- const result = yield* simulate(input, events, options);
82
- if (result.finalState._tag !== expectedTag) return yield* new AssertionError({ message: `Expected final state "${expectedTag}" but got "${result.finalState._tag}". States visited: ${result.states.map((s) => s._tag).join(" -> ")}` });
83
- return result.finalState;
84
- });
85
- /**
86
- * Assert that a machine follows a specific path of state tags
87
- *
88
- * @example
89
- * ```ts
90
- * yield* assertPath(
91
- * machine,
92
- * [Event.Start(), Event.Increment(), Event.Stop()],
93
- * ["Idle", "Counting", "Counting", "Done"]
94
- * )
95
- * ```
96
- */
97
- const assertPath = Effect.fn("effect-machine.assertPath")(function* (input, events, expectedPath, options) {
98
- const result = yield* simulate(input, events, options);
99
- const actualPath = result.states.map((s) => s._tag);
100
- if (actualPath.length !== expectedPath.length) return yield* new AssertionError({ message: `Path length mismatch. Expected ${expectedPath.length} states but got ${actualPath.length}.\nExpected: ${expectedPath.join(" -> ")}\nActual: ${actualPath.join(" -> ")}` });
101
- for (let i = 0; i < expectedPath.length; i++) if (actualPath[i] !== expectedPath[i]) return yield* new AssertionError({ message: `Path mismatch at position ${i}. Expected "${expectedPath[i]}" but got "${actualPath[i]}".\nExpected: ${expectedPath.join(" -> ")}\nActual: ${actualPath.join(" -> ")}` });
102
- return result;
103
- });
104
- /**
105
- * Assert that a machine never reaches a specific state given a sequence of events
106
- *
107
- * @example
108
- * ```ts
109
- * // Verify error handling doesn't reach crash state
110
- * yield* assertNeverReaches(
111
- * machine,
112
- * [Event.Error(), Event.Retry(), Event.Success()],
113
- * "Crashed"
114
- * )
115
- * ```
116
- */
117
- const assertNeverReaches = Effect.fn("effect-machine.assertNeverReaches")(function* (input, events, forbiddenTag, options) {
118
- const result = yield* simulate(input, events, options);
119
- const visitedIndex = result.states.findIndex((s) => s._tag === forbiddenTag);
120
- if (visitedIndex !== -1) return yield* new AssertionError({ message: `Machine reached forbidden state "${forbiddenTag}" at position ${visitedIndex}.\nStates visited: ${result.states.map((s) => s._tag).join(" -> ")}` });
121
- return result;
122
- });
123
- /**
124
- * Create a test harness for step-by-step testing.
125
- * Does not run onEnter/spawn/background effects, but does run slots
126
- * within transition handlers.
127
- *
128
- * @example Basic usage
129
- * ```ts
130
- * const harness = yield* createTestHarness(machine)
131
- * yield* harness.send(Event.Start())
132
- * const state = yield* harness.getState
133
- * ```
134
- *
135
- * @example With transition observer
136
- * ```ts
137
- * const transitions: Array<{ from: string; event: string; to: string }> = []
138
- * const harness = yield* createTestHarness(machine, {
139
- * onTransition: (from, event, to) =>
140
- * transitions.push({ from: from._tag, event: event._tag, to: to._tag })
141
- * })
142
- * ```
143
- */
144
- const createTestHarness = Effect.fn("effect-machine.createTestHarness")(function* (input, options) {
145
- const machine = materializeMachine(input, options?.slots);
146
- const dummySelf = makeDummySelf("effect-machine.testing.harness");
147
- const stateRef = yield* SubscriptionRef.make(machine.initial);
148
- const hasPostponeRules = machine.postponeRules.length > 0;
149
- const postponed = [];
150
- return {
151
- state: stateRef,
152
- send: Effect.fn("effect-machine.testHarness.send")(function* (event) {
153
- const currentState = yield* SubscriptionRef.get(stateRef);
154
- if (hasPostponeRules && shouldPostpone(machine, currentState._tag, event._tag)) {
155
- postponed.push(event);
156
- return currentState;
157
- }
158
- const result = yield* executeTransition(machine, currentState, event, dummySelf, stubSystem, "test-harness");
159
- if (!result.transitioned) return currentState;
160
- const prevTag = currentState._tag;
161
- const newState = result.newState;
162
- yield* SubscriptionRef.set(stateRef, newState);
163
- if (options?.onTransition !== void 0) options.onTransition(currentState, event, newState);
164
- let drainTag = prevTag;
165
- let currentTag = newState._tag;
166
- while (currentTag !== drainTag && postponed.length > 0) {
167
- drainTag = currentTag;
168
- const drained = postponed.splice(0);
169
- for (const postponedEvent of drained) {
170
- const state = yield* SubscriptionRef.get(stateRef);
171
- if (shouldPostpone(machine, state._tag, postponedEvent._tag)) {
172
- postponed.push(postponedEvent);
173
- continue;
174
- }
175
- const drainResult = yield* executeTransition(machine, state, postponedEvent, dummySelf, stubSystem, "test-harness");
176
- if (drainResult.transitioned) {
177
- yield* SubscriptionRef.set(stateRef, drainResult.newState);
178
- currentTag = drainResult.newState._tag;
179
- if (options?.onTransition !== void 0) options.onTransition(state, postponedEvent, drainResult.newState);
180
- }
181
- }
182
- }
183
- return newState;
184
- }),
185
- getState: SubscriptionRef.get(stateRef)
186
- };
187
- });
188
- //#endregion
189
- export { AssertionError, assertNeverReaches, assertPath, assertReaches, createTestHarness, simulate };