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.
- package/LICENSE +21 -0
- package/README.md +97 -54
- package/dist/actor.d.ts +8 -45
- package/dist/actor.js +157 -231
- package/dist/atom.d.ts +31 -0
- package/dist/atom.js +29 -0
- package/dist/cluster/adapters/in-memory.d.ts +0 -1
- package/dist/cluster/adapters/in-memory.js +10 -5
- package/dist/cluster/entity-actor-ref.d.ts +2 -3
- package/dist/cluster/entity-actor-ref.js +14 -17
- package/dist/cluster/entity-machine.d.ts +2 -8
- package/dist/cluster/entity-machine.js +57 -34
- package/dist/cluster/index.js +1 -1
- package/dist/cluster/persistence.d.ts +0 -1
- package/dist/cluster/to-entity.d.ts +19 -21
- package/dist/cluster/to-entity.js +18 -19
- package/dist/errors.d.ts +11 -37
- package/dist/errors.js +12 -30
- package/dist/index.d.ts +4 -5
- package/dist/index.js +4 -5
- package/dist/inspection.d.ts +0 -1
- package/dist/inspection.js +24 -13
- package/dist/internal/brands.d.ts +0 -1
- package/dist/internal/event-advancement.d.ts +50 -0
- package/dist/internal/event-advancement.js +79 -0
- package/dist/internal/inspection.d.ts +5 -9
- package/dist/internal/inspection.js +31 -10
- package/dist/internal/machine-definition.d.ts +16 -0
- package/dist/internal/runtime.d.ts +1 -161
- package/dist/internal/runtime.js +191 -150
- package/dist/internal/transition.d.ts +4 -135
- package/dist/internal/transition.js +82 -157
- package/dist/internal/utils.d.ts +3 -40
- package/dist/internal/utils.js +2 -2
- package/dist/machine.d.ts +47 -137
- package/dist/machine.js +147 -215
- package/dist/schema.d.ts +42 -21
- package/dist/schema.js +28 -18
- package/dist/supervision.d.ts +1 -24
- package/dist/supervision.js +2 -3
- package/dist/testing.d.ts +15 -29
- package/dist/testing.js +76 -95
- package/package.json +41 -35
- package/dist/slot.d.ts +0 -159
- package/dist/slot.js +0 -165
- package/v3/dist/_virtual/_rolldown/runtime.js +0 -13
- package/v3/dist/actor.d.ts +0 -250
- package/v3/dist/actor.js +0 -577
- package/v3/dist/cluster/adapters/in-memory.d.ts +0 -15
- package/v3/dist/cluster/adapters/in-memory.js +0 -62
- package/v3/dist/cluster/entity-actor-ref.d.ts +0 -49
- package/v3/dist/cluster/entity-actor-ref.js +0 -19
- package/v3/dist/cluster/entity-machine.d.ts +0 -74
- package/v3/dist/cluster/entity-machine.js +0 -166
- package/v3/dist/cluster/index.d.ts +0 -6
- package/v3/dist/cluster/index.js +0 -6
- package/v3/dist/cluster/persistence.d.ts +0 -48
- package/v3/dist/cluster/persistence.js +0 -14
- package/v3/dist/cluster/to-entity.d.ts +0 -69
- package/v3/dist/cluster/to-entity.js +0 -59
- package/v3/dist/errors.d.ts +0 -95
- package/v3/dist/errors.js +0 -54
- package/v3/dist/index.d.ts +0 -11
- package/v3/dist/index.js +0 -9
- package/v3/dist/inspection.d.ts +0 -151
- package/v3/dist/inspection.js +0 -128
- package/v3/dist/internal/brands.d.ts +0 -50
- package/v3/dist/internal/inspection.d.ts +0 -11
- package/v3/dist/internal/inspection.js +0 -20
- package/v3/dist/internal/runtime.d.ts +0 -161
- package/v3/dist/internal/runtime.js +0 -360
- package/v3/dist/internal/transition.d.ts +0 -190
- package/v3/dist/internal/transition.js +0 -278
- package/v3/dist/internal/utils.d.ts +0 -101
- package/v3/dist/internal/utils.js +0 -75
- package/v3/dist/machine.d.ts +0 -398
- package/v3/dist/machine.js +0 -487
- package/v3/dist/schema.d.ts +0 -174
- package/v3/dist/schema.js +0 -206
- package/v3/dist/slot.d.ts +0 -158
- package/v3/dist/slot.js +0 -165
- package/v3/dist/supervision.d.ts +0 -97
- package/v3/dist/supervision.js +0 -42
- package/v3/dist/testing.d.ts +0 -151
- package/v3/dist/testing.js +0 -189
- /package/{v3/dist/internal/brands.js → dist/internal/machine-definition.js} +0 -0
package/v3/dist/machine.js
DELETED
|
@@ -1,487 +0,0 @@
|
|
|
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";
|
|
5
|
-
import { emitWithTimestamp } from "./internal/inspection.js";
|
|
6
|
-
import { Inspector } from "./inspection.js";
|
|
7
|
-
import { MachineContextTag } from "./slot.js";
|
|
8
|
-
import { ActorScope, createActor } from "./actor.js";
|
|
9
|
-
import { Cause, Effect, Exit, Option, Random, Schema, Scope } from "effect";
|
|
10
|
-
//#region src/machine.ts
|
|
11
|
-
var machine_exports = /* @__PURE__ */ __exportAll({
|
|
12
|
-
Machine: () => Machine,
|
|
13
|
-
deferReply: () => deferReply,
|
|
14
|
-
findTransitions: () => findTransitions,
|
|
15
|
-
make: () => make,
|
|
16
|
-
materializeMachine: () => materializeMachine,
|
|
17
|
-
replay: () => replay,
|
|
18
|
-
reply: () => reply,
|
|
19
|
-
scoped: () => scoped,
|
|
20
|
-
spawn: () => spawn
|
|
21
|
-
});
|
|
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
|
-
})));
|
|
31
|
-
/**
|
|
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
|
|
37
|
-
*/
|
|
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;
|
|
66
|
-
};
|
|
67
|
-
/**
|
|
68
|
-
* Machine definition with fluent builder API.
|
|
69
|
-
*
|
|
70
|
-
* Type parameters:
|
|
71
|
-
* - `State`: The state union type
|
|
72
|
-
* - `Event`: The event union type
|
|
73
|
-
* - `R`: Effect requirements
|
|
74
|
-
* - `_SD`: State schema definition (for compile-time validation)
|
|
75
|
-
* - `_ED`: Event schema definition (for compile-time validation)
|
|
76
|
-
* - `SD`: Slot definitions
|
|
77
|
-
*/
|
|
78
|
-
var Machine = class Machine {
|
|
79
|
-
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;
|
|
89
|
-
stateSchema;
|
|
90
|
-
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
|
-
}
|
|
118
|
-
/** @internal */
|
|
119
|
-
constructor(initial, stateSchema, eventSchema, slotsSchema, slotValidation = true) {
|
|
120
|
-
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;
|
|
130
|
-
this.stateSchema = stateSchema;
|
|
131
|
-
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
|
-
}
|
|
180
|
-
from(stateOrStates, build) {
|
|
181
|
-
const states = Array.isArray(stateOrStates) ? stateOrStates : [stateOrStates];
|
|
182
|
-
build(new TransitionScope(this, states));
|
|
183
|
-
return this;
|
|
184
|
-
}
|
|
185
|
-
/** @internal */
|
|
186
|
-
scopeTransition(states, event, handler, reenter) {
|
|
187
|
-
for (const state of states) this.addTransition(state, event, handler, reenter);
|
|
188
|
-
return this;
|
|
189
|
-
}
|
|
190
|
-
on(stateOrStates, event, handler) {
|
|
191
|
-
const states = Array.isArray(stateOrStates) ? stateOrStates : [stateOrStates];
|
|
192
|
-
for (const s of states) this.addTransition(s, event, handler, false);
|
|
193
|
-
return this;
|
|
194
|
-
}
|
|
195
|
-
reenter(stateOrStates, event, handler) {
|
|
196
|
-
const states = Array.isArray(stateOrStates) ? stateOrStates : [stateOrStates];
|
|
197
|
-
for (const s of states) this.addTransition(s, event, handler, true);
|
|
198
|
-
return this;
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Register a wildcard transition that fires from any state when no specific transition matches.
|
|
202
|
-
* Specific `.on()` transitions always take priority over `.onAny()`.
|
|
203
|
-
*/
|
|
204
|
-
onAny(event, handler) {
|
|
205
|
-
const transition = {
|
|
206
|
-
stateTag: "*",
|
|
207
|
-
eventTag: getTag(event),
|
|
208
|
-
handler,
|
|
209
|
-
reenter: false
|
|
210
|
-
};
|
|
211
|
-
this._transitions.push(transition);
|
|
212
|
-
invalidateIndex(this);
|
|
213
|
-
return this;
|
|
214
|
-
}
|
|
215
|
-
/** @internal */
|
|
216
|
-
addTransition(state, event, handler, reenter) {
|
|
217
|
-
const transition = {
|
|
218
|
-
stateTag: getTag(state),
|
|
219
|
-
eventTag: getTag(event),
|
|
220
|
-
handler,
|
|
221
|
-
reenter
|
|
222
|
-
};
|
|
223
|
-
this._transitions.push(transition);
|
|
224
|
-
invalidateIndex(this);
|
|
225
|
-
return this;
|
|
226
|
-
}
|
|
227
|
-
spawn(stateOrStates, handler) {
|
|
228
|
-
const states = Array.isArray(stateOrStates) ? stateOrStates : [stateOrStates];
|
|
229
|
-
for (const s of states) {
|
|
230
|
-
const stateTag = getTag(s);
|
|
231
|
-
this._spawnEffects.push({
|
|
232
|
-
stateTag,
|
|
233
|
-
handler
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
invalidateIndex(this);
|
|
237
|
-
return this;
|
|
238
|
-
}
|
|
239
|
-
task(stateOrStates, run, options) {
|
|
240
|
-
const handler = Effect.fn("effect-machine.task")(function* (ctx) {
|
|
241
|
-
yield* emitTaskInspection({
|
|
242
|
-
actorId: ctx.actorId,
|
|
243
|
-
state: ctx.state,
|
|
244
|
-
taskName: options.name,
|
|
245
|
-
phase: "start"
|
|
246
|
-
});
|
|
247
|
-
const exit = yield* Effect.exit(run(ctx));
|
|
248
|
-
if (Exit.isSuccess(exit)) {
|
|
249
|
-
yield* emitTaskInspection({
|
|
250
|
-
actorId: ctx.actorId,
|
|
251
|
-
state: ctx.state,
|
|
252
|
-
taskName: options.name,
|
|
253
|
-
phase: "success"
|
|
254
|
-
});
|
|
255
|
-
const successEvent = options.onSuccess !== void 0 ? options.onSuccess(exit.value, ctx) : exit.value;
|
|
256
|
-
yield* ctx.self.send(successEvent);
|
|
257
|
-
yield* Effect.yieldNow();
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
const cause = exit.cause;
|
|
261
|
-
if (Cause.isInterruptedOnly(cause)) {
|
|
262
|
-
yield* emitTaskInspection({
|
|
263
|
-
actorId: ctx.actorId,
|
|
264
|
-
state: ctx.state,
|
|
265
|
-
taskName: options.name,
|
|
266
|
-
phase: "interrupt"
|
|
267
|
-
});
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
yield* emitTaskInspection({
|
|
271
|
-
actorId: ctx.actorId,
|
|
272
|
-
state: ctx.state,
|
|
273
|
-
taskName: options.name,
|
|
274
|
-
phase: "failure",
|
|
275
|
-
error: Cause.pretty(cause)
|
|
276
|
-
});
|
|
277
|
-
if (options.onFailure !== void 0) {
|
|
278
|
-
yield* ctx.self.send(options.onFailure(cause, ctx));
|
|
279
|
-
yield* Effect.yieldNow();
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
return yield* Effect.failCause(cause).pipe(Effect.orDie);
|
|
283
|
-
});
|
|
284
|
-
return this.spawn(stateOrStates, handler);
|
|
285
|
-
}
|
|
286
|
-
/**
|
|
287
|
-
* State timeout — gen_statem's `state_timeout`.
|
|
288
|
-
*
|
|
289
|
-
* Entering the state starts a timer. Leaving cancels it (via state scope).
|
|
290
|
-
* `.reenter()` restarts the timer with fresh state values.
|
|
291
|
-
* Compiles to `.task()` internally — preserves `@machine.task` inspection events.
|
|
292
|
-
*
|
|
293
|
-
* @example
|
|
294
|
-
* ```ts
|
|
295
|
-
* machine
|
|
296
|
-
* .timeout(State.Loading, {
|
|
297
|
-
* duration: Duration.seconds(30),
|
|
298
|
-
* event: Event.Timeout,
|
|
299
|
-
* })
|
|
300
|
-
* // Dynamic duration from state
|
|
301
|
-
* .timeout(State.Retrying, {
|
|
302
|
-
* duration: (state) => Duration.seconds(state.backoff),
|
|
303
|
-
* event: Event.GiveUp,
|
|
304
|
-
* })
|
|
305
|
-
* ```
|
|
306
|
-
*/
|
|
307
|
-
timeout(state, config) {
|
|
308
|
-
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;
|
|
311
|
-
return this.task(state, (ctx) => Effect.sleep(resolveDuration(ctx.state)), {
|
|
312
|
-
onSuccess: (_, ctx) => resolveEvent(ctx.state),
|
|
313
|
-
name: `$timeout:${stateTag}`
|
|
314
|
-
});
|
|
315
|
-
}
|
|
316
|
-
/**
|
|
317
|
-
* Machine-lifetime effect that is forked on actor spawn and runs until the actor stops.
|
|
318
|
-
*
|
|
319
|
-
* @example
|
|
320
|
-
* ```ts
|
|
321
|
-
* machine.background(({ self }) =>
|
|
322
|
-
* Effect.forever(
|
|
323
|
-
* Effect.sleep("30 seconds").pipe(Effect.andThen(self.send(Event.Ping))),
|
|
324
|
-
* ),
|
|
325
|
-
* );
|
|
326
|
-
* ```
|
|
327
|
-
*/
|
|
328
|
-
background(handler) {
|
|
329
|
-
this._backgroundEffects.push({ handler });
|
|
330
|
-
return this;
|
|
331
|
-
}
|
|
332
|
-
/**
|
|
333
|
-
* Postpone events — gen_statem's event postpone.
|
|
334
|
-
*
|
|
335
|
-
* When a matching event arrives in the given state, it is buffered instead of
|
|
336
|
-
* processed. After the next state transition (tag change), all buffered events
|
|
337
|
-
* are drained through the loop in FIFO order.
|
|
338
|
-
*
|
|
339
|
-
* Reply-bearing events (from `call`/`ask`) in the postpone buffer are settled
|
|
340
|
-
* with `ActorStoppedError` on stop/interrupt/final-state.
|
|
341
|
-
*
|
|
342
|
-
* @example
|
|
343
|
-
* ```ts
|
|
344
|
-
* machine
|
|
345
|
-
* .postpone(State.Connecting, Event.Data) // single event
|
|
346
|
-
* .postpone(State.Connecting, [Event.Data, Event.Cmd]) // multiple events
|
|
347
|
-
* ```
|
|
348
|
-
*/
|
|
349
|
-
postpone(state, events) {
|
|
350
|
-
const stateTag = getTag(state);
|
|
351
|
-
const eventList = Array.isArray(events) ? events : [events];
|
|
352
|
-
for (const ev of eventList) {
|
|
353
|
-
const eventTag = getTag(ev);
|
|
354
|
-
this._postponeRules.push({
|
|
355
|
-
stateTag,
|
|
356
|
-
eventTag
|
|
357
|
-
});
|
|
358
|
-
}
|
|
359
|
-
return this;
|
|
360
|
-
}
|
|
361
|
-
final(state) {
|
|
362
|
-
const stateTag = getTag(state);
|
|
363
|
-
this._finalStates.add(stateTag);
|
|
364
|
-
return this;
|
|
365
|
-
}
|
|
366
|
-
static make(config) {
|
|
367
|
-
return new Machine(config.initial, config.state, config.event, config.slots, config.slotValidation ?? true);
|
|
368
|
-
}
|
|
369
|
-
};
|
|
370
|
-
var TransitionScope = class {
|
|
371
|
-
constructor(machine, states) {
|
|
372
|
-
this.machine = machine;
|
|
373
|
-
this.states = states;
|
|
374
|
-
}
|
|
375
|
-
on(event, handler) {
|
|
376
|
-
this.machine.scopeTransition(this.states, event, handler, false);
|
|
377
|
-
return this;
|
|
378
|
-
}
|
|
379
|
-
reenter(event, handler) {
|
|
380
|
-
this.machine.scopeTransition(this.states, event, handler, true);
|
|
381
|
-
return this;
|
|
382
|
-
}
|
|
383
|
-
};
|
|
384
|
-
const make = Machine.make;
|
|
385
|
-
/**
|
|
386
|
-
* Spawn an actor from a machine.
|
|
387
|
-
*
|
|
388
|
-
* For machines with slots, pass implementations via `{ slots: { ... } }`.
|
|
389
|
-
*
|
|
390
|
-
* @example
|
|
391
|
-
* ```ts
|
|
392
|
-
* // No slots
|
|
393
|
-
* const actor = yield* Machine.spawn(machine);
|
|
394
|
-
*
|
|
395
|
-
* // With slots
|
|
396
|
-
* const actor = yield* Machine.spawn(machine, {
|
|
397
|
-
* slots: { canRetry: ({ max }) => attempts < max },
|
|
398
|
-
* });
|
|
399
|
-
*
|
|
400
|
-
* // With lifecycle (recovery + durability)
|
|
401
|
-
* const actor = yield* Machine.spawn(machine, {
|
|
402
|
-
* lifecycle: {
|
|
403
|
-
* recovery: { resolve: ({ machineInitial }) => storage.get("actor-state") },
|
|
404
|
-
* durability: { save: ({ nextState }) => storage.set("actor-state", nextState) },
|
|
405
|
-
* },
|
|
406
|
-
* });
|
|
407
|
-
* ```
|
|
408
|
-
*/
|
|
409
|
-
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), {
|
|
412
|
-
initialState: opts?.hydrate,
|
|
413
|
-
supervision: opts?.supervision,
|
|
414
|
-
lifecycle: opts?.lifecycle
|
|
415
|
-
});
|
|
416
|
-
const maybeScope = yield* Effect.serviceOption(ActorScope);
|
|
417
|
-
if (Option.isSome(maybeScope)) yield* Scope.addFinalizer(maybeScope.value, actor.stop);
|
|
418
|
-
return actor;
|
|
419
|
-
});
|
|
420
|
-
/**
|
|
421
|
-
* Wrap an effect to provide an `ActorScope` from the current `Scope`.
|
|
422
|
-
*
|
|
423
|
-
* Actors spawned inside will attach cleanup finalizers to this scope,
|
|
424
|
-
* so they are automatically stopped when the scope closes.
|
|
425
|
-
*
|
|
426
|
-
* @example
|
|
427
|
-
* ```ts
|
|
428
|
-
* yield* Effect.scoped(
|
|
429
|
-
* Machine.scoped(
|
|
430
|
-
* Effect.gen(function* () {
|
|
431
|
-
* const actor = yield* Machine.spawn(machine);
|
|
432
|
-
* yield* actor.start;
|
|
433
|
-
* // actor auto-stopped when scope closes
|
|
434
|
-
* }),
|
|
435
|
-
* ),
|
|
436
|
-
* );
|
|
437
|
-
* ```
|
|
438
|
-
*/
|
|
439
|
-
const scoped = (effect) => Effect.flatMap(Scope.Scope, (scope) => Effect.provideService(effect, ActorScope, scope));
|
|
440
|
-
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;
|
|
483
|
-
});
|
|
484
|
-
const reply = makeReply;
|
|
485
|
-
const deferReply = makeDeferReply;
|
|
486
|
-
//#endregion
|
|
487
|
-
export { Machine, deferReply, findTransitions, machine_exports, make, materializeMachine, replay, reply, scoped, spawn };
|
package/v3/dist/schema.d.ts
DELETED
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
import { FullEventBrand, FullStateBrand, ReplyTypeBrand } from "./internal/brands.js";
|
|
2
|
-
import { Schema } from "effect";
|
|
3
|
-
|
|
4
|
-
//#region src/schema.d.ts
|
|
5
|
-
declare const ReplySchemaSymbol: unique symbol;
|
|
6
|
-
type ReplySchemaSymbol = typeof ReplySchemaSymbol;
|
|
7
|
-
/**
|
|
8
|
-
* Fields annotated with a reply schema.
|
|
9
|
-
* Structurally identical to Schema.Struct.Fields at runtime,
|
|
10
|
-
* but carries the reply schema type at compile time.
|
|
11
|
-
*/
|
|
12
|
-
type ReplyFields<F extends Schema.Struct.Fields, RS extends Schema.Schema.Any> = F & {
|
|
13
|
-
readonly [ReplySchemaSymbol]: RS;
|
|
14
|
-
};
|
|
15
|
-
/**
|
|
16
|
-
* Payload fields that actually flow through constructors and runtime values.
|
|
17
|
-
* Reply schema metadata is type-only and must not leak into payload shapes.
|
|
18
|
-
*/
|
|
19
|
-
type PayloadFields<F extends Schema.Struct.Fields> = { readonly [K in keyof F as K extends ReplySchemaSymbol ? never : K]: F[K] };
|
|
20
|
-
/**
|
|
21
|
-
* Extract the TypeScript type from a TaggedStruct schema
|
|
22
|
-
*/
|
|
23
|
-
type TaggedStructType<Tag extends string, Fields extends Schema.Struct.Fields> = Schema.Schema.Type<Schema.TaggedStruct<Tag, PayloadFields<Fields>>>;
|
|
24
|
-
/**
|
|
25
|
-
* Build variant schemas type from definition
|
|
26
|
-
*/
|
|
27
|
-
type VariantSchemas<D extends Record<string, Schema.Struct.Fields>> = { readonly [K in keyof D & string]: Schema.TaggedStruct<K, PayloadFields<D[K]>> };
|
|
28
|
-
/**
|
|
29
|
-
* Build union type from variant schemas.
|
|
30
|
-
* Reply-bearing variants carry ReplyTypeBrand<R> for ask() inference.
|
|
31
|
-
*/
|
|
32
|
-
type VariantsUnion<D extends Record<string, Schema.Struct.Fields>> = { [K in keyof D & string]: TaggedStructType<K, D[K]> & (D[K] extends {
|
|
33
|
-
readonly [ReplySchemaSymbol]: Schema.Schema<infer R>;
|
|
34
|
-
} ? ReplyTypeBrand<R> : unknown) }[keyof D & string];
|
|
35
|
-
/**
|
|
36
|
-
* Check if fields are empty (no required string properties).
|
|
37
|
-
* Symbol keys (like ReplySchemaSymbol) are metadata, not payload fields.
|
|
38
|
-
*/
|
|
39
|
-
type IsEmptyFields<Fields extends Schema.Struct.Fields> = string & keyof Fields extends never ? true : false;
|
|
40
|
-
/**
|
|
41
|
-
* Resolve the reply brand for a variant's fields.
|
|
42
|
-
* If fields carry ReplySchemaSymbol, adds ReplyTypeBrand<R>.
|
|
43
|
-
*/
|
|
44
|
-
type VariantReplyBrand<Fields extends Schema.Struct.Fields> = Fields extends {
|
|
45
|
-
readonly [ReplySchemaSymbol]: Schema.Schema<infer R>;
|
|
46
|
-
} ? ReplyTypeBrand<R> : unknown;
|
|
47
|
-
/**
|
|
48
|
-
* Constructor functions for each variant.
|
|
49
|
-
* Empty structs: plain values with `_tag`: `State.Idle`
|
|
50
|
-
* Non-empty structs require args: `State.Loading({ url })`
|
|
51
|
-
*
|
|
52
|
-
* Each variant also has a `with` method for constructing from a source object,
|
|
53
|
-
* copying matching fields and overriding with a partial.
|
|
54
|
-
* The source type uses `object` to accept branded state types without index signature issues.
|
|
55
|
-
* Reply-bearing variants carry ReplyTypeBrand<R> for ask() type inference.
|
|
56
|
-
*/
|
|
57
|
-
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]> & {
|
|
58
|
-
readonly with: (source: object) => TaggedStructType<K, D[K]> & Brand;
|
|
59
|
-
} : ((args: Schema.Struct.Type<PayloadFields<D[K]>>) => TaggedStructType<K, D[K]> & Brand & VariantReplyBrand<D[K]>) & {
|
|
60
|
-
readonly with: (source: object, partial?: Partial<Schema.Struct.Type<PayloadFields<D[K]>>>) => TaggedStructType<K, D[K]> & Brand;
|
|
61
|
-
readonly _tag: K;
|
|
62
|
-
} };
|
|
63
|
-
/**
|
|
64
|
-
* Keys present in ALL variants (intersection of field names).
|
|
65
|
-
* Used by union-level `with` to accept only fields safe to update
|
|
66
|
-
* regardless of which variant the source is.
|
|
67
|
-
*/
|
|
68
|
-
type SharedKeys<D extends Record<string, Schema.Struct.Fields>> = keyof D[keyof D & string] & string;
|
|
69
|
-
/**
|
|
70
|
-
* Pattern matching cases type
|
|
71
|
-
*/
|
|
72
|
-
type MatchCases<D extends Record<string, Schema.Struct.Fields>, R> = { readonly [K in keyof D & string]: (value: TaggedStructType<K, D[K]>) => R };
|
|
73
|
-
/**
|
|
74
|
-
* Base schema interface with pattern matching helpers
|
|
75
|
-
*/
|
|
76
|
-
interface MachineSchemaBase<D extends Record<string, Schema.Struct.Fields>, Brand> {
|
|
77
|
-
/**
|
|
78
|
-
* Raw definition record for introspection
|
|
79
|
-
*/
|
|
80
|
-
readonly _definition: D;
|
|
81
|
-
/**
|
|
82
|
-
* Per-variant schemas for fine-grained operations
|
|
83
|
-
*/
|
|
84
|
-
readonly variants: VariantSchemas<D>;
|
|
85
|
-
/**
|
|
86
|
-
* Type guard: `OrderState.$is("Pending")(value)`
|
|
87
|
-
*/
|
|
88
|
-
readonly $is: <Tag extends keyof D & string>(tag: Tag) => (u: unknown) => u is TaggedStructType<Tag, D[Tag]> & Brand;
|
|
89
|
-
/**
|
|
90
|
-
* Pattern matching (curried and uncurried)
|
|
91
|
-
*/
|
|
92
|
-
readonly $match: {
|
|
93
|
-
<R>(cases: MatchCases<D, R>): (value: VariantsUnion<D> & Brand) => R;
|
|
94
|
-
<R>(value: VariantsUnion<D> & Brand, cases: MatchCases<D, R>): R;
|
|
95
|
-
};
|
|
96
|
-
/**
|
|
97
|
-
* Copy fields from `source` into the same variant, overriding with `partial`.
|
|
98
|
-
* Preserves the specific variant subtype in the return.
|
|
99
|
-
*
|
|
100
|
-
* The partial accepts fields common to all variants, so it works safely
|
|
101
|
-
* when `S` is a generic type parameter (e.g., `<S extends MyState>`).
|
|
102
|
-
*
|
|
103
|
-
* @example
|
|
104
|
-
* ```ts
|
|
105
|
-
* // Per-variant field update
|
|
106
|
-
* const next = MyState.Streaming.with(state, { draft: newDraft })
|
|
107
|
-
*
|
|
108
|
-
* // Cross-variant shared field — works with generic state
|
|
109
|
-
* const updateQueue = <S extends MyState>(state: S, queue: Queue): S =>
|
|
110
|
-
* MyState.with(state, { queue })
|
|
111
|
-
* ```
|
|
112
|
-
*/
|
|
113
|
-
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.Schema.Any>;
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* Schema-first state definition that provides:
|
|
122
|
-
* - Schema for encode/decode/validate
|
|
123
|
-
* - Variant constructors: `OrderState.Pending({ orderId: "x" })`
|
|
124
|
-
* - Pattern matching: `$is`, `$match`
|
|
125
|
-
* - Type inference: `typeof OrderState.Type`
|
|
126
|
-
*
|
|
127
|
-
* The D type parameter captures the definition, creating a unique brand
|
|
128
|
-
* per distinct schema definition shape.
|
|
129
|
-
*/
|
|
130
|
-
type MachineStateSchema<D extends Record<string, Schema.Struct.Fields>> = Schema.Schema<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
|
-
};
|
|
133
|
-
/**
|
|
134
|
-
* Schema-first event definition (same structure as state, different brand)
|
|
135
|
-
*
|
|
136
|
-
* The D type parameter captures the definition, creating a unique brand
|
|
137
|
-
* per distinct schema definition shape.
|
|
138
|
-
*/
|
|
139
|
-
type MachineEventSchema<D extends Record<string, Schema.Struct.Fields>> = Schema.Schema<VariantsUnion<D> & FullEventBrand<D>, unknown> & MachineSchemaBase<D, FullEventBrand<D>> & VariantConstructors<D, FullEventBrand<D>>;
|
|
140
|
-
/**
|
|
141
|
-
* Create a schema-first State definition.
|
|
142
|
-
*
|
|
143
|
-
* The schema's definition type D creates a unique brand, preventing
|
|
144
|
-
* accidental use of constructors from different state schemas
|
|
145
|
-
* (unless they have identical definitions).
|
|
146
|
-
*
|
|
147
|
-
* @example
|
|
148
|
-
* ```ts
|
|
149
|
-
* const OrderState = MachineSchema.State({
|
|
150
|
-
* Pending: { orderId: Schema.String },
|
|
151
|
-
* Shipped: { trackingId: Schema.String },
|
|
152
|
-
* })
|
|
153
|
-
*
|
|
154
|
-
* type OrderState = typeof OrderState.Type
|
|
155
|
-
*
|
|
156
|
-
* // Construct
|
|
157
|
-
* const s = OrderState.Pending({ orderId: "123" })
|
|
158
|
-
*
|
|
159
|
-
* // Pattern match
|
|
160
|
-
* OrderState.$match(s, {
|
|
161
|
-
* Pending: (v) => v.orderId,
|
|
162
|
-
* Shipped: (v) => v.trackingId,
|
|
163
|
-
* })
|
|
164
|
-
*
|
|
165
|
-
* // Validate
|
|
166
|
-
* Schema.decodeUnknownSync(OrderState)(rawJson)
|
|
167
|
-
* ```
|
|
168
|
-
*/
|
|
169
|
-
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.Any>(fields: F, replySchema: RS) => ReplyFields<F, RS>;
|
|
172
|
-
};
|
|
173
|
-
//#endregion
|
|
174
|
-
export { Event, MachineEventSchema, MachineStateSchema, ReplyFields, ReplySchemaSymbol, State, VariantsUnion };
|