effect-machine 0.1.0 → 0.2.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.
package/src/testing.ts CHANGED
@@ -33,7 +33,7 @@ export interface SimulationResult<S> {
33
33
  * expect(result.states).toHaveLength(3) // Idle -> Loading -> Success
34
34
  * ```
35
35
  */
36
- export const simulate = <
36
+ export const simulate = Effect.fn("effect-machine.simulate")(function* <
37
37
  S extends { readonly _tag: string },
38
38
  E extends { readonly _tag: string },
39
39
  R,
@@ -43,34 +43,33 @@ export const simulate = <
43
43
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Schema fields need wide acceptance
44
44
  machine: Machine<S, E, R, any, any, GD, EFD>,
45
45
  events: ReadonlyArray<E>,
46
- ): Effect.Effect<SimulationResult<S>, never, R> =>
47
- Effect.gen(function* () {
48
- // Create a dummy self for slot accessors
49
- const dummySelf: MachineRef<E> = {
50
- send: () => Effect.void,
51
- };
46
+ ) {
47
+ // Create a dummy self for slot accessors
48
+ const dummySelf: MachineRef<E> = {
49
+ send: Effect.fn("effect-machine.testing.simulate.send")((_event: E) => Effect.void),
50
+ };
52
51
 
53
- let currentState = machine.initial;
54
- const states: S[] = [currentState];
52
+ let currentState = machine.initial;
53
+ const states: S[] = [currentState];
55
54
 
56
- for (const event of events) {
57
- const result = yield* executeTransition(machine, currentState, event, dummySelf);
55
+ for (const event of events) {
56
+ const result = yield* executeTransition(machine, currentState, event, dummySelf);
58
57
 
59
- if (!result.transitioned) {
60
- continue;
61
- }
58
+ if (!result.transitioned) {
59
+ continue;
60
+ }
62
61
 
63
- currentState = result.newState;
64
- states.push(currentState);
62
+ currentState = result.newState;
63
+ states.push(currentState);
65
64
 
66
- // Stop if final state
67
- if (machine.finalStates.has(currentState._tag)) {
68
- break;
69
- }
65
+ // Stop if final state
66
+ if (machine.finalStates.has(currentState._tag)) {
67
+ break;
70
68
  }
69
+ }
71
70
 
72
- return { states, finalState: currentState };
73
- });
71
+ return { states, finalState: currentState };
72
+ });
74
73
 
75
74
  // AssertionError is exported from errors.ts
76
75
  export { AssertionError } from "./errors.js";
@@ -78,7 +77,7 @@ export { AssertionError } from "./errors.js";
78
77
  /**
79
78
  * Assert that a machine can reach a specific state given a sequence of events
80
79
  */
81
- export const assertReaches = <
80
+ export const assertReaches = Effect.fn("effect-machine.assertReaches")(function* <
82
81
  S extends { readonly _tag: string },
83
82
  E extends { readonly _tag: string },
84
83
  R,
@@ -89,18 +88,17 @@ export const assertReaches = <
89
88
  machine: Machine<S, E, R, any, any, GD, EFD>,
90
89
  events: ReadonlyArray<E>,
91
90
  expectedTag: string,
92
- ): Effect.Effect<S, AssertionError, R> =>
93
- Effect.gen(function* () {
94
- const result = yield* simulate(machine, events);
95
- if (result.finalState._tag !== expectedTag) {
96
- return yield* new AssertionError({
97
- message:
98
- `Expected final state "${expectedTag}" but got "${result.finalState._tag}". ` +
99
- `States visited: ${result.states.map((s) => s._tag).join(" -> ")}`,
100
- });
101
- }
102
- return result.finalState;
103
- });
91
+ ) {
92
+ const result = yield* simulate(machine, events);
93
+ if (result.finalState._tag !== expectedTag) {
94
+ return yield* new AssertionError({
95
+ message:
96
+ `Expected final state "${expectedTag}" but got "${result.finalState._tag}". ` +
97
+ `States visited: ${result.states.map((s) => s._tag).join(" -> ")}`,
98
+ });
99
+ }
100
+ return result.finalState;
101
+ });
104
102
 
105
103
  /**
106
104
  * Assert that a machine follows a specific path of state tags
@@ -114,7 +112,7 @@ export const assertReaches = <
114
112
  * )
115
113
  * ```
116
114
  */
117
- export const assertPath = <
115
+ export const assertPath = Effect.fn("effect-machine.assertPath")(function* <
118
116
  S extends { readonly _tag: string },
119
117
  E extends { readonly _tag: string },
120
118
  R,
@@ -125,33 +123,32 @@ export const assertPath = <
125
123
  machine: Machine<S, E, R, any, any, GD, EFD>,
126
124
  events: ReadonlyArray<E>,
127
125
  expectedPath: ReadonlyArray<string>,
128
- ): Effect.Effect<SimulationResult<S>, AssertionError, R> =>
129
- Effect.gen(function* () {
130
- const result = yield* simulate(machine, events);
131
- const actualPath = result.states.map((s) => s._tag);
126
+ ) {
127
+ const result = yield* simulate(machine, events);
128
+ const actualPath = result.states.map((s) => s._tag);
129
+
130
+ if (actualPath.length !== expectedPath.length) {
131
+ return yield* new AssertionError({
132
+ message:
133
+ `Path length mismatch. Expected ${expectedPath.length} states but got ${actualPath.length}.\n` +
134
+ `Expected: ${expectedPath.join(" -> ")}\n` +
135
+ `Actual: ${actualPath.join(" -> ")}`,
136
+ });
137
+ }
132
138
 
133
- if (actualPath.length !== expectedPath.length) {
139
+ for (let i = 0; i < expectedPath.length; i++) {
140
+ if (actualPath[i] !== expectedPath[i]) {
134
141
  return yield* new AssertionError({
135
142
  message:
136
- `Path length mismatch. Expected ${expectedPath.length} states but got ${actualPath.length}.\n` +
143
+ `Path mismatch at position ${i}. Expected "${expectedPath[i]}" but got "${actualPath[i]}".\n` +
137
144
  `Expected: ${expectedPath.join(" -> ")}\n` +
138
145
  `Actual: ${actualPath.join(" -> ")}`,
139
146
  });
140
147
  }
148
+ }
141
149
 
142
- for (let i = 0; i < expectedPath.length; i++) {
143
- if (actualPath[i] !== expectedPath[i]) {
144
- return yield* new AssertionError({
145
- message:
146
- `Path mismatch at position ${i}. Expected "${expectedPath[i]}" but got "${actualPath[i]}".\n` +
147
- `Expected: ${expectedPath.join(" -> ")}\n` +
148
- `Actual: ${actualPath.join(" -> ")}`,
149
- });
150
- }
151
- }
152
-
153
- return result;
154
- });
150
+ return result;
151
+ });
155
152
 
