effect-machine 0.3.1 → 0.3.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 (61) hide show
  1. package/dist/_virtual/_rolldown/runtime.js +18 -0
  2. package/dist/actor.d.ts +251 -0
  3. package/dist/actor.js +385 -0
  4. package/dist/cluster/entity-machine.d.ts +90 -0
  5. package/dist/cluster/entity-machine.js +74 -0
  6. package/dist/cluster/index.d.ts +3 -0
  7. package/dist/cluster/index.js +4 -0
  8. package/dist/cluster/to-entity.d.ts +64 -0
  9. package/dist/cluster/to-entity.js +53 -0
  10. package/dist/errors.d.ts +61 -0
  11. package/dist/errors.js +38 -0
  12. package/dist/index.d.ts +13 -0
  13. package/dist/index.js +14 -0
  14. package/dist/inspection.d.ts +125 -0
  15. package/dist/inspection.js +50 -0
  16. package/dist/internal/brands.d.ts +40 -0
  17. package/dist/internal/brands.js +0 -0
  18. package/dist/internal/inspection.d.ts +11 -0
  19. package/dist/internal/inspection.js +15 -0
  20. package/dist/internal/transition.d.ts +159 -0
  21. package/dist/internal/transition.js +235 -0
  22. package/dist/internal/utils.d.ts +52 -0
  23. package/dist/internal/utils.js +31 -0
  24. package/dist/machine.d.ts +271 -0
  25. package/dist/machine.js +317 -0
  26. package/{src/persistence/adapter.ts → dist/persistence/adapter.d.ts} +40 -72
  27. package/dist/persistence/adapter.js +27 -0
  28. package/dist/persistence/adapters/in-memory.d.ts +32 -0
  29. package/dist/persistence/adapters/in-memory.js +176 -0
  30. package/dist/persistence/index.d.ts +5 -0
  31. package/dist/persistence/index.js +6 -0
  32. package/dist/persistence/persistent-actor.d.ts +50 -0
  33. package/dist/persistence/persistent-actor.js +348 -0
  34. package/{src/persistence/persistent-machine.ts → dist/persistence/persistent-machine.d.ts} +28 -54
  35. package/dist/persistence/persistent-machine.js +24 -0
  36. package/dist/schema.d.ts +141 -0
  37. package/dist/schema.js +165 -0
  38. package/dist/slot.d.ts +128 -0
  39. package/dist/slot.js +99 -0
  40. package/dist/testing.d.ts +142 -0
  41. package/dist/testing.js +131 -0
  42. package/package.json +18 -7
  43. package/src/actor.ts +0 -1058
  44. package/src/cluster/entity-machine.ts +0 -201
  45. package/src/cluster/index.ts +0 -43
  46. package/src/cluster/to-entity.ts +0 -99
  47. package/src/errors.ts +0 -64
  48. package/src/index.ts +0 -105
  49. package/src/inspection.ts +0 -178
  50. package/src/internal/brands.ts +0 -51
  51. package/src/internal/inspection.ts +0 -18
  52. package/src/internal/transition.ts +0 -489
  53. package/src/internal/utils.ts +0 -80
  54. package/src/machine.ts +0 -836
  55. package/src/persistence/adapters/in-memory.ts +0 -294
  56. package/src/persistence/index.ts +0 -24
  57. package/src/persistence/persistent-actor.ts +0 -791
  58. package/src/schema.ts +0 -362
  59. package/src/slot.ts +0 -281
  60. package/src/testing.ts +0 -284
  61. package/tsconfig.json +0 -65
