effect-machine 0.17.1 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +50 -54
- package/dist/actor.d.ts +8 -45
- package/dist/actor.js +157 -231
- 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 +19 -38
- 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/dist/machine.js
CHANGED
|
@@ -1,69 +1,46 @@
|
|
|
1
1
|
import { __exportAll } from "./_virtual/_rolldown/runtime.js";
|
|
2
|
-
import { getTag, makeDeferReply, makeReply
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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,
|
|
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
|
|
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
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
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
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
|
65
|
+
constructor(initial, stateSchema, eventSchema) {
|
|
120
66
|
this.initial = initial;
|
|
121
|
-
this
|
|
122
|
-
this
|
|
123
|
-
this
|
|
124
|
-
this
|
|
125
|
-
this
|
|
126
|
-
this
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
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.
|
|
224
|
-
invalidateIndex(this);
|
|
120
|
+
this.registerTransition(transition);
|
|
225
121
|
return this;
|
|
226
122
|
}
|
|
227
123
|
spawn(stateOrStates, handler) {
|
|
228
|
-
const states =
|
|
124
|
+
const states = toReadonlyArray(stateOrStates);
|
|
229
125
|
for (const s of states) {
|
|
230
126
|
const stateTag = getTag(s);
|
|
231
|
-
|
|
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
|
-
|
|
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
|
|
310
|
-
const
|
|
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.
|
|
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 =
|
|
309
|
+
const eventList = toReadonlyArray(events);
|
|
352
310
|
for (const ev of eventList) {
|
|
353
311
|
const eventTag = getTag(ev);
|
|
354
|
-
this.
|
|
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.
|
|
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
|
|
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
|
-
|
|
411
|
-
|
|
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 =
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
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,
|
|
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 =
|
|
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
|
-
|
|
171
|
-
|
|
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 };
|