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
package/dist/machine.js CHANGED
@@ -1,69 +1,46 @@
1
1
  import { __exportAll } from "./_virtual/_rolldown/runtime.js";
2
- import { getTag, makeDeferReply, makeReply, stubSystem } from "./internal/utils.js";
3
- import { ProvisionValidationError, SlotCodecError, SlotProvisionError } from "./errors.js";
4
- import { findTransitions, invalidateIndex, resolveTransition, runTransitionHandler, shouldPostpone } from "./internal/transition.js";
2
+ import { getTag, makeDeferReply, makeReply } from "./internal/utils.js";
3
+ import { getReplySchemas } from "./schema.js";
4
+ import { makeEventAdvancement } from "./internal/event-advancement.js";
5
+ import { executeTransition, shouldPostpone } from "./internal/transition.js";
5
6
  import { emitWithTimestamp } from "./internal/inspection.js";
6
7
  import { Inspector } from "./inspection.js";
7
- import { MachineContextTag } from "./slot.js";
8
8
  import { ActorScope, createActor } from "./actor.js";
9
- import { Cause, Effect, Exit, Option, Random, Schema, Scope } from "effect";
9
+ import { Cause, Effect, Exit, Option, Random, Scope } from "effect";
10
10
  //#region src/machine.ts
11
11
  var machine_exports = /* @__PURE__ */ __exportAll({
12
12
  Machine: () => Machine,
13
13
  deferReply: () => deferReply,
14
- findTransitions: () => findTransitions,
15
14
  make: () => make,
16
- materializeMachine: () => materializeMachine,
17
15
  replay: () => replay,
18
16
  reply: () => reply,
19
17
  scoped: () => scoped,
20
18
  spawn: () => spawn
21
19
  });
22
- const emitTaskInspection = (input) => Effect.flatMap(Effect.serviceOption(Inspector), (inspector) => Option.isNone(inspector) ? Effect.void : emitWithTimestamp(inspector.value, (timestamp) => ({
23
- type: "@machine.task",
24
- actorId: input.actorId,
25
- state: input.state,
26
- taskName: input.taskName,
27
- phase: input.phase,
28
- error: input.error,
29
- timestamp
30
- })));
20
+ const isStateResolver = (value) => typeof value === "function";
31
21
  /**
32
- * Bind slot handlers to a machine, returning a fresh copy with handlers installed.
33
- * If no handlers provided and machine has no slots, returns the machine as-is.
34
- * Validates that all required slots are provided and no extra slots are given.
35
- *
36
- * @internal — used by spawn, replay, simulate, test harness, entity-machine
22
+ * `Array.isArray` widens to `any[]` and does not narrow a
23
+ * `T | ReadonlyArray<T>` union, so the normalization below needs an explicit
24
+ * predicate to stay cast-free.
37
25
  */