package/src/testing.ts DELETED
@@ -1,284 +0,0 @@
1
- import { Effect, SubscriptionRef } from "effect";
2
-
3
- import type { Machine, MachineRef } from "./machine.js";
4
- import { BuiltMachine } from "./machine.js";
5
- import { AssertionError } from "./errors.js";
6
- import type { GuardsDef, EffectsDef } from "./slot.js";
7
- import { executeTransition } from "./internal/transition.js";
8
-
9
- /** Accept either Machine or BuiltMachine for testing utilities. */
10
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
- type MachineInput<S, E, R, GD extends GuardsDef, EFD extends EffectsDef> =
12
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
- Machine<S, E, R, any, any, GD, EFD> | BuiltMachine<S, E, R>;
14
-
15
- /**
16
- * Result of simulating events through a machine
17
- */
18
- export interface SimulationResult<S> {
19
- readonly states: ReadonlyArray<S>;
20
- readonly finalState: S;
21
- }
22
-
23
- /**
24
- * Simulate a sequence of events through a machine without running an actor.
25
- * Useful for testing state transitions in isolation.
26
- * Does not run onEnter/spawn/background effects, but does run guard/effect slots
27
- * within transition handlers.
28
- *
29
- * @example
30
- * ```ts
31
- * const result = yield* simulate(
32
- * fetcherMachine,
33
- * [
34
- * Event.Fetch({ url: "https://example.com" }),
35
- * Event._Done({ data: { foo: "bar" } })
36
- * ]
37
- * )
38
- *
39
- * expect(result.finalState._tag).toBe("Success")
40
- * expect(result.states).toHaveLength(3) // Idle -> Loading -> Success
41
- * ```
42
- */
43
- export const simulate = Effect.fn("effect-machine.simulate")(function* <
44
- S extends { readonly _tag: string },
45
- E extends { readonly _tag: string },
46
- R,
47
- GD extends GuardsDef = Record<string, never>,
48
- EFD extends EffectsDef = Record<string, never>,
49
- >(input: MachineInput<S, E, R, GD, EFD>, events: ReadonlyArray<E>) {
50
- const machine = (input instanceof BuiltMachine ? input._inner : input) as Machine<
51
- S,
52
- E,
53
- R,
54
- Record<string, never>,
55
- Record<string, never>,
56
- GD,
57
- EFD
58
- >;
59
-
60
- // Create a dummy self for slot accessors
61
- const dummySelf: MachineRef<E> = {
62
- send: Effect.fn("effect-machine.testing.simulate.send")((_event: E) => Effect.void),
63
- };
64
-
65
- let currentState = machine.initial;
66
- const states: S[] = [currentState];
67
-
68
- for (const event of events) {
69
- const result = yield* executeTransition(machine, currentState, event, dummySelf);
70
-
71
- if (!result.transitioned) {
72
- continue;
73
- }
74
-
75
- currentState = result.newState;
76
- states.push(currentState);
77
-
78
- // Stop if final state
79
- if (machine.finalStates.has(currentState._tag)) {
80
- break;
81
- }
82
- }
83
-
84
- return { states, finalState: currentState };
85
- });
86
-
87
- // AssertionError is exported from errors.ts
88
- export { AssertionError } from "./errors.js";
89
-
90
- /**
91
- * Assert that a machine can reach a specific state given a sequence of events
92
- */
93
- export const assertReaches = Effect.fn("effect-machine.assertReaches")(function* <
94
- S extends { readonly _tag: string },
95
- E extends { readonly _tag: string },
96
- R,
97
- GD extends GuardsDef = Record<string, never>,
98
- EFD extends EffectsDef = Record<string, never>,
99
- >(input: MachineInput<S, E, R, GD, EFD>, events: ReadonlyArray<E>, expectedTag: string) {
100
- const result = yield* simulate(input, events);
101
- if (result.finalState._tag !== expectedTag) {
102
- return yield* new AssertionError({
103
- message:
104
- `Expected final state "${expectedTag}" but got "${result.finalState._tag}". ` +
105
- `States visited: ${result.states.map((s) => s._tag).join(" -> ")}`,
106
- });
107
- }
108
- return result.finalState;
109
- });
110
-
111
- /**
112
- * Assert that a machine follows a specific path of state tags
113
- *
114
- * @example
115
- * ```ts
116
- * yield* assertPath(
117
- * machine,
118
- * [Event.Start(), Event.Increment(), Event.Stop()],
119
- * ["Idle", "Counting", "Counting", "Done"]
120
- * )
121
- * ```
122
- */
123
- export const assertPath = Effect.fn("effect-machine.assertPath")(function* <
124
- S extends { readonly _tag: string },
125
- E extends { readonly _tag: string },
126
- R,
127
- GD extends GuardsDef = Record<string, never>,
128
- EFD extends EffectsDef = Record<string, never>,
129
- >(
130
- input: MachineInput<S, E, R, GD, EFD>,
131
- events: ReadonlyArray<E>,
132
- expectedPath: ReadonlyArray<string>,
133
- ) {
134
- const result = yield* simulate(input, events);
135
- const actualPath = result.states.map((s) => s._tag);
136
-
137
- if (actualPath.length !== expectedPath.length) {
138
- return yield* new AssertionError({
139
- message:
140
- `Path length mismatch. Expected ${expectedPath.length} states but got ${actualPath.length}.\n` +
141
- `Expected: ${expectedPath.join(" -> ")}\n` +
142
- `Actual: ${actualPath.join(" -> ")}`,
143
- });
144
- }
145
-
146
- for (let i = 0; i < expectedPath.length; i++) {
147
- if (actualPath[i] !== expectedPath[i]) {
148
- return yield* new AssertionError({
149
- message:
150
- `Path mismatch at position ${i}. Expected "${expectedPath[i]}" but got "${actualPath[i]}".\n` +
151
- `Expected: ${expectedPath.join(" -> ")}\n` +
152
- `Actual: ${actualPath.join(" -> ")}`,
153
- });
154
- }
155
- }
156
-
157
- return result;
158
- });
159
-
160
- /**
161
- * Assert that a machine never reaches a specific state given a sequence of events
162
- *
163
- * @example
164
- * ```ts
165
- * // Verify error handling doesn't reach crash state
166
- * yield* assertNeverReaches(
167
- * machine,
168
- * [Event.Error(), Event.Retry(), Event.Success()],
169
- * "Crashed"
170
- * )
171
- * ```
172
- */
173
- export const assertNeverReaches = Effect.fn("effect-machine.assertNeverReaches")(function* <
174
- S extends { readonly _tag: string },
175
- E extends { readonly _tag: string },
176
- R,
177
- GD extends GuardsDef = Record<string, never>,
178
- EFD extends EffectsDef = Record<string, never>,
179
- >(input: MachineInput<S, E, R, GD, EFD>, events: ReadonlyArray<E>, forbiddenTag: string) {
180
- const result = yield* simulate(input, events);
181
-
182
- const visitedIndex = result.states.findIndex((s) => s._tag === forbiddenTag);
183
- if (visitedIndex !== -1) {
184
- return yield* new AssertionError({
185
- message:
186
- `Machine reached forbidden state "${forbiddenTag}" at position ${visitedIndex}.\n` +
187
- `States visited: ${result.states.map((s) => s._tag).join(" -> ")}`,
188
- });
189
- }
190
-
191
- return result;
192
- });
193
-
194
- /**
195
- * Create a controllable test harness for a machine
196
- */
197
- export interface TestHarness<S, E, R> {
198
- readonly state: SubscriptionRef.SubscriptionRef<S>;
199
- readonly send: (event: E) => Effect.Effect<S, never, R>;
200
- readonly getState: Effect.Effect<S>;
201
- }
202
-
203
- /**
204
- * Options for creating a test harness
205
- */
206
- export interface TestHarnessOptions<S, E> {
207
- /**
208
- * Called after each transition with the previous state, event, and new state.
209
- * Useful for logging or spying on transitions.
210
- */
211
- readonly onTransition?: (from: S, event: E, to: S) => void;
212
- }
213
-
214
- /**
215
- * Create a test harness for step-by-step testing.
216
- * Does not run onEnter/spawn/background effects, but does run guard/effect slots
217
- * within transition handlers.
218
- *
219
- * @example Basic usage
220
- * ```ts
221
- * const harness = yield* createTestHarness(machine)
222
- * yield* harness.send(Event.Start())
223
- * const state = yield* harness.getState
224
- * ```
225
- *
226
- * @example With transition observer
227
- * ```ts
228
- * const transitions: Array<{ from: string; event: string; to: string }> = []
229
- * const harness = yield* createTestHarness(machine, {
230
- * onTransition: (from, event, to) =>
231
- * transitions.push({ from: from._tag, event: event._tag, to: to._tag })
232
- * })
233
- * ```
234
- */
235
- export const createTestHarness = Effect.fn("effect-machine.createTestHarness")(function* <
236
- S extends { readonly _tag: string },
237
- E extends { readonly _tag: string },
238
- R,
239
- GD extends GuardsDef = Record<string, never>,
240
- EFD extends EffectsDef = Record<string, never>,
241
- >(input: MachineInput<S, E, R, GD, EFD>, options?: TestHarnessOptions<S, E>) {
242
- const machine = (input instanceof BuiltMachine ? input._inner : input) as Machine<
243
- S,
244
- E,
245
- R,
246
- Record<string, never>,
247
- Record<string, never>,
248
- GD,
249
- EFD
250
- >;
251
-
252
- // Create a dummy self for slot accessors
253
- const dummySelf: MachineRef<E> = {
254
- send: Effect.fn("effect-machine.testing.harness.send")((_event: E) => Effect.void),
255
- };
256
-
257
- const stateRef = yield* SubscriptionRef.make(machine.initial);
258
-
259
- const send = Effect.fn("effect-machine.testHarness.send")(function* (event: E) {
260
- const currentState = yield* SubscriptionRef.get(stateRef);
261
-
262
- const result = yield* executeTransition(machine, currentState, event, dummySelf);
263
-
264
- if (!result.transitioned) {
265
- return currentState;
266
- }
267
-
268
- const newState = result.newState;
269
- yield* SubscriptionRef.set(stateRef, newState);
270
-
271
- // Call transition observer
272
- if (options?.onTransition !== undefined) {
273
- options.onTransition(currentState, event, newState);
274
- }
275
-
276
- return newState;
277
- });
278
-
279
- return {
280
- state: stateRef,
281
- send,
282
- getState: SubscriptionRef.get(stateRef),
283
- };
284
- });
package/tsconfig.json DELETED
@@ -1,65 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "strict": true,
4
- "noUncheckedIndexedAccess": true,
5
- "noFallthroughCasesInSwitch": true,
6
- "noImplicitOverride": true,
7
- "noPropertyAccessFromIndexSignature": true,
8
- "target": "ESNext",
9
- "module": "ESNext",
10
- "moduleResolution": "bundler",
11
- "moduleDetection": "force",
12
- "esModuleInterop": true,
13
- "skipLibCheck": true,
14
- "noEmit": true,
15
- "plugins": [
16
- {
17
- "name": "@effect/language-service",
18
- "diagnostics": true,
19
- "diagnosticsName": true,
20
- "diagnosticSeverity": {
21
- "anyUnknownInErrorContext": "error",
22
- "deterministicKeys": "warning",
23
- "importFromBarrel": "warning",
24
- "instanceOfSchema": "warning",
25
- "missedPipeableOpportunity": "warning",
26
- "missingEffectServiceDependency": "warning",
27
- "schemaUnionOfLiterals": "warning",
28
- "strictBooleanExpressions": "warning",
29
- "strictEffectProvide": "warning",
30
- "catchAllToMapError": "warning",
31
- "catchUnfailableEffect": "warning",
32
- "effectFnOpportunity": "warning",
33
- "effectMapVoid": "warning",
34
- "effectSucceedWithVoid": "warning",
35
- "leakingRequirements": "warning",
36
- "preferSchemaOverJson": "warning",
37
- "redundantSchemaTagIdentifier": "warning",
38
- "returnEffectInGen": "warning",
39
- "runEffectInsideEffect": "error",
40
- "schemaStructWithTag": "warning",
41
- "schemaSyncInEffect": "warning",
42
- "tryCatchInEffectGen": "warning",
43
- "unnecessaryEffectGen": "warning",
44
- "unnecessaryFailYieldableError": "warning",
45
- "unnecessaryPipe": "warning",
46
- "unnecessaryPipeChain": "warning"
47
- },
48
- "keyPatterns": [
49
- {
50
- "target": "service",
51
- "pattern": "default",
52
- "skipLeadingPath": ["packages/"]
53
- },
54
- {
55
- "target": "error",
56
- "pattern": "default",
57
- "skipLeadingPath": ["packages/"]
58
- }
59
- ]
60
- }
61
- ]
62
- },
63
- "include": ["src", "test"],
64
- "exclude": ["node_modules"]
65
- }