effect-machine 0.19.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +171 -173
- package/dist/actor.d.ts +95 -24
- package/dist/actor.js +215 -84
- package/dist/atom.d.ts +56 -3
- package/dist/atom.js +33 -2
- package/dist/cluster/entity-machine.d.ts +9 -3
- package/dist/cluster/entity-machine.js +8 -8
- package/dist/cluster/index.d.ts +2 -2
- package/dist/cluster/to-entity.d.ts +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +2 -2
- package/dist/inspection.d.ts +31 -3
- package/dist/inspection.js +21 -0
- package/dist/internal/inspection.d.ts +1 -1
- package/dist/internal/inspection.js +23 -1
- package/dist/internal/machine-definition.d.ts +1 -0
- package/dist/internal/machine-initialization.d.ts +21 -0
- package/dist/internal/machine-initialization.js +27 -0
- package/dist/internal/runtime.d.ts +15 -1
- package/dist/internal/runtime.js +67 -26
- package/dist/internal/transition.d.ts +51 -3
- package/dist/internal/transition.js +216 -38
- package/dist/internal/utils.js +1 -0
- package/dist/machine.d.ts +151 -40
- package/dist/machine.js +139 -40
- package/dist/supervision.d.ts +3 -2
- package/dist/supervision.js +3 -2
- package/dist/testing.d.ts +39 -67
- package/dist/testing.js +13 -52
- package/package.json +3 -2
package/dist/actor.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { ActorStoppedError, DuplicateActorError } from "./errors.js";
|
|
2
|
-
import { resolveTransition } from "./internal/transition.js";
|
|
2
|
+
import { resolveTransition, resolveTransitionEffect } from "./internal/transition.js";
|
|
3
3
|
import { emitWithTimestamp, makeInspectionHooks } from "./internal/inspection.js";
|
|
4
4
|
import { Inspector } from "./inspection.js";
|
|
5
|
+
import { ActorExit } from "./supervision.js";
|
|
5
6
|
import { createRuntime } from "./internal/runtime.js";
|
|
6
|
-
import { Context, Deferred, Effect, Exit, Fiber, Layer, MutableHashMap, Option, PubSub, Queue, Ref, Schedule, Scope, Semaphore, Stream, SubscriptionRef } from "effect";
|
|
7
|
+
import { Cause, Context, Deferred, Effect, Exit, Fiber, Layer, MutableHashMap, Option, PubSub, Queue, Ref, Schedule, Scope, Semaphore, Stream, SubscriptionRef } from "effect";
|
|
7
8
|
//#region src/actor.ts
|
|
8
9
|
/**
|
|
9
10
|
* Actor system: spawning, lifecycle, and event processing.
|
|
@@ -13,6 +14,19 @@ import { Context, Deferred, Effect, Exit, Fiber, Layer, MutableHashMap, Option,
|
|
|
13
14
|
* - ActorSystem service (spawn/stop/get actors)
|
|
14
15
|
* - Actor creation and event loop
|
|
15
16
|
*/
|
|
17
|
+
/** A typed identity for an actor stored in an ActorSystem. */
|
|
18
|
+
var ActorSystemKey = class {
|
|
19
|
+
id;
|
|
20
|
+
constructor(id) {
|
|
21
|
+
this.id = id;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
/** Create a typed ActorSystem identity. */
|
|
25
|
+
const actorSystemKey = (id) => new ActorSystemKey(id);
|
|
26
|
+
const actorSystemId = (id) => {
|
|
27
|
+
if (typeof id === "string") return id;
|
|
28
|
+
return id.id;
|
|
29
|
+
};
|
|
16
30
|
/**
|
|
17
31
|
* ActorSystem service tag
|
|
18
32
|
*/
|
|
@@ -35,11 +49,20 @@ const notifyListeners = (listeners, state) => {
|
|
|
35
49
|
listener(state);
|
|
36
50
|
} catch {}
|
|
37
51
|
};
|
|
52
|
+
const toActorExit = (machine, exit) => {
|
|
53
|
+
if (exit._tag === "Final") return ActorExit.Final(exit.state, machine._output(exit.state));
|
|
54
|
+
if (exit._tag === "Defect") return {
|
|
55
|
+
_tag: "Defect",
|
|
56
|
+
cause: exit.cause,
|
|
57
|
+
phase: exit.phase
|
|
58
|
+
};
|
|
59
|
+
return { _tag: "Stopped" };
|
|
60
|
+
};
|
|
38
61
|
/**
|
|
39
62
|
* Build core ActorRef methods.
|
|
40
63
|
*/
|
|
41
|
-
const buildActorRefCore = (cell, stop, start) => {
|
|
42
|
-
const { id, machine, stateRef, runtimeRef, listeners, system } = cell;
|
|
64
|
+
const buildActorRefCore = (cell, stop, start, serviceContext) => {
|
|
65
|
+
const { id, machine, stateRef, runtimeRef, listeners, system, lifecycleRef, latestTransitionRef } = cell;
|
|
43
66
|
const send = (event) => Effect.gen(function* () {
|
|
44
67
|
const runtime = runtimeRef.current;
|
|
45
68
|
if (runtime !== void 0) yield* runtime.send(event);
|
|
@@ -59,7 +82,8 @@ const buildActorRefCore = (cell, stop, start) => {
|
|
|
59
82
|
reply: void 0,
|
|
60
83
|
postponed: false,
|
|
61
84
|
lifecycleRan: false,
|
|
62
|
-
isFinal: machine._isFinal(currentState._tag)
|
|
85
|
+
isFinal: machine._isFinal(currentState._tag),
|
|
86
|
+
transitions: []
|
|
63
87
|
};
|
|
64
88
|
});
|
|
65
89
|
const call = (event) => Effect.suspend(() => {
|
|
@@ -76,10 +100,11 @@ const buildActorRefCore = (cell, stop, start) => {
|
|
|
76
100
|
const matches = Effect.fn("effect-machine.actor.matches")(function* (tag) {
|
|
77
101
|
return (yield* SubscriptionRef.get(stateRef))._tag === tag;
|
|
78
102
|
});
|
|
79
|
-
const
|
|
103
|
+
const canEffect = Effect.fn("effect-machine.actor.can")(function* (event) {
|
|
80
104
|
const state = yield* SubscriptionRef.get(stateRef);
|
|
81
|
-
return
|
|
105
|
+
return (yield* resolveTransitionEffect(machine, state, event)) !== void 0;
|
|
82
106
|
});
|
|
107
|
+
const can = (event) => canEffect(event).pipe(Effect.provide(serviceContext));
|
|
83
108
|
const waitFor = Effect.fn("effect-machine.actor.waitFor")(function* (predicateOrState) {
|
|
84
109
|
let predicate;
|
|
85
110
|
if (typeof predicateOrState === "function" && !("_tag" in predicateOrState)) predicate = predicateOrState;
|
|
@@ -101,18 +126,53 @@ const buildActorRefCore = (cell, stop, start) => {
|
|
|
101
126
|
return result;
|
|
102
127
|
});
|
|
103
128
|
const awaitFinal = waitFor((state) => machine._isFinal(state._tag)).pipe(Effect.withSpan("effect-machine.actor.awaitFinal"));
|
|
129
|
+
const awaitOutput = Deferred.await(cell.terminalExitDeferred).pipe(Effect.flatMap((exit) => {
|
|
130
|
+
if (exit._tag === "Final") return Effect.succeed(exit.output);
|
|
131
|
+
if (exit._tag === "Defect") return Effect.die(Cause.squash(exit.cause));
|
|
132
|
+
return ActorStoppedError.make({ actorId: id });
|
|
133
|
+
}), Effect.withSpan("effect-machine.actor.awaitOutput"));
|
|
104
134
|
const sendAndWait = Effect.fn("effect-machine.actor.sendAndWait")(function* (event, predicateOrState) {
|
|
105
135
|
yield* send(event);
|
|
106
136
|
if (predicateOrState !== void 0) return yield* waitFor(predicateOrState);
|
|
107
137
|
return yield* awaitFinal;
|
|
108
138
|
});
|
|
109
139
|
const transitions = Stream.fromPubSub(cell.transitions);
|
|
140
|
+
const subscribe = (listener) => {
|
|
141
|
+
listeners.add(listener);
|
|
142
|
+
return () => {
|
|
143
|
+
listeners.delete(listener);
|
|
144
|
+
};
|
|
145
|
+
};
|
|
146
|
+
const sendClient = (event) => {
|
|
147
|
+
runtimeRef.current?.sendSync(event);
|
|
148
|
+
};
|
|
149
|
+
const stopClient = () => {
|
|
150
|
+
Effect.runFork(stop);
|
|
151
|
+
};
|
|
152
|
+
const getSnapshot = () => Effect.runSync(SubscriptionRef.get(stateRef));
|
|
153
|
+
const matchesClient = (tag) => getSnapshot()._tag === tag;
|
|
154
|
+
const canSync = (event) => resolveTransition(machine, getSnapshot(), event) !== void 0;
|
|
155
|
+
const getLifecycle = () => Effect.runSync(SubscriptionRef.get(lifecycleRef));
|
|
156
|
+
const getLatestTransition = () => Effect.runSync(SubscriptionRef.get(latestTransitionRef));
|
|
157
|
+
const client = {
|
|
158
|
+
send: sendClient,
|
|
159
|
+
stop: stopClient,
|
|
160
|
+
getSnapshot,
|
|
161
|
+
matches: matchesClient,
|
|
162
|
+
canSync,
|
|
163
|
+
can: (event) => Effect.runPromise(can(event)),
|
|
164
|
+
getLifecycle,
|
|
165
|
+
getLatestTransition,
|
|
166
|
+
subscribe
|
|
167
|
+
};
|
|
110
168
|
return {
|
|
111
169
|
id,
|
|
112
170
|
send,
|
|
113
171
|
call,
|
|
114
172
|
ask,
|
|
115
173
|
state: stateRef,
|
|
174
|
+
lifecycle: lifecycleRef,
|
|
175
|
+
latestTransition: latestTransitionRef,
|
|
116
176
|
stop,
|
|
117
177
|
start,
|
|
118
178
|
snapshot,
|
|
@@ -122,26 +182,20 @@ const buildActorRefCore = (cell, stop, start) => {
|
|
|
122
182
|
transitions,
|
|
123
183
|
waitFor,
|
|
124
184
|
awaitFinal,
|
|
185
|
+
awaitOutput,
|
|
125
186
|
sendAndWait,
|
|
126
|
-
subscribe
|
|
127
|
-
|
|
128
|
-
return () => {
|
|
129
|
-
listeners.delete(fn);
|
|
130
|
-
};
|
|
131
|
-
},
|
|
187
|
+
subscribe,
|
|
188
|
+
client,
|
|
132
189
|
awaitExit: Deferred.await(cell.terminalExitDeferred),
|
|
133
190
|
drain: Effect.suspend(() => runtimeRef.current?.drain ?? Effect.void),
|
|
134
191
|
sync: {
|
|
135
|
-
send:
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const state = Effect.runSync(SubscriptionRef.get(stateRef));
|
|
143
|
-
return resolveTransition(machine, state, event) !== void 0;
|
|
144
|
-
}
|
|
192
|
+
send: sendClient,
|
|
193
|
+
stop: stopClient,
|
|
194
|
+
snapshot: getSnapshot,
|
|
195
|
+
matches: matchesClient,
|
|
196
|
+
can: canSync,
|
|
197
|
+
lifecycle: getLifecycle,
|
|
198
|
+
latestTransition: getLatestTransition
|
|
145
199
|
},
|
|
146
200
|
system,
|
|
147
201
|
children: cell.children
|
|
@@ -175,7 +229,7 @@ const runSupervisionLoop = (cell, options) => Effect.gen(function* () {
|
|
|
175
229
|
if (currentRuntime === void 0) return;
|
|
176
230
|
const generationExit = yield* Deferred.await(currentRuntime.exitDeferred);
|
|
177
231
|
if (generationExit._tag !== "Defect") {
|
|
178
|
-
yield* Deferred.succeed(cell.terminalExitDeferred, generationExit);
|
|
232
|
+
yield* Deferred.succeed(cell.terminalExitDeferred, toActorExit(cell.machine, generationExit));
|
|
179
233
|
return;
|
|
180
234
|
}
|
|
181
235
|
if (options.supervision.shouldRestart !== void 0 && !options.supervision.shouldRestart(generationExit)) {
|
|
@@ -188,26 +242,33 @@ const runSupervisionLoop = (cell, options) => Effect.gen(function* () {
|
|
|
188
242
|
}
|
|
189
243
|
const nextGeneration = cell.generation.current + 1;
|
|
190
244
|
cell.generation.current = nextGeneration;
|
|
191
|
-
let restartState = cell.
|
|
245
|
+
let restartState = cell.machineInitial;
|
|
192
246
|
if (options.lifecycle?.recovery !== void 0) {
|
|
193
247
|
const resolved = yield* options.lifecycle.recovery.resolve({
|
|
194
248
|
actorId: cell.id,
|
|
195
249
|
generation: nextGeneration,
|
|
196
|
-
machineInitial: cell.
|
|
250
|
+
machineInitial: cell.machineInitial
|
|
197
251
|
});
|
|
198
252
|
if (Option.isSome(resolved)) restartState = resolved.value;
|
|
199
253
|
}
|
|
200
254
|
yield* currentRuntime.settlePendingRequests;
|
|
255
|
+
yield* SubscriptionRef.set(cell.lifecycleRef, {
|
|
256
|
+
_tag: "Starting",
|
|
257
|
+
generation: nextGeneration
|
|
258
|
+
});
|
|
201
259
|
const freshQueue = yield* Queue.unbounded();
|
|
202
260
|
yield* Ref.set(cell.eventQueueRef, freshQueue);
|
|
203
261
|
yield* SubscriptionRef.set(cell.stateRef, restartState);
|
|
204
262
|
yield* Ref.set(cell.stoppedRef, false);
|
|
205
263
|
cell.children.clear();
|
|
206
|
-
|
|
207
|
-
if (restartState !== cell.machine.initial) machineForRestart = cell.machine._withInitial(restartState);
|
|
208
|
-
const newRuntime = yield* options.spawnGeneration(machineForRestart);
|
|
264
|
+
const newRuntime = yield* options.spawnGeneration(cell.machine);
|
|
209
265
|
cell.runtimeRef.current = newRuntime;
|
|
210
266
|
yield* newRuntime.start;
|
|
267
|
+
const restartExit = yield* Deferred.poll(newRuntime.exitDeferred);
|
|
268
|
+
if (Option.isNone(restartExit)) yield* SubscriptionRef.set(cell.lifecycleRef, {
|
|
269
|
+
_tag: "Active",
|
|
270
|
+
generation: nextGeneration
|
|
271
|
+
});
|
|
211
272
|
if (options.onRestart !== void 0) yield* options.onRestart(nextGeneration, generationExit);
|
|
212
273
|
notifyListeners(cell.listeners, restartState);
|
|
213
274
|
}
|
|
@@ -217,9 +278,9 @@ const runSupervisionLoop = (cell, options) => Effect.gen(function* () {
|
|
|
217
278
|
* Delegates to the shared runtime kernel with actor-specific lifecycle hooks.
|
|
218
279
|
*/
|
|
219
280
|
const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machine, options) {
|
|
220
|
-
const lifecycle = options
|
|
281
|
+
const lifecycle = options.lifecycle;
|
|
221
282
|
const serviceContext = yield* Effect.context();
|
|
222
|
-
const initial = options
|
|
283
|
+
const initial = options.initialState;
|
|
223
284
|
yield* Effect.annotateCurrentSpan("effect_machine.actor.id", id);
|
|
224
285
|
yield* Effect.annotateCurrentSpan("effect_machine.actor.initial_state", initial._tag);
|
|
225
286
|
const { system, implicitSystemScope } = yield* resolveActorSystem();
|
|
@@ -227,22 +288,24 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
|
|
|
227
288
|
const childrenMap = /* @__PURE__ */ new Map();
|
|
228
289
|
const listeners = /* @__PURE__ */ new Set();
|
|
229
290
|
const transitionsPubSub = yield* PubSub.unbounded();
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
291
|
+
const generation = { current: 0 };
|
|
292
|
+
const inspectionHooks = (runtimeGeneration) => {
|
|
293
|
+
if (inspectorValue === void 0) return void 0;
|
|
294
|
+
return makeInspectionHooks(id, inspectorValue, () => runtimeGeneration);
|
|
295
|
+
};
|
|
234
296
|
const stateRef = yield* SubscriptionRef.make(initial);
|
|
297
|
+
const lifecycleRef = yield* SubscriptionRef.make({ _tag: "Created" });
|
|
298
|
+
const latestTransitionRef = yield* SubscriptionRef.make(void 0);
|
|
235
299
|
const stoppedRef = yield* Ref.make(false);
|
|
236
300
|
const initialQueue = yield* Queue.unbounded();
|
|
237
301
|
const eventQueueRef = yield* Ref.make(initialQueue);
|
|
238
302
|
const terminalExitDeferred = yield* Deferred.make();
|
|
239
|
-
let stopEmitted = false;
|
|
240
|
-
const generation = { current: 0 };
|
|
241
303
|
const runtimeRef = { current: void 0 };
|
|
242
304
|
const supervisorFiberRef = { current: void 0 };
|
|
243
305
|
const cell = {
|
|
244
306
|
id,
|
|
245
307
|
machine,
|
|
308
|
+
machineInitial: options.machineInitial,
|
|
246
309
|
stateRef,
|
|
247
310
|
stoppedRef,
|
|
248
311
|
eventQueueRef,
|
|
@@ -251,16 +314,19 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
|
|
|
251
314
|
listeners,
|
|
252
315
|
children: childrenMap,
|
|
253
316
|
transitions: transitionsPubSub,
|
|
317
|
+
lifecycleRef,
|
|
318
|
+
latestTransitionRef,
|
|
254
319
|
system,
|
|
255
320
|
generation
|
|
256
321
|
};
|
|
257
322
|
/** Build lifecycle hooks for a generation */
|
|
258
|
-
const buildRuntimeLifecycle = () => {
|
|
259
|
-
stopEmitted = false;
|
|
323
|
+
const buildRuntimeLifecycle = (runtimeGeneration) => {
|
|
324
|
+
let stopEmitted = false;
|
|
260
325
|
let onEvent = void 0;
|
|
261
326
|
if (inspectorValue !== void 0) onEvent = (state, event) => emitWithTimestamp(inspectorValue, (timestamp) => ({
|
|
262
327
|
type: "@machine.event",
|
|
263
328
|
actorId: id,
|
|
329
|
+
generation: runtimeGeneration,
|
|
264
330
|
state,
|
|
265
331
|
event,
|
|
266
332
|
timestamp
|
|
@@ -271,6 +337,7 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
|
|
|
271
337
|
yield* emitWithTimestamp(inspectorValue, (timestamp) => ({
|
|
272
338
|
type: "@machine.stop",
|
|
273
339
|
actorId: id,
|
|
340
|
+
generation: runtimeGeneration,
|
|
274
341
|
finalState: state,
|
|
275
342
|
timestamp
|
|
276
343
|
}));
|
|
@@ -279,32 +346,39 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
|
|
|
279
346
|
if (inspectorValue !== void 0) onInitialSpawnEffects = (state) => emitWithTimestamp(inspectorValue, (timestamp) => ({
|
|
280
347
|
type: "@machine.effect",
|
|
281
348
|
actorId: id,
|
|
349
|
+
generation: runtimeGeneration,
|
|
282
350
|
effectType: "spawn",
|
|
283
351
|
state,
|
|
284
352
|
timestamp
|
|
285
353
|
}));
|
|
286
354
|
return {
|
|
287
355
|
onEvent,
|
|
288
|
-
onStateChange: (result, event) => {
|
|
356
|
+
onStateChange: (result, event) => Effect.gen(function* () {
|
|
357
|
+
const latest = result.transitions.at(-1);
|
|
358
|
+
if (latest !== void 0) yield* SubscriptionRef.set(latestTransitionRef, {
|
|
359
|
+
fromState: latest.previousState,
|
|
360
|
+
toState: latest.newState,
|
|
361
|
+
event: latest.event
|
|
362
|
+
});
|
|
289
363
|
notifyListeners(listeners, result.newState);
|
|
290
364
|
const durability = lifecycle?.durability;
|
|
291
365
|
if (durability === void 0 || !result.transitioned) return;
|
|
292
366
|
if (!(durability.shouldSave === void 0 || durability.shouldSave(result.newState, result.previousState))) return;
|
|
293
|
-
|
|
367
|
+
yield* durability.save({
|
|
294
368
|
actorId: id,
|
|
295
|
-
generation:
|
|
369
|
+
generation: runtimeGeneration,
|
|
296
370
|
previousState: result.previousState,
|
|
297
371
|
nextState: result.newState,
|
|
298
372
|
event
|
|
299
373
|
});
|
|
300
|
-
},
|
|
301
|
-
onProcessed: (result,
|
|
374
|
+
}),
|
|
375
|
+
onProcessed: (result, _event) => {
|
|
302
376
|
if (!result.transitioned || transitionsPubSub.subscribers.size === 0) return;
|
|
303
|
-
return PubSub.publish(transitionsPubSub, {
|
|
304
|
-
fromState:
|
|
305
|
-
toState:
|
|
306
|
-
event
|
|
307
|
-
})
|
|
377
|
+
return Effect.forEach(result.transitions, (transition) => PubSub.publish(transitionsPubSub, {
|
|
378
|
+
fromState: transition.previousState,
|
|
379
|
+
toState: transition.newState,
|
|
380
|
+
event: transition.event
|
|
381
|
+
}), { discard: true });
|
|
308
382
|
},
|
|
309
383
|
onFinal,
|
|
310
384
|
onShutdown: () => Effect.gen(function* () {
|
|
@@ -313,6 +387,7 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
|
|
|
313
387
|
yield* emitWithTimestamp(inspectorValue, (timestamp) => ({
|
|
314
388
|
type: "@machine.stop",
|
|
315
389
|
actorId: id,
|
|
390
|
+
generation: runtimeGeneration,
|
|
316
391
|
finalState,
|
|
317
392
|
timestamp
|
|
318
393
|
}));
|
|
@@ -322,45 +397,52 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
|
|
|
322
397
|
};
|
|
323
398
|
};
|
|
324
399
|
/** Create a single runtime generation. machineForGen is machineWithState for initial, machine for restarts. */
|
|
325
|
-
const spawnGeneration = (machineForGen) =>
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
childrenMap.
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
400
|
+
const spawnGeneration = (machineForGen) => {
|
|
401
|
+
const runtimeGeneration = generation.current;
|
|
402
|
+
return Ref.get(eventQueueRef).pipe(Effect.flatMap((currentQueue) => createRuntime(machineForGen, system, {
|
|
403
|
+
actorId: id,
|
|
404
|
+
generation: runtimeGeneration,
|
|
405
|
+
hooks: inspectionHooks(runtimeGeneration),
|
|
406
|
+
skipFinalizer: true,
|
|
407
|
+
cellResources: {
|
|
408
|
+
stateRef,
|
|
409
|
+
stoppedRef,
|
|
410
|
+
eventQueue: currentQueue
|
|
411
|
+
},
|
|
412
|
+
lifecycle: buildRuntimeLifecycle(runtimeGeneration),
|
|
413
|
+
onChildSpawned: (childId, child) => Effect.gen(function* () {
|
|
414
|
+
childrenMap.set(childId, child);
|
|
415
|
+
const maybeScope = yield* Effect.serviceOption(Scope.Scope);
|
|
416
|
+
if (Option.isSome(maybeScope)) yield* Scope.addFinalizer(maybeScope.value, Effect.sync(() => {
|
|
417
|
+
childrenMap.delete(childId);
|
|
418
|
+
}));
|
|
419
|
+
})
|
|
420
|
+
})));
|
|
421
|
+
};
|
|
422
|
+
runtimeRef.current = yield* spawnGeneration(machine);
|
|
423
|
+
const supervision = options.supervision;
|
|
345
424
|
const stop = Effect.fn("effect-machine.actor.stop")(function* () {
|
|
346
425
|
if (supervisorFiberRef.current !== void 0) yield* Fiber.interrupt(supervisorFiberRef.current);
|
|
347
426
|
const currentRuntime = runtimeRef.current;
|
|
348
427
|
if (currentRuntime !== void 0) yield* currentRuntime.stop;
|
|
349
|
-
yield* Deferred.succeed(terminalExitDeferred,
|
|
428
|
+
yield* Deferred.succeed(terminalExitDeferred, ActorExit.Stopped);
|
|
350
429
|
if (implicitSystemScope !== void 0) yield* Scope.close(implicitSystemScope, Exit.void);
|
|
351
430
|
})().pipe(Effect.provide(serviceContext), Effect.asVoid);
|
|
352
|
-
const isHydrated = options
|
|
431
|
+
const isHydrated = options.hydrated === true;
|
|
353
432
|
const start = Effect.fn("effect-machine.actor.start")(function* () {
|
|
433
|
+
yield* SubscriptionRef.set(lifecycleRef, {
|
|
434
|
+
_tag: "Starting",
|
|
435
|
+
generation: generation.current
|
|
436
|
+
});
|
|
354
437
|
if (lifecycle?.recovery !== void 0 && !isHydrated) {
|
|
355
438
|
const resolved = yield* lifecycle.recovery.resolve({
|
|
356
439
|
actorId: id,
|
|
357
440
|
generation: generation.current,
|
|
358
|
-
machineInitial:
|
|
441
|
+
machineInitial: options.machineInitial
|
|
359
442
|
});
|
|
360
443
|
if (Option.isSome(resolved)) {
|
|
361
444
|
yield* SubscriptionRef.set(stateRef, resolved.value);
|
|
362
|
-
const
|
|
363
|
-
const newRuntime = yield* spawnGeneration(recoveredMachine);
|
|
445
|
+
const newRuntime = yield* spawnGeneration(machine);
|
|
364
446
|
runtimeRef.current = newRuntime;
|
|
365
447
|
}
|
|
366
448
|
}
|
|
@@ -368,6 +450,7 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
|
|
|
368
450
|
yield* emitWithTimestamp(inspectorValue, (timestamp) => ({
|
|
369
451
|
type: "@machine.spawn",
|
|
370
452
|
actorId: id,
|
|
453
|
+
generation: generation.current,
|
|
371
454
|
initialState: currentState,
|
|
372
455
|
timestamp
|
|
373
456
|
}));
|
|
@@ -375,16 +458,24 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
|
|
|
375
458
|
supervision,
|
|
376
459
|
spawnGeneration,
|
|
377
460
|
lifecycle,
|
|
378
|
-
onRestart: options
|
|
461
|
+
onRestart: options.onRestart
|
|
379
462
|
}));
|
|
380
463
|
else {
|
|
381
464
|
const currentRuntime = runtimeRef.current;
|
|
382
|
-
if (currentRuntime !== void 0) yield* Effect.forkDetach(Deferred.await(currentRuntime.exitDeferred).pipe(Effect.tap((exit) => Deferred.succeed(terminalExitDeferred, exit))));
|
|
465
|
+
if (currentRuntime !== void 0) yield* Effect.forkDetach(Deferred.await(currentRuntime.exitDeferred).pipe(Effect.tap((exit) => Deferred.succeed(terminalExitDeferred, toActorExit(machine, exit)))));
|
|
383
466
|
}
|
|
384
467
|
const currentRuntime = runtimeRef.current;
|
|
385
|
-
if (currentRuntime !== void 0)
|
|
468
|
+
if (currentRuntime !== void 0) {
|
|
469
|
+
yield* currentRuntime.start;
|
|
470
|
+
const currentExit = yield* Deferred.poll(currentRuntime.exitDeferred);
|
|
471
|
+
if (Option.isNone(currentExit)) yield* SubscriptionRef.set(lifecycleRef, {
|
|
472
|
+
_tag: "Active",
|
|
473
|
+
generation: generation.current
|
|
474
|
+
});
|
|
475
|
+
}
|
|
386
476
|
})().pipe(Effect.provide(serviceContext), Effect.asVoid);
|
|
387
|
-
|
|
477
|
+
yield* Effect.forkDetach(Deferred.await(terminalExitDeferred).pipe(Effect.flatMap((exit) => SubscriptionRef.set(lifecycleRef, exit)), Effect.provide(serviceContext)));
|
|
478
|
+
return buildActorRefCore(cell, stop, start, serviceContext);
|
|
388
479
|
});
|
|
389
480
|
/** Notify all system event listeners (sync). */
|
|
390
481
|
const notifySystemListeners = (listeners, event) => {
|
|
@@ -418,6 +509,17 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
|
|
|
418
509
|
id,
|
|
419
510
|
actor: actorRef
|
|
420
511
|
});
|
|
512
|
+
yield* Effect.forkDetach(actorRef.awaitExit.pipe(Effect.flatMap((exit) => {
|
|
513
|
+
const registered = MutableHashMap.get(actorsMap, id);
|
|
514
|
+
if (Option.isNone(registered) || registered.value !== actorRef) return Effect.void;
|
|
515
|
+
MutableHashMap.remove(actorsMap, id);
|
|
516
|
+
return emitSystemEvent({
|
|
517
|
+
_tag: "ActorStopped",
|
|
518
|
+
id,
|
|
519
|
+
actor: actorRef,
|
|
520
|
+
exit
|
|
521
|
+
});
|
|
522
|
+
})));
|
|
421
523
|
const maybeScope = yield* Effect.serviceOption(ActorScope);
|
|
422
524
|
if (Option.isSome(maybeScope)) yield* Scope.addFinalizer(maybeScope.value, Effect.gen(function* () {
|
|
423
525
|
if (MutableHashMap.has(actorsMap, id)) {
|
|
@@ -448,7 +550,12 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
|
|
|
448
550
|
exit
|
|
449
551
|
});
|
|
450
552
|
};
|
|
553
|
+
const machineInitial = machine._initial(spawnOptions?.input);
|
|
554
|
+
const initialState = spawnOptions?.hydrate ?? machineInitial;
|
|
451
555
|
const actor = yield* createActor(id, machine, {
|
|
556
|
+
initialState,
|
|
557
|
+
machineInitial,
|
|
558
|
+
hydrated: spawnOptions?.hydrate !== void 0,
|
|
452
559
|
supervision: spawnOptions?.supervision,
|
|
453
560
|
lifecycle: spawnOptions?.lifecycle,
|
|
454
561
|
onRestart
|
|
@@ -458,11 +565,34 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
|
|
|
458
565
|
yield* actor.start.pipe(Effect.catchCause((cause) => actor.stop.pipe(Effect.andThen(Effect.failCause(cause)))));
|
|
459
566
|
return actor;
|
|
460
567
|
});
|
|
461
|
-
const spawn = (
|
|
462
|
-
|
|
463
|
-
return
|
|
464
|
-
}
|
|
465
|
-
|
|
568
|
+
const spawn = (idOrKey, machine, options) => {
|
|
569
|
+
const id = actorSystemId(idOrKey);
|
|
570
|
+
return withSpawnGate(spawnRegular(id, machine, options));
|
|
571
|
+
};
|
|
572
|
+
function get(idOrKey) {
|
|
573
|
+
return Effect.sync(() => MutableHashMap.get(actorsMap, actorSystemId(idOrKey)));
|
|
574
|
+
}
|
|
575
|
+
const sameActor = (left, right) => {
|
|
576
|
+
if (Option.isNone(left)) return Option.isNone(right);
|
|
577
|
+
return Option.isSome(right) && Object.is(left.value, right.value);
|
|
578
|
+
};
|
|
579
|
+
function watch(idOrKey) {
|
|
580
|
+
const id = actorSystemId(idOrKey);
|
|
581
|
+
return Stream.callback((queue) => Effect.acquireRelease(Effect.sync(() => {
|
|
582
|
+
const unsubscribe = (event) => {
|
|
583
|
+
if (event.id !== id) return;
|
|
584
|
+
if (event._tag === "ActorSpawned") Queue.offerUnsafe(queue, Option.some(event.actor));
|
|
585
|
+
else if (event._tag === "ActorStopped") Queue.offerUnsafe(queue, Option.none());
|
|
586
|
+
};
|
|
587
|
+
eventListeners.add(unsubscribe);
|
|
588
|
+
Queue.offerUnsafe(queue, MutableHashMap.get(actorsMap, id));
|
|
589
|
+
return () => {
|
|
590
|
+
eventListeners.delete(unsubscribe);
|
|
591
|
+
};
|
|
592
|
+
}), (unsubscribe) => Effect.sync(unsubscribe))).pipe(Stream.changesWith(sameActor));
|
|
593
|
+
}
|
|
594
|
+
const stop = Effect.fn("effect-machine.actorSystem.stop")(function* (idOrKey) {
|
|
595
|
+
const id = actorSystemId(idOrKey);
|
|
466
596
|
const maybeActor = MutableHashMap.get(actorsMap, id);
|
|
467
597
|
if (Option.isNone(maybeActor)) return false;
|
|
468
598
|
const actor = maybeActor.value;
|
|
@@ -479,6 +609,7 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
|
|
|
479
609
|
return ActorSystem.of({
|
|
480
610
|
spawn,
|
|
481
611
|
get,
|
|
612
|
+
watch,
|
|
482
613
|
stop,
|
|
483
614
|
events: Stream.fromPubSub(eventPubSub),
|
|
484
615
|
get actors() {
|
|
@@ -506,4 +637,4 @@ const makeSystem = make;
|
|
|
506
637
|
*/
|
|
507
638
|
const Default = Layer.effect(ActorSystem, make());
|
|
508
639
|
//#endregion
|
|
509
|
-
export { ActorScope, ActorSystem, Default, createActor, makeSystem };
|
|
640
|
+
export { ActorScope, ActorSystem, ActorSystemKey, Default, actorSystemKey, createActor, makeSystem };
|
package/dist/atom.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import { ActorRef } from "./actor.js";
|
|
1
|
+
import { ActorLifecycle, ActorRef, ActorSystemKey, ActorSystemService, TransitionInfo } from "./actor.js";
|
|
2
|
+
import * as Option from "effect/Option";
|
|
2
3
|
import * as Atom from "effect/unstable/reactivity/Atom";
|
|
4
|
+
import * as AsyncResult from "effect/unstable/reactivity/AsyncResult";
|
|
5
|
+
import * as Cause from "effect/Cause";
|
|
3
6
|
//#region src/atom.d.ts
|
|
4
7
|
/**
|
|
5
8
|
* A writable Atom projection of an actor.
|
|
@@ -7,6 +10,32 @@ import * as Atom from "effect/unstable/reactivity/Atom";
|
|
|
7
10
|
* The Atom value is the current actor state. Atom writes send actor events.
|
|
8
11
|
*/
|
|
9
12
|
type ActorAtom<State, Event> = Atom.Writable<State, Event>;
|
|
13
|
+
/** A reactive ActorSystem lookup for one typed actor identity. */
|
|
14
|
+
type ActorLookupAtom<State extends {
|
|
15
|
+
readonly _tag: string;
|
|
16
|
+
}, Event, Output> = Atom.Atom<AsyncResult.AsyncResult<Option.Option<ActorRef<State, Event, Output>>, Cause.NoSuchElementError>>;
|
|
17
|
+
/** A Suspense-ready actor acquisition that follows later actor generations. */
|
|
18
|
+
type ActorAcquireAtom<State extends {
|
|
19
|
+
readonly _tag: string;
|
|
20
|
+
}, Event, Output> = Atom.Atom<AsyncResult.AsyncResult<ActorRef<State, Event, Output>, Cause.NoSuchElementError>>;
|
|
21
|
+
/** Observe the current ActorRef for one ActorSystem key. */
|
|
22
|
+
declare const fromSystem: {
|
|
23
|
+
<State extends {
|
|
24
|
+
readonly _tag: string;
|
|
25
|
+
}, Event, Output>(key: ActorSystemKey<State, Event, Output>): (system: ActorSystemService) => ActorLookupAtom<State, Event, Output>;
|
|
26
|
+
<State extends {
|
|
27
|
+
readonly _tag: string;
|
|
28
|
+
}, Event, Output>(system: ActorSystemService, key: ActorSystemKey<State, Event, Output>): ActorLookupAtom<State, Event, Output>;
|
|
29
|
+
};
|
|
30
|
+
/** Suspend while an ActorSystem key is absent and follow later generations. */
|
|
31
|
+
declare const acquire: {
|
|
32
|
+
<State extends {
|
|
33
|
+
readonly _tag: string;
|
|
34
|
+
}, Event, Output>(key: ActorSystemKey<State, Event, Output>): (system: ActorSystemService) => ActorAcquireAtom<State, Event, Output>;
|
|
35
|
+
<State extends {
|
|
36
|
+
readonly _tag: string;
|
|
37
|
+
}, Event, Output>(system: ActorSystemService, key: ActorSystemKey<State, Event, Output>): ActorAcquireAtom<State, Event, Output>;
|
|
38
|
+
};
|
|
10
39
|
/**
|
|
11
40
|
* Make a writable Atom from an actor.
|
|
12
41
|
*
|
|
@@ -16,7 +45,7 @@ type ActorAtom<State, Event> = Atom.Writable<State, Event>;
|
|
|
16
45
|
*/
|
|
17
46
|
declare const make: <State extends {
|
|
18
47
|
readonly _tag: string;
|
|
19
|
-
}, Event>(actor: ActorRef<State, Event>) => ActorAtom<State, Event>;
|
|
48
|
+
}, Event, Output>(actor: ActorRef<State, Event, Output>) => ActorAtom<State, Event>;
|
|
20
49
|
/**
|
|
21
50
|
* Select part of an actor state.
|
|
22
51
|
*
|
|
@@ -27,5 +56,29 @@ declare const select: {
|
|
|
27
56
|
<State, Selection>(selector: (state: State) => Selection, equals?: (value: Selection, next: Selection) => boolean): <Event>(self: ActorAtom<State, Event>) => ActorAtom<Selection, Event>;
|
|
28
57
|
<State, Event, Selection>(self: ActorAtom<State, Event>, selector: (state: State) => Selection, equals?: (value: Selection, next: Selection) => boolean): ActorAtom<Selection, Event>;
|
|
29
58
|
};
|
|
59
|
+
/** Observe actor lifecycle without coupling it to domain state. */
|
|
60
|
+
declare const lifecycle: <State extends {
|
|
61
|
+
readonly _tag: string;
|
|
62
|
+
}, Event, Output>(actor: ActorRef<State, Event, Output>) => Atom.Atom<ActorLifecycle<State, Output>>;
|
|
63
|
+
/** Observe the latest accepted edge. The value remains after actor exit. */
|
|
64
|
+
declare const latestTransition: <State extends {
|
|
65
|
+
readonly _tag: string;
|
|
66
|
+
}, Event, Output>(actor: ActorRef<State, Event, Output>) => Atom.Atom<TransitionInfo<State, Event> | undefined>;
|
|
67
|
+
/** A reactive result for whether an actor can accept one event. */
|
|
68
|
+
type CanAtom = Atom.Atom<AsyncResult.AsyncResult<boolean>>;
|
|
69
|
+
/**
|
|
70
|
+
* Observe whether an event has an enabled transition.
|
|
71
|
+
*
|
|
72
|
+
* The Atom reevaluates after each actor state change. It supports pure and
|
|
73
|
+
* Effect predicates. Effect predicates use the context captured by the actor.
|
|
74
|
+
*/
|
|
75
|
+
declare const can: {
|
|
76
|
+
<Event>(event: Event): <State extends {
|
|
77
|
+
readonly _tag: string;
|
|
78
|
+
}, Output>(actor: ActorRef<State, Event, Output>) => CanAtom;
|
|
79
|
+
<State extends {
|
|
80
|
+
readonly _tag: string;
|
|
81
|
+
}, Event, Output>(actor: ActorRef<State, Event, Output>, event: Event): CanAtom;
|
|
82
|
+
};
|
|
30
83
|
//#endregion
|
|
31
|
-
export { ActorAtom, make, select };
|
|
84
|
+
export { ActorAcquireAtom, ActorAtom, ActorLookupAtom, CanAtom, acquire, can, fromSystem, latestTransition, lifecycle, make, select };
|