effect-machine 0.17.0 → 0.18.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 (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +50 -54
  3. package/dist/actor.d.ts +8 -45
  4. package/dist/actor.js +157 -231
  5. package/dist/cluster/adapters/in-memory.d.ts +0 -1
  6. package/dist/cluster/adapters/in-memory.js +10 -5
  7. package/dist/cluster/entity-actor-ref.d.ts +2 -3
  8. package/dist/cluster/entity-actor-ref.js +14 -17
  9. package/dist/cluster/entity-machine.d.ts +2 -8
  10. package/dist/cluster/entity-machine.js +57 -34
  11. package/dist/cluster/index.js +2 -2
  12. package/dist/cluster/persistence.d.ts +0 -1
  13. package/dist/cluster/to-entity.d.ts +19 -21
  14. package/dist/cluster/to-entity.js +18 -19
  15. package/dist/errors.d.ts +11 -37
  16. package/dist/errors.js +12 -30
  17. package/dist/index.d.ts +4 -5
  18. package/dist/index.js +4 -5
  19. package/dist/inspection.d.ts +0 -1
  20. package/dist/inspection.js +24 -13
  21. package/dist/internal/brands.d.ts +0 -1
  22. package/dist/internal/event-advancement.d.ts +50 -0
  23. package/dist/internal/event-advancement.js +79 -0
  24. package/dist/internal/inspection.d.ts +5 -9
  25. package/dist/internal/inspection.js +31 -10
  26. package/dist/internal/machine-definition.d.ts +16 -0
  27. package/dist/internal/runtime.d.ts +1 -161
  28. package/dist/internal/runtime.js +191 -150
  29. package/dist/internal/transition.d.ts +4 -135
  30. package/dist/internal/transition.js +82 -157
  31. package/dist/internal/utils.d.ts +3 -40
  32. package/dist/internal/utils.js +2 -2
  33. package/dist/machine.d.ts +47 -137
  34. package/dist/machine.js +147 -215
  35. package/dist/schema.d.ts +42 -21
  36. package/dist/schema.js +28 -18
  37. package/dist/supervision.d.ts +1 -24
  38. package/dist/supervision.js +2 -3
  39. package/dist/testing.d.ts +15 -29
  40. package/dist/testing.js +76 -95
  41. package/package.json +19 -38
  42. package/dist/slot.d.ts +0 -159
  43. package/dist/slot.js +0 -165
  44. package/v3/dist/_virtual/_rolldown/runtime.js +0 -13
  45. package/v3/dist/actor.d.ts +0 -250
  46. package/v3/dist/actor.js +0 -577
  47. package/v3/dist/cluster/adapters/in-memory.d.ts +0 -15
  48. package/v3/dist/cluster/adapters/in-memory.js +0 -62
  49. package/v3/dist/cluster/entity-actor-ref.d.ts +0 -49
  50. package/v3/dist/cluster/entity-actor-ref.js +0 -19
  51. package/v3/dist/cluster/entity-machine.d.ts +0 -74
  52. package/v3/dist/cluster/entity-machine.js +0 -166
  53. package/v3/dist/cluster/index.d.ts +0 -6
  54. package/v3/dist/cluster/index.js +0 -6
  55. package/v3/dist/cluster/persistence.d.ts +0 -48
  56. package/v3/dist/cluster/persistence.js +0 -14
  57. package/v3/dist/cluster/to-entity.d.ts +0 -69
  58. package/v3/dist/cluster/to-entity.js +0 -59
  59. package/v3/dist/errors.d.ts +0 -95
  60. package/v3/dist/errors.js +0 -54
  61. package/v3/dist/index.d.ts +0 -11
  62. package/v3/dist/index.js +0 -9
  63. package/v3/dist/inspection.d.ts +0 -151
  64. package/v3/dist/inspection.js +0 -128
  65. package/v3/dist/internal/brands.d.ts +0 -50
  66. package/v3/dist/internal/inspection.d.ts +0 -11
  67. package/v3/dist/internal/inspection.js +0 -20
  68. package/v3/dist/internal/runtime.d.ts +0 -161
  69. package/v3/dist/internal/runtime.js +0 -360
  70. package/v3/dist/internal/transition.d.ts +0 -190
  71. package/v3/dist/internal/transition.js +0 -278
  72. package/v3/dist/internal/utils.d.ts +0 -101
  73. package/v3/dist/internal/utils.js +0 -75
  74. package/v3/dist/machine.d.ts +0 -398
  75. package/v3/dist/machine.js +0 -487
  76. package/v3/dist/schema.d.ts +0 -174
  77. package/v3/dist/schema.js +0 -206
  78. package/v3/dist/slot.d.ts +0 -158
  79. package/v3/dist/slot.js +0 -165
  80. package/v3/dist/supervision.d.ts +0 -97
  81. package/v3/dist/supervision.js +0 -42
  82. package/v3/dist/testing.d.ts +0 -151
  83. package/v3/dist/testing.js +0 -189
  84. /package/{v3/dist/internal/brands.js → dist/internal/machine-definition.js} +0 -0
@@ -1,278 +0,0 @@
1
- import { INTERNAL_ENTER_EVENT, isDeferReplyResult, isEffect, isReplyResult } from "./utils.js";
2
- import { Cause, Effect, Exit, Scope } from "effect";
3
- //#region src/internal/transition.ts
4
- /**
5
- * Transition execution and indexing.
6
- *
7
- * Combines:
8
- * - Transition execution logic (for event processing, simulation, test harness)
9
- * - Event processing core (shared between actor and cluster entity)
10
- * - O(1) indexed lookup by state/event tag
11
- *
12
- * @internal
13
- */
14
- /**
15
- * Run a transition handler and return the new state.
16
- * Shared logic for executing handlers with proper context.
17
- *
18
- * Used by:
19
- * - executeTransition (actor event loop, testing)
20
- * - Machine.replay (event sourcing restore)
21
- *
22
- * @internal
23
- */
24
- const runTransitionHandler = Effect.fn("effect-machine.runTransitionHandler")(function* (machine, transition, state, event, self, system, actorId) {
25
- const ctx = {
26
- actorId,
27
- state,
28
- event,
29
- self,
30
- system
31
- };
32
- const handlerCtx = {
33
- state,
34
- event,
35
- slots: machine._slots
36
- };
37
- const raw = transition.handler(handlerCtx);
38
- const resolved = isEffect(raw) ? yield* raw.pipe(Effect.provideService(machine.Context, ctx)) : raw;
39
- if (isReplyResult(resolved)) return {
40
- newState: resolved.state,
41
- hasReply: true,
42
- deferReply: false,
43
- reply: resolved.reply
44
- };
45
- if (isDeferReplyResult(resolved)) return {
46
- newState: resolved.state,
47
- hasReply: false,
48
- deferReply: true,
49
- reply: void 0
50
- };
51
- return {
52
- newState: resolved,
53
- hasReply: false,
54
- deferReply: false,
55
- reply: void 0
56
- };
57
- });
58
- /**
59
- * Execute a transition for a given state and event.
60
- * Handles transition resolution, handler invocation, and guard/effect slot creation.
61
- *
62
- * Used by:
63
- * - processEvent in actor.ts (actual actor event loop)
64
- * - simulate in testing.ts (pure transition simulation)
65
- * - createTestHarness.send in testing.ts (step-by-step testing)
66
- *
67
- * @internal
68
- */
69
- const executeTransition = Effect.fn("effect-machine.executeTransition")(function* (machine, currentState, event, self, system, actorId) {
70
- const transition = resolveTransition(machine, currentState, event);
71
- if (transition === void 0) return {
72
- newState: currentState,
73
- transitioned: false,
74
- reenter: false,
75
- hasReply: false,
76
- deferReply: false,
77
- reply: void 0
78
- };
79
- const { newState, hasReply, deferReply, reply } = yield* runTransitionHandler(machine, transition, currentState, event, self, system, actorId);
80
- return {
81
- newState,
82
- transitioned: true,
83
- reenter: transition.reenter === true,
84
- hasReply,
85
- deferReply,
86
- reply
87
- };
88
- });
89
- /**
90
- * Check if an event should be postponed in the current state.
91
- * @internal
92
- */
93
- const shouldPostpone = (machine, stateTag, eventTag) => {
94
- for (const rule of machine.postponeRules) if (rule.stateTag === stateTag && rule.eventTag === eventTag) return true;
95
- return false;
96
- };
97
- /**
98
- * Process a single event through the machine.
99
- *
100
- * Handles:
101
- * - Transition execution
102
- * - State scope lifecycle (close old, create new)
103
- * - Running spawn effects
104
- *
105
- * Optional hooks allow inspection/tracing without coupling to specific impl.
106
- *
107
- * @internal
108
- */
109
- const processEventCore = Effect.fn("effect-machine.processEventCore")(function* (machine, currentState, event, self, stateScopeRef, system, actorId, hooks) {
110
- const result = yield* executeTransition(machine, currentState, event, self, system, actorId).pipe(Effect.catchAllCause((cause) => {
111
- if (Cause.isInterruptedOnly(cause)) return Effect.interrupt;
112
- const onError = hooks?.onError;
113
- if (onError === void 0) return Effect.failCause(cause).pipe(Effect.orDie);
114
- return onError({
115
- phase: "transition",
116
- state: currentState,
117
- event,
118
- cause
119
- }).pipe(Effect.andThen(Effect.failCause(cause).pipe(Effect.orDie)));
120
- }));
121
- if (!result.transitioned) return {
122
- newState: currentState,
123
- previousState: currentState,
124
- transitioned: false,
125
- lifecycleRan: false,
126
- isFinal: false,
127
- hasReply: false,
128
- deferReply: false,
129
- reply: void 0,
130
- postponed: false
131
- };
132
- const newState = result.newState;
133
- const runLifecycle = newState._tag !== currentState._tag || result.reenter;
134
- if (runLifecycle) {
135
- yield* Scope.close(stateScopeRef.current, Exit.void);
136
- stateScopeRef.current = yield* Scope.make();
137
- if (hooks?.onTransition !== void 0) yield* hooks.onTransition(currentState, newState, event);
138
- if (hooks?.onSpawnEffect !== void 0) yield* hooks.onSpawnEffect(newState);
139
- yield* runSpawnEffects(machine, newState, { _tag: INTERNAL_ENTER_EVENT }, self, stateScopeRef.current, system, actorId, hooks?.onError, hooks?.onSpawnDefect);
140
- }
141
- return {
142
- newState,
143
- previousState: currentState,
144
- transitioned: true,
145
- lifecycleRan: runLifecycle,
146
- isFinal: machine.finalStates.has(newState._tag),
147
- hasReply: result.hasReply,
148
- deferReply: result.deferReply,
149
- reply: result.reply,
150
- postponed: false
151
- };
152
- });
153
- /**
154
- * Run spawn effects for a state (forked into state scope, auto-cancelled on state exit).
155
- *
156
- * @internal
157
- */
158
- const runSpawnEffects = Effect.fn("effect-machine.runSpawnEffects")(function* (machine, state, event, self, stateScope, system, actorId, onError, onSpawnDefect) {
159
- const spawnEffects = findSpawnEffects(machine, state._tag);
160
- const ctx = {
161
- actorId,
162
- state,
163
- event,
164
- self,
165
- system
166
- };
167
- const slots = machine._slots;
168
- const reportError = onError;
169
- const defectSignal = onSpawnDefect;
170
- for (const spawnEffect of spawnEffects) {
171
- const effect = spawnEffect.handler({
172
- actorId,
173
- state,
174
- event,
175
- self,
176
- slots,
177
- system
178
- }).pipe(Effect.provideService(machine.Context, ctx), Effect.catchAllCause((cause) => {
179
- if (Cause.isInterruptedOnly(cause)) return Effect.interrupt;
180
- const report = reportError !== void 0 ? reportError({
181
- phase: "spawn",
182
- state,
183
- event,
184
- cause
185
- }) : Effect.void;
186
- const signal = defectSignal !== void 0 ? defectSignal(cause) : Effect.void;
187
- return report.pipe(Effect.andThen(signal), Effect.andThen(Effect.failCause(cause).pipe(Effect.orDie)));
188
- }));
189
- yield* Effect.forkScoped(effect).pipe(Effect.provideService(Scope.Scope, stateScope));
190
- }
191
- });
192
- /**
193
- * Resolve which transition should fire for a given state and event.
194
- * Uses indexed O(1) lookup. First matching transition wins.
195
- */
196
- const resolveTransition = (machine, currentState, event) => {
197
- return findTransitions(machine, currentState._tag, event._tag)[0];
198
- };
199
- const indexCache = /* @__PURE__ */ new WeakMap();
200
- /**
201
- * Invalidate cached index for a machine (call after mutation).
202
- */
203
- const invalidateIndex = (machine) => {
204
- indexCache.delete(machine);
205
- };
206
- /**
207
- * Build transition index from machine definition.
208
- * O(n) where n = number of transitions.
209
- */
210
- const buildTransitionIndex = (transitions) => {
211
- const index = /* @__PURE__ */ new Map();
212
- for (const t of transitions) {
213
- let stateMap = index.get(t.stateTag);
214
- if (stateMap === void 0) {
215
- stateMap = /* @__PURE__ */ new Map();
216
- index.set(t.stateTag, stateMap);
217
- }
218
- let eventList = stateMap.get(t.eventTag);
219
- if (eventList === void 0) {
220
- eventList = [];
221
- stateMap.set(t.eventTag, eventList);
222
- }
223
- eventList.push(t);
224
- }
225
- return index;
226
- };
227
- /**
228
- * Build spawn index from machine definition.
229
- */
230
- const buildSpawnIndex = (effects) => {
231
- const index = /* @__PURE__ */ new Map();
232
- for (const e of effects) {
233
- let stateList = index.get(e.stateTag);
234
- if (stateList === void 0) {
235
- stateList = [];
236
- index.set(e.stateTag, stateList);
237
- }
238
- stateList.push(e);
239
- }
240
- return index;
241
- };
242
- /**
243
- * Get or build index for a machine.
244
- */
245
- const getIndex = (machine) => {
246
- let index = indexCache.get(machine);
247
- if (index === void 0) {
248
- index = {
249
- transitions: buildTransitionIndex(machine.transitions),
250
- spawn: buildSpawnIndex(machine.spawnEffects)
251
- };
252
- indexCache.set(machine, index);
253
- }
254
- return index;
255
- };
256
- /**
257
- * Find all transitions matching a state/event pair.
258
- * Returns empty array if no matches.
259
- *
260
- * O(1) lookup after first access (index is lazily built).
261
- */
262
- const findTransitions = (machine, stateTag, eventTag) => {
263
- const index = getIndex(machine);
264
- const specific = index.transitions.get(stateTag)?.get(eventTag) ?? [];
265
- if (specific.length > 0) return specific;
266
- return index.transitions.get("*")?.get(eventTag) ?? [];
267
- };
268
- /**
269
- * Find all spawn effects for a state.
270
- * Returns empty array if no matches.
271
- *
272
- * O(1) lookup after first access (index is lazily built).
273
- */
274
- const findSpawnEffects = (machine, stateTag) => {
275
- return getIndex(machine).spawn.get(stateTag) ?? [];
276
- };
277
- //#endregion
278
- export { executeTransition, findSpawnEffects, findTransitions, invalidateIndex, processEventCore, resolveTransition, runSpawnEffects, runTransitionHandler, shouldPostpone };
@@ -1,101 +0,0 @@
1
- import { ActorSystem } from "../actor.js";
2
- import { Effect } from "effect";
3
-
4
- //#region src/internal/utils.d.ts
5
- /**
6
- * Extracts _tag from a tagged union member
7
- */
8
- type TagOf<T> = T extends {
9
- readonly _tag: infer Tag;
10
- } ? Tag : never;
11
- /**
12
- * Extracts args type from a Data.taggedEnum constructor
13
- */
14
- type ArgsOf<C> = C extends ((args: infer A) => unknown) ? A : never;
15
- /**
16
- * Extracts return type from a Data.taggedEnum constructor
17
- * @internal
18
- */
19
- type InstanceOf<C> = C extends ((...args: unknown[]) => infer R) ? R : never;
20
- /**
21
- * A tagged union constructor (from Data.taggedEnum)
22
- */
23
- type TaggedConstructor<T extends {
24
- readonly _tag: string;
25
- }> = (args: Omit<T, "_tag">) => T;
26
- declare const ReplyResultSymbol: unique symbol;
27
- type ReplyResultSymbol = typeof ReplyResultSymbol;
28
- /**
29
- * Branded reply result from a transition handler.
30
- * Created via `Machine.reply(state, value)`.
31
- */
32
- interface ReplyResult<State, Reply> {
33
- readonly state: State;
34
- readonly reply: Reply;
35
- readonly [ReplyResultSymbol]: true;
36
- }
37
- /**
38
- * Create a reply result for ask-bearing event handlers.
39
- */
40
- declare const makeReply: <State, Reply>(state: State, reply: Reply) => ReplyResult<State, Reply>;
41
- /**
42
- * Type guard for ReplyResult (symbol-based, replaces duck-typing).
43
- */
44
- declare const isReplyResult: (value: unknown) => value is ReplyResult<unknown, unknown>;
45
- declare const DeferReplySymbol: unique symbol;
46
- type DeferReplySymbol = typeof DeferReplySymbol;
47
- /**
48
- * Branded deferred reply result from a transition handler.
49
- * Signals that the reply will be settled later by `self.reply()` in a spawn handler.
50
- * Created via `Machine.deferReply(state)`.
51
- */
52
- interface DeferReplyResult<State> {
53
- readonly state: State;
54
- readonly [DeferReplySymbol]: true;
55
- }
56
- /**
57
- * Create a deferred reply result. Handler returns this to signal
58
- * "spawn handler will call self.reply(value) later".
59
- */
60
- declare const makeDeferReply: <State>(state: State) => DeferReplyResult<State>;
61
- /**
62
- * Type guard for DeferReplyResult.
63
- */
64
- declare const isDeferReplyResult: (value: unknown) => value is DeferReplyResult<unknown>;
65
- /**
66
- * Transition handler result.
67
- * - When Reply is `never`: handler returns plain State (no reply allowed)
68
- * - When Reply is concrete: handler must return ReplyResult via Machine.reply()
69
- */
70
- type TransitionResult<State, R, Reply = never> = [Reply] extends [never] ? State | Effect.Effect<State, never, R> : ReplyResult<State, Reply> | DeferReplyResult<State> | Effect.Effect<ReplyResult<State, Reply> | DeferReplyResult<State>, never, R>;
71
- /**
72
- * Internal event tags used for lifecycle effect contexts.
73
- * Prefixed with $ to distinguish from user events.
74
- * @internal
75
- */
76
- declare const INTERNAL_INIT_EVENT: "$init";
77
- declare const INTERNAL_ENTER_EVENT: "$enter";
78
- /**
79
- * Extract _tag from a tagged value or constructor.
80
- *
81
- * Supports:
82
- * - Plain values with `_tag` (MachineSchema empty structs)
83
- * - Constructors with static `_tag` (MachineSchema non-empty structs)
84
- * - Data.taggedEnum constructors (fallback via instantiation)
85
- */
86
- declare const getTag: (constructorOrValue: {
87
- _tag: string;
88
- } | ((...args: never[]) => {
89
- _tag: string;
90
- })) => string;
91
- /** Check if a value is an Effect */
92
- declare const isEffect: (value: unknown) => value is Effect.Effect<unknown, unknown, unknown>;
93
- /**
94
- * Stub ActorSystem that dies on any method call.
95
- * Used in contexts where spawning/system access isn't supported
96
- * (testing simulation, persistent actor replay).
97
- * @internal
98
- */
99
- declare const stubSystem: ActorSystem;
100
- //#endregion
101
- export { ArgsOf, DeferReplyResult, DeferReplySymbol, INTERNAL_ENTER_EVENT, INTERNAL_INIT_EVENT, InstanceOf, ReplyResult, ReplyResultSymbol, TagOf, TaggedConstructor, TransitionResult, getTag, isDeferReplyResult, isEffect, isReplyResult, makeDeferReply, makeReply, stubSystem };
@@ -1,75 +0,0 @@
1
- import { Effect, Stream } from "effect";
2
- //#region src/internal/utils.ts
3
- /**
4
- * Internal utilities for effect-machine.
5
- * @internal
6
- */
7
- const ReplyResultSymbol = Symbol.for("effect-machine/ReplyResult");
8
- /**
9
- * Create a reply result for ask-bearing event handlers.
10
- */
11
- const makeReply = (state, reply) => ({
12
- state,
13
- reply,
14
- [ReplyResultSymbol]: true
15
- });
16
- /**
17
- * Type guard for ReplyResult (symbol-based, replaces duck-typing).
18
- */
19
- const isReplyResult = (value) => value !== null && typeof value === "object" && ReplyResultSymbol in value;
20
- const DeferReplySymbol = Symbol.for("effect-machine/DeferReply");
21
- /**
22
- * Create a deferred reply result. Handler returns this to signal
23
- * "spawn handler will call self.reply(value) later".
24
- */
25
- const makeDeferReply = (state) => ({
26
- state,
27
- [DeferReplySymbol]: true
28
- });
29
- /**
30
- * Type guard for DeferReplyResult.
31
- */
32
- const isDeferReplyResult = (value) => value !== null && typeof value === "object" && DeferReplySymbol in value;
33
- /**
34
- * Internal event tags used for lifecycle effect contexts.
35
- * Prefixed with $ to distinguish from user events.
36
- * @internal
37
- */
38
- const INTERNAL_INIT_EVENT = "$init";
39
- const INTERNAL_ENTER_EVENT = "$enter";
40
- /**
41
- * Extract _tag from a tagged value or constructor.
42
- *
43
- * Supports:
44
- * - Plain values with `_tag` (MachineSchema empty structs)
45
- * - Constructors with static `_tag` (MachineSchema non-empty structs)
46
- * - Data.taggedEnum constructors (fallback via instantiation)
47
- */
48
- const getTag = (constructorOrValue) => {
49
- if ("_tag" in constructorOrValue && typeof constructorOrValue._tag === "string") return constructorOrValue._tag;
50
- try {
51
- return constructorOrValue()._tag;
52
- } catch {
53
- return constructorOrValue({})._tag;
54
- }
55
- };
56
- /** Check if a value is an Effect */
57
- const isEffect = Effect.isEffect;
58
- /**
59
- * Stub ActorSystem that dies on any method call.
60
- * Used in contexts where spawning/system access isn't supported
61
- * (testing simulation, persistent actor replay).
62
- * @internal
63
- */
64
- const stubSystem = {
65
- spawn: () => Effect.die("spawn not supported in stub system"),
66
- get: () => Effect.die("get not supported in stub system"),
67
- stop: () => Effect.die("stop not supported in stub system"),
68
- events: Stream.empty,
69
- get actors() {
70
- return /* @__PURE__ */ new Map();
71
- },
72
- subscribe: () => () => {}
73
- };
74
- //#endregion
75
- export { INTERNAL_ENTER_EVENT, INTERNAL_INIT_EVENT, getTag, isDeferReplyResult, isEffect, isReplyResult, makeDeferReply, makeReply, stubSystem };