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/v3/dist/actor.js
DELETED
|
@@ -1,577 +0,0 @@
|
|
|
1
|
-
import { ActorStoppedError, DuplicateActorError } from "./errors.js";
|
|
2
|
-
import { processEventCore, resolveTransition, runSpawnEffects } from "./internal/transition.js";
|
|
3
|
-
import { emitWithTimestamp } from "./internal/inspection.js";
|
|
4
|
-
import { Inspector } from "./inspection.js";
|
|
5
|
-
import { materializeMachine } from "./machine.js";
|
|
6
|
-
import { createRuntime } from "./internal/runtime.js";
|
|
7
|
-
import { Cause, Context, Deferred, Effect, Exit, Fiber, Layer, MutableHashMap, Option, PubSub, Queue, Ref, Schedule, Scope, Stream, SubscriptionRef } from "effect";
|
|
8
|
-
//#region src/actor.ts
|
|
9
|
-
/**
|
|
10
|
-
* Actor system: spawning, lifecycle, and event processing.
|
|
11
|
-
*
|
|
12
|
-
* Combines:
|
|
13
|
-
* - ActorRef interface (running actor handle)
|
|
14
|
-
* - ActorSystem service (spawn/stop/get actors)
|
|
15
|
-
* - Actor creation and event loop
|
|
16
|
-
*/
|
|
17
|
-
/**
|
|
18
|
-
* ActorSystem service tag
|
|
19
|
-
*/
|
|
20
|
-
const ActorSystem = Context.GenericTag("@effect/machine/ActorSystem");
|
|
21
|
-
/**
|
|
22
|
-
* Explicit scope for actor lifecycle management.
|
|
23
|
-
*
|
|
24
|
-
* When present in context, actors attach cleanup finalizers to this scope.
|
|
25
|
-
* This replaces ambient `Scope.Scope` detection which caused bugs where
|
|
26
|
-
* unrelated scopes would tear down actors unexpectedly.
|
|
27
|
-
*
|
|
28
|
-
* Provide via `Machine.scoped` or `Effect.provideService(ActorScope, scope)`.
|
|
29
|
-
*/
|
|
30
|
-
const ActorScope = Context.GenericTag("@effect/machine/ActorScope");
|
|
31
|
-
/**
|
|
32
|
-
* Notify all listeners of state change.
|
|
33
|
-
*/
|
|
34
|
-
const notifyListeners = (listeners, state) => {
|
|
35
|
-
for (const listener of listeners) try {
|
|
36
|
-
listener(state);
|
|
37
|
-
} catch {}
|
|
38
|
-
};
|
|
39
|
-
/**
|
|
40
|
-
* Build core ActorRef methods.
|
|
41
|
-
*/
|
|
42
|
-
const buildActorRefCore = (id, machine, stateRef, eventQueueRef, stoppedRef, listeners, stop, start, system, childrenMap, pendingReplies, transitionsPubSub, exitDeferred) => {
|
|
43
|
-
const send = Effect.fn("effect-machine.actor.send")(function* (event) {
|
|
44
|
-
if (yield* Ref.get(stoppedRef)) return;
|
|
45
|
-
const q = yield* Ref.get(eventQueueRef);
|
|
46
|
-
yield* Queue.offer(q, {
|
|
47
|
-
_tag: "send",
|
|
48
|
-
event
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
const call = Effect.fn("effect-machine.actor.call")(function* (event) {
|
|
52
|
-
if (yield* Ref.get(stoppedRef)) {
|
|
53
|
-
yield* Effect.logWarning("effect-machine.actor.call.stopped").pipe(Effect.annotateLogs({
|
|
54
|
-
actorId: id,
|
|
55
|
-
eventTag: event._tag
|
|
56
|
-
}));
|
|
57
|
-
const currentState = yield* SubscriptionRef.get(stateRef);
|
|
58
|
-
return {
|
|
59
|
-
newState: currentState,
|
|
60
|
-
previousState: currentState,
|
|
61
|
-
transitioned: false,
|
|
62
|
-
hasReply: false,
|
|
63
|
-
deferReply: false,
|
|
64
|
-
reply: void 0,
|
|
65
|
-
postponed: false,
|
|
66
|
-
lifecycleRan: false,
|
|
67
|
-
isFinal: machine.finalStates.has(currentState._tag)
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
const reply = yield* Deferred.make();
|
|
71
|
-
pendingReplies.add(reply);
|
|
72
|
-
const q = yield* Ref.get(eventQueueRef);
|
|
73
|
-
yield* Queue.offer(q, {
|
|
74
|
-
_tag: "call",
|
|
75
|
-
event,
|
|
76
|
-
reply
|
|
77
|
-
});
|
|
78
|
-
return yield* Deferred.await(reply).pipe(Effect.ensuring(Effect.sync(() => pendingReplies.delete(reply))), Effect.catchTag("ActorStoppedError", () => SubscriptionRef.get(stateRef).pipe(Effect.map((currentState) => ({
|
|
79
|
-
newState: currentState,
|
|
80
|
-
previousState: currentState,
|
|
81
|
-
transitioned: false,
|
|
82
|
-
hasReply: false,
|
|
83
|
-
deferReply: false,
|
|
84
|
-
reply: void 0,
|
|
85
|
-
postponed: false,
|
|
86
|
-
lifecycleRan: false,
|
|
87
|
-
isFinal: machine.finalStates.has(currentState._tag)
|
|
88
|
-
})))));
|
|
89
|
-
});
|
|
90
|
-
const ask = Effect.fn("effect-machine.actor.ask")(function* (event) {
|
|
91
|
-
if (yield* Ref.get(stoppedRef)) return yield* new ActorStoppedError({ actorId: id });
|
|
92
|
-
const reply = yield* Deferred.make();
|
|
93
|
-
pendingReplies.add(reply);
|
|
94
|
-
const q = yield* Ref.get(eventQueueRef);
|
|
95
|
-
yield* Queue.offer(q, {
|
|
96
|
-
_tag: "ask",
|
|
97
|
-
event,
|
|
98
|
-
reply
|
|
99
|
-
});
|
|
100
|
-
return yield* Deferred.await(reply).pipe(Effect.ensuring(Effect.sync(() => pendingReplies.delete(reply))));
|
|
101
|
-
});
|
|
102
|
-
const snapshot = SubscriptionRef.get(stateRef).pipe(Effect.withSpan("effect-machine.actor.snapshot"));
|
|
103
|
-
const matches = Effect.fn("effect-machine.actor.matches")(function* (tag) {
|
|
104
|
-
return (yield* SubscriptionRef.get(stateRef))._tag === tag;
|
|
105
|
-
});
|
|
106
|
-
const can = Effect.fn("effect-machine.actor.can")(function* (event) {
|
|
107
|
-
return resolveTransition(machine, yield* SubscriptionRef.get(stateRef), event) !== void 0;
|
|
108
|
-
});
|
|
109
|
-
const waitFor = Effect.fn("effect-machine.actor.waitFor")(function* (predicateOrState) {
|
|
110
|
-
const predicate = typeof predicateOrState === "function" && !("_tag" in predicateOrState) ? predicateOrState : (s) => s._tag === predicateOrState._tag;
|
|
111
|
-
const current = yield* SubscriptionRef.get(stateRef);
|
|
112
|
-
if (predicate(current)) return current;
|
|
113
|
-
const done = yield* Deferred.make();
|
|
114
|
-
const listener = (state) => {
|
|
115
|
-
if (predicate(state)) Effect.runFork(Deferred.succeed(done, state));
|
|
116
|
-
};
|
|
117
|
-
listeners.add(listener);
|
|
118
|
-
const afterSubscribe = yield* SubscriptionRef.get(stateRef);
|
|
119
|
-
if (predicate(afterSubscribe)) {
|
|
120
|
-
listeners.delete(listener);
|
|
121
|
-
return afterSubscribe;
|
|
122
|
-
}
|
|
123
|
-
const result = yield* Deferred.await(done);
|
|
124
|
-
listeners.delete(listener);
|
|
125
|
-
return result;
|
|
126
|
-
});
|
|
127
|
-
const awaitFinal = waitFor((state) => machine.finalStates.has(state._tag)).pipe(Effect.withSpan("effect-machine.actor.awaitFinal"));
|
|
128
|
-
const sendAndWait = Effect.fn("effect-machine.actor.sendAndWait")(function* (event, predicateOrState) {
|
|
129
|
-
yield* send(event);
|
|
130
|
-
if (predicateOrState !== void 0) return yield* waitFor(predicateOrState);
|
|
131
|
-
return yield* awaitFinal;
|
|
132
|
-
});
|
|
133
|
-
return {
|
|
134
|
-
id,
|
|
135
|
-
send,
|
|
136
|
-
cast: send,
|
|
137
|
-
call,
|
|
138
|
-
ask,
|
|
139
|
-
state: stateRef,
|
|
140
|
-
stop,
|
|
141
|
-
start,
|
|
142
|
-
snapshot,
|
|
143
|
-
matches,
|
|
144
|
-
can,
|
|
145
|
-
changes: stateRef.changes,
|
|
146
|
-
transitions: transitionsPubSub !== void 0 ? Stream.fromPubSub(transitionsPubSub) : Stream.empty,
|
|
147
|
-
waitFor,
|
|
148
|
-
awaitFinal,
|
|
149
|
-
sendAndWait,
|
|
150
|
-
subscribe: (fn) => {
|
|
151
|
-
listeners.add(fn);
|
|
152
|
-
return () => {
|
|
153
|
-
listeners.delete(fn);
|
|
154
|
-
};
|
|
155
|
-
},
|
|
156
|
-
awaitExit: Deferred.await(exitDeferred),
|
|
157
|
-
watch: (other) => other.awaitExit,
|
|
158
|
-
drain: Effect.gen(function* () {
|
|
159
|
-
if (yield* Ref.get(stoppedRef)) return;
|
|
160
|
-
const q = yield* Ref.get(eventQueueRef);
|
|
161
|
-
const done = yield* Deferred.make();
|
|
162
|
-
yield* Queue.offer(q, {
|
|
163
|
-
_tag: "drain",
|
|
164
|
-
done
|
|
165
|
-
});
|
|
166
|
-
yield* Deferred.await(done);
|
|
167
|
-
}).pipe(Effect.asVoid),
|
|
168
|
-
sync: {
|
|
169
|
-
send: (event) => {
|
|
170
|
-
if (!Effect.runSync(Ref.get(stoppedRef))) {
|
|
171
|
-
const q = Effect.runSync(Ref.get(eventQueueRef));
|
|
172
|
-
Effect.runSync(Queue.offer(q, {
|
|
173
|
-
_tag: "send",
|
|
174
|
-
event
|
|
175
|
-
}));
|
|
176
|
-
}
|
|
177
|
-
},
|
|
178
|
-
stop: () => Effect.runFork(stop),
|
|
179
|
-
snapshot: () => Effect.runSync(SubscriptionRef.get(stateRef)),
|
|
180
|
-
matches: (tag) => Effect.runSync(SubscriptionRef.get(stateRef))._tag === tag,
|
|
181
|
-
can: (event) => {
|
|
182
|
-
return resolveTransition(machine, Effect.runSync(SubscriptionRef.get(stateRef)), event) !== void 0;
|
|
183
|
-
}
|
|
184
|
-
},
|
|
185
|
-
system,
|
|
186
|
-
children: childrenMap
|
|
187
|
-
};
|
|
188
|
-
};
|
|
189
|
-
/** Build ProcessEventHooks from an inspector */
|
|
190
|
-
const buildInspectionHooks = (actorId, inspector) => ({
|
|
191
|
-
onSpawnEffect: (state) => emitWithTimestamp(inspector, (timestamp) => ({
|
|
192
|
-
type: "@machine.effect",
|
|
193
|
-
actorId,
|
|
194
|
-
effectType: "spawn",
|
|
195
|
-
state,
|
|
196
|
-
timestamp
|
|
197
|
-
})),
|
|
198
|
-
onTransition: (from, to, ev) => emitWithTimestamp(inspector, (timestamp) => ({
|
|
199
|
-
type: "@machine.transition",
|
|
200
|
-
actorId,
|
|
201
|
-
fromState: from,
|
|
202
|
-
toState: to,
|
|
203
|
-
event: ev,
|
|
204
|
-
timestamp
|
|
205
|
-
})),
|
|
206
|
-
onError: (info) => emitWithTimestamp(inspector, (timestamp) => ({
|
|
207
|
-
type: "@machine.error",
|
|
208
|
-
actorId,
|
|
209
|
-
phase: info.phase,
|
|
210
|
-
state: info.state,
|
|
211
|
-
event: info.event,
|
|
212
|
-
error: Cause.pretty(info.cause),
|
|
213
|
-
timestamp
|
|
214
|
-
}))
|
|
215
|
-
});
|
|
216
|
-
/**
|
|
217
|
-
* Resolve actor system from context, creating an implicit one if none exists.
|
|
218
|
-
* @internal
|
|
219
|
-
*/
|
|
220
|
-
const resolveActorSystem = Effect.fn("effect-machine.resolveActorSystem")(function* () {
|
|
221
|
-
const existingSystem = yield* Effect.serviceOption(ActorSystem);
|
|
222
|
-
if (Option.isSome(existingSystem)) return {
|
|
223
|
-
system: existingSystem.value,
|
|
224
|
-
implicitSystemScope: void 0
|
|
225
|
-
};
|
|
226
|
-
const scope = yield* Scope.make();
|
|
227
|
-
return {
|
|
228
|
-
system: yield* make().pipe(Effect.provideService(Scope.Scope, scope)),
|
|
229
|
-
implicitSystemScope: scope
|
|
230
|
-
};
|
|
231
|
-
});
|
|
232
|
-
/**
|
|
233
|
-
* Run the supervision loop for a supervised actor.
|
|
234
|
-
* Observes exit deferred, applies restart policy, resets cell resources on restart.
|
|
235
|
-
* @internal
|
|
236
|
-
*/
|
|
237
|
-
const runSupervisionLoop = (params) => Effect.gen(function* () {
|
|
238
|
-
const driver = yield* Schedule.driver(params.supervision.schedule);
|
|
239
|
-
let generation = 0;
|
|
240
|
-
while (true) {
|
|
241
|
-
const currentRuntime = params.runtimeRef.current;
|
|
242
|
-
if (currentRuntime === void 0) return;
|
|
243
|
-
const generationExit = yield* Deferred.await(currentRuntime.exitDeferred);
|
|
244
|
-
if (generationExit._tag !== "Defect") {
|
|
245
|
-
yield* Deferred.succeed(params.terminalExitDeferred, generationExit);
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
if (params.supervision.shouldRestart !== void 0 && !params.supervision.shouldRestart(generationExit)) {
|
|
249
|
-
yield* Deferred.succeed(params.terminalExitDeferred, generationExit);
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
if ((yield* driver.next(generationExit).pipe(Effect.exit))._tag === "Failure") {
|
|
253
|
-
yield* Deferred.succeed(params.terminalExitDeferred, generationExit);
|
|
254
|
-
return;
|
|
255
|
-
}
|
|
256
|
-
let restartState = params.machine.initial;
|
|
257
|
-
if (params.lifecycle?.recovery !== void 0) {
|
|
258
|
-
const resolved = yield* params.lifecycle.recovery.resolve({
|
|
259
|
-
actorId: params.id,
|
|
260
|
-
generation: generation + 1,
|
|
261
|
-
machineInitial: params.machine.initial
|
|
262
|
-
});
|
|
263
|
-
if (Option.isSome(resolved)) restartState = resolved.value;
|
|
264
|
-
}
|
|
265
|
-
yield* settlePendingReplies(params.pendingReplies, params.id);
|
|
266
|
-
const freshQueue = yield* Queue.unbounded();
|
|
267
|
-
yield* Ref.set(params.eventQueueRef, freshQueue);
|
|
268
|
-
yield* SubscriptionRef.set(params.stateRef, restartState);
|
|
269
|
-
yield* Ref.set(params.stoppedRef, false);
|
|
270
|
-
params.childrenMap.clear();
|
|
271
|
-
const machineForRestart = restartState !== params.machine.initial ? Object.create(params.machine, { initial: {
|
|
272
|
-
value: restartState,
|
|
273
|
-
enumerable: true
|
|
274
|
-
} }) : params.machine;
|
|
275
|
-
const newRuntime = yield* params.spawnGeneration(machineForRestart);
|
|
276
|
-
params.runtimeRef.current = newRuntime;
|
|
277
|
-
yield* newRuntime.start;
|
|
278
|
-
generation++;
|
|
279
|
-
if (params.onRestart !== void 0) yield* params.onRestart(generation, generationExit);
|
|
280
|
-
notifyListeners(params.listeners, restartState);
|
|
281
|
-
}
|
|
282
|
-
});
|
|
283
|
-
/**
|
|
284
|
-
* Create and start an actor for a machine.
|
|
285
|
-
* Delegates to the shared runtime kernel with actor-specific lifecycle hooks.
|
|
286
|
-
*/
|
|
287
|
-
const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machine, options) {
|
|
288
|
-
const lifecycle = options?.lifecycle;
|
|
289
|
-
const initial = options?.initialState ?? machine.initial;
|
|
290
|
-
yield* Effect.annotateCurrentSpan("effect_machine.actor.id", id);
|
|
291
|
-
yield* Effect.annotateCurrentSpan("effect_machine.actor.initial_state", initial._tag);
|
|
292
|
-
const { system, implicitSystemScope } = yield* resolveActorSystem();
|
|
293
|
-
const inspectorValue = Option.getOrUndefined(yield* Effect.serviceOption(Inspector));
|
|
294
|
-
const childrenMap = /* @__PURE__ */ new Map();
|
|
295
|
-
const pendingReplies = /* @__PURE__ */ new Set();
|
|
296
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
297
|
-
const transitionsPubSub = yield* PubSub.unbounded();
|
|
298
|
-
const hooks = inspectorValue !== void 0 ? buildInspectionHooks(id, inspectorValue) : void 0;
|
|
299
|
-
const machineWithState = initial !== machine.initial ? Object.create(machine, { initial: {
|
|
300
|
-
value: initial,
|
|
301
|
-
enumerable: true
|
|
302
|
-
} }) : machine;
|
|
303
|
-
const stateRef = yield* SubscriptionRef.make(initial);
|
|
304
|
-
const stoppedRef = yield* Ref.make(false);
|
|
305
|
-
const initialQueue = yield* Queue.unbounded();
|
|
306
|
-
const eventQueueRef = yield* Ref.make(initialQueue);
|
|
307
|
-
const terminalExitDeferred = yield* Deferred.make();
|
|
308
|
-
let stopEmitted = false;
|
|
309
|
-
let generation = 0;
|
|
310
|
-
const runtimeRef = { current: void 0 };
|
|
311
|
-
/** Build lifecycle hooks for a generation */
|
|
312
|
-
const buildLifecycle = () => {
|
|
313
|
-
stopEmitted = false;
|
|
314
|
-
return {
|
|
315
|
-
onEvent: inspectorValue !== void 0 ? (state, event) => emitWithTimestamp(inspectorValue, (timestamp) => ({
|
|
316
|
-
type: "@machine.event",
|
|
317
|
-
actorId: id,
|
|
318
|
-
state,
|
|
319
|
-
event,
|
|
320
|
-
timestamp
|
|
321
|
-
})) : void 0,
|
|
322
|
-
onStateChange: (result, event) => Effect.gen(function* () {
|
|
323
|
-
notifyListeners(listeners, result.newState);
|
|
324
|
-
if (lifecycle?.durability !== void 0 && result.transitioned) {
|
|
325
|
-
const durability = lifecycle.durability;
|
|
326
|
-
if (durability.shouldSave === void 0 || durability.shouldSave(result.newState, result.previousState)) yield* durability.save({
|
|
327
|
-
actorId: id,
|
|
328
|
-
generation,
|
|
329
|
-
previousState: result.previousState,
|
|
330
|
-
nextState: result.newState,
|
|
331
|
-
event
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
|
-
yield* Effect.annotateCurrentSpan("effect_machine.transition.matched", true);
|
|
335
|
-
if (result.lifecycleRan) {
|
|
336
|
-
yield* Effect.annotateCurrentSpan("effect_machine.state.from", result.previousState._tag);
|
|
337
|
-
yield* Effect.annotateCurrentSpan("effect_machine.state.to", result.newState._tag);
|
|
338
|
-
}
|
|
339
|
-
}),
|
|
340
|
-
onProcessed: (result, event) => result.transitioned ? PubSub.publish(transitionsPubSub, {
|
|
341
|
-
fromState: result.previousState,
|
|
342
|
-
toState: result.newState,
|
|
343
|
-
event
|
|
344
|
-
}).pipe(Effect.asVoid) : Effect.void,
|
|
345
|
-
onFinal: inspectorValue !== void 0 ? (state) => Effect.gen(function* () {
|
|
346
|
-
stopEmitted = true;
|
|
347
|
-
yield* emitWithTimestamp(inspectorValue, (timestamp) => ({
|
|
348
|
-
type: "@machine.stop",
|
|
349
|
-
actorId: id,
|
|
350
|
-
finalState: state,
|
|
351
|
-
timestamp
|
|
352
|
-
}));
|
|
353
|
-
}) : void 0,
|
|
354
|
-
onShutdown: () => Effect.gen(function* () {
|
|
355
|
-
if (!stopEmitted) {
|
|
356
|
-
const finalState = yield* SubscriptionRef.get(stateRef);
|
|
357
|
-
yield* emitWithTimestamp(inspectorValue, (timestamp) => ({
|
|
358
|
-
type: "@machine.stop",
|
|
359
|
-
actorId: id,
|
|
360
|
-
finalState,
|
|
361
|
-
timestamp
|
|
362
|
-
}));
|
|
363
|
-
}
|
|
364
|
-
yield* settlePendingReplies(pendingReplies, id);
|
|
365
|
-
}),
|
|
366
|
-
onInitialSpawnEffects: inspectorValue !== void 0 ? (state) => emitWithTimestamp(inspectorValue, (timestamp) => ({
|
|
367
|
-
type: "@machine.effect",
|
|
368
|
-
actorId: id,
|
|
369
|
-
effectType: "spawn",
|
|
370
|
-
state,
|
|
371
|
-
timestamp
|
|
372
|
-
})) : void 0
|
|
373
|
-
};
|
|
374
|
-
};
|
|
375
|
-
/** Create a single runtime generation. machineForGen is machineWithState for initial, machine for restarts. */
|
|
376
|
-
const spawnGeneration = (machineForGen) => Ref.get(eventQueueRef).pipe(Effect.flatMap((currentQueue) => createRuntime(machineForGen, system, {
|
|
377
|
-
actorId: id,
|
|
378
|
-
hooks,
|
|
379
|
-
skipFinalizer: true,
|
|
380
|
-
cellResources: {
|
|
381
|
-
stateRef,
|
|
382
|
-
stoppedRef,
|
|
383
|
-
eventQueue: currentQueue
|
|
384
|
-
},
|
|
385
|
-
lifecycle: buildLifecycle(),
|
|
386
|
-
wrapProcess: (state, event, inner) => Effect.withSpan("effect-machine.event.process", { attributes: {
|
|
387
|
-
"effect_machine.actor.id": id,
|
|
388
|
-
"effect_machine.state.current": state._tag,
|
|
389
|
-
"effect_machine.event.type": event._tag
|
|
390
|
-
} })(inner.pipe(Effect.tap((r) => Effect.annotateCurrentSpan("effect_machine.transition.matched", r.result.transitioned)))),
|
|
391
|
-
onChildSpawned: (childId, child) => Effect.gen(function* () {
|
|
392
|
-
childrenMap.set(childId, child);
|
|
393
|
-
const maybeScope = yield* Effect.serviceOption(Scope.Scope);
|
|
394
|
-
if (Option.isSome(maybeScope)) yield* Scope.addFinalizer(maybeScope.value, Effect.sync(() => {
|
|
395
|
-
childrenMap.delete(childId);
|
|
396
|
-
}));
|
|
397
|
-
})
|
|
398
|
-
})));
|
|
399
|
-
runtimeRef.current = yield* spawnGeneration(machineWithState);
|
|
400
|
-
const supervision = options?.supervision;
|
|
401
|
-
const supervisorFiberRef = { current: void 0 };
|
|
402
|
-
const stop = Effect.gen(function* () {
|
|
403
|
-
if (supervisorFiberRef.current !== void 0) yield* Fiber.interrupt(supervisorFiberRef.current);
|
|
404
|
-
const currentRuntime = runtimeRef.current;
|
|
405
|
-
if (currentRuntime !== void 0) yield* currentRuntime.stop;
|
|
406
|
-
yield* Deferred.succeed(terminalExitDeferred, { _tag: "Stopped" });
|
|
407
|
-
if (implicitSystemScope !== void 0) yield* Scope.close(implicitSystemScope, Exit.void);
|
|
408
|
-
}).pipe(Effect.withSpan("effect-machine.actor.stop"), Effect.asVoid);
|
|
409
|
-
const isHydrated = options?.initialState !== void 0;
|
|
410
|
-
return buildActorRefCore(id, machine, stateRef, eventQueueRef, stoppedRef, listeners, stop, Effect.gen(function* () {
|
|
411
|
-
if (lifecycle?.recovery !== void 0 && !isHydrated) {
|
|
412
|
-
const resolved = yield* lifecycle.recovery.resolve({
|
|
413
|
-
actorId: id,
|
|
414
|
-
generation,
|
|
415
|
-
machineInitial: machine.initial
|
|
416
|
-
});
|
|
417
|
-
if (Option.isSome(resolved)) {
|
|
418
|
-
yield* SubscriptionRef.set(stateRef, resolved.value);
|
|
419
|
-
runtimeRef.current = yield* spawnGeneration(Object.create(machine, { initial: {
|
|
420
|
-
value: resolved.value,
|
|
421
|
-
enumerable: true
|
|
422
|
-
} }));
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
const currentState = yield* SubscriptionRef.get(stateRef);
|
|
426
|
-
yield* emitWithTimestamp(inspectorValue, (timestamp) => ({
|
|
427
|
-
type: "@machine.spawn",
|
|
428
|
-
actorId: id,
|
|
429
|
-
initialState: currentState,
|
|
430
|
-
timestamp
|
|
431
|
-
}));
|
|
432
|
-
if (supervision !== void 0) supervisorFiberRef.current = yield* Effect.forkDaemon(runSupervisionLoop({
|
|
433
|
-
supervision,
|
|
434
|
-
machine,
|
|
435
|
-
id,
|
|
436
|
-
runtimeRef,
|
|
437
|
-
terminalExitDeferred,
|
|
438
|
-
pendingReplies,
|
|
439
|
-
eventQueueRef,
|
|
440
|
-
stateRef,
|
|
441
|
-
stoppedRef,
|
|
442
|
-
childrenMap,
|
|
443
|
-
listeners,
|
|
444
|
-
spawnGeneration,
|
|
445
|
-
lifecycle,
|
|
446
|
-
onRestart: options?.onRestart
|
|
447
|
-
}));
|
|
448
|
-
else {
|
|
449
|
-
const currentRuntime = runtimeRef.current;
|
|
450
|
-
if (currentRuntime !== void 0) yield* Effect.forkDaemon(Deferred.await(currentRuntime.exitDeferred).pipe(Effect.tap((exit) => Deferred.succeed(terminalExitDeferred, exit))));
|
|
451
|
-
}
|
|
452
|
-
const currentRuntime = runtimeRef.current;
|
|
453
|
-
if (currentRuntime !== void 0) yield* currentRuntime.start;
|
|
454
|
-
}).pipe(Effect.withSpan("effect-machine.actor.start"), Effect.asVoid), system, childrenMap, pendingReplies, transitionsPubSub, terminalExitDeferred);
|
|
455
|
-
});
|
|
456
|
-
/** Fail all pending call/ask Deferreds with ActorStoppedError. Safe to call multiple times. */
|
|
457
|
-
const settlePendingReplies = (pendingReplies, actorId) => Effect.sync(() => {
|
|
458
|
-
const error = new ActorStoppedError({ actorId });
|
|
459
|
-
for (const deferred of pendingReplies) Effect.runFork(Deferred.fail(deferred, error));
|
|
460
|
-
pendingReplies.clear();
|
|
461
|
-
});
|
|
462
|
-
/** Notify all system event listeners (sync). */
|
|
463
|
-
const notifySystemListeners = (listeners, event) => {
|
|
464
|
-
for (const listener of listeners) try {
|
|
465
|
-
listener(event);
|
|
466
|
-
} catch {}
|
|
467
|
-
};
|
|
468
|
-
const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
|
|
469
|
-
const actorsMap = MutableHashMap.empty();
|
|
470
|
-
const withSpawnGate = (yield* Effect.makeSemaphore(1)).withPermits(1);
|
|
471
|
-
const eventPubSub = yield* PubSub.unbounded();
|
|
472
|
-
const eventListeners = /* @__PURE__ */ new Set();
|
|
473
|
-
const emitSystemEvent = (event) => Effect.sync(() => notifySystemListeners(eventListeners, event)).pipe(Effect.andThen(PubSub.publish(eventPubSub, event)), Effect.catchAllCause(() => Effect.void), Effect.asVoid);
|
|
474
|
-
yield* Effect.addFinalizer(() => {
|
|
475
|
-
const stops = [];
|
|
476
|
-
MutableHashMap.forEach(actorsMap, (actor) => {
|
|
477
|
-
stops.push(actor.stop);
|
|
478
|
-
});
|
|
479
|
-
return Effect.all(stops, { concurrency: "unbounded" }).pipe(Effect.andThen(PubSub.shutdown(eventPubSub)), Effect.asVoid);
|
|
480
|
-
});
|
|
481
|
-
/** Check for duplicate ID, register actor, attach scope cleanup if available */
|
|
482
|
-
const registerActor = Effect.fn("effect-machine.actorSystem.register")(function* (id, actor) {
|
|
483
|
-
if (MutableHashMap.has(actorsMap, id)) {
|
|
484
|
-
yield* actor.stop;
|
|
485
|
-
return yield* new DuplicateActorError({ actorId: id });
|
|
486
|
-
}
|
|
487
|
-
const actorRef = actor;
|
|
488
|
-
MutableHashMap.set(actorsMap, id, actorRef);
|
|
489
|
-
yield* emitSystemEvent({
|
|
490
|
-
_tag: "ActorSpawned",
|
|
491
|
-
id,
|
|
492
|
-
actor: actorRef
|
|
493
|
-
});
|
|
494
|
-
const maybeScope = yield* Effect.serviceOption(ActorScope);
|
|
495
|
-
if (Option.isSome(maybeScope)) yield* Scope.addFinalizer(maybeScope.value, Effect.gen(function* () {
|
|
496
|
-
if (MutableHashMap.has(actorsMap, id)) {
|
|
497
|
-
yield* emitSystemEvent({
|
|
498
|
-
_tag: "ActorStopped",
|
|
499
|
-
id,
|
|
500
|
-
actor: actorRef,
|
|
501
|
-
exit: { _tag: "Stopped" }
|
|
502
|
-
});
|
|
503
|
-
MutableHashMap.remove(actorsMap, id);
|
|
504
|
-
}
|
|
505
|
-
yield* actor.stop;
|
|
506
|
-
}));
|
|
507
|
-
return actor;
|
|
508
|
-
});
|
|
509
|
-
const spawnRegular = Effect.fn("effect-machine.actorSystem.spawnRegular")(function* (id, machine, spawnOptions) {
|
|
510
|
-
if (MutableHashMap.has(actorsMap, id)) return yield* new DuplicateActorError({ actorId: id });
|
|
511
|
-
const materialized = spawnOptions?.slots !== void 0 ? materializeMachine(machine, spawnOptions.slots) : machine;
|
|
512
|
-
let actorRef;
|
|
513
|
-
const actor = yield* createActor(id, materialized, {
|
|
514
|
-
supervision: spawnOptions?.supervision,
|
|
515
|
-
lifecycle: spawnOptions?.lifecycle,
|
|
516
|
-
onRestart: spawnOptions?.supervision !== void 0 ? (generation, exit) => actorRef !== void 0 ? emitSystemEvent({
|
|
517
|
-
_tag: "ActorRestarted",
|
|
518
|
-
id,
|
|
519
|
-
actor: actorRef,
|
|
520
|
-
generation,
|
|
521
|
-
exit
|
|
522
|
-
}) : Effect.void : void 0
|
|
523
|
-
});
|
|
524
|
-
actorRef = actor;
|
|
525
|
-
yield* registerActor(id, actor);
|
|
526
|
-
yield* actor.start.pipe(Effect.catchAllCause((cause) => actor.stop.pipe(Effect.andThen(Effect.failCause(cause)))));
|
|
527
|
-
return actor;
|
|
528
|
-
});
|
|
529
|
-
const spawn = (id, machine, options) => withSpawnGate(spawnRegular(id, machine, options));
|
|
530
|
-
const get = Effect.fn("effect-machine.actorSystem.get")(function* (id) {
|
|
531
|
-
return yield* Effect.sync(() => MutableHashMap.get(actorsMap, id));
|
|
532
|
-
});
|
|
533
|
-
const stop = Effect.fn("effect-machine.actorSystem.stop")(function* (id) {
|
|
534
|
-
const maybeActor = MutableHashMap.get(actorsMap, id);
|
|
535
|
-
if (Option.isNone(maybeActor)) return false;
|
|
536
|
-
const actor = maybeActor.value;
|
|
537
|
-
MutableHashMap.remove(actorsMap, id);
|
|
538
|
-
yield* emitSystemEvent({
|
|
539
|
-
_tag: "ActorStopped",
|
|
540
|
-
id,
|
|
541
|
-
actor,
|
|
542
|
-
exit: { _tag: "Stopped" }
|
|
543
|
-
});
|
|
544
|
-
yield* actor.stop;
|
|
545
|
-
return true;
|
|
546
|
-
});
|
|
547
|
-
return ActorSystem.of({
|
|
548
|
-
spawn,
|
|
549
|
-
get,
|
|
550
|
-
stop,
|
|
551
|
-
events: Stream.fromPubSub(eventPubSub),
|
|
552
|
-
get actors() {
|
|
553
|
-
const snapshot = /* @__PURE__ */ new Map();
|
|
554
|
-
MutableHashMap.forEach(actorsMap, (actor, id) => {
|
|
555
|
-
snapshot.set(id, actor);
|
|
556
|
-
});
|
|
557
|
-
return snapshot;
|
|
558
|
-
},
|
|
559
|
-
subscribe: (fn) => {
|
|
560
|
-
eventListeners.add(fn);
|
|
561
|
-
return () => {
|
|
562
|
-
eventListeners.delete(fn);
|
|
563
|
-
};
|
|
564
|
-
}
|
|
565
|
-
});
|
|
566
|
-
});
|
|
567
|
-
/**
|
|
568
|
-
* Create an ActorSystem instance. Must be run in a Scope.
|
|
569
|
-
* @internal — use Default layer for normal usage
|
|
570
|
-
*/
|
|
571
|
-
const makeSystem = make;
|
|
572
|
-
/**
|
|
573
|
-
* Default ActorSystem layer
|
|
574
|
-
*/
|
|
575
|
-
const Default = Layer.scoped(ActorSystem, make());
|
|
576
|
-
//#endregion
|
|
577
|
-
export { ActorScope, ActorSystem, Default, buildActorRefCore, createActor, makeSystem, notifyListeners, processEventCore, resolveTransition, runSpawnEffects, settlePendingReplies };
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { PersistedEvent, PersistenceAdapter, Snapshot } from "../persistence.js";
|
|
2
|
-
import { Effect, Layer, Ref } from "effect";
|
|
3
|
-
|
|
4
|
-
//#region src/cluster/adapters/in-memory.d.ts
|
|
5
|
-
interface EntityStore {
|
|
6
|
-
snapshot: Snapshot<unknown> | undefined;
|
|
7
|
-
events: Array<PersistedEvent<unknown>>;
|
|
8
|
-
}
|
|
9
|
-
declare const makeInMemoryPersistenceAdapter: Effect.Effect<{
|
|
10
|
-
adapter: PersistenceAdapter;
|
|
11
|
-
storeRef: Ref.Ref<Map<string, EntityStore>>;
|
|
12
|
-
layer: Layer.Layer<PersistenceAdapter, never, never>;
|
|
13
|
-
}, never, never>;
|
|
14
|
-
//#endregion
|
|
15
|
-
export { makeInMemoryPersistenceAdapter };
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { VersionConflictError } from "../../errors.js";
|
|
2
|
-
import { PersistenceAdapter } from "../persistence.js";
|
|
3
|
-
import { Effect, Layer, Option, Ref } from "effect";
|
|
4
|
-
//#region src/cluster/adapters/in-memory.ts
|
|
5
|
-
/**
|
|
6
|
-
* In-memory persistence adapter for testing and development (v3).
|
|
7
|
-
*
|
|
8
|
-
* @module
|
|
9
|
-
*/
|
|
10
|
-
const makeKey = (key) => `${key.entityType}/${key.entityId}`;
|
|
11
|
-
const getOrCreate = (store, key) => {
|
|
12
|
-
let entry = store.get(key);
|
|
13
|
-
if (entry === void 0) {
|
|
14
|
-
entry = {
|
|
15
|
-
snapshot: void 0,
|
|
16
|
-
events: []
|
|
17
|
-
};
|
|
18
|
-
store.set(key, entry);
|
|
19
|
-
}
|
|
20
|
-
return entry;
|
|
21
|
-
};
|
|
22
|
-
const makeInMemoryPersistenceAdapter = Effect.gen(function* () {
|
|
23
|
-
const store = /* @__PURE__ */ new Map();
|
|
24
|
-
const storeRef = yield* Ref.make(store);
|
|
25
|
-
const adapter = {
|
|
26
|
-
saveSnapshot: (key, snapshot) => Effect.gen(function* () {
|
|
27
|
-
const entry = getOrCreate(yield* Ref.get(storeRef), makeKey(key));
|
|
28
|
-
if (entry.snapshot !== void 0 && snapshot.version < entry.snapshot.version) return yield* new VersionConflictError({
|
|
29
|
-
expected: snapshot.version,
|
|
30
|
-
actual: entry.snapshot.version
|
|
31
|
-
});
|
|
32
|
-
entry.snapshot = snapshot;
|
|
33
|
-
}),
|
|
34
|
-
loadSnapshot: (key) => Effect.gen(function* () {
|
|
35
|
-
const entry = (yield* Ref.get(storeRef)).get(makeKey(key));
|
|
36
|
-
return Option.fromNullable(entry?.snapshot);
|
|
37
|
-
}),
|
|
38
|
-
appendEvents: (key, events, expectedVersion) => Effect.gen(function* () {
|
|
39
|
-
const entry = getOrCreate(yield* Ref.get(storeRef), makeKey(key));
|
|
40
|
-
const lastEvent = entry.events[entry.events.length - 1];
|
|
41
|
-
const currentVersion = lastEvent !== void 0 ? lastEvent.version : 0;
|
|
42
|
-
if (currentVersion !== expectedVersion) return yield* new VersionConflictError({
|
|
43
|
-
expected: expectedVersion,
|
|
44
|
-
actual: currentVersion
|
|
45
|
-
});
|
|
46
|
-
for (const event of events) entry.events.push(event);
|
|
47
|
-
}),
|
|
48
|
-
loadEvents: (key, afterVersion) => Effect.gen(function* () {
|
|
49
|
-
const entry = (yield* Ref.get(storeRef)).get(makeKey(key));
|
|
50
|
-
if (entry === void 0) return [];
|
|
51
|
-
if (afterVersion === void 0) return entry.events;
|
|
52
|
-
return entry.events.filter((e) => e.version > afterVersion);
|
|
53
|
-
})
|
|
54
|
-
};
|
|
55
|
-
return {
|
|
56
|
-
adapter,
|
|
57
|
-
storeRef,
|
|
58
|
-
layer: Layer.succeed(PersistenceAdapter, adapter)
|
|
59
|
-
};
|
|
60
|
-
});
|
|
61
|
-
//#endregion
|
|
62
|
-
export { makeInMemoryPersistenceAdapter };
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { ExtractReply, ReplyTypeBrand } from "../internal/brands.js";
|
|
2
|
-
import { NoReplyError } from "../errors.js";
|
|
3
|
-
import { Effect } from "effect";
|
|
4
|
-
import { RpcClient } from "effect/unstable/rpc";
|
|
5
|
-
|
|
6
|
-
//#region src/cluster/entity-actor-ref.d.ts
|
|
7
|
-
/**
|
|
8
|
-
* Typed client wrapper for remote entity machines.
|
|
9
|
-
*
|
|
10
|
-
* Unlike local `ActorRef`, this communicates over cluster RPCs.
|
|
11
|
-
* Only operations that make sense over the network are exposed.
|
|
12
|
-
*
|
|
13
|
-
* @example
|
|
14
|
-
* ```ts
|
|
15
|
-
* const ref = yield* EntityActorRef.make(OrderEntity, OrderEntityLayer, "order-123")
|
|
16
|
-
* yield* ref.send(OrderEvent.Ship({ trackingId: "abc" }))
|
|
17
|
-
* const state = yield* ref.snapshot
|
|
18
|
-
* ```
|
|
19
|
-
*/
|
|
20
|
-
interface EntityActorRef<State extends {
|
|
21
|
-
readonly _tag: string;
|
|
22
|
-
}, Event extends {
|
|
23
|
-
readonly _tag: string;
|
|
24
|
-
}> {
|
|
25
|
-
readonly entityId: string;
|
|
26
|
-
/** Send event (fire-and-forget). Returns new state after processing. */
|
|
27
|
-
readonly send: (event: Event) => Effect.Effect<State>;
|
|
28
|
-
/** Send event and get typed domain reply (via Event.reply() schema). */
|
|
29
|
-
readonly ask: <E extends Event & ReplyTypeBrand<unknown>>(event: E) => Effect.Effect<ExtractReply<E>, NoReplyError>;
|
|
30
|
-
/** Get current state. */
|
|
31
|
-
readonly snapshot: Effect.Effect<State>;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Create an EntityActorRef from a test client factory and entity ID.
|
|
35
|
-
*
|
|
36
|
-
* @example
|
|
37
|
-
* ```ts
|
|
38
|
-
* const makeClient = yield* Entity.makeTestClient(entity, entityLayer)
|
|
39
|
-
* const ref = yield* makeEntityActorRef(makeClient, "order-123")
|
|
40
|
-
* yield* ref.send(OrderEvent.Process)
|
|
41
|
-
* ```
|
|
42
|
-
*/
|
|
43
|
-
declare const makeEntityActorRef: <State extends {
|
|
44
|
-
readonly _tag: string;
|
|
45
|
-
}, Event extends {
|
|
46
|
-
readonly _tag: string;
|
|
47
|
-
}>(client: RpcClient.RpcClient<any>, entityId: string) => EntityActorRef<State, Event>;
|
|
48
|
-
//#endregion
|
|
49
|
-
export { EntityActorRef, makeEntityActorRef };
|