38
- const materializeMachine = (machine, handlers) => {
39
- if (handlers === void 0) {
40
- if (machine._slotsSchema !== void 0 && Object.keys(machine._slotsSchema.definitions).length > 0) throw new ProvisionValidationError({
41
- missing: Object.keys(machine._slotsSchema.definitions),
42
- extra: []
43
- });
44
- return machine;
45
- }
46
- const requiredSlots = /* @__PURE__ */ new Set();
47
- if (machine._slotsSchema !== void 0) for (const name of Object.keys(machine._slotsSchema.definitions)) requiredSlots.add(name);
48
- const providedSlots = new Set(Object.keys(handlers));
49
- const missing = [];
50
- const extra = [];
51
- for (const name of requiredSlots) if (!providedSlots.has(name)) missing.push(name);
52
- for (const name of providedSlots) if (!requiredSlots.has(name)) extra.push(name);
53
- if (missing.length > 0 || extra.length > 0) throw new ProvisionValidationError({
54
- missing,
55
- extra
56
- });
57
- const result = new Machine(machine.initial, machine.stateSchema, machine.eventSchema, machine._slotsSchema, machine._slotValidation);
58
- result._transitions = [...machine._transitions];
59
- result._finalStates = new Set(machine._finalStates);
60
- result._spawnEffects = [...machine._spawnEffects];
61
- result._backgroundEffects = [...machine._backgroundEffects];
62
- result._postponeRules = [...machine._postponeRules];
63
- result._replySchemas = machine._replySchemas;
64
- if (machine._slotsSchema !== void 0) for (const name of Object.keys(machine._slotsSchema.definitions)) result._slotHandlers.set(name, handlers[name]);
65
- return result;
26
+ const isReadonlyArray = (value) => Array.isArray(value);
27
+ /** Normalize a single value or a readonly array of values into a readonly array. */
28
+ const toReadonlyArray = (valueOrValues) => {
29
+ if (isReadonlyArray(valueOrValues)) return valueOrValues;
30
+ return [valueOrValues];
66
31
  };
32
+ const emitTaskInspection = (input) => Effect.flatMap(Effect.serviceOption(Inspector), (inspector) => {
33
+ if (Option.isNone(inspector)) return Effect.void;
34
+ return emitWithTimestamp(inspector.value, (timestamp) => ({
35
+ type: "@machine.task",
36
+ actorId: input.actorId,
37
+ state: input.state,
38
+ taskName: input.taskName,
39
+ phase: input.phase,
40
+ error: input.error,
41
+ timestamp
42
+ }));
43
+ });
67
44
  /**
68
45
  * Machine definition with fluent builder API.
69
46
  *
@@ -73,112 +50,31 @@ const materializeMachine = (machine, handlers) => {
73
50
  * - `R`: Effect requirements
74
51
  * - `_SD`: State schema definition (for compile-time validation)
75
52
  * - `_ED`: Event schema definition (for compile-time validation)
76
- * - `SD`: Slot definitions
77
53
  */
78
54
  var Machine = class Machine {
79
55
  initial;
80
- /** @internal */ _transitions;
81
- /** @internal */ _spawnEffects;
82
- /** @internal */ _backgroundEffects;
83
- /** @internal */ _finalStates;
84
- /** @internal */ _postponeRules;
85
- /** @internal */ _slotsSchema;
86
- /** @internal */ _slotHandlers;
87
- /** @internal */ _slots;
88
- /** @internal */ _slotValidation;
56
+ #backgroundEffects;
57
+ #finalStates;
58
+ #postponeRules;
89
59
  stateSchema;
90
60
  eventSchema;
91
- /** @internal */ _replySchemas;
92
- /**
93
- * Context tag for accessing machine state/event/self in slot handlers.
94
- * Uses shared module-level tag for all machines.
95
- */
96
- Context = MachineContextTag;
97
- get transitions() {
98
- return this._transitions;
99
- }
100
- get spawnEffects() {
101
- return this._spawnEffects;
102
- }
103
- get backgroundEffects() {
104
- return this._backgroundEffects;
105
- }
106
- get finalStates() {
107
- return this._finalStates;
108
- }
109
- get postponeRules() {
110
- return this._postponeRules;
111
- }
112
- get slotsSchema() {
113
- return this._slotsSchema;
114
- }
115
- get replySchemas() {
116
- return this._replySchemas;
117
- }
61
+ #replySchemas;
62
+ #transitionIndex;
63
+ #spawnIndex;
118
64
  /** @internal */
119
- constructor(initial, stateSchema, eventSchema, slotsSchema, slotValidation = true) {
65
+ constructor(initial, stateSchema, eventSchema) {
120
66
  this.initial = initial;
121
- this._transitions = [];
122
- this._spawnEffects = [];
123
- this._backgroundEffects = [];
124
- this._finalStates = /* @__PURE__ */ new Set();
125
- this._postponeRules = [];
126
- this._slotsSchema = slotsSchema;
127
- this._replySchemas = eventSchema?._replySchemas ?? /* @__PURE__ */ new Map();
128
- this._slotHandlers = /* @__PURE__ */ new Map();
129
- this._slotValidation = slotValidation;
67
+ this.#backgroundEffects = [];
68
+ this.#finalStates = /* @__PURE__ */ new Set();
69
+ this.#postponeRules = [];
70
+ this.#transitionIndex = /* @__PURE__ */ new Map();
71
+ this.#spawnIndex = /* @__PURE__ */ new Map();
72
+ this.#replySchemas = getReplySchemas(eventSchema) ?? /* @__PURE__ */ new Map();
130
73
  this.stateSchema = stateSchema;
131
74
  this.eventSchema = eventSchema;
132
- const validators = slotValidation && slotsSchema !== void 0 ? new Map(Object.entries(slotsSchema.definitions).map(([name, def]) => [name, {
133
- decodeInput: Schema.decodeUnknownSync(def.inputSchema),
134
- decodeOutput: Schema.decodeUnknownSync(def.outputSchema)
135
- }])) : void 0;
136
- const resolve = (name, params) => Effect.flatMap(Effect.serviceOption(this.Context), (maybeCtx) => {
137
- if (Option.isNone(maybeCtx)) return Effect.die("MachineContext not available");
138
- const handler = this._slotHandlers.get(name);
139
- if (handler === void 0) return Effect.die(new SlotProvisionError({
140
- slotName: name,
141
- slotType: "slot"
142
- }));
143
- const validatedParams = validators !== void 0 ? (() => {
144
- try {
145
- const v = validators.get(name);
146
- return v !== void 0 ? v.decodeInput(params) : params;
147
- } catch (e) {
148
- return Effect.die(new SlotCodecError({
149
- slotName: name,
150
- phase: "input",
151
- message: e instanceof Error ? e.message : String(e)
152
- }));
153
- }
154
- })() : params;
155
- if (Effect.isEffect(validatedParams)) return validatedParams;
156
- const result = handler(validatedParams);
157
- let resultEffect;
158
- if (result === void 0 || result === null) resultEffect = Effect.void;
159
- else if (Effect.isEffect(result)) resultEffect = result;
160
- else resultEffect = Effect.succeed(result);
161
- if (validators !== void 0) {
162
- const v = validators.get(name);
163
- if (v !== void 0) return Effect.flatMap(resultEffect, (value) => {
164
- try {
165
- const decoded = v.decodeOutput(value);
166
- return Effect.succeed(decoded);
167
- } catch (e) {
168
- return Effect.die(new SlotCodecError({
169
- slotName: name,
170
- phase: "output",
171
- message: e instanceof Error ? e.message : String(e)
172
- }));
173
- }
174
- });
175
- }
176
- return resultEffect;
177
- });
178
- this._slots = this._slotsSchema !== void 0 ? this._slotsSchema._createSlots(resolve) : {};
179
75
  }
180
76
  from(stateOrStates, build) {
181
- const states = Array.isArray(stateOrStates) ? stateOrStates : [stateOrStates];
77
+ const states = toReadonlyArray(stateOrStates);
182
78
  build(new TransitionScope(this, states));
183
79
  return this;
184
80
  }
@@ -188,12 +84,14 @@ var Machine = class Machine {
188
84
  return this;
189
85
  }
190
86
  on(stateOrStates, event, handler) {
191
- const states = Array.isArray(stateOrStates) ? stateOrStates : [stateOrStates];
87
+ const states = toReadonlyArray(stateOrStates);
192
88
  for (const s of states) this.addTransition(s, event, handler, false);
193
89
  return this;
194
90
  }
195
91
  reenter(stateOrStates, event, handler) {
196
- const states = Array.isArray(stateOrStates) ? stateOrStates : [stateOrStates];
92
+ let states;
93
+ if (Array.isArray(stateOrStates)) states = stateOrStates;
94
+ else states = [stateOrStates];
197
95
  for (const s of states) this.addTransition(s, event, handler, true);
198
96
  return this;
199
97
  }
@@ -208,8 +106,7 @@ var Machine = class Machine {
208
106
  handler,
209
107
  reenter: false
210
108
  };
211
- this._transitions.push(transition);
212
- invalidateIndex(this);
109
+ this.registerTransition(transition);
213
110
  return this;
214
111
  }
215
112
  /** @internal */
@@ -220,22 +117,74 @@ var Machine = class Machine {
220
117
  handler,
221
118
  reenter
222
119
  };
223
- this._transitions.push(transition);
224
- invalidateIndex(this);
120
+ this.registerTransition(transition);
225
121
  return this;
226
122
  }
227
123
  spawn(stateOrStates, handler) {
228
- const states = Array.isArray(stateOrStates) ? stateOrStates : [stateOrStates];
124
+ const states = toReadonlyArray(stateOrStates);
229
125
  for (const s of states) {
230
126
  const stateTag = getTag(s);
231
- this._spawnEffects.push({
127
+ const spawnEffect = {
232
128
  stateTag,
233
129
  handler
234
- });
130
+ };
131
+ const effects = this.#spawnIndex.get(stateTag) ?? [];
132
+ effects.push(spawnEffect);
133
+ this.#spawnIndex.set(stateTag, effects);
235
134
  }
236
- invalidateIndex(this);
237
135
  return this;
238
136
  }
137
+ /** @internal */
138
+ _findTransitions(stateTag, eventTag) {
139
+ const specific = this.#transitionIndex.get(stateTag)?.get(eventTag) ?? [];
140
+ if (specific.length > 0) return specific;
141
+ return this.#transitionIndex.get("*")?.get(eventTag) ?? [];
142
+ }
143
+ /** @internal */
144
+ _findSpawnEffects(stateTag) {
145
+ return this.#spawnIndex.get(stateTag) ?? [];
146
+ }
147
+ /** @internal */
148
+ _backgroundEffectEntries() {
149
+ return this.#backgroundEffects.values();
150
+ }
151
+ /** @internal */
152
+ _isFinal(stateTag) {
153
+ return this.#finalStates.has(stateTag);
154
+ }
155
+ /** @internal */
156
+ _shouldPostpone(stateTag, eventTag) {
157
+ return this.#postponeRules.some((rule) => rule.stateTag === stateTag && rule.eventTag === eventTag);
158
+ }
159
+ /** @internal */
160
+ _hasPostponeRules() {
161
+ return this.#postponeRules.length > 0;
162
+ }
163
+ /** @internal */
164
+ _replySchema(eventTag) {
165
+ return this.#replySchemas.get(eventTag);
166
+ }
167
+ /** @internal */
168
+ _withInitial(initial) {
169
+ const copy = new Machine(initial, this.stateSchema, this.eventSchema);
170
+ copy.#backgroundEffects.push(...this.#backgroundEffects);
171
+ for (const stateTag of this.#finalStates) copy.#finalStates.add(stateTag);
172
+ copy.#postponeRules.push(...this.#postponeRules);
173
+ for (const [stateTag, events] of this.#transitionIndex) {
174
+ const eventCopy = /* @__PURE__ */ new Map();
175
+ for (const [eventTag, transitions] of events) eventCopy.set(eventTag, transitions.slice());
176
+ copy.#transitionIndex.set(stateTag, eventCopy);
177
+ }
178
+ for (const [stateTag, effects] of this.#spawnIndex) copy.#spawnIndex.set(stateTag, effects.slice());
179
+ return copy;
180
+ }
181
+ registerTransition(transition) {
182
+ const events = this.#transitionIndex.get(transition.stateTag) ?? /* @__PURE__ */ new Map();
183
+ const transitions = events.get(transition.eventTag) ?? [];
184
+ transitions.push(transition);
185
+ events.set(transition.eventTag, transitions);
186
+ this.#transitionIndex.set(transition.stateTag, events);
187
+ }
239
188
  task(stateOrStates, run, options) {
240
189
  const handler = Effect.fn("effect-machine.task")(function* (ctx) {
241
190
  yield* emitTaskInspection({
@@ -252,7 +201,8 @@ var Machine = class Machine {
252
201
  taskName: options.name,
253
202
  phase: "success"
254
203
  });
255
- const successEvent = options.onSuccess !== void 0 ? options.onSuccess(exit.value, ctx) : exit.value;
204
+ let successEvent = exit.value;
205
+ if (options.onSuccess !== void 0) successEvent = options.onSuccess(exit.value, ctx);
256
206
  yield* ctx.self.send(successEvent);
257
207
  yield* Effect.yieldNow;
258
208
  return;
@@ -306,8 +256,16 @@ var Machine = class Machine {
306
256
  */
307
257
  timeout(state, config) {
308
258
  const stateTag = getTag(state);
309
- const resolveDuration = typeof config.duration === "function" ? config.duration : () => config.duration;
310
- const resolveEvent = typeof config.event === "function" ? config.event : () => config.event;
259
+ const duration = config.duration;
260
+ const event = config.event;
261
+ const resolveDuration = (currentState) => {
262
+ if (isStateResolver(duration)) return duration(currentState);
263
+ return duration;
264
+ };
265
+ const resolveEvent = (currentState) => {
266
+ if (isStateResolver(event)) return event(currentState);
267
+ return event;
268
+ };
311
269
  return this.task(state, (ctx) => Effect.sleep(resolveDuration(ctx.state)), {
312
270
  onSuccess: (_, ctx) => resolveEvent(ctx.state),
313
271
  name: `$timeout:${stateTag}`
@@ -326,7 +284,7 @@ var Machine = class Machine {
326
284
  * ```
327
285
  */
328
286
  background(handler) {
329
- this._backgroundEffects.push({ handler });
287
+ this.#backgroundEffects.push({ handler });
330
288
  return this;
331
289
  }
332
290
  /**
@@ -348,10 +306,10 @@ var Machine = class Machine {
348
306
  */
349
307
  postpone(state, events) {
350
308
  const stateTag = getTag(state);
351
- const eventList = Array.isArray(events) ? events : [events];
309
+ const eventList = toReadonlyArray(events);
352
310
  for (const ev of eventList) {
353
311
  const eventTag = getTag(ev);
354
- this._postponeRules.push({
312
+ this.#postponeRules.push({
355
313
  stateTag,
356
314
  eventTag
357
315
  });
@@ -360,14 +318,16 @@ var Machine = class Machine {
360
318
  }
361
319
  final(state) {
362
320
  const stateTag = getTag(state);
363
- this._finalStates.add(stateTag);
321
+ this.#finalStates.add(stateTag);
364
322
  return this;
365
323
  }
366
324
  static make(config) {
367
- return new Machine(config.initial, config.state, config.event, config.slots, config.slotValidation ?? true);
325
+ return new Machine(config.initial, config.state, config.event);
368
326
  }
369
327
  };
370
328
  var TransitionScope = class {
329
+ machine;
330
+ states;
371
331
  constructor(machine, states) {
372
332
  this.machine = machine;
373
333
  this.states = states;
@@ -385,18 +345,10 @@ const make = Machine.make;
385
345
  /**
386
346
  * Spawn an actor from a machine.
387
347
  *
388
- * For machines with slots, pass implementations via `{ slots: { ... } }`.
389
- *
390
348
  * @example
391
349
  * ```ts
392
- * // No slots
393
350
  * const actor = yield* Machine.spawn(machine);
394
351
  *
395
- * // With slots
396
- * const actor = yield* Machine.spawn(machine, {
397
- * slots: { canRetry: ({ max }) => attempts < max },
398
- * });
399
- *
400
352
  * // With lifecycle (recovery + durability)
401
353
  * const actor = yield* Machine.spawn(machine, {
402
354
  * lifecycle: {
@@ -407,8 +359,11 @@ const make = Machine.make;
407
359
  * ```
408
360
  */
409
361
  const spawn = Effect.fn("effect-machine.spawn")(function* (machine, idOrOptions) {
410
- const opts = typeof idOrOptions === "string" ? { id: idOrOptions } : idOrOptions;
411
- const actor = yield* createActor(opts?.id ?? `actor-${(yield* Random.next).toString(36).slice(2)}`, materializeMachine(machine, opts?.slots), {
362
+ let opts;
363
+ if (typeof idOrOptions === "string") opts = { id: idOrOptions };
364
+ else opts = idOrOptions;
365
+ const actorId = opts?.id ?? `actor-${(yield* Random.next).toString(36).slice(2)}`;
366
+ const actor = yield* createActor(actorId, machine, {
412
367
  initialState: opts?.hydrate,
413
368
  supervision: opts?.supervision,
414
369
  lifecycle: opts?.lifecycle
@@ -438,50 +393,27 @@ const spawn = Effect.fn("effect-machine.spawn")(function* (machine, idOrOptions)
438
393
  */
439
394
  const scoped = (effect) => Effect.flatMap(Effect.service(Scope.Scope), (scope) => Effect.provideService(effect, ActorScope, scope));
440
395
  const replay = Effect.fn("effect-machine.replay")(function* (input, events, options) {
441
- const machine = materializeMachine(input, options?.slots);
442
- let state = options?.from ?? machine.initial;
443
- const hasPostponeRules = machine.postponeRules.length > 0;
444
- const postponed = [];
445
- const dummySend = Effect.fn("effect-machine.replay.send")((_event) => Effect.void);
446
- const self = {
447
- send: dummySend,
448
- cast: dummySend,
449
- spawn: () => Effect.die("spawn not supported in replay"),
450
- reply: () => Effect.succeed(false)
451
- };
452
- for (const event of events) {
453
- if (machine.finalStates.has(state._tag)) break;
454
- if (hasPostponeRules && shouldPostpone(machine, state._tag, event._tag)) {
455
- postponed.push(event);
456
- continue;
457
- }
458
- const transition = resolveTransition(machine, state, event);
459
- if (transition !== void 0) {
460
- const result = yield* runTransitionHandler(machine, transition, state, event, self, stubSystem, "replay");
461
- const previousTag = state._tag;
462
- state = result.newState;
463
- if ((state._tag !== previousTag || transition.reenter === true) && postponed.length > 0) {
464
- let drainTag = previousTag;
465
- while (state._tag !== drainTag && postponed.length > 0) {
466
- if (machine.finalStates.has(state._tag)) break;
467
- drainTag = state._tag;
468
- const drained = postponed.splice(0);
469
- for (const postponedEvent of drained) {
470
- if (machine.finalStates.has(state._tag)) break;
471
- if (shouldPostpone(machine, state._tag, postponedEvent._tag)) {
472
- postponed.push(postponedEvent);
473
- continue;
474
- }
475
- const pTransition = resolveTransition(machine, state, postponedEvent);
476
- if (pTransition !== void 0) state = (yield* runTransitionHandler(machine, pTransition, state, postponedEvent, self, stubSystem, "replay")).newState;
477
- }
478
- }
479
- }
480
- }
481
- }
482
- return state;
396
+ const machine = input;
397
+ const advancement = makeEventAdvancement({
398
+ initial: options?.from ?? machine.initial,
399
+ isFinal: (state) => machine._isFinal(state._tag),
400
+ shouldPostpone: (state, event) => shouldPostpone(machine, state._tag, event._tag),
401
+ postpone: (_state, event) => Effect.succeed({
402
+ input: event,
403
+ value: void 0
404
+ }),
405
+ process: (state, event) => executeTransition(machine, state, event).pipe(Effect.map((result) => ({
406
+ state: result.newState,
407
+ transitioned: result.transitioned,
408
+ stateChanged: result.transitioned && (result.newState._tag !== state._tag || result.reenter),
409
+ shouldStop: result.transitioned && machine._isFinal(result.newState._tag),
410
+ value: void 0
411
+ })))
412
+ });
413
+ for (const event of events) yield* advancement.advance(event);
414
+ return advancement.state;
483
415
  });
484
416
  const reply = makeReply;
485
417
  const deferReply = makeDeferReply;
486
418
  //#endregion
487
- export { Machine, deferReply, findTransitions, machine_exports, make, materializeMachine, replay, reply, scoped, spawn };
419
+ export { Machine, deferReply, machine_exports, make, replay, reply, scoped, spawn };
package/dist/schema.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { FullEventBrand, FullStateBrand, ReplyTypeBrand } from "./internal/brands.js";
2
2
  import { Schema } from "effect";
3
-
4
3
  //#region src/schema.d.ts
5
4
  declare const ReplySchemaSymbol: unique symbol;
6
5
  type ReplySchemaSymbol = typeof ReplySchemaSymbol;
@@ -16,7 +15,7 @@ type ReplyFields<F extends Schema.Struct.Fields, RS extends Schema.Schema<unknow
16
15
  * Payload fields that actually flow through constructors and runtime values.
17
16
  * Reply schema metadata is type-only and must not leak into payload shapes.
18
17
  */
19
- type PayloadFields<F extends Schema.Struct.Fields> = { readonly [K in keyof F as K extends ReplySchemaSymbol ? never : K]: F[K] };
18
+ type PayloadFields<F extends Schema.Struct.Fields> = { readonly [K in keyof F as K extends ReplySchemaSymbol ? never : K]: F[K]; };
20
19
  /**
21
20
  * Extract the TypeScript type from a TaggedStruct schema
22
21
  */
@@ -24,14 +23,14 @@ type TaggedStructType<Tag extends string, Fields extends Schema.Struct.Fields> =
24
23
  /**
25
24
  * Build variant schemas type from definition
26
25
  */
27
- type VariantSchemas<D extends Record<string, Schema.Struct.Fields>> = { readonly [K in keyof D & string]: Schema.TaggedStruct<K, PayloadFields<D[K]>> };
26
+ type VariantSchemas<D extends Record<string, Schema.Struct.Fields>> = { readonly [K in keyof D & string]: Schema.TaggedStruct<K, PayloadFields<D[K]>>; };
28
27
  /**
29
28
  * Build union type from variant schemas.
30
29
  * Reply-bearing variants carry ReplyTypeBrand<R> for ask() inference.
31
30
  */
32
31
  type VariantsUnion<D extends Record<string, Schema.Struct.Fields>> = { [K in keyof D & string]: TaggedStructType<K, D[K]> & (D[K] extends {
33
32
  readonly [ReplySchemaSymbol]: Schema.Schema<infer R>;
34
- } ? ReplyTypeBrand<R> : unknown) }[keyof D & string];
33
+ } ? ReplyTypeBrand<R> : unknown); }[keyof D & string];
35
34
  /**
36
35
  * Check if fields are empty (no required string properties).
37
36
  * Symbol keys (like ReplySchemaSymbol) are metadata, not payload fields.
@@ -59,7 +58,7 @@ type VariantConstructors<D extends Record<string, Schema.Struct.Fields>, Brand>
59
58
  } : ((args: Schema.Struct.Type<PayloadFields<D[K]>>) => TaggedStructType<K, D[K]> & Brand & VariantReplyBrand<D[K]>) & {
60
59
  readonly with: (source: object, partial?: Partial<Schema.Struct.Type<PayloadFields<D[K]>>>) => TaggedStructType<K, D[K]> & Brand;
61
60
  readonly _tag: K;
62
- } };
61
+ }; };
63
62
  /**
64
63
  * Keys present in ALL variants (intersection of field names).
65
64
  * Used by union-level `with` to accept only fields safe to update
@@ -69,15 +68,11 @@ type SharedKeys<D extends Record<string, Schema.Struct.Fields>> = keyof D[keyof
69
68
  /**
70
69
  * Pattern matching cases type
71
70
  */
72
- type MatchCases<D extends Record<string, Schema.Struct.Fields>, R> = { readonly [K in keyof D & string]: (value: TaggedStructType<K, D[K]>) => R };
71
+ type MatchCases<D extends Record<string, Schema.Struct.Fields>, R> = { readonly [K in keyof D & string]: (value: TaggedStructType<K, D[K]>) => R; };
73
72
  /**
74
73
  * Base schema interface with pattern matching helpers
75
74
  */
76
75
  interface MachineSchemaBase<D extends Record<string, Schema.Struct.Fields>, Brand> {
77
- /**
78
- * Raw definition record for introspection
79
- */
80
- readonly _definition: D;
81
76
  /**
82
77
  * Per-variant schemas for fine-grained operations
83
78
  */
@@ -111,11 +106,6 @@ interface MachineSchemaBase<D extends Record<string, Schema.Struct.Fields>, Bran
111
106
  * ```
112
107
  */
113
108
  readonly with: <S extends VariantsUnion<D> & Brand>(source: S, partial?: Partial<Record<SharedKeys<D>, unknown>>) => S;
114
- /**
115
- * Reply schemas per variant tag. Only populated for event schemas
116
- * with variants defined via `Event.reply()`.
117
- */
118
- readonly _replySchemas: ReadonlyMap<string, Schema.Decoder<unknown>>;
119
109
  }
120
110
  /**
121
111
  * Schema-first state definition that provides:
@@ -127,9 +117,7 @@ interface MachineSchemaBase<D extends Record<string, Schema.Struct.Fields>, Bran
127
117
  * The D type parameter captures the definition, creating a unique brand
128
118
  * per distinct schema definition shape.
129
119
  */
130
- type MachineStateSchema<D extends Record<string, Schema.Struct.Fields>> = Schema.Codec<VariantsUnion<D> & FullStateBrand<D>, unknown> & MachineSchemaBase<D, FullStateBrand<D>> & VariantConstructors<D, FullStateBrand<D>> & {
131
- /** Schema for persistence, config, and registration. */readonly schema: Schema.Schema<VariantsUnion<D> & FullStateBrand<D>>;
132
- };
120
+ type MachineStateSchema<D extends Record<string, Schema.Struct.Fields>> = Schema.Codec<VariantsUnion<D> & FullStateBrand<D>, unknown> & MachineSchemaBase<D, FullStateBrand<D>> & VariantConstructors<D, FullStateBrand<D>>;
133
121
  /**
134
122
  * Schema-first event definition (same structure as state, different brand)
135
123
  *
@@ -146,7 +134,7 @@ type MachineEventSchema<D extends Record<string, Schema.Struct.Fields>> = Schema
146
134
  *
147
135
  * @example
148
136
  * ```ts
149
- * const OrderState = MachineSchema.State({
137
+ * const OrderState = State({
150
138
  * Pending: { orderId: Schema.String },
151
139
  * Shipped: { trackingId: Schema.String },
152
140
  * })
@@ -167,8 +155,41 @@ type MachineEventSchema<D extends Record<string, Schema.Struct.Fields>> = Schema
167
155
  * ```
168
156
  */
169
157
  declare const State: <const D extends Record<string, Schema.Struct.Fields>>(definition: D) => MachineStateSchema<D>;
170
- declare const Event: (<const D extends Record<string, Schema.Struct.Fields>>(definition: D) => MachineEventSchema<D>) & {
171
- reply: <F extends Schema.Struct.Fields, RS extends Schema.Schema<unknown>>(fields: F, replySchema: RS) => ReplyFields<F, RS>;
158
+ /**
159
+ * Create a schema-first Event definition.
160
+ *
161
+ * The schema's definition type D creates a unique brand, preventing
162
+ * accidental use of constructors from different event schemas
163
+ * (unless they have identical definitions).
164
+ *
165
+ * Use `Event.reply(fields, replySchema)` to define events that support
166
+ * typed `ask()` replies.
167
+ *
168
+ * @example
169
+ * ```ts
170
+ * const OrderEvent = Event({
171
+ * Ship: { trackingId: Schema.String },
172
+ * Cancel: {},
173
+ * GetTotal: Event.reply({}, Schema.Number),
174
+ * })
175
+ *
176
+ * type OrderEvent = typeof OrderEvent.Type
177
+ *
178
+ * // Construct
179
+ * const e = OrderEvent.Ship({ trackingId: "abc" })
180
+ *
181
+ * // Typed ask
182
+ * const total = yield* actor.ask(OrderEvent.GetTotal) // number
183
+ * ```
184
+ */
185
+ declare const EventImpl: <const D extends Record<string, Schema.Struct.Fields>>(definition: D) => MachineEventSchema<D>;
186
+ /**
187
+ * Annotate event fields with a reply schema.
188
+ * Events defined with `Event.reply(fields, replySchema)` enable typed `ask()`.
189
+ */
190
+ declare const replyFieldsFn: <F extends Schema.Struct.Fields, RS extends Schema.Schema<unknown>>(fields: F, replySchema: RS) => ReplyFields<F, RS>;
191
+ declare const Event: typeof EventImpl & {
192
+ reply: typeof replyFieldsFn;
172
193
  };
173
194
  //#endregion
174
195
  export { Event, MachineEventSchema, MachineStateSchema, ReplyFields, ReplySchemaSymbol, State, VariantsUnion };