effect-machine 0.12.0 → 0.14.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.
- package/README.md +133 -324
- package/dist/actor.d.ts +46 -28
- package/dist/actor.js +276 -315
- package/dist/cluster/entity-machine.d.ts +1 -1
- package/dist/cluster/entity-machine.js +20 -9
- package/dist/cluster/to-entity.d.ts +3 -3
- package/dist/errors.d.ts +24 -20
- package/dist/errors.js +10 -6
- package/dist/index.d.ts +5 -4
- package/dist/index.js +3 -2
- package/dist/internal/runtime.d.ts +82 -7
- package/dist/internal/runtime.js +162 -58
- package/dist/internal/transition.d.ts +12 -11
- package/dist/internal/transition.js +12 -14
- package/dist/machine.d.ts +148 -140
- package/dist/machine.js +141 -155
- package/dist/schema.d.ts +14 -0
- package/dist/schema.js +10 -1
- package/dist/slot.d.ts +112 -86
- package/dist/slot.js +92 -59
- package/dist/supervision.d.ts +97 -0
- package/dist/supervision.js +42 -0
- package/dist/testing.d.ts +21 -12
- package/dist/testing.js +23 -26
- package/package.json +7 -7
- package/v3/dist/actor.d.ts +53 -30
- package/v3/dist/actor.js +286 -311
- package/v3/dist/cluster/entity-machine.d.ts +1 -1
- package/v3/dist/cluster/entity-machine.js +5 -5
- package/v3/dist/cluster/to-entity.d.ts +1 -1
- package/v3/dist/errors.d.ts +14 -10
- package/v3/dist/errors.js +11 -7
- package/v3/dist/index.d.ts +6 -5
- package/v3/dist/index.js +3 -2
- package/v3/dist/inspection.d.ts +3 -22
- package/v3/dist/inspection.js +1 -15
- package/v3/dist/internal/brands.d.ts +4 -8
- package/v3/dist/internal/inspection.js +1 -1
- package/v3/dist/internal/runtime.d.ts +87 -10
- package/v3/dist/internal/runtime.js +177 -61
- package/v3/dist/internal/transition.d.ts +13 -12
- package/v3/dist/internal/transition.js +14 -16
- package/v3/dist/internal/utils.js +5 -1
- package/v3/dist/machine.d.ts +158 -143
- package/v3/dist/machine.js +148 -155
- package/v3/dist/schema.d.ts +25 -11
- package/v3/dist/schema.js +18 -5
- package/v3/dist/slot.d.ts +112 -86
- package/v3/dist/slot.js +92 -59
- package/v3/dist/supervision.d.ts +97 -0
- package/v3/dist/supervision.js +42 -0
- package/v3/dist/testing.d.ts +21 -12
- package/v3/dist/testing.js +23 -24
package/v3/dist/machine.js
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
import { __exportAll } from "./_virtual/_rolldown/runtime.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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";
|
|
5
5
|
import { emitWithTimestamp } from "./internal/inspection.js";
|
|
6
|
+
import { Inspector } from "./inspection.js";
|
|
6
7
|
import { MachineContextTag } from "./slot.js";
|
|
7
|
-
import { findTransitions, invalidateIndex, resolveTransition, runTransitionHandler, shouldPostpone } from "./internal/transition.js";
|
|
8
8
|
import { createActor } from "./actor.js";
|
|
9
|
-
import { Cause, Effect, Exit, Option, Scope } from "effect";
|
|
9
|
+
import { Cause, Effect, Exit, Option, Random, Schema, Scope } from "effect";
|
|
10
10
|
//#region src/machine.ts
|
|
11
11
|
var machine_exports = /* @__PURE__ */ __exportAll({
|
|
12
|
-
BuiltMachine: () => BuiltMachine,
|
|
13
12
|
Machine: () => Machine,
|
|
13
|
+
deferReply: () => deferReply,
|
|
14
14
|
findTransitions: () => findTransitions,
|
|
15
15
|
make: () => make,
|
|
16
|
+
materializeMachine: () => materializeMachine,
|
|
16
17
|
replay: () => replay,
|
|
17
18
|
reply: () => reply,
|
|
18
19
|
spawn: () => spawn
|
|
19
20
|
});
|
|
20
|
-
const emitTaskInspection = (input) => Effect.flatMap(Effect.
|
|
21
|
+
const emitTaskInspection = (input) => Effect.flatMap(Effect.serviceOption(Inspector), (inspector) => Option.isNone(inspector) ? Effect.void : emitWithTimestamp(inspector.value, (timestamp) => ({
|
|
21
22
|
type: "@machine.task",
|
|
22
23
|
actorId: input.actorId,
|
|
23
24
|
state: input.state,
|
|
@@ -27,22 +28,40 @@ const emitTaskInspection = (input) => Effect.flatMap(Effect.serviceOptional(Insp
|
|
|
27
28
|
timestamp
|
|
28
29
|
})));
|
|
29
30
|
/**
|
|
30
|
-
*
|
|
31
|
+
* Bind slot handlers to a machine, returning a fresh copy with handlers installed.
|
|
32
|
+
* If no handlers provided and machine has no slots, returns the machine as-is.
|
|
33
|
+
* Validates that all required slots are provided and no extra slots are given.
|
|
31
34
|
*
|
|
32
|
-
*
|
|
33
|
-
* accepted by `Machine.spawn` and `ActorSystem.spawn` (regular overload).
|
|
34
|
-
* Testing utilities (`simulate`, `createTestHarness`, etc.) still accept `Machine`.
|
|
35
|
+
* @internal — used by spawn, replay, simulate, test harness, entity-machine
|
|
35
36
|
*/
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
get initial() {
|
|
44
|
-
return this._inner.initial;
|
|
37
|
+
const materializeMachine = (machine, handlers) => {
|
|
38
|
+
if (handlers === void 0) {
|
|
39
|
+
if (machine._slotsSchema !== void 0 && Object.keys(machine._slotsSchema.definitions).length > 0) throw new ProvisionValidationError({
|
|
40
|
+
missing: Object.keys(machine._slotsSchema.definitions),
|
|
41
|
+
extra: []
|
|
42
|
+
});
|
|
43
|
+
return machine;
|
|
45
44
|
}
|
|
45
|
+
const requiredSlots = /* @__PURE__ */ new Set();
|
|
46
|
+
if (machine._slotsSchema !== void 0) for (const name of Object.keys(machine._slotsSchema.definitions)) requiredSlots.add(name);
|
|
47
|
+
const providedSlots = new Set(Object.keys(handlers));
|
|
48
|
+
const missing = [];
|
|
49
|
+
const extra = [];
|
|
50
|
+
for (const name of requiredSlots) if (!providedSlots.has(name)) missing.push(name);
|
|
51
|
+
for (const name of providedSlots) if (!requiredSlots.has(name)) extra.push(name);
|
|
52
|
+
if (missing.length > 0 || extra.length > 0) throw new ProvisionValidationError({
|
|
53
|
+
missing,
|
|
54
|
+
extra
|
|
55
|
+
});
|
|
56
|
+
const result = new Machine(machine.initial, machine.stateSchema, machine.eventSchema, machine._slotsSchema, machine._slotValidation);
|
|
57
|
+
result._transitions = [...machine._transitions];
|
|
58
|
+
result._finalStates = new Set(machine._finalStates);
|
|
59
|
+
result._spawnEffects = [...machine._spawnEffects];
|
|
60
|
+
result._backgroundEffects = [...machine._backgroundEffects];
|
|
61
|
+
result._postponeRules = [...machine._postponeRules];
|
|
62
|
+
result._replySchemas = machine._replySchemas;
|
|
63
|
+
if (machine._slotsSchema !== void 0) for (const name of Object.keys(machine._slotsSchema.definitions)) result._slotHandlers.set(name, handlers[name]);
|
|
64
|
+
return result;
|
|
46
65
|
};
|
|
47
66
|
/**
|
|
48
67
|
* Machine definition with fluent builder API.
|
|
@@ -53,8 +72,7 @@ var BuiltMachine = class {
|
|
|
53
72
|
* - `R`: Effect requirements
|
|
54
73
|
* - `_SD`: State schema definition (for compile-time validation)
|
|
55
74
|
* - `_ED`: Event schema definition (for compile-time validation)
|
|
56
|
-
* - `
|
|
57
|
-
* - `EFD`: Effect definitions
|
|
75
|
+
* - `SD`: Slot definitions
|
|
58
76
|
*/
|
|
59
77
|
var Machine = class Machine {
|
|
60
78
|
initial;
|
|
@@ -63,11 +81,10 @@ var Machine = class Machine {
|
|
|
63
81
|
/** @internal */ _backgroundEffects;
|
|
64
82
|
/** @internal */ _finalStates;
|
|
65
83
|
/** @internal */ _postponeRules;
|
|
66
|
-
/** @internal */
|
|
67
|
-
/** @internal */
|
|
68
|
-
/** @internal */ _guardHandlers;
|
|
69
|
-
/** @internal */ _effectHandlers;
|
|
84
|
+
/** @internal */ _slotsSchema;
|
|
85
|
+
/** @internal */ _slotHandlers;
|
|
70
86
|
/** @internal */ _slots;
|
|
87
|
+
/** @internal */ _slotValidation;
|
|
71
88
|
stateSchema;
|
|
72
89
|
eventSchema;
|
|
73
90
|
/** @internal */ _replySchemas;
|
|
@@ -91,49 +108,73 @@ var Machine = class Machine {
|
|
|
91
108
|
get postponeRules() {
|
|
92
109
|
return this._postponeRules;
|
|
93
110
|
}
|
|
94
|
-
get
|
|
95
|
-
return this.
|
|
96
|
-
}
|
|
97
|
-
get effectsSchema() {
|
|
98
|
-
return this._effectsSchema;
|
|
111
|
+
get slotsSchema() {
|
|
112
|
+
return this._slotsSchema;
|
|
99
113
|
}
|
|
100
114
|
get replySchemas() {
|
|
101
115
|
return this._replySchemas;
|
|
102
116
|
}
|
|
103
117
|
/** @internal */
|
|
104
|
-
constructor(initial, stateSchema, eventSchema,
|
|
118
|
+
constructor(initial, stateSchema, eventSchema, slotsSchema, slotValidation = true) {
|
|
105
119
|
this.initial = initial;
|
|
106
120
|
this._transitions = [];
|
|
107
121
|
this._spawnEffects = [];
|
|
108
122
|
this._backgroundEffects = [];
|
|
109
123
|
this._finalStates = /* @__PURE__ */ new Set();
|
|
110
124
|
this._postponeRules = [];
|
|
111
|
-
this.
|
|
112
|
-
this.
|
|
113
|
-
this.
|
|
114
|
-
this.
|
|
125
|
+
this._slotsSchema = slotsSchema;
|
|
126
|
+
this._replySchemas = eventSchema?._replySchemas ?? /* @__PURE__ */ new Map();
|
|
127
|
+
this._slotHandlers = /* @__PURE__ */ new Map();
|
|
128
|
+
this._slotValidation = slotValidation;
|
|
115
129
|
this.stateSchema = stateSchema;
|
|
116
130
|
this.eventSchema = eventSchema;
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}))
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
131
|
+
const validators = slotValidation && slotsSchema !== void 0 ? new Map(Object.entries(slotsSchema.definitions).map(([name, def]) => [name, {
|
|
132
|
+
decodeInput: Schema.decodeUnknownSync(def.inputSchema),
|
|
133
|
+
decodeOutput: Schema.decodeUnknownSync(def.outputSchema)
|
|
134
|
+
}])) : void 0;
|
|
135
|
+
const resolve = (name, params) => Effect.flatMap(Effect.serviceOption(this.Context), (maybeCtx) => {
|
|
136
|
+
if (Option.isNone(maybeCtx)) return Effect.die("MachineContext not available");
|
|
137
|
+
const handler = this._slotHandlers.get(name);
|
|
138
|
+
if (handler === void 0) return Effect.die(new SlotProvisionError({
|
|
139
|
+
slotName: name,
|
|
140
|
+
slotType: "slot"
|
|
141
|
+
}));
|
|
142
|
+
const validatedParams = validators !== void 0 ? (() => {
|
|
143
|
+
try {
|
|
144
|
+
const v = validators.get(name);
|
|
145
|
+
return v !== void 0 ? v.decodeInput(params) : params;
|
|
146
|
+
} catch (e) {
|
|
147
|
+
return Effect.die(new SlotCodecError({
|
|
148
|
+
slotName: name,
|
|
149
|
+
phase: "input",
|
|
150
|
+
message: e instanceof Error ? e.message : String(e)
|
|
151
|
+
}));
|
|
152
|
+
}
|
|
153
|
+
})() : params;
|
|
154
|
+
if (Effect.isEffect(validatedParams)) return validatedParams;
|
|
155
|
+
const result = handler(validatedParams);
|
|
156
|
+
let resultEffect;
|
|
157
|
+
if (result === void 0 || result === null) resultEffect = Effect.void;
|
|
158
|
+
else if (Effect.isEffect(result)) resultEffect = result;
|
|
159
|
+
else resultEffect = Effect.succeed(result);
|
|
160
|
+
if (validators !== void 0) {
|
|
161
|
+
const v = validators.get(name);
|
|
162
|
+
if (v !== void 0) return Effect.flatMap(resultEffect, (value) => {
|
|
163
|
+
try {
|
|
164
|
+
const decoded = v.decodeOutput(value);
|
|
165
|
+
return Effect.succeed(decoded);
|
|
166
|
+
} catch (e) {
|
|
167
|
+
return Effect.die(new SlotCodecError({
|
|
168
|
+
slotName: name,
|
|
169
|
+
phase: "output",
|
|
170
|
+
message: e instanceof Error ? e.message : String(e)
|
|
171
|
+
}));
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
return resultEffect;
|
|
176
|
+
});
|
|
177
|
+
this._slots = this._slotsSchema !== void 0 ? this._slotsSchema._createSlots(resolve) : {};
|
|
137
178
|
}
|
|
138
179
|
from(stateOrStates, build) {
|
|
139
180
|
build(new TransitionScope(this, Array.isArray(stateOrStates) ? stateOrStates : [stateOrStates]));
|
|
@@ -181,42 +222,19 @@ var Machine = class Machine {
|
|
|
181
222
|
invalidateIndex(this);
|
|
182
223
|
return this;
|
|
183
224
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
*
|
|
194
|
-
* machine
|
|
195
|
-
* .spawn(State.Loading, ({ effects, state }) => effects.fetchData({ url: state.url }))
|
|
196
|
-
* .build({
|
|
197
|
-
* fetchData: ({ url }, { self }) =>
|
|
198
|
-
* Effect.gen(function* () {
|
|
199
|
-
* yield* Effect.addFinalizer(() => Effect.log("Leaving Loading"));
|
|
200
|
-
* const data = yield* Http.get(url);
|
|
201
|
-
* yield* self.send(Event.Loaded({ data }));
|
|
202
|
-
* }),
|
|
203
|
-
* });
|
|
204
|
-
* ```
|
|
205
|
-
*/
|
|
206
|
-
spawn(state, handler) {
|
|
207
|
-
const stateTag = getTag(state);
|
|
208
|
-
this._spawnEffects.push({
|
|
209
|
-
stateTag,
|
|
210
|
-
handler
|
|
211
|
-
});
|
|
225
|
+
spawn(stateOrStates, handler) {
|
|
226
|
+
const states = Array.isArray(stateOrStates) ? stateOrStates : [stateOrStates];
|
|
227
|
+
for (const s of states) {
|
|
228
|
+
const stateTag = getTag(s);
|
|
229
|
+
this._spawnEffects.push({
|
|
230
|
+
stateTag,
|
|
231
|
+
handler
|
|
232
|
+
});
|
|
233
|
+
}
|
|
212
234
|
invalidateIndex(this);
|
|
213
235
|
return this;
|
|
214
236
|
}
|
|
215
|
-
|
|
216
|
-
* State-scoped task that runs on entry and sends success/failure events.
|
|
217
|
-
* Interrupts do not emit failure events.
|
|
218
|
-
*/
|
|
219
|
-
task(state, run, options) {
|
|
237
|
+
task(stateOrStates, run, options) {
|
|
220
238
|
const handler = Effect.fn("effect-machine.task")(function* (ctx) {
|
|
221
239
|
yield* emitTaskInspection({
|
|
222
240
|
actorId: ctx.actorId,
|
|
@@ -232,7 +250,8 @@ var Machine = class Machine {
|
|
|
232
250
|
taskName: options.name,
|
|
233
251
|
phase: "success"
|
|
234
252
|
});
|
|
235
|
-
|
|
253
|
+
const successEvent = options.onSuccess !== void 0 ? options.onSuccess(exit.value, ctx) : exit.value;
|
|
254
|
+
yield* ctx.self.send(successEvent);
|
|
236
255
|
yield* Effect.yieldNow();
|
|
237
256
|
return;
|
|
238
257
|
}
|
|
@@ -260,7 +279,7 @@ var Machine = class Machine {
|
|
|
260
279
|
}
|
|
261
280
|
return yield* Effect.failCause(cause).pipe(Effect.orDie);
|
|
262
281
|
});
|
|
263
|
-
return this.spawn(
|
|
282
|
+
return this.spawn(stateOrStates, handler);
|
|
264
283
|
}
|
|
265
284
|
/**
|
|
266
285
|
* State timeout — gen_statem's `state_timeout`.
|
|
@@ -294,22 +313,14 @@ var Machine = class Machine {
|
|
|
294
313
|
}
|
|
295
314
|
/**
|
|
296
315
|
* Machine-lifetime effect that is forked on actor spawn and runs until the actor stops.
|
|
297
|
-
* Use effect slots defined via `Slot.Effects` for the actual work.
|
|
298
316
|
*
|
|
299
317
|
* @example
|
|
300
318
|
* ```ts
|
|
301
|
-
*
|
|
302
|
-
*
|
|
303
|
-
*
|
|
304
|
-
*
|
|
305
|
-
*
|
|
306
|
-
* .background(({ effects }) => effects.heartbeat())
|
|
307
|
-
* .build({
|
|
308
|
-
* heartbeat: (_, { self }) =>
|
|
309
|
-
* Effect.forever(
|
|
310
|
-
* Effect.sleep("30 seconds").pipe(Effect.andThen(self.send(Event.Ping)))
|
|
311
|
-
* ),
|
|
312
|
-
* });
|
|
319
|
+
* machine.background(({ self }) =>
|
|
320
|
+
* Effect.forever(
|
|
321
|
+
* Effect.sleep("30 seconds").pipe(Effect.andThen(self.send(Event.Ping))),
|
|
322
|
+
* ),
|
|
323
|
+
* );
|
|
313
324
|
* ```
|
|
314
325
|
*/
|
|
315
326
|
background(handler) {
|
|
@@ -350,43 +361,8 @@ var Machine = class Machine {
|
|
|
350
361
|
this._finalStates.add(stateTag);
|
|
351
362
|
return this;
|
|
352
363
|
}
|
|
353
|
-
/**
|
|
354
|
-
* Finalize the machine. Returns a `BuiltMachine` — the only type accepted by `Machine.spawn`.
|
|
355
|
-
*
|
|
356
|
-
* - Machines with slots: pass implementations as the first argument.
|
|
357
|
-
* - Machines without slots: call with no arguments.
|
|
358
|
-
*/
|
|
359
|
-
build(...args) {
|
|
360
|
-
const handlers = args[0];
|
|
361
|
-
if (handlers !== void 0) {
|
|
362
|
-
const requiredSlots = /* @__PURE__ */ new Set();
|
|
363
|
-
if (this._guardsSchema !== void 0) for (const name of Object.keys(this._guardsSchema.definitions)) requiredSlots.add(name);
|
|
364
|
-
if (this._effectsSchema !== void 0) for (const name of Object.keys(this._effectsSchema.definitions)) requiredSlots.add(name);
|
|
365
|
-
const providedSlots = new Set(Object.keys(handlers));
|
|
366
|
-
const missing = [];
|
|
367
|
-
const extra = [];
|
|
368
|
-
for (const name of requiredSlots) if (!providedSlots.has(name)) missing.push(name);
|
|
369
|
-
for (const name of providedSlots) if (!requiredSlots.has(name)) extra.push(name);
|
|
370
|
-
if (missing.length > 0 || extra.length > 0) throw new ProvisionValidationError({
|
|
371
|
-
missing,
|
|
372
|
-
extra
|
|
373
|
-
});
|
|
374
|
-
const result = new Machine(this.initial, this.stateSchema, this.eventSchema, this._guardsSchema, this._effectsSchema);
|
|
375
|
-
result._transitions = [...this._transitions];
|
|
376
|
-
result._finalStates = new Set(this._finalStates);
|
|
377
|
-
result._spawnEffects = [...this._spawnEffects];
|
|
378
|
-
result._backgroundEffects = [...this._backgroundEffects];
|
|
379
|
-
result._postponeRules = [...this._postponeRules];
|
|
380
|
-
result._replySchemas = this._replySchemas;
|
|
381
|
-
const anyHandlers = handlers;
|
|
382
|
-
if (this._guardsSchema !== void 0) for (const name of Object.keys(this._guardsSchema.definitions)) result._guardHandlers.set(name, anyHandlers[name]);
|
|
383
|
-
if (this._effectsSchema !== void 0) for (const name of Object.keys(this._effectsSchema.definitions)) result._effectHandlers.set(name, anyHandlers[name]);
|
|
384
|
-
return new BuiltMachine(result);
|
|
385
|
-
}
|
|
386
|
-
return new BuiltMachine(this);
|
|
387
|
-
}
|
|
388
364
|
static make(config) {
|
|
389
|
-
return new Machine(config.initial, config.state, config.event, config.
|
|
365
|
+
return new Machine(config.initial, config.state, config.event, config.slots, config.slotValidation ?? true);
|
|
390
366
|
}
|
|
391
367
|
};
|
|
392
368
|
var TransitionScope = class {
|
|
@@ -405,27 +381,42 @@ var TransitionScope = class {
|
|
|
405
381
|
};
|
|
406
382
|
const make = Machine.make;
|
|
407
383
|
/**
|
|
408
|
-
* Spawn an actor from a
|
|
384
|
+
* Spawn an actor from a machine.
|
|
385
|
+
*
|
|
386
|
+
* For machines with slots, pass implementations via `{ slots: { ... } }`.
|
|
387
|
+
*
|
|
388
|
+
* @example
|
|
389
|
+
* ```ts
|
|
390
|
+
* // No slots
|
|
391
|
+
* const actor = yield* Machine.spawn(machine);
|
|
409
392
|
*
|
|
410
|
-
*
|
|
411
|
-
*
|
|
412
|
-
*
|
|
413
|
-
*
|
|
414
|
-
* for that state (timers, scoped resources, etc.). Transition history
|
|
415
|
-
* is not replayed — only the current state's entry effects run.
|
|
393
|
+
* // With slots
|
|
394
|
+
* const actor = yield* Machine.spawn(machine, {
|
|
395
|
+
* slots: { canRetry: ({ max }) => attempts < max },
|
|
396
|
+
* });
|
|
416
397
|
*
|
|
417
|
-
*
|
|
418
|
-
*
|
|
398
|
+
* // With persistence
|
|
399
|
+
* const actor = yield* Machine.spawn(machine, {
|
|
400
|
+
* persist: {
|
|
401
|
+
* load: () => storage.get("actor-state"),
|
|
402
|
+
* save: (state) => storage.set("actor-state", state),
|
|
403
|
+
* },
|
|
404
|
+
* });
|
|
405
|
+
* ```
|
|
419
406
|
*/
|
|
420
|
-
const spawn = Effect.fn("effect-machine.spawn")(function* (
|
|
407
|
+
const spawn = Effect.fn("effect-machine.spawn")(function* (machine, idOrOptions) {
|
|
421
408
|
const opts = typeof idOrOptions === "string" ? { id: idOrOptions } : idOrOptions;
|
|
422
|
-
const actor = yield* createActor(opts?.id ?? `actor-${
|
|
409
|
+
const actor = yield* createActor(opts?.id ?? `actor-${(yield* Random.next).toString(36).slice(2)}`, materializeMachine(machine, opts?.slots), {
|
|
410
|
+
initialState: opts?.hydrate,
|
|
411
|
+
supervision: opts?.supervision,
|
|
412
|
+
persist: opts?.persist
|
|
413
|
+
});
|
|
423
414
|
const maybeScope = yield* Effect.serviceOption(Scope.Scope);
|
|
424
415
|
if (Option.isSome(maybeScope)) yield* Scope.addFinalizer(maybeScope.value, actor.stop);
|
|
425
416
|
return actor;
|
|
426
417
|
});
|
|
427
|
-
const replay = Effect.fn("effect-machine.replay")(function* (
|
|
428
|
-
const machine =
|
|
418
|
+
const replay = Effect.fn("effect-machine.replay")(function* (input, events, options) {
|
|
419
|
+
const machine = materializeMachine(input, options?.slots);
|
|
429
420
|
let state = options?.from ?? machine.initial;
|
|
430
421
|
const hasPostponeRules = machine.postponeRules.length > 0;
|
|
431
422
|
const postponed = [];
|
|
@@ -433,7 +424,8 @@ const replay = Effect.fn("effect-machine.replay")(function* (built, events, opti
|
|
|
433
424
|
const self = {
|
|
434
425
|
send: dummySend,
|
|
435
426
|
cast: dummySend,
|
|
436
|
-
spawn: () => Effect.die("spawn not supported in replay")
|
|
427
|
+
spawn: () => Effect.die("spawn not supported in replay"),
|
|
428
|
+
reply: () => Effect.succeed(false)
|
|
437
429
|
};
|
|
438
430
|
for (const event of events) {
|
|
439
431
|
if (machine.finalStates.has(state._tag)) break;
|
|
@@ -468,5 +460,6 @@ const replay = Effect.fn("effect-machine.replay")(function* (built, events, opti
|
|
|
468
460
|
return state;
|
|
469
461
|
});
|
|
470
462
|
const reply = makeReply;
|
|
463
|
+
const deferReply = makeDeferReply;
|
|
471
464
|
//#endregion
|
|
472
|
-
export {
|
|
465
|
+
export { Machine, deferReply, findTransitions, machine_exports, make, materializeMachine, replay, reply, spawn };
|
package/v3/dist/schema.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ type VariantSchemas<D extends Record<string, Schema.Struct.Fields>> = { readonly
|
|
|
25
25
|
* Reply-bearing variants carry ReplyTypeBrand<R> for ask() inference.
|
|
26
26
|
*/
|
|
27
27
|
type VariantsUnion<D extends Record<string, Schema.Struct.Fields>> = { [K in keyof D & string]: TaggedStructType<K, D[K]> & (D[K] extends {
|
|
28
|
-
readonly [ReplySchemaSymbol]: Schema.Schema<infer R
|
|
28
|
+
readonly [ReplySchemaSymbol]: Schema.Schema<infer R>;
|
|
29
29
|
} ? ReplyTypeBrand<R> : unknown) }[keyof D & string];
|
|
30
30
|
/**
|
|
31
31
|
* Check if fields are empty (no required string properties).
|
|
@@ -37,7 +37,7 @@ type IsEmptyFields<Fields extends Schema.Struct.Fields> = string & keyof Fields
|
|
|
37
37
|
* If fields carry ReplySchemaSymbol, adds ReplyTypeBrand<R>.
|
|
38
38
|
*/
|
|
39
39
|
type VariantReplyBrand<Fields extends Schema.Struct.Fields> = Fields extends {
|
|
40
|
-
readonly [ReplySchemaSymbol]: Schema.Schema<infer R
|
|
40
|
+
readonly [ReplySchemaSymbol]: Schema.Schema<infer R>;
|
|
41
41
|
} ? ReplyTypeBrand<R> : unknown;
|
|
42
42
|
/**
|
|
43
43
|
* Constructor functions for each variant.
|
|
@@ -50,8 +50,8 @@ type VariantReplyBrand<Fields extends Schema.Struct.Fields> = Fields extends {
|
|
|
50
50
|
*/
|
|
51
51
|
type VariantConstructors<D extends Record<string, Schema.Struct.Fields>, Brand> = { readonly [K in keyof D & string]: IsEmptyFields<D[K]> extends true ? TaggedStructType<K, D[K]> & Brand & VariantReplyBrand<D[K]> & {
|
|
52
52
|
readonly derive: (source: object) => TaggedStructType<K, D[K]> & Brand;
|
|
53
|
-
} : ((args: Schema.Struct.
|
|
54
|
-
readonly derive: (source: object, partial?: Partial<Schema.Struct.
|
|
53
|
+
} : ((args: Schema.Struct.Type<D[K]>) => TaggedStructType<K, D[K]> & Brand & VariantReplyBrand<D[K]>) & {
|
|
54
|
+
readonly derive: (source: object, partial?: Partial<Schema.Struct.Type<D[K]>>) => TaggedStructType<K, D[K]> & Brand;
|
|
55
55
|
readonly _tag: K;
|
|
56
56
|
} };
|
|
57
57
|
/**
|
|
@@ -70,11 +70,6 @@ interface MachineSchemaBase<D extends Record<string, Schema.Struct.Fields>, Bran
|
|
|
70
70
|
* Per-variant schemas for fine-grained operations
|
|
71
71
|
*/
|
|
72
72
|
readonly variants: VariantSchemas<D>;
|
|
73
|
-
/**
|
|
74
|
-
* Reply schemas per variant tag. Only populated for event schemas
|
|
75
|
-
* with variants defined via `Event.reply()`.
|
|
76
|
-
*/
|
|
77
|
-
readonly _replySchemas: ReadonlyMap<string, Schema.Schema.Any>;
|
|
78
73
|
/**
|
|
79
74
|
* Type guard: `OrderState.$is("Pending")(value)`
|
|
80
75
|
*/
|
|
@@ -86,6 +81,25 @@ interface MachineSchemaBase<D extends Record<string, Schema.Struct.Fields>, Bran
|
|
|
86
81
|
<R>(cases: MatchCases<D, R>): (value: VariantsUnion<D> & Brand) => R;
|
|
87
82
|
<R>(value: VariantsUnion<D> & Brand, cases: MatchCases<D, R>): R;
|
|
88
83
|
};
|
|
84
|
+
/**
|
|
85
|
+
* Union-level derive: copies fields from `source` into the same variant,
|
|
86
|
+
* overriding with `partial`. Preserves the specific variant subtype.
|
|
87
|
+
*
|
|
88
|
+
* Dispatches to the per-variant `derive` based on `source._tag`.
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```ts
|
|
92
|
+
* // Instead of switching on _tag to call per-variant derive:
|
|
93
|
+
* const updated = AgentLoopState.derive(state, { queue: newQueue })
|
|
94
|
+
* // If state is StreamingState, returns StreamingState (not LoopState)
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
readonly derive: <S extends VariantsUnion<D> & Brand>(source: S, partial?: Partial<Omit<S, "_tag">>) => S;
|
|
98
|
+
/**
|
|
99
|
+
* Reply schemas per variant tag. Only populated for event schemas
|
|
100
|
+
* with variants defined via `Event.reply()`.
|
|
101
|
+
*/
|
|
102
|
+
readonly _replySchemas: ReadonlyMap<string, Schema.Schema.Any>;
|
|
89
103
|
}
|
|
90
104
|
/**
|
|
91
105
|
* Schema-first state definition that provides:
|
|
@@ -97,14 +111,14 @@ interface MachineSchemaBase<D extends Record<string, Schema.Struct.Fields>, Bran
|
|
|
97
111
|
* The D type parameter captures the definition, creating a unique brand
|
|
98
112
|
* per distinct schema definition shape.
|
|
99
113
|
*/
|
|
100
|
-
type MachineStateSchema<D extends Record<string, Schema.Struct.Fields>> = Schema.Schema<VariantsUnion<D> & FullStateBrand<D>,
|
|
114
|
+
type MachineStateSchema<D extends Record<string, Schema.Struct.Fields>> = Schema.Schema<VariantsUnion<D> & FullStateBrand<D>, unknown, never> & MachineSchemaBase<D, FullStateBrand<D>> & VariantConstructors<D, FullStateBrand<D>>;
|
|
101
115
|
/**
|
|
102
116
|
* Schema-first event definition (same structure as state, different brand)
|
|
103
117
|
*
|
|
104
118
|
* The D type parameter captures the definition, creating a unique brand
|
|
105
119
|
* per distinct schema definition shape.
|
|
106
120
|
*/
|
|
107
|
-
type MachineEventSchema<D extends Record<string, Schema.Struct.Fields>> = Schema.Schema<VariantsUnion<D> & FullEventBrand<D>,
|
|
121
|
+
type MachineEventSchema<D extends Record<string, Schema.Struct.Fields>> = Schema.Schema<VariantsUnion<D> & FullEventBrand<D>, unknown, never> & MachineSchemaBase<D, FullEventBrand<D>> & VariantConstructors<D, FullEventBrand<D>>;
|
|
108
122
|
/**
|
|
109
123
|
* Create a schema-first State definition.
|
|
110
124
|
*
|
package/v3/dist/schema.js
CHANGED
|
@@ -42,6 +42,7 @@ const ReplySchemaSymbol = Symbol.for("effect-machine/ReplySchema");
|
|
|
42
42
|
/**
|
|
43
43
|
* Build a schema-first definition from a record of tag -> fields
|
|
44
44
|
*/
|
|
45
|
+
const RESERVED_DERIVE_KEYS = new Set(["_tag"]);
|
|
45
46
|
const buildMachineSchema = (definition) => {
|
|
46
47
|
const variants = {};
|
|
47
48
|
const constructors = {};
|
|
@@ -65,7 +66,8 @@ const buildMachineSchema = (definition) => {
|
|
|
65
66
|
const result = { _tag: tag };
|
|
66
67
|
for (const key of fieldNames) if (key in source) result[key] = source[key];
|
|
67
68
|
if (partial !== void 0) for (const [key, value] of Object.entries(partial)) {
|
|
68
|
-
if (key
|
|
69
|
+
if (RESERVED_DERIVE_KEYS.has(key)) continue;
|
|
70
|
+
if (!fieldNames.has(key)) continue;
|
|
69
71
|
result[key] = value;
|
|
70
72
|
}
|
|
71
73
|
return result;
|
|
@@ -77,7 +79,7 @@ const buildMachineSchema = (definition) => {
|
|
|
77
79
|
};
|
|
78
80
|
}
|
|
79
81
|
const variantArray = Object.values(variants);
|
|
80
|
-
if (variantArray.length === 0) throw new InvalidSchemaError();
|
|
82
|
+
if (variantArray.length === 0) throw new InvalidSchemaError({ message: "Schema must have at least one variant" });
|
|
81
83
|
const unionSchema = variantArray.length === 1 ? variantArray[0] : Schema.Union(...variantArray);
|
|
82
84
|
const $is = (tag) => (u) => typeof u === "object" && u !== null && "_tag" in u && u._tag === tag;
|
|
83
85
|
const $match = (valueOrCases, maybeCases) => {
|
|
@@ -99,7 +101,7 @@ const buildMachineSchema = (definition) => {
|
|
|
99
101
|
variants,
|
|
100
102
|
constructors,
|
|
101
103
|
_definition: definition,
|
|
102
|
-
|
|
104
|
+
replySchemas,
|
|
103
105
|
$is,
|
|
104
106
|
$match
|
|
105
107
|
};
|
|
@@ -109,13 +111,21 @@ const buildMachineSchema = (definition) => {
|
|
|
109
111
|
* Builds the schema object with variants, constructors, $is, and $match.
|
|
110
112
|
*/
|
|
111
113
|
const createMachineSchema = (definition) => {
|
|
112
|
-
const { schema, variants, constructors, _definition,
|
|
114
|
+
const { schema, variants, constructors, _definition, replySchemas, $is, $match } = buildMachineSchema(definition);
|
|
115
|
+
const derive = (source, partial) => {
|
|
116
|
+
const ctor = constructors[source._tag];
|
|
117
|
+
if (ctor === void 0) throw new MissingMatchHandlerError({ tag: source._tag });
|
|
118
|
+
const deriveFn = ctor.derive;
|
|
119
|
+
if (deriveFn === void 0) throw new MissingMatchHandlerError({ tag: source._tag });
|
|
120
|
+
return deriveFn(source, partial);
|
|
121
|
+
};
|
|
113
122
|
return Object.assign(Object.create(schema), {
|
|
114
123
|
variants,
|
|
115
124
|
_definition,
|
|
116
|
-
_replySchemas,
|
|
125
|
+
_replySchemas: replySchemas,
|
|
117
126
|
$is,
|
|
118
127
|
$match,
|
|
128
|
+
derive,
|
|
119
129
|
...constructors
|
|
120
130
|
});
|
|
121
131
|
};
|
|
@@ -171,6 +181,9 @@ const State = (definition) => createMachineSchema(definition);
|
|
|
171
181
|
*
|
|
172
182
|
* // Construct
|
|
173
183
|
* const e = OrderEvent.Ship({ trackingId: "abc" })
|
|
184
|
+
*
|
|
185
|
+
* // Typed ask
|
|
186
|
+
* const total = yield* actor.ask(OrderEvent.GetTotal) // number
|
|
174
187
|
* ```
|
|
175
188
|
*/
|
|
176
189
|
const EventImpl = (definition) => createMachineSchema(definition);
|