156
153
  /**
157
154
  * Assert that a machine never reaches a specific state given a sequence of events
@@ -166,7 +163,7 @@ export const assertPath = <
166
163
  * )
167
164
  * ```
168
165
  */
169
- export const assertNeverReaches = <
166
+ export const assertNeverReaches = Effect.fn("effect-machine.assertNeverReaches")(function* <
170
167
  S extends { readonly _tag: string },
171
168
  E extends { readonly _tag: string },
172
169
  R,
@@ -177,21 +174,20 @@ export const assertNeverReaches = <
177
174
  machine: Machine<S, E, R, any, any, GD, EFD>,
178
175
  events: ReadonlyArray<E>,
179
176
  forbiddenTag: string,
180
- ): Effect.Effect<SimulationResult<S>, AssertionError, R> =>
181
- Effect.gen(function* () {
182
- const result = yield* simulate(machine, events);
177
+ ) {
178
+ const result = yield* simulate(machine, events);
183
179
 
184
- const visitedIndex = result.states.findIndex((s) => s._tag === forbiddenTag);
185
- if (visitedIndex !== -1) {
186
- return yield* new AssertionError({
187
- message:
188
- `Machine reached forbidden state "${forbiddenTag}" at position ${visitedIndex}.\n` +
189
- `States visited: ${result.states.map((s) => s._tag).join(" -> ")}`,
190
- });
191
- }
180
+ const visitedIndex = result.states.findIndex((s) => s._tag === forbiddenTag);
181
+ if (visitedIndex !== -1) {
182
+ return yield* new AssertionError({
183
+ message:
184
+ `Machine reached forbidden state "${forbiddenTag}" at position ${visitedIndex}.\n` +
185
+ `States visited: ${result.states.map((s) => s._tag).join(" -> ")}`,
186
+ });
187
+ }
192
188
 
193
- return result;
194
- });
189
+ return result;
190
+ });
195
191
 
