effect-machine 0.17.1 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +97 -54
- package/dist/actor.d.ts +8 -45
- package/dist/actor.js +157 -231
- package/dist/atom.d.ts +31 -0
- package/dist/atom.js +29 -0
- package/dist/cluster/adapters/in-memory.d.ts +0 -1
- package/dist/cluster/adapters/in-memory.js +10 -5
- package/dist/cluster/entity-actor-ref.d.ts +2 -3
- package/dist/cluster/entity-actor-ref.js +14 -17
- package/dist/cluster/entity-machine.d.ts +2 -8
- package/dist/cluster/entity-machine.js +57 -34
- package/dist/cluster/index.js +1 -1
- package/dist/cluster/persistence.d.ts +0 -1
- package/dist/cluster/to-entity.d.ts +19 -21
- package/dist/cluster/to-entity.js +18 -19
- package/dist/errors.d.ts +11 -37
- package/dist/errors.js +12 -30
- package/dist/index.d.ts +4 -5
- package/dist/index.js +4 -5
- package/dist/inspection.d.ts +0 -1
- package/dist/inspection.js +24 -13
- package/dist/internal/brands.d.ts +0 -1
- package/dist/internal/event-advancement.d.ts +50 -0
- package/dist/internal/event-advancement.js +79 -0
- package/dist/internal/inspection.d.ts +5 -9
- package/dist/internal/inspection.js +31 -10
- package/dist/internal/machine-definition.d.ts +16 -0
- package/dist/internal/runtime.d.ts +1 -161
- package/dist/internal/runtime.js +191 -150
- package/dist/internal/transition.d.ts +4 -135
- package/dist/internal/transition.js +82 -157
- package/dist/internal/utils.d.ts +3 -40
- package/dist/internal/utils.js +2 -2
- package/dist/machine.d.ts +47 -137
- package/dist/machine.js +147 -215
- package/dist/schema.d.ts +42 -21
- package/dist/schema.js +28 -18
- package/dist/supervision.d.ts +1 -24
- package/dist/supervision.js +2 -3
- package/dist/testing.d.ts +15 -29
- package/dist/testing.js +76 -95
- package/package.json +41 -35
- package/dist/slot.d.ts +0 -159
- package/dist/slot.js +0 -165
- package/v3/dist/_virtual/_rolldown/runtime.js +0 -13
- package/v3/dist/actor.d.ts +0 -250
- package/v3/dist/actor.js +0 -577
- package/v3/dist/cluster/adapters/in-memory.d.ts +0 -15
- package/v3/dist/cluster/adapters/in-memory.js +0 -62
- package/v3/dist/cluster/entity-actor-ref.d.ts +0 -49
- package/v3/dist/cluster/entity-actor-ref.js +0 -19
- package/v3/dist/cluster/entity-machine.d.ts +0 -74
- package/v3/dist/cluster/entity-machine.js +0 -166
- package/v3/dist/cluster/index.d.ts +0 -6
- package/v3/dist/cluster/index.js +0 -6
- package/v3/dist/cluster/persistence.d.ts +0 -48
- package/v3/dist/cluster/persistence.js +0 -14
- package/v3/dist/cluster/to-entity.d.ts +0 -69
- package/v3/dist/cluster/to-entity.js +0 -59
- package/v3/dist/errors.d.ts +0 -95
- package/v3/dist/errors.js +0 -54
- package/v3/dist/index.d.ts +0 -11
- package/v3/dist/index.js +0 -9
- package/v3/dist/inspection.d.ts +0 -151
- package/v3/dist/inspection.js +0 -128
- package/v3/dist/internal/brands.d.ts +0 -50
- package/v3/dist/internal/inspection.d.ts +0 -11
- package/v3/dist/internal/inspection.js +0 -20
- package/v3/dist/internal/runtime.d.ts +0 -161
- package/v3/dist/internal/runtime.js +0 -360
- package/v3/dist/internal/transition.d.ts +0 -190
- package/v3/dist/internal/transition.js +0 -278
- package/v3/dist/internal/utils.d.ts +0 -101
- package/v3/dist/internal/utils.js +0 -75
- package/v3/dist/machine.d.ts +0 -398
- package/v3/dist/machine.js +0 -487
- package/v3/dist/schema.d.ts +0 -174
- package/v3/dist/schema.js +0 -206
- package/v3/dist/slot.d.ts +0 -158
- package/v3/dist/slot.js +0 -165
- package/v3/dist/supervision.d.ts +0 -97
- package/v3/dist/supervision.js +0 -42
- package/v3/dist/testing.d.ts +0 -151
- package/v3/dist/testing.js +0 -189
- /package/{v3/dist/internal/brands.js → dist/internal/machine-definition.js} +0 -0
package/dist/actor.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { ActorStoppedError, DuplicateActorError } from "./errors.js";
|
|
2
|
-
import {
|
|
3
|
-
import { emitWithTimestamp } from "./internal/inspection.js";
|
|
2
|
+
import { resolveTransition } from "./internal/transition.js";
|
|
3
|
+
import { emitWithTimestamp, makeInspectionHooks } from "./internal/inspection.js";
|
|
4
4
|
import { Inspector } from "./inspection.js";
|
|
5
|
-
import { materializeMachine } from "./machine.js";
|
|
6
5
|
import { createRuntime } from "./internal/runtime.js";
|
|
7
|
-
import {
|
|
6
|
+
import { Context, Deferred, Effect, Exit, Fiber, Layer, MutableHashMap, Option, PubSub, Queue, Ref, Schedule, Scope, Semaphore, Stream, SubscriptionRef } from "effect";
|
|
8
7
|
//#region src/actor.ts
|
|
9
8
|
/**
|
|
10
9
|
* Actor system: spawning, lifecycle, and event processing.
|
|
@@ -39,43 +38,19 @@ const notifyListeners = (listeners, state) => {
|
|
|
39
38
|
/**
|
|
40
39
|
* Build core ActorRef methods.
|
|
41
40
|
*/
|
|
42
|
-
const buildActorRefCore = (
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
yield*
|
|
47
|
-
_tag: "send",
|
|
48
|
-
event
|
|
49
|
-
});
|
|
41
|
+
const buildActorRefCore = (cell, stop, start) => {
|
|
42
|
+
const { id, machine, stateRef, runtimeRef, listeners, system } = cell;
|
|
43
|
+
const send = (event) => Effect.gen(function* () {
|
|
44
|
+
const runtime = runtimeRef.current;
|
|
45
|
+
if (runtime !== void 0) yield* runtime.send(event);
|
|
50
46
|
});
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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) => ({
|
|
47
|
+
const stoppedCall = (event) => Effect.gen(function* () {
|
|
48
|
+
yield* Effect.logWarning("effect-machine.actor.call.stopped").pipe(Effect.annotateLogs({
|
|
49
|
+
actorId: id,
|
|
50
|
+
eventTag: event._tag
|
|
51
|
+
}));
|
|
52
|
+
const currentState = yield* SubscriptionRef.get(stateRef);
|
|
53
|
+
return {
|
|
79
54
|
newState: currentState,
|
|
80
55
|
previousState: currentState,
|
|
81
56
|
transitioned: false,
|
|
@@ -84,30 +59,31 @@ const buildActorRefCore = (id, machine, stateRef, eventQueueRef, stoppedRef, lis
|
|
|
84
59
|
reply: void 0,
|
|
85
60
|
postponed: false,
|
|
86
61
|
lifecycleRan: false,
|
|
87
|
-
isFinal: machine.
|
|
88
|
-
}
|
|
62
|
+
isFinal: machine._isFinal(currentState._tag)
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
const call = (event) => Effect.suspend(() => {
|
|
66
|
+
const runtime = runtimeRef.current;
|
|
67
|
+
if (runtime === void 0) return stoppedCall(event);
|
|
68
|
+
return runtime.call(event).pipe(Effect.catch(() => stoppedCall(event)));
|
|
89
69
|
});
|
|
90
70
|
const ask = Effect.fn("effect-machine.actor.ask")(function* (event) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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))));
|
|
71
|
+
const runtime = runtimeRef.current;
|
|
72
|
+
if (runtime === void 0) return yield* ActorStoppedError.make({ actorId: id });
|
|
73
|
+
return yield* runtime.ask(event);
|
|
101
74
|
});
|
|
102
75
|
const snapshot = SubscriptionRef.get(stateRef).pipe(Effect.withSpan("effect-machine.actor.snapshot"));
|
|
103
76
|
const matches = Effect.fn("effect-machine.actor.matches")(function* (tag) {
|
|
104
77
|
return (yield* SubscriptionRef.get(stateRef))._tag === tag;
|
|
105
78
|
});
|
|
106
79
|
const can = Effect.fn("effect-machine.actor.can")(function* (event) {
|
|
107
|
-
|
|
80
|
+
const state = yield* SubscriptionRef.get(stateRef);
|
|
81
|
+
return resolveTransition(machine, state, event) !== void 0;
|
|
108
82
|
});
|
|
109
83
|
const waitFor = Effect.fn("effect-machine.actor.waitFor")(function* (predicateOrState) {
|
|
110
|
-
|
|
84
|
+
let predicate;
|
|
85
|
+
if (typeof predicateOrState === "function" && !("_tag" in predicateOrState)) predicate = predicateOrState;
|
|
86
|
+
else predicate = (s) => s._tag === predicateOrState._tag;
|
|
111
87
|
const current = yield* SubscriptionRef.get(stateRef);
|
|
112
88
|
if (predicate(current)) return current;
|
|
113
89
|
const done = yield* Deferred.make();
|
|
@@ -124,16 +100,16 @@ const buildActorRefCore = (id, machine, stateRef, eventQueueRef, stoppedRef, lis
|
|
|
124
100
|
listeners.delete(listener);
|
|
125
101
|
return result;
|
|
126
102
|
});
|
|
127
|
-
const awaitFinal = waitFor((state) => machine.
|
|
103
|
+
const awaitFinal = waitFor((state) => machine._isFinal(state._tag)).pipe(Effect.withSpan("effect-machine.actor.awaitFinal"));
|
|
128
104
|
const sendAndWait = Effect.fn("effect-machine.actor.sendAndWait")(function* (event, predicateOrState) {
|
|
129
105
|
yield* send(event);
|
|
130
106
|
if (predicateOrState !== void 0) return yield* waitFor(predicateOrState);
|
|
131
107
|
return yield* awaitFinal;
|
|
132
108
|
});
|
|
109
|
+
const transitions = Stream.fromPubSub(cell.transitions);
|
|
133
110
|
return {
|
|
134
111
|
id,
|
|
135
112
|
send,
|
|
136
|
-
cast: send,
|
|
137
113
|
call,
|
|
138
114
|
ask,
|
|
139
115
|
state: stateRef,
|
|
@@ -143,7 +119,7 @@ const buildActorRefCore = (id, machine, stateRef, eventQueueRef, stoppedRef, lis
|
|
|
143
119
|
matches,
|
|
144
120
|
can,
|
|
145
121
|
changes: SubscriptionRef.changes(stateRef),
|
|
146
|
-
transitions
|
|
122
|
+
transitions,
|
|
147
123
|
waitFor,
|
|
148
124
|
awaitFinal,
|
|
149
125
|
sendAndWait,
|
|
@@ -153,66 +129,24 @@ const buildActorRefCore = (id, machine, stateRef, eventQueueRef, stoppedRef, lis
|
|
|
153
129
|
listeners.delete(fn);
|
|
154
130
|
};
|
|
155
131
|
},
|
|
156
|
-
awaitExit: Deferred.await(
|
|
157
|
-
|
|
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),
|
|
132
|
+
awaitExit: Deferred.await(cell.terminalExitDeferred),
|
|
133
|
+
drain: Effect.suspend(() => runtimeRef.current?.drain ?? Effect.void),
|
|
168
134
|
sync: {
|
|
169
135
|
send: (event) => {
|
|
170
|
-
|
|
171
|
-
const q = Effect.runSync(Ref.get(eventQueueRef));
|
|
172
|
-
Effect.runSync(Queue.offer(q, {
|
|
173
|
-
_tag: "send",
|
|
174
|
-
event
|
|
175
|
-
}));
|
|
176
|
-
}
|
|
136
|
+
runtimeRef.current?.sendSync(event);
|
|
177
137
|
},
|
|
178
138
|
stop: () => Effect.runFork(stop),
|
|
179
139
|
snapshot: () => Effect.runSync(SubscriptionRef.get(stateRef)),
|
|
180
140
|
matches: (tag) => Effect.runSync(SubscriptionRef.get(stateRef))._tag === tag,
|
|
181
141
|
can: (event) => {
|
|
182
|
-
|
|
142
|
+
const state = Effect.runSync(SubscriptionRef.get(stateRef));
|
|
143
|
+
return resolveTransition(machine, state, event) !== void 0;
|
|
183
144
|
}
|
|
184
145
|
},
|
|
185
146
|
system,
|
|
186
|
-
children:
|
|
147
|
+
children: cell.children
|
|
187
148
|
};
|
|
188
149
|
};
|
|
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
150
|
/**
|
|
217
151
|
* Resolve actor system from context, creating an implicit one if none exists.
|
|
218
152
|
* @internal
|
|
@@ -234,50 +168,48 @@ const resolveActorSystem = Effect.fn("effect-machine.resolveActorSystem")(functi
|
|
|
234
168
|
* Observes exit deferred, applies restart policy, resets cell resources on restart.
|
|
235
169
|
* @internal
|
|
236
170
|
*/
|
|
237
|
-
const runSupervisionLoop = (
|
|
238
|
-
const step = yield* Schedule.toStepWithSleep(
|
|
171
|
+
const runSupervisionLoop = (cell, options) => Effect.gen(function* () {
|
|
172
|
+
const step = yield* Schedule.toStepWithSleep(options.supervision.schedule);
|
|
239
173
|
while (true) {
|
|
240
|
-
const currentRuntime =
|
|
174
|
+
const currentRuntime = cell.runtimeRef.current;
|
|
241
175
|
if (currentRuntime === void 0) return;
|
|
242
176
|
const generationExit = yield* Deferred.await(currentRuntime.exitDeferred);
|
|
243
177
|
if (generationExit._tag !== "Defect") {
|
|
244
|
-
yield* Deferred.succeed(
|
|
178
|
+
yield* Deferred.succeed(cell.terminalExitDeferred, generationExit);
|
|
245
179
|
return;
|
|
246
180
|
}
|
|
247
|
-
if (
|
|
248
|
-
yield* Deferred.succeed(
|
|
181
|
+
if (options.supervision.shouldRestart !== void 0 && !options.supervision.shouldRestart(generationExit)) {
|
|
182
|
+
yield* Deferred.succeed(cell.terminalExitDeferred, generationExit);
|
|
249
183
|
return;
|
|
250
184
|
}
|
|
251
185
|
if ((yield* step(generationExit).pipe(Effect.exit))._tag === "Failure") {
|
|
252
|
-
yield* Deferred.succeed(
|
|
186
|
+
yield* Deferred.succeed(cell.terminalExitDeferred, generationExit);
|
|
253
187
|
return;
|
|
254
188
|
}
|
|
255
|
-
const nextGeneration =
|
|
256
|
-
|
|
257
|
-
let restartState =
|
|
258
|
-
if (
|
|
259
|
-
const resolved = yield*
|
|
260
|
-
actorId:
|
|
189
|
+
const nextGeneration = cell.generation.current + 1;
|
|
190
|
+
cell.generation.current = nextGeneration;
|
|
191
|
+
let restartState = cell.machine.initial;
|
|
192
|
+
if (options.lifecycle?.recovery !== void 0) {
|
|
193
|
+
const resolved = yield* options.lifecycle.recovery.resolve({
|
|
194
|
+
actorId: cell.id,
|
|
261
195
|
generation: nextGeneration,
|
|
262
|
-
machineInitial:
|
|
196
|
+
machineInitial: cell.machine.initial
|
|
263
197
|
});
|
|
264
198
|
if (Option.isSome(resolved)) restartState = resolved.value;
|
|
265
199
|
}
|
|
266
|
-
yield*
|
|
200
|
+
yield* currentRuntime.settlePendingRequests;
|
|
267
201
|
const freshQueue = yield* Queue.unbounded();
|
|
268
|
-
yield* Ref.set(
|
|
269
|
-
yield* SubscriptionRef.set(
|
|
270
|
-
yield* Ref.set(
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
const newRuntime = yield* params.spawnGeneration(machineForRestart);
|
|
277
|
-
params.runtimeRef.current = newRuntime;
|
|
202
|
+
yield* Ref.set(cell.eventQueueRef, freshQueue);
|
|
203
|
+
yield* SubscriptionRef.set(cell.stateRef, restartState);
|
|
204
|
+
yield* Ref.set(cell.stoppedRef, false);
|
|
205
|
+
cell.children.clear();
|
|
206
|
+
let machineForRestart = cell.machine;
|
|
207
|
+
if (restartState !== cell.machine.initial) machineForRestart = cell.machine._withInitial(restartState);
|
|
208
|
+
const newRuntime = yield* options.spawnGeneration(machineForRestart);
|
|
209
|
+
cell.runtimeRef.current = newRuntime;
|
|
278
210
|
yield* newRuntime.start;
|
|
279
|
-
if (
|
|
280
|
-
notifyListeners(
|
|
211
|
+
if (options.onRestart !== void 0) yield* options.onRestart(nextGeneration, generationExit);
|
|
212
|
+
notifyListeners(cell.listeners, restartState);
|
|
281
213
|
}
|
|
282
214
|
});
|
|
283
215
|
/**
|
|
@@ -286,72 +218,95 @@ const runSupervisionLoop = (params) => Effect.gen(function* () {
|
|
|
286
218
|
*/
|
|
287
219
|
const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machine, options) {
|
|
288
220
|
const lifecycle = options?.lifecycle;
|
|
221
|
+
const serviceContext = yield* Effect.context();
|
|
289
222
|
const initial = options?.initialState ?? machine.initial;
|
|
290
223
|
yield* Effect.annotateCurrentSpan("effect_machine.actor.id", id);
|
|
291
224
|
yield* Effect.annotateCurrentSpan("effect_machine.actor.initial_state", initial._tag);
|
|
292
225
|
const { system, implicitSystemScope } = yield* resolveActorSystem();
|
|
293
226
|
const inspectorValue = Option.getOrUndefined(yield* Effect.serviceOption(Inspector));
|
|
294
227
|
const childrenMap = /* @__PURE__ */ new Map();
|
|
295
|
-
const pendingReplies = /* @__PURE__ */ new Set();
|
|
296
228
|
const listeners = /* @__PURE__ */ new Set();
|
|
297
229
|
const transitionsPubSub = yield* PubSub.unbounded();
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
} }) : machine;
|
|
230
|
+
let hooks = void 0;
|
|
231
|
+
if (inspectorValue !== void 0) hooks = makeInspectionHooks(id, inspectorValue);
|
|
232
|
+
let machineWithState = machine;
|
|
233
|
+
if (initial !== machine.initial) machineWithState = machine._withInitial(initial);
|
|
303
234
|
const stateRef = yield* SubscriptionRef.make(initial);
|
|
304
235
|
const stoppedRef = yield* Ref.make(false);
|
|
305
236
|
const initialQueue = yield* Queue.unbounded();
|
|
306
237
|
const eventQueueRef = yield* Ref.make(initialQueue);
|
|
307
238
|
const terminalExitDeferred = yield* Deferred.make();
|
|
308
239
|
let stopEmitted = false;
|
|
309
|
-
|
|
240
|
+
const generation = { current: 0 };
|
|
310
241
|
const runtimeRef = { current: void 0 };
|
|
311
242
|
const supervisorFiberRef = { current: void 0 };
|
|
243
|
+
const cell = {
|
|
244
|
+
id,
|
|
245
|
+
machine,
|
|
246
|
+
stateRef,
|
|
247
|
+
stoppedRef,
|
|
248
|
+
eventQueueRef,
|
|
249
|
+
runtimeRef,
|
|
250
|
+
terminalExitDeferred,
|
|
251
|
+
listeners,
|
|
252
|
+
children: childrenMap,
|
|
253
|
+
transitions: transitionsPubSub,
|
|
254
|
+
system,
|
|
255
|
+
generation
|
|
256
|
+
};
|
|
312
257
|
/** Build lifecycle hooks for a generation */
|
|
313
258
|
const buildRuntimeLifecycle = () => {
|
|
314
259
|
stopEmitted = false;
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
260
|
+
let onEvent = void 0;
|
|
261
|
+
if (inspectorValue !== void 0) onEvent = (state, event) => emitWithTimestamp(inspectorValue, (timestamp) => ({
|
|
262
|
+
type: "@machine.event",
|
|
263
|
+
actorId: id,
|
|
264
|
+
state,
|
|
265
|
+
event,
|
|
266
|
+
timestamp
|
|
267
|
+
}));
|
|
268
|
+
let onFinal = void 0;
|
|
269
|
+
if (inspectorValue !== void 0) onFinal = (state) => Effect.gen(function* () {
|
|
270
|
+
stopEmitted = true;
|
|
271
|
+
yield* emitWithTimestamp(inspectorValue, (timestamp) => ({
|
|
272
|
+
type: "@machine.stop",
|
|
318
273
|
actorId: id,
|
|
319
|
-
state,
|
|
320
|
-
event,
|
|
274
|
+
finalState: state,
|
|
321
275
|
timestamp
|
|
322
|
-
}))
|
|
323
|
-
|
|
276
|
+
}));
|
|
277
|
+
});
|
|
278
|
+
let onInitialSpawnEffects = void 0;
|
|
279
|
+
if (inspectorValue !== void 0) onInitialSpawnEffects = (state) => emitWithTimestamp(inspectorValue, (timestamp) => ({
|
|
280
|
+
type: "@machine.effect",
|
|
281
|
+
actorId: id,
|
|
282
|
+
effectType: "spawn",
|
|
283
|
+
state,
|
|
284
|
+
timestamp
|
|
285
|
+
}));
|
|
286
|
+
return {
|
|
287
|
+
onEvent,
|
|
288
|
+
onStateChange: (result, event) => {
|
|
324
289
|
notifyListeners(listeners, result.newState);
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
generation,
|
|
330
|
-
previousState: result.previousState,
|
|
331
|
-
nextState: result.newState,
|
|
332
|
-
event
|
|
333
|
-
});
|
|
334
|
-
}
|
|
335
|
-
yield* Effect.annotateCurrentSpan("effect_machine.transition.matched", true);
|
|
336
|
-
if (result.lifecycleRan) {
|
|
337
|
-
yield* Effect.annotateCurrentSpan("effect_machine.state.from", result.previousState._tag);
|
|
338
|
-
yield* Effect.annotateCurrentSpan("effect_machine.state.to", result.newState._tag);
|
|
339
|
-
}
|
|
340
|
-
}),
|
|
341
|
-
onProcessed: (result, event) => result.transitioned ? PubSub.publish(transitionsPubSub, {
|
|
342
|
-
fromState: result.previousState,
|
|
343
|
-
toState: result.newState,
|
|
344
|
-
event
|
|
345
|
-
}).pipe(Effect.asVoid) : Effect.void,
|
|
346
|
-
onFinal: inspectorValue !== void 0 ? (state) => Effect.gen(function* () {
|
|
347
|
-
stopEmitted = true;
|
|
348
|
-
yield* emitWithTimestamp(inspectorValue, (timestamp) => ({
|
|
349
|
-
type: "@machine.stop",
|
|
290
|
+
const durability = lifecycle?.durability;
|
|
291
|
+
if (durability === void 0 || !result.transitioned) return;
|
|
292
|
+
if (!(durability.shouldSave === void 0 || durability.shouldSave(result.newState, result.previousState))) return;
|
|
293
|
+
return durability.save({
|
|
350
294
|
actorId: id,
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
295
|
+
generation: generation.current,
|
|
296
|
+
previousState: result.previousState,
|
|
297
|
+
nextState: result.newState,
|
|
298
|
+
event
|
|
299
|
+
});
|
|
300
|
+
},
|
|
301
|
+
onProcessed: (result, event) => {
|
|
302
|
+
if (!result.transitioned || transitionsPubSub.subscribers.size === 0) return;
|
|
303
|
+
return PubSub.publish(transitionsPubSub, {
|
|
304
|
+
fromState: result.previousState,
|
|
305
|
+
toState: result.newState,
|
|
306
|
+
event
|
|
307
|
+
}).pipe(Effect.asVoid);
|
|
308
|
+
},
|
|
309
|
+
onFinal,
|
|
355
310
|
onShutdown: () => Effect.gen(function* () {
|
|
356
311
|
if (!stopEmitted) {
|
|
357
312
|
const finalState = yield* SubscriptionRef.get(stateRef);
|
|
@@ -362,15 +317,8 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
|
|
|
362
317
|
timestamp
|
|
363
318
|
}));
|
|
364
319
|
}
|
|
365
|
-
yield* settlePendingReplies(pendingReplies, id);
|
|
366
320
|
}),
|
|
367
|
-
onInitialSpawnEffects
|
|
368
|
-
type: "@machine.effect",
|
|
369
|
-
actorId: id,
|
|
370
|
-
effectType: "spawn",
|
|
371
|
-
state,
|
|
372
|
-
timestamp
|
|
373
|
-
})) : void 0
|
|
321
|
+
onInitialSpawnEffects
|
|
374
322
|
};
|
|
375
323
|
};
|
|
376
324
|
/** Create a single runtime generation. machineForGen is machineWithState for initial, machine for restarts. */
|
|
@@ -384,11 +332,6 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
|
|
|
384
332
|
eventQueue: currentQueue
|
|
385
333
|
},
|
|
386
334
|
lifecycle: buildRuntimeLifecycle(),
|
|
387
|
-
wrapProcess: (state, event, inner) => Effect.withSpan("effect-machine.event.process", { attributes: {
|
|
388
|
-
"effect_machine.actor.id": id,
|
|
389
|
-
"effect_machine.state.current": state._tag,
|
|
390
|
-
"effect_machine.event.type": event._tag
|
|
391
|
-
} })(inner.pipe(Effect.tap((r) => Effect.annotateCurrentSpan("effect_machine.transition.matched", r.result.transitioned)))),
|
|
392
335
|
onChildSpawned: (childId, child) => Effect.gen(function* () {
|
|
393
336
|
childrenMap.set(childId, child);
|
|
394
337
|
const maybeScope = yield* Effect.serviceOption(Scope.Scope);
|
|
@@ -399,27 +342,26 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
|
|
|
399
342
|
})));
|
|
400
343
|
runtimeRef.current = yield* spawnGeneration(machineWithState);
|
|
401
344
|
const supervision = options?.supervision;
|
|
402
|
-
const stop = Effect.
|
|
345
|
+
const stop = Effect.fn("effect-machine.actor.stop")(function* () {
|
|
403
346
|
if (supervisorFiberRef.current !== void 0) yield* Fiber.interrupt(supervisorFiberRef.current);
|
|
404
347
|
const currentRuntime = runtimeRef.current;
|
|
405
348
|
if (currentRuntime !== void 0) yield* currentRuntime.stop;
|
|
406
349
|
yield* Deferred.succeed(terminalExitDeferred, { _tag: "Stopped" });
|
|
407
350
|
if (implicitSystemScope !== void 0) yield* Scope.close(implicitSystemScope, Exit.void);
|
|
408
|
-
}).pipe(Effect.
|
|
351
|
+
})().pipe(Effect.provide(serviceContext), Effect.asVoid);
|
|
409
352
|
const isHydrated = options?.initialState !== void 0;
|
|
410
|
-
|
|
353
|
+
const start = Effect.fn("effect-machine.actor.start")(function* () {
|
|
411
354
|
if (lifecycle?.recovery !== void 0 && !isHydrated) {
|
|
412
355
|
const resolved = yield* lifecycle.recovery.resolve({
|
|
413
356
|
actorId: id,
|
|
414
|
-
generation,
|
|
357
|
+
generation: generation.current,
|
|
415
358
|
machineInitial: machine.initial
|
|
416
359
|
});
|
|
417
360
|
if (Option.isSome(resolved)) {
|
|
418
361
|
yield* SubscriptionRef.set(stateRef, resolved.value);
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
} }));
|
|
362
|
+
const recoveredMachine = machine._withInitial(resolved.value);
|
|
363
|
+
const newRuntime = yield* spawnGeneration(recoveredMachine);
|
|
364
|
+
runtimeRef.current = newRuntime;
|
|
423
365
|
}
|
|
424
366
|
}
|
|
425
367
|
const currentState = yield* SubscriptionRef.get(stateRef);
|
|
@@ -429,26 +371,10 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
|
|
|
429
371
|
initialState: currentState,
|
|
430
372
|
timestamp
|
|
431
373
|
}));
|
|
432
|
-
if (supervision !== void 0) supervisorFiberRef.current = yield* Effect.forkDetach(runSupervisionLoop({
|
|
374
|
+
if (supervision !== void 0) supervisorFiberRef.current = yield* Effect.forkDetach(runSupervisionLoop(cell, {
|
|
433
375
|
supervision,
|
|
434
|
-
machine,
|
|
435
|
-
id,
|
|
436
|
-
runtimeRef,
|
|
437
|
-
terminalExitDeferred,
|
|
438
|
-
pendingReplies,
|
|
439
|
-
eventQueueRef,
|
|
440
|
-
stateRef,
|
|
441
|
-
stoppedRef,
|
|
442
|
-
childrenMap,
|
|
443
|
-
listeners,
|
|
444
376
|
spawnGeneration,
|
|
445
377
|
lifecycle,
|
|
446
|
-
generationRef: {
|
|
447
|
-
get: () => generation,
|
|
448
|
-
set: (g) => {
|
|
449
|
-
generation = g;
|
|
450
|
-
}
|
|
451
|
-
},
|
|
452
378
|
onRestart: options?.onRestart
|
|
453
379
|
}));
|
|
454
380
|
else {
|
|
@@ -457,13 +383,8 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
|
|
|
457
383
|
}
|
|
458
384
|
const currentRuntime = runtimeRef.current;
|
|
459
385
|
if (currentRuntime !== void 0) yield* currentRuntime.start;
|
|
460
|
-
}).pipe(Effect.
|
|
461
|
-
|
|
462
|
-
/** Fail all pending call/ask Deferreds with ActorStoppedError. Safe to call multiple times. */
|
|
463
|
-
const settlePendingReplies = (pendingReplies, actorId) => Effect.sync(() => {
|
|
464
|
-
const error = new ActorStoppedError({ actorId });
|
|
465
|
-
for (const deferred of pendingReplies) Effect.runFork(Deferred.fail(deferred, error));
|
|
466
|
-
pendingReplies.clear();
|
|
386
|
+
})().pipe(Effect.provide(serviceContext), Effect.asVoid);
|
|
387
|
+
return buildActorRefCore(cell, stop, start);
|
|
467
388
|
});
|
|
468
389
|
/** Notify all system event listeners (sync). */
|
|
469
390
|
const notifySystemListeners = (listeners, event) => {
|
|
@@ -482,13 +403,13 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
|
|
|
482
403
|
MutableHashMap.forEach(actorsMap, (actor) => {
|
|
483
404
|
stops.push(actor.stop);
|
|
484
405
|
});
|
|
485
|
-
return Effect.all(stops
|
|
406
|
+
return Effect.all(stops).pipe(Effect.andThen(PubSub.shutdown(eventPubSub)), Effect.asVoid);
|
|
486
407
|
});
|
|
487
408
|
/** Check for duplicate ID, register actor, attach scope cleanup if available */
|
|
488
409
|
const registerActor = Effect.fn("effect-machine.actorSystem.register")(function* (id, actor) {
|
|
489
410
|
if (MutableHashMap.has(actorsMap, id)) {
|
|
490
411
|
yield* actor.stop;
|
|
491
|
-
return yield*
|
|
412
|
+
return yield* DuplicateActorError.make({ actorId: id });
|
|
492
413
|
}
|
|
493
414
|
const actorRef = actor;
|
|
494
415
|
MutableHashMap.set(actorsMap, id, actorRef);
|
|
@@ -513,19 +434,24 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
|
|
|
513
434
|
return actor;
|
|
514
435
|
});
|
|
515
436
|
const spawnRegular = Effect.fn("effect-machine.actorSystem.spawnRegular")(function* (id, machine, spawnOptions) {
|
|
516
|
-
if (MutableHashMap.has(actorsMap, id)) return yield*
|
|
517
|
-
const materialized = spawnOptions?.slots !== void 0 ? materializeMachine(machine, spawnOptions.slots) : machine;
|
|
437
|
+
if (MutableHashMap.has(actorsMap, id)) return yield* DuplicateActorError.make({ actorId: id });
|
|
518
438
|
let actorRef;
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
439
|
+
let onRestart = void 0;
|
|
440
|
+
if (spawnOptions?.supervision !== void 0) onRestart = (generation, exit) => {
|
|
441
|
+
const currentActor = actorRef;
|
|
442
|
+
if (currentActor === void 0) return Effect.void;
|
|
443
|
+
return emitSystemEvent({
|
|
523
444
|
_tag: "ActorRestarted",
|
|
524
445
|
id,
|
|
525
|
-
actor:
|
|
446
|
+
actor: currentActor,
|
|
526
447
|
generation,
|
|
527
448
|
exit
|
|
528
|
-
})
|
|
449
|
+
});
|
|
450
|
+
};
|
|
451
|
+
const actor = yield* createActor(id, machine, {
|
|
452
|
+
supervision: spawnOptions?.supervision,
|
|
453
|
+
lifecycle: spawnOptions?.lifecycle,
|
|
454
|
+
onRestart
|
|
529
455
|
});
|
|
530
456
|
actorRef = actor;
|
|
531
457
|
yield* registerActor(id, actor);
|
|
@@ -580,4 +506,4 @@ const makeSystem = make;
|
|
|
580
506
|
*/
|
|
581
507
|
const Default = Layer.effect(ActorSystem, make());
|
|
582
508
|
//#endregion
|
|
583
|
-
export { ActorScope, ActorSystem, Default,
|
|
509
|
+
export { ActorScope, ActorSystem, Default, createActor, makeSystem };
|
package/dist/atom.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ActorRef } from "./actor.js";
|
|
2
|
+
import * as Atom from "effect/unstable/reactivity/Atom";
|
|
3
|
+
//#region src/atom.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* A writable Atom projection of an actor.
|
|
6
|
+
*
|
|
7
|
+
* The Atom value is the current actor state. Atom writes send actor events.
|
|
8
|
+
*/
|
|
9
|
+
type ActorAtom<State, Event> = Atom.Writable<State, Event>;
|
|
10
|
+
/**
|
|
11
|
+
* Make a writable Atom from an actor.
|
|
12
|
+
*
|
|
13
|
+
* The actor stays the single state owner. The Atom follows recovery,
|
|
14
|
+
* supervision restarts, and normal transitions through the actor's
|
|
15
|
+
* SubscriptionRef.
|
|
16
|
+
*/
|
|
17
|
+
declare const make: <State extends {
|
|
18
|
+
readonly _tag: string;
|
|
19
|
+
}, Event>(actor: ActorRef<State, Event>) => ActorAtom<State, Event>;
|
|
20
|
+
/**
|
|
21
|
+
* Select part of an actor state.
|
|
22
|
+
*
|
|
23
|
+
* The selected Atom stays writable. Writes still send events to the actor.
|
|
24
|
+
* The equality function controls when Atom subscribers receive a new value.
|
|
25
|
+
*/
|
|
26
|
+
declare const select: {
|
|
27
|
+
<State, Selection>(selector: (state: State) => Selection, equals?: (value: Selection, next: Selection) => boolean): <Event>(self: ActorAtom<State, Event>) => ActorAtom<Selection, Event>;
|
|
28
|
+
<State, Event, Selection>(self: ActorAtom<State, Event>, selector: (state: State) => Selection, equals?: (value: Selection, next: Selection) => boolean): ActorAtom<Selection, Event>;
|
|
29
|
+
};
|
|
30
|
+
//#endregion
|
|
31
|
+
export { ActorAtom, make, select };
|
package/dist/atom.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { dual } from "effect/Function";
|
|
2
|
+
import * as Atom from "effect/unstable/reactivity/Atom";
|
|
3
|
+
//#region src/atom.ts
|
|
4
|
+
/**
|
|
5
|
+
* Effect Atom integration for actors.
|
|
6
|
+
*
|
|
7
|
+
* The adapter keeps the actor as the state owner. Atom registries observe the
|
|
8
|
+
* actor's SubscriptionRef and write events through its synchronous boundary.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Make a writable Atom from an actor.
|
|
12
|
+
*
|
|
13
|
+
* The actor stays the single state owner. The Atom follows recovery,
|
|
14
|
+
* supervision restarts, and normal transitions through the actor's
|
|
15
|
+
* SubscriptionRef.
|
|
16
|
+
*/
|
|
17
|
+
const make = (actor) => {
|
|
18
|
+
const state = Atom.subscriptionRef(actor.state);
|
|
19
|
+
return Atom.writable((get) => get(state), (_ctx, event) => actor.sync.send(event));
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Select part of an actor state.
|
|
23
|
+
*
|
|
24
|
+
* The selected Atom stays writable. Writes still send events to the actor.
|
|
25
|
+
* The equality function controls when Atom subscribers receive a new value.
|
|
26
|
+
*/
|
|
27
|
+
const select = dual((args) => Atom.isAtom(args[0]), (self, selector, equals = Object.is) => Atom.withEquality(Atom.map(self, selector), equals));
|
|
28
|
+
//#endregion
|
|
29
|
+
export { make, select };
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { PersistedEvent, PersistenceAdapter, PersistenceAdapterService, Snapshot } from "../persistence.js";
|
|
2
2
|
import { Effect, Layer, Ref } from "effect";
|
|
3
|
-
|
|
4
3
|
//#region src/cluster/adapters/in-memory.d.ts
|
|
5
4
|
interface EntityStore {
|
|
6
5
|
snapshot: Snapshot<unknown> | undefined;
|