196
192
  /**
197
193
  * Create a controllable test harness for a machine
@@ -234,7 +230,7 @@ export interface TestHarnessOptions<S, E> {
234
230
  * })
235
231
  * ```
236
232
  */
237
- export const createTestHarness = <
233
+ export const createTestHarness = Effect.fn("effect-machine.createTestHarness")(function* <
238
234
  S extends { readonly _tag: string },
239
235
  E extends { readonly _tag: string },
240
236
  R,
@@ -244,39 +240,37 @@ export const createTestHarness = <
244
240
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Schema fields need wide acceptance
245
241
  machine: Machine<S, E, R, any, any, GD, EFD>,
246
242
  options?: TestHarnessOptions<S, E>,
247
- ): Effect.Effect<TestHarness<S, E, R>, never, R> =>
248
- Effect.gen(function* () {
249
- // Create a dummy self for slot accessors
250
- const dummySelf: MachineRef<E> = {
251
- send: () => Effect.void,
252
- };
243
+ ) {
244
+ // Create a dummy self for slot accessors
245
+ const dummySelf: MachineRef<E> = {
246
+ send: Effect.fn("effect-machine.testing.harness.send")((_event: E) => Effect.void),
247
+ };
253
248
 
254
- const stateRef = yield* SubscriptionRef.make(machine.initial);
249
+ const stateRef = yield* SubscriptionRef.make(machine.initial);
255
250
 
256
- const send = (event: E): Effect.Effect<S, never, R> =>
257
- Effect.gen(function* () {
258
- const currentState = yield* SubscriptionRef.get(stateRef);
251
+ const send = Effect.fn("effect-machine.testHarness.send")(function* (event: E) {
252
+ const currentState = yield* SubscriptionRef.get(stateRef);
259
253
 
260
- const result = yield* executeTransition(machine, currentState, event, dummySelf);
254
+ const result = yield* executeTransition(machine, currentState, event, dummySelf);
261
255
 
262
- if (!result.transitioned) {
263
- return currentState;
264
- }
265
-
266
- const newState = result.newState;
267
- yield* SubscriptionRef.set(stateRef, newState);
256
+ if (!result.transitioned) {
257
+ return currentState;
258
+ }
268
259
 
269
- // Call transition observer
270
- if (options?.onTransition !== undefined) {
271
- options.onTransition(currentState, event, newState);
272
- }
260
+ const newState = result.newState;
261
+ yield* SubscriptionRef.set(stateRef, newState);
273
262
 
274
- return newState;
275
- });
263
+ // Call transition observer
264
+ if (options?.onTransition !== undefined) {
265
+ options.onTransition(currentState, event, newState);
266
+ }
276
267
 
277
- return {
278
- state: stateRef,
279
- send,
280
- getState: SubscriptionRef.get(stateRef),
281
- };
268
+ return newState;
282
269
  });
270
+
271
+ return {
272
+ state: stateRef,
273
+ send,
274
+ getState: SubscriptionRef.get(stateRef),
275
+ };
276
+ });
package/tsconfig.json CHANGED
@@ -18,7 +18,6 @@
18
18
  "diagnostics": true,
19
19
  "diagnosticsName": true,
20
20
  "diagnosticSeverity": {
21
- // Enable rules that are off by default
22
21
  "anyUnknownInErrorContext": "error",
23
22
  "deterministicKeys": "warning",
24
23
  "importFromBarrel": "warning",
@@ -28,8 +27,6 @@
28
27
  "schemaUnionOfLiterals": "warning",
29
28
  "strictBooleanExpressions": "warning",
30
29
  "strictEffectProvide": "warning",
31
-
32
- // Upgrade suggestions to warnings for stricter enforcement
33
30
  "catchAllToMapError": "warning",
34
31
  "catchUnfailableEffect": "warning",
35
32
  "effectFnOpportunity": "warning",
@@ -52,12 +49,12 @@
52
49
  {
53
50
  "target": "service",
54
51
  "pattern": "default",
55
- "skipLeadingPath": ["src/"]
52
+ "skipLeadingPath": ["packages/"]
56
53
  },
57
54
  {
58
55
  "target": "error",
59
56
  "pattern": "default",
60
- "skipLeadingPath": ["src/"]
57
+ "skipLeadingPath": ["packages/"]
61
58
  }
62
59
  ]
63
60
  }