@typeonce/effect-machine 0.3.0 → 0.5.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 +410 -17
- package/dist/Machine.d.ts +1191 -510
- package/dist/Machine.d.ts.map +1 -1
- package/dist/Machine.js +371 -565
- package/dist/Machine.js.map +1 -1
- package/dist/internal/machine/activities.d.ts +57 -0
- package/dist/internal/machine/activities.d.ts.map +1 -0
- package/dist/internal/machine/activities.js +49 -0
- package/dist/internal/machine/activities.js.map +1 -0
- package/dist/internal/machine/atom.d.ts +69 -0
- package/dist/internal/machine/atom.d.ts.map +1 -0
- package/dist/{AtomMachine.js → internal/machine/atom.js} +20 -95
- package/dist/internal/machine/atom.js.map +1 -0
- package/dist/internal/machine/cluster.d.ts +63 -0
- package/dist/internal/machine/cluster.d.ts.map +1 -0
- package/dist/{ClusterMachine.js → internal/machine/cluster.js} +15 -119
- package/dist/internal/machine/cluster.js.map +1 -0
- package/dist/internal/machine/command.d.ts +16 -0
- package/dist/internal/machine/command.d.ts.map +1 -0
- package/dist/internal/machine/command.js +45 -0
- package/dist/internal/machine/command.js.map +1 -0
- package/dist/internal/machine/commandRuntime.d.ts +13 -0
- package/dist/internal/machine/commandRuntime.d.ts.map +1 -0
- package/dist/internal/machine/commandRuntime.js +16 -0
- package/dist/internal/machine/commandRuntime.js.map +1 -0
- package/dist/internal/{machineModel.d.ts → machine/configuration.d.ts} +12 -61
- package/dist/internal/machine/configuration.d.ts.map +1 -0
- package/dist/internal/{machineModel.js → machine/configuration.js} +250 -545
- package/dist/internal/machine/configuration.js.map +1 -0
- package/dist/internal/{machineErrors.d.ts → machine/errors.d.ts} +8 -8
- package/dist/internal/machine/errors.d.ts.map +1 -0
- package/dist/internal/{machineErrors.js → machine/errors.js} +8 -8
- package/dist/internal/machine/errors.js.map +1 -0
- package/dist/internal/machine/executionPlan.d.ts +49 -0
- package/dist/internal/machine/executionPlan.d.ts.map +1 -0
- package/dist/internal/machine/executionPlan.js +676 -0
- package/dist/internal/machine/executionPlan.js.map +1 -0
- package/dist/internal/machine/invocation.d.ts +23 -0
- package/dist/internal/machine/invocation.d.ts.map +1 -0
- package/dist/internal/machine/invocation.js +77 -0
- package/dist/internal/machine/invocation.js.map +1 -0
- package/dist/internal/machine/machine.d.ts +183 -0
- package/dist/internal/machine/machine.d.ts.map +1 -0
- package/dist/internal/machine/machine.js +552 -0
- package/dist/internal/machine/machine.js.map +1 -0
- package/dist/internal/machine/planner.d.ts +182 -0
- package/dist/internal/machine/planner.d.ts.map +1 -0
- package/dist/internal/machine/planner.js +1082 -0
- package/dist/internal/machine/planner.js.map +1 -0
- package/dist/internal/{machineProcess.d.ts → machine/process.d.ts} +11 -5
- package/dist/internal/machine/process.d.ts.map +1 -0
- package/dist/internal/machine/process.js +498 -0
- package/dist/internal/machine/process.js.map +1 -0
- package/dist/internal/machine/protocol.d.ts +34 -0
- package/dist/internal/machine/protocol.d.ts.map +1 -0
- package/dist/internal/machine/protocol.js +182 -0
- package/dist/internal/machine/protocol.js.map +1 -0
- package/dist/internal/machine/readiness.d.ts +4 -0
- package/dist/internal/machine/readiness.d.ts.map +1 -0
- package/dist/internal/machine/readiness.js +2 -0
- package/dist/internal/machine/readiness.js.map +1 -0
- package/dist/internal/machine/runtime.d.ts +255 -0
- package/dist/internal/machine/runtime.d.ts.map +1 -0
- package/dist/internal/machine/runtime.js +1489 -0
- package/dist/internal/machine/runtime.js.map +1 -0
- package/dist/internal/machine/serialization.d.ts +14 -0
- package/dist/internal/machine/serialization.d.ts.map +1 -0
- package/dist/internal/machine/serialization.js +338 -0
- package/dist/internal/machine/serialization.js.map +1 -0
- package/dist/internal/machine/stateDefinition.d.ts +15 -0
- package/dist/internal/machine/stateDefinition.d.ts.map +1 -0
- package/dist/internal/machine/stateDefinition.js +210 -0
- package/dist/internal/machine/stateDefinition.js.map +1 -0
- package/dist/internal/machine/symbols.d.ts +3 -0
- package/dist/internal/machine/symbols.d.ts.map +1 -0
- package/dist/internal/machine/symbols.js +3 -0
- package/dist/internal/machine/symbols.js.map +1 -0
- package/dist/internal/machine/topology.d.ts +98 -0
- package/dist/internal/machine/topology.d.ts.map +1 -0
- package/dist/internal/machine/topology.js +347 -0
- package/dist/internal/machine/topology.js.map +1 -0
- package/dist/internal/testing/machine/arbitrary.d.ts +36 -0
- package/dist/internal/testing/machine/arbitrary.d.ts.map +1 -0
- package/dist/internal/testing/machine/arbitrary.js +68 -0
- package/dist/internal/testing/machine/arbitrary.js.map +1 -0
- package/dist/internal/testing/machine/exploration.d.ts +30 -0
- package/dist/internal/testing/machine/exploration.d.ts.map +1 -0
- package/dist/internal/testing/machine/exploration.js +237 -0
- package/dist/internal/testing/machine/exploration.js.map +1 -0
- package/dist/internal/testing/machine/finiteModel.d.ts +322 -0
- package/dist/internal/testing/machine/finiteModel.d.ts.map +1 -0
- package/dist/internal/testing/machine/finiteModel.js +976 -0
- package/dist/internal/testing/machine/finiteModel.js.map +1 -0
- package/dist/internal/testing/machine/invariant.d.ts +31 -0
- package/dist/internal/testing/machine/invariant.d.ts.map +1 -0
- package/dist/internal/testing/machine/invariant.js +214 -0
- package/dist/internal/testing/machine/invariant.js.map +1 -0
- package/dist/internal/testing/machine/probe.d.ts +26 -0
- package/dist/internal/testing/machine/probe.d.ts.map +1 -0
- package/dist/internal/testing/machine/probe.js +45 -0
- package/dist/internal/testing/machine/probe.js.map +1 -0
- package/dist/internal/testing/machine/referenceModel.d.ts +203 -0
- package/dist/internal/testing/machine/referenceModel.d.ts.map +1 -0
- package/dist/internal/testing/machine/referenceModel.js +1012 -0
- package/dist/internal/testing/machine/referenceModel.js.map +1 -0
- package/dist/internal/testing/machine/runtime.d.ts +631 -0
- package/dist/internal/testing/machine/runtime.d.ts.map +1 -0
- package/dist/internal/testing/machine/runtime.js +726 -0
- package/dist/internal/testing/machine/runtime.js.map +1 -0
- package/dist/internal/testing/machine/runtimeInvariant.d.ts +32 -0
- package/dist/internal/testing/machine/runtimeInvariant.d.ts.map +1 -0
- package/dist/internal/testing/machine/runtimeInvariant.js +334 -0
- package/dist/internal/testing/machine/runtimeInvariant.js.map +1 -0
- package/dist/internal/testing/machine/trace.d.ts +17 -0
- package/dist/internal/testing/machine/trace.d.ts.map +1 -0
- package/dist/internal/testing/machine/trace.js +94 -0
- package/dist/internal/testing/machine/trace.js.map +1 -0
- package/dist/internal/testing/machine/verification.d.ts +36 -0
- package/dist/internal/testing/machine/verification.d.ts.map +1 -0
- package/dist/internal/testing/machine/verification.js +1375 -0
- package/dist/internal/testing/machine/verification.js.map +1 -0
- package/dist/testing/MachineTest.d.ts +1588 -0
- package/dist/testing/MachineTest.d.ts.map +1 -0
- package/dist/testing/MachineTest.js +406 -0
- package/dist/testing/MachineTest.js.map +1 -0
- package/dist/testing/index.d.ts +7 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +7 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/{ClusterMachine.d.ts → unstable/cluster/ClusterMachine.d.ts} +56 -45
- package/dist/unstable/cluster/ClusterMachine.d.ts.map +1 -0
- package/dist/unstable/cluster/ClusterMachine.js +144 -0
- package/dist/unstable/cluster/ClusterMachine.js.map +1 -0
- package/dist/{cluster.js → unstable/cluster/index.d.ts} +1 -1
- package/dist/unstable/cluster/index.d.ts.map +1 -0
- package/dist/{cluster.d.ts → unstable/cluster/index.js} +1 -1
- package/dist/unstable/cluster/index.js.map +1 -0
- package/dist/{AtomMachine.d.ts → unstable/reactivity/AtomMachine.d.ts} +116 -50
- package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -0
- package/dist/unstable/reactivity/AtomMachine.js +164 -0
- package/dist/unstable/reactivity/AtomMachine.js.map +1 -0
- package/dist/{reactivity.js → unstable/reactivity/index.d.ts} +1 -1
- package/dist/unstable/reactivity/index.d.ts.map +1 -0
- package/dist/{reactivity.d.ts → unstable/reactivity/index.js} +1 -1
- package/dist/unstable/reactivity/index.js.map +1 -0
- package/docs/agent-guide.md +316 -31
- package/package.json +28 -10
- package/dist/AtomMachine.d.ts.map +0 -1
- package/dist/AtomMachine.js.map +0 -1
- package/dist/ClusterMachine.d.ts.map +0 -1
- package/dist/ClusterMachine.js.map +0 -1
- package/dist/cluster.d.ts.map +0 -1
- package/dist/cluster.js.map +0 -1
- package/dist/internal/machineErrors.d.ts.map +0 -1
- package/dist/internal/machineErrors.js.map +0 -1
- package/dist/internal/machineModel.d.ts.map +0 -1
- package/dist/internal/machineModel.js.map +0 -1
- package/dist/internal/machinePlanner.d.ts +0 -74
- package/dist/internal/machinePlanner.d.ts.map +0 -1
- package/dist/internal/machinePlanner.js +0 -797
- package/dist/internal/machinePlanner.js.map +0 -1
- package/dist/internal/machineProcess.d.ts.map +0 -1
- package/dist/internal/machineProcess.js +0 -181
- package/dist/internal/machineProcess.js.map +0 -1
- package/dist/internal/machineRuntime.d.ts +0 -125
- package/dist/internal/machineRuntime.d.ts.map +0 -1
- package/dist/internal/machineRuntime.js +0 -365
- package/dist/internal/machineRuntime.js.map +0 -1
- package/dist/reactivity.d.ts.map +0 -1
- package/dist/reactivity.js.map +0 -1
|
@@ -0,0 +1,1489 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal machine process runtime helpers.
|
|
3
|
+
*
|
|
4
|
+
* @since 0.4.0
|
|
5
|
+
*/
|
|
6
|
+
import * as Cause from "effect/Cause";
|
|
7
|
+
import * as Channel from "effect/Channel";
|
|
8
|
+
import * as Context from "effect/Context";
|
|
9
|
+
import * as Deferred from "effect/Deferred";
|
|
10
|
+
import * as Effect from "effect/Effect";
|
|
11
|
+
import * as Exit from "effect/Exit";
|
|
12
|
+
import * as Fiber from "effect/Fiber";
|
|
13
|
+
import * as Option from "effect/Option";
|
|
14
|
+
import * as PubSub from "effect/PubSub";
|
|
15
|
+
import * as Queue from "effect/Queue";
|
|
16
|
+
import * as Scope from "effect/Scope";
|
|
17
|
+
import * as Stream from "effect/Stream";
|
|
18
|
+
import * as SynchronizedRef from "effect/SynchronizedRef";
|
|
19
|
+
import { ChildAlreadyExistsError, StoppedError } from "./errors.js";
|
|
20
|
+
/** @internal */
|
|
21
|
+
export const activeSnapshotObserver = Symbol.for("effect/Machine/activeSnapshotObserver");
|
|
22
|
+
/** @internal */
|
|
23
|
+
export const sendParentOverride = Symbol.for("effect/Machine/sendParentOverride");
|
|
24
|
+
/** @internal */
|
|
25
|
+
export const acknowledgedSend = Symbol.for("effect/Machine/acknowledgedSend");
|
|
26
|
+
const AcknowledgedMessageTypeId = Symbol("effect/Machine/AcknowledgedMessage");
|
|
27
|
+
/** @internal */
|
|
28
|
+
export const isAcknowledgedMessage = (message) => typeof message === "object" && message !== null && AcknowledgedMessageTypeId in message;
|
|
29
|
+
/** @internal */
|
|
30
|
+
export const messageEvent = (message) => isAcknowledgedMessage(message) ? message.event : message;
|
|
31
|
+
const succeedAcknowledgedMessage = (message, delivery) => {
|
|
32
|
+
if (message !== undefined && isAcknowledgedMessage(message)) {
|
|
33
|
+
Deferred.doneUnsafe(message.deferred, Effect.succeed(delivery));
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const failAcknowledgedMessage = (message, cause) => {
|
|
37
|
+
if (message !== undefined && isAcknowledgedMessage(message)) {
|
|
38
|
+
Deferred.doneUnsafe(message.deferred, Effect.failCause(cause));
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const stopAcknowledgedMessage = (message) => {
|
|
42
|
+
if (message !== undefined && isAcknowledgedMessage(message)) {
|
|
43
|
+
Deferred.doneUnsafe(message.deferred, Effect.fail(new StoppedError()));
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const offerChildObservation = (observer, value) => {
|
|
47
|
+
if (observer.values === undefined) {
|
|
48
|
+
observer.values = [value];
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
observer.values.push(value);
|
|
52
|
+
}
|
|
53
|
+
if (observer.waiter !== undefined) {
|
|
54
|
+
const waiter = observer.waiter;
|
|
55
|
+
observer.waiter = undefined;
|
|
56
|
+
Deferred.doneUnsafe(waiter, Effect.void);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
const takeChildObservations = (observer) => Effect.suspend(() => {
|
|
60
|
+
if (observer.values !== undefined) {
|
|
61
|
+
const values = observer.values;
|
|
62
|
+
observer.values = undefined;
|
|
63
|
+
return Effect.succeed(values);
|
|
64
|
+
}
|
|
65
|
+
const waiter = Deferred.makeUnsafe();
|
|
66
|
+
observer.waiter = waiter;
|
|
67
|
+
return Deferred.await(waiter).pipe(Effect.andThen(takeChildObservations(observer)));
|
|
68
|
+
});
|
|
69
|
+
const matchesChild = (entry, child) => entry._tag === "Started" && (typeof child === "string" || (entry.descriptor !== undefined &&
|
|
70
|
+
entry.descriptor.id === child.id &&
|
|
71
|
+
entry.descriptor.machine === child.machine));
|
|
72
|
+
const selectRegistryChild = (registry, id, child) => {
|
|
73
|
+
if (registry.closed)
|
|
74
|
+
return Option.none();
|
|
75
|
+
const entry = registry.children.get(id);
|
|
76
|
+
return entry !== undefined && matchesChild(entry, child) ? Option.some(entry.ref) : Option.none();
|
|
77
|
+
};
|
|
78
|
+
const publishRegistryChange = (registry) => {
|
|
79
|
+
if (registry.observers === undefined)
|
|
80
|
+
return;
|
|
81
|
+
for (const observer of registry.observers) {
|
|
82
|
+
offerChildObservation(observer, selectRegistryChild(registry, observer.id, observer.child));
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
const unregisterChild = (registry, key, token) => {
|
|
86
|
+
const entry = registry.children.get(key);
|
|
87
|
+
if (entry === undefined || entry.token !== token)
|
|
88
|
+
return;
|
|
89
|
+
registry.children.delete(key);
|
|
90
|
+
if (typeof key === "string")
|
|
91
|
+
publishRegistryChange(registry);
|
|
92
|
+
};
|
|
93
|
+
const registerChild = (registry, key, token, ref, descriptor) => {
|
|
94
|
+
const entry = registry.children.get(key);
|
|
95
|
+
if (registry.closed || entry === undefined || entry._tag !== "Starting" || entry.token !== token) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
registry.children.delete(key);
|
|
99
|
+
const started = entry.ownerKey === undefined
|
|
100
|
+
? { _tag: "Started", token, descriptor, ref }
|
|
101
|
+
: {
|
|
102
|
+
_tag: "Started",
|
|
103
|
+
token,
|
|
104
|
+
descriptor,
|
|
105
|
+
ref,
|
|
106
|
+
ownerKey: entry.ownerKey,
|
|
107
|
+
ownerPath: entry.ownerPath,
|
|
108
|
+
ownerActive: entry.ownerActive === true
|
|
109
|
+
};
|
|
110
|
+
registry.children.set(key, started);
|
|
111
|
+
if (typeof key === "string")
|
|
112
|
+
publishRegistryChange(registry);
|
|
113
|
+
return true;
|
|
114
|
+
};
|
|
115
|
+
const executionIsChildless = (execution) => execution?._tag === "Childless" || execution?.childless === true;
|
|
116
|
+
const offerCompactMailbox = (mailbox, event) => {
|
|
117
|
+
const items = mailbox.items ?? [];
|
|
118
|
+
mailbox.items = items;
|
|
119
|
+
items.push(event);
|
|
120
|
+
};
|
|
121
|
+
const pollCompactMailbox = (mailbox) => {
|
|
122
|
+
if (mailbox.items === undefined) {
|
|
123
|
+
return Option.none();
|
|
124
|
+
}
|
|
125
|
+
const event = mailbox.items[mailbox.index];
|
|
126
|
+
mailbox.index += 1;
|
|
127
|
+
if (mailbox.index === mailbox.items.length) {
|
|
128
|
+
mailbox.items = undefined;
|
|
129
|
+
mailbox.index = 0;
|
|
130
|
+
}
|
|
131
|
+
return Option.some(event);
|
|
132
|
+
};
|
|
133
|
+
const closeCompactMailbox = (mailbox) => {
|
|
134
|
+
if (mailbox.items !== undefined) {
|
|
135
|
+
for (let index = mailbox.index; index < mailbox.items.length; index += 1) {
|
|
136
|
+
stopAcknowledgedMessage(mailbox.items[index]);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
mailbox.closed = true;
|
|
140
|
+
mailbox.items = undefined;
|
|
141
|
+
mailbox.index = 0;
|
|
142
|
+
};
|
|
143
|
+
export class MachineRuntime extends Context.Service()("effect/Machine/MachineRuntime") {
|
|
144
|
+
}
|
|
145
|
+
export const provideMachineRuntime = (effect, scope) => Effect.provideService(effect, MachineRuntime, scope);
|
|
146
|
+
const classifyOutcome = (snapshot) => {
|
|
147
|
+
switch (snapshot.status) {
|
|
148
|
+
case "active": {
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
case "done": {
|
|
152
|
+
return {
|
|
153
|
+
_tag: "Done",
|
|
154
|
+
output: snapshot.output,
|
|
155
|
+
snapshot
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
case "error": {
|
|
159
|
+
const failure = snapshot.cause.reasons.find(Cause.isFailReason);
|
|
160
|
+
if (failure !== undefined) {
|
|
161
|
+
return {
|
|
162
|
+
_tag: "Failure",
|
|
163
|
+
error: failure.error,
|
|
164
|
+
cause: snapshot.cause,
|
|
165
|
+
snapshot
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
const defect = snapshot.cause.reasons.find(Cause.isDieReason);
|
|
169
|
+
if (defect !== undefined) {
|
|
170
|
+
return {
|
|
171
|
+
_tag: "Defect",
|
|
172
|
+
defect: defect.defect,
|
|
173
|
+
cause: snapshot.cause,
|
|
174
|
+
snapshot
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
const interrupted = snapshot.cause.reasons.find(Cause.isInterruptReason);
|
|
178
|
+
if (interrupted !== undefined) {
|
|
179
|
+
return {
|
|
180
|
+
_tag: "Interrupted",
|
|
181
|
+
cause: snapshot.cause,
|
|
182
|
+
snapshot
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
_tag: "Cause",
|
|
187
|
+
cause: snapshot.cause,
|
|
188
|
+
snapshot
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
case "stopped": {
|
|
192
|
+
return {
|
|
193
|
+
_tag: "Stopped",
|
|
194
|
+
snapshot
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
const notifyActiveSnapshot = (onSnapshot, snapshot) => Effect.suspend(() => onSnapshot(snapshot)).pipe(Effect.exit, Effect.asVoid);
|
|
200
|
+
export const watch = (ref) => ref.changes.pipe(Stream.filter((snapshot) => snapshot.status !== "active"), Stream.map((snapshot) => classifyOutcome(snapshot)), Stream.take(1));
|
|
201
|
+
const makeProcessRuntime = Effect.sync(() => {
|
|
202
|
+
let sessionIdCounter = 0;
|
|
203
|
+
return {
|
|
204
|
+
nextSessionId: Effect.sync(() => `machine:${sessionIdCounter++}`)
|
|
205
|
+
};
|
|
206
|
+
});
|
|
207
|
+
class OwnedChildRuntimeImpl {
|
|
208
|
+
registry;
|
|
209
|
+
self;
|
|
210
|
+
runtime;
|
|
211
|
+
services;
|
|
212
|
+
scopedServices;
|
|
213
|
+
constructor(registry, self, runtime, services) {
|
|
214
|
+
this.registry = registry;
|
|
215
|
+
this.self = self;
|
|
216
|
+
this.runtime = runtime;
|
|
217
|
+
this.services = services;
|
|
218
|
+
}
|
|
219
|
+
has(key) {
|
|
220
|
+
for (const entry of this.registry.children.values()) {
|
|
221
|
+
if (entry.ownerActive && entry.ownerKey === key)
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
stopEntry(entry) {
|
|
227
|
+
entry.ownerActive = false;
|
|
228
|
+
return entry._tag === "Started" ? entry.ref.stop : Effect.void;
|
|
229
|
+
}
|
|
230
|
+
spawn(makeLogic, options) {
|
|
231
|
+
const token = Symbol();
|
|
232
|
+
let startedChild;
|
|
233
|
+
const isCurrent = () => {
|
|
234
|
+
const entry = this.registry.children.get(options.id);
|
|
235
|
+
return entry?.token === token && entry.ownerKey === options.key && entry.ownerActive === true;
|
|
236
|
+
};
|
|
237
|
+
return Effect.suspend(() => {
|
|
238
|
+
if (this.registry.closed)
|
|
239
|
+
return Effect.interrupt;
|
|
240
|
+
if (this.has(options.key) || this.registry.children.has(options.id)) {
|
|
241
|
+
return Effect.fail(new ChildAlreadyExistsError({ id: options.duplicateId }));
|
|
242
|
+
}
|
|
243
|
+
const logic = makeLogic();
|
|
244
|
+
const scope = this.registry.scope ??= Scope.makeUnsafe("parallel");
|
|
245
|
+
this.registry.children.set(options.id, {
|
|
246
|
+
_tag: "Starting",
|
|
247
|
+
token,
|
|
248
|
+
ownerKey: options.key,
|
|
249
|
+
ownerPath: options.path,
|
|
250
|
+
ownerActive: true
|
|
251
|
+
});
|
|
252
|
+
const startOptions = {
|
|
253
|
+
detached: true,
|
|
254
|
+
id: options.id,
|
|
255
|
+
sendParent: (event) => options.sendParent(isCurrent, event),
|
|
256
|
+
onOutcome: (outcome) => options.onOutcome(isCurrent, outcome),
|
|
257
|
+
...(options.onSnapshot === undefined
|
|
258
|
+
? undefined
|
|
259
|
+
: { onSnapshot: (snapshot) => options.onSnapshot(isCurrent, snapshot) }),
|
|
260
|
+
onReadySync: (child) => {
|
|
261
|
+
startedChild = child;
|
|
262
|
+
return registerChild(this.registry, options.id, token, child, options.descriptor);
|
|
263
|
+
},
|
|
264
|
+
onStopSync: () => unregisterChild(this.registry, options.id, token),
|
|
265
|
+
skipStoppedOutcome: true,
|
|
266
|
+
parent: this.self,
|
|
267
|
+
runtime: this.runtime
|
|
268
|
+
};
|
|
269
|
+
const execution = logic.execution;
|
|
270
|
+
const synchronous = this.services !== undefined && options.onSnapshot === undefined &&
|
|
271
|
+
execution?._tag === "Compiled" && execution.childless && execution.drain._tag === "Owned" &&
|
|
272
|
+
execution.initialSync !== undefined;
|
|
273
|
+
const start = synchronous
|
|
274
|
+
? Effect.flatMap(this.runtime.nextSessionId, (sessionId) => new CompiledProcess(logic, startOptions, this.scopedServices ??= Context.add(this.services, Scope.Scope, scope), sessionId).initializeCompiledSync())
|
|
275
|
+
: startLogicInternal(logic, startOptions);
|
|
276
|
+
const guarded = start.pipe(Effect.onExit((exit) => {
|
|
277
|
+
if (Exit.isSuccess(exit))
|
|
278
|
+
return Effect.void;
|
|
279
|
+
unregisterChild(this.registry, options.id, token);
|
|
280
|
+
return startedChild === undefined ? Effect.void : startedChild.stop;
|
|
281
|
+
}));
|
|
282
|
+
return (synchronous ? guarded : Scope.provide(guarded, scope)).pipe(Effect.asVoid);
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
stopAll() {
|
|
286
|
+
return Effect.suspend(() => {
|
|
287
|
+
const effects = [];
|
|
288
|
+
for (const entry of this.registry.children.values()) {
|
|
289
|
+
if (entry.ownerActive)
|
|
290
|
+
effects.push(this.stopEntry(entry));
|
|
291
|
+
}
|
|
292
|
+
return effects.length === 0
|
|
293
|
+
? Effect.void
|
|
294
|
+
: effects.length === 1
|
|
295
|
+
? effects[0]
|
|
296
|
+
: Effect.all(effects, { concurrency: "unbounded", discard: true });
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
stopPaths(paths) {
|
|
300
|
+
if (paths.length === 0)
|
|
301
|
+
return undefined;
|
|
302
|
+
const pathSet = new Set(paths);
|
|
303
|
+
const effects = [];
|
|
304
|
+
for (const entry of this.registry.children.values()) {
|
|
305
|
+
if (entry.ownerActive && entry.ownerPath !== undefined && pathSet.has(entry.ownerPath)) {
|
|
306
|
+
effects.push(this.stopEntry(entry));
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return effects.length === 0
|
|
310
|
+
? undefined
|
|
311
|
+
: effects.length === 1
|
|
312
|
+
? effects[0]
|
|
313
|
+
: Effect.all(effects, { concurrency: "unbounded", discard: true });
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
const noChildChanges = Stream.succeed(Option.none()).pipe(Stream.concat(Stream.never));
|
|
317
|
+
const noParentSend = (_event) => Effect.void;
|
|
318
|
+
const childlessRuntime = {
|
|
319
|
+
close: () => Effect.void,
|
|
320
|
+
spawn: (() => Effect.die(new Error("Childless machine logic cannot spawn a process"))),
|
|
321
|
+
get: () => Effect.succeed(Option.none()),
|
|
322
|
+
changes: () => noChildChanges,
|
|
323
|
+
sendTo: () => Effect.void,
|
|
324
|
+
stop: () => Effect.void,
|
|
325
|
+
owned: {
|
|
326
|
+
spawn: () => Effect.die(new Error("Childless machine logic cannot spawn an owned process")),
|
|
327
|
+
stopAll: () => Effect.void,
|
|
328
|
+
stopPaths: () => undefined
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
const makeChildRuntimeSync = (self, runtime, services) => {
|
|
332
|
+
// Child-registry decisions are synchronous and every access below runs in
|
|
333
|
+
// one Effect.sync / Effect.suspend step. Keep the unobserved representation
|
|
334
|
+
// compact; selector-specific handoffs are installed only while
|
|
335
|
+
// childChanges streams are running.
|
|
336
|
+
const registry = {
|
|
337
|
+
closed: false,
|
|
338
|
+
children: new Map(),
|
|
339
|
+
observers: undefined,
|
|
340
|
+
scope: undefined
|
|
341
|
+
};
|
|
342
|
+
const close = (exit) => Effect.suspend(() => {
|
|
343
|
+
if (registry.closed) {
|
|
344
|
+
return Effect.void;
|
|
345
|
+
}
|
|
346
|
+
registry.closed = true;
|
|
347
|
+
if (registry.scope === undefined) {
|
|
348
|
+
return Effect.void;
|
|
349
|
+
}
|
|
350
|
+
const finalizers = Scope.closeUnsafe(registry.scope, exit);
|
|
351
|
+
let first;
|
|
352
|
+
let rest;
|
|
353
|
+
for (const entry of registry.children.values()) {
|
|
354
|
+
if (entry._tag !== "Started") {
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
if (first === undefined) {
|
|
358
|
+
first = entry.ref.stop;
|
|
359
|
+
}
|
|
360
|
+
else {
|
|
361
|
+
rest ??= [first];
|
|
362
|
+
rest.push(entry.ref.stop);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
if (finalizers !== undefined) {
|
|
366
|
+
if (first === undefined) {
|
|
367
|
+
first = finalizers;
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
rest ??= [first];
|
|
371
|
+
rest.push(finalizers);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
const cleanup = rest ?? first;
|
|
375
|
+
return cleanup === undefined
|
|
376
|
+
? Effect.void
|
|
377
|
+
: Array.isArray(cleanup)
|
|
378
|
+
? Effect.all(cleanup, { concurrency: "unbounded", discard: true })
|
|
379
|
+
: cleanup;
|
|
380
|
+
});
|
|
381
|
+
const unregister = (key, token) => Effect.sync(() => unregisterChild(registry, key, token));
|
|
382
|
+
const register = (key, token, ref, descriptor) => Effect.sync(() => registerChild(registry, key, token, ref, descriptor));
|
|
383
|
+
const get = (child) => {
|
|
384
|
+
const id = typeof child === "string" ? child : child.id;
|
|
385
|
+
return Effect.sync(() => {
|
|
386
|
+
if (registry.closed) {
|
|
387
|
+
return Option.none();
|
|
388
|
+
}
|
|
389
|
+
const entry = registry.children.get(id);
|
|
390
|
+
return entry !== undefined && matchesChild(entry, child)
|
|
391
|
+
? Option.some(entry.ref)
|
|
392
|
+
: Option.none();
|
|
393
|
+
});
|
|
394
|
+
};
|
|
395
|
+
const changes = (child) => {
|
|
396
|
+
const id = typeof child === "string" ? child : child.id;
|
|
397
|
+
return Stream.fromChannel(Channel.fromTransform((_, streamScope) => Effect.sync(() => ({ child, id, values: undefined, waiter: undefined })).pipe(Effect.flatMap((observer) => {
|
|
398
|
+
const removeObserver = Effect.sync(() => {
|
|
399
|
+
if (registry.observers !== undefined) {
|
|
400
|
+
registry.observers.delete(observer);
|
|
401
|
+
if (registry.observers.size === 0) {
|
|
402
|
+
registry.observers = undefined;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
observer.values = undefined;
|
|
406
|
+
observer.waiter = undefined;
|
|
407
|
+
});
|
|
408
|
+
return Scope.addFinalizer(streamScope, removeObserver).pipe(Effect.andThen(Effect.sync(() => {
|
|
409
|
+
if (!registry.closed && streamScope.state._tag !== "Closed") {
|
|
410
|
+
registry.observers ??= new Set();
|
|
411
|
+
registry.observers.add(observer);
|
|
412
|
+
}
|
|
413
|
+
offerChildObservation(observer, selectRegistryChild(registry, id, child));
|
|
414
|
+
})), Effect.as(takeChildObservations(observer)));
|
|
415
|
+
}))));
|
|
416
|
+
};
|
|
417
|
+
const sendTo = (child, event) => {
|
|
418
|
+
const id = typeof child === "string" ? child : child.id;
|
|
419
|
+
return Effect.suspend(() => {
|
|
420
|
+
if (registry.closed) {
|
|
421
|
+
return Effect.void;
|
|
422
|
+
}
|
|
423
|
+
const entry = registry.children.get(id);
|
|
424
|
+
return entry !== undefined && matchesChild(entry, child)
|
|
425
|
+
? entry.ref.send(event)
|
|
426
|
+
: Effect.void;
|
|
427
|
+
});
|
|
428
|
+
};
|
|
429
|
+
const stop = (child) => {
|
|
430
|
+
const id = typeof child === "string" ? child : child.id;
|
|
431
|
+
return Effect.suspend(() => {
|
|
432
|
+
if (registry.closed) {
|
|
433
|
+
return Effect.void;
|
|
434
|
+
}
|
|
435
|
+
const entry = registry.children.get(id);
|
|
436
|
+
return entry !== undefined && matchesChild(entry, child)
|
|
437
|
+
? entry.ref.stop
|
|
438
|
+
: Effect.void;
|
|
439
|
+
});
|
|
440
|
+
};
|
|
441
|
+
function spawn(logic, spawnOptions) {
|
|
442
|
+
const token = Symbol();
|
|
443
|
+
const key = spawnOptions?.id ?? token;
|
|
444
|
+
let startedChild;
|
|
445
|
+
return Effect.suspend(() => {
|
|
446
|
+
if (registry.closed) {
|
|
447
|
+
return Effect.interrupt;
|
|
448
|
+
}
|
|
449
|
+
if (typeof key === "string" && registry.children.has(key)) {
|
|
450
|
+
return Effect.fail(new ChildAlreadyExistsError({ id: key }));
|
|
451
|
+
}
|
|
452
|
+
registry.scope ??= Scope.makeUnsafe("parallel");
|
|
453
|
+
registry.children.set(key, { _tag: "Starting", token });
|
|
454
|
+
return startLogicInternal(logic, {
|
|
455
|
+
detached: true,
|
|
456
|
+
...(spawnOptions?.id === undefined ? undefined : { id: spawnOptions.id }),
|
|
457
|
+
...(spawnOptions?.onOutcome === undefined ? undefined : { onOutcome: spawnOptions.onOutcome }),
|
|
458
|
+
...(spawnOptions?.[activeSnapshotObserver] === undefined
|
|
459
|
+
? undefined
|
|
460
|
+
: { onSnapshot: spawnOptions[activeSnapshotObserver] }),
|
|
461
|
+
...(spawnOptions?.[sendParentOverride] === undefined
|
|
462
|
+
? undefined
|
|
463
|
+
: { sendParent: spawnOptions[sendParentOverride] }),
|
|
464
|
+
onReady: (child, requestChildStop) => Effect.sync(() => {
|
|
465
|
+
startedChild = child;
|
|
466
|
+
}).pipe(Effect.andThen(register(key, token, child, spawnOptions?.descriptor)), Effect.flatMap((registered) => registered ? Effect.void : requestChildStop)),
|
|
467
|
+
onStop: unregister(key, token),
|
|
468
|
+
parent: self,
|
|
469
|
+
runtime
|
|
470
|
+
}).pipe(Effect.onExit((exit) => Exit.isFailure(exit)
|
|
471
|
+
? unregister(key, token).pipe(Effect.andThen(startedChild === undefined ? Effect.void : startedChild.stop))
|
|
472
|
+
: Effect.void), Scope.provide(registry.scope));
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
return {
|
|
476
|
+
close,
|
|
477
|
+
spawn,
|
|
478
|
+
get,
|
|
479
|
+
changes,
|
|
480
|
+
sendTo,
|
|
481
|
+
stop,
|
|
482
|
+
owned: new OwnedChildRuntimeImpl(registry, self, runtime, services)
|
|
483
|
+
};
|
|
484
|
+
};
|
|
485
|
+
const makeChildRuntime = (self, runtime, services) => Effect.sync(() => makeChildRuntimeSync(self, runtime, services));
|
|
486
|
+
// `Machine.logic` permits an arbitrary Effect program, including programs that
|
|
487
|
+
// suspend or supervise their own fibers. Keep its two-fiber worker/supervisor
|
|
488
|
+
// protocol as the general contract rather than weakening it for statecharts.
|
|
489
|
+
const startGenericInternal = Effect.fnUntraced(function* (logic, options) {
|
|
490
|
+
const { detached, id: requestedId, onOutcome, onReady, onReadySync, onSnapshot, onStop, onStopSync, parent, runtime, sendParent: overrideSendParent } = options;
|
|
491
|
+
const sessionId = yield* runtime.nextSessionId;
|
|
492
|
+
const id = requestedId ?? sessionId;
|
|
493
|
+
const queue = yield* Queue.unbounded();
|
|
494
|
+
const termination = yield* Deferred.make();
|
|
495
|
+
const done = yield* Deferred.make();
|
|
496
|
+
const awaitCompletion = Deferred.await(done).pipe(Effect.exit, Effect.asVoid);
|
|
497
|
+
let initializing = true;
|
|
498
|
+
let inFlightMessage;
|
|
499
|
+
const requestStop = Deferred.succeed(termination, { _tag: "Stopped" }).pipe(Effect.asVoid);
|
|
500
|
+
const self = {
|
|
501
|
+
id,
|
|
502
|
+
sessionId,
|
|
503
|
+
// Initialization must finish constructing a state before a stopped
|
|
504
|
+
// snapshot can be published. A stop requested there is therefore recorded
|
|
505
|
+
// and returns so initialization can finish. Once running, the requesting
|
|
506
|
+
// process waits forever and is interrupted by the supervisor after the
|
|
507
|
+
// stop request wins, so execution never continues after `self.stop`.
|
|
508
|
+
stop: Effect.suspend(() => initializing
|
|
509
|
+
? requestStop
|
|
510
|
+
: requestStop.pipe(Effect.andThen(Effect.never))),
|
|
511
|
+
send: (event) => Queue.offer(queue, event).pipe(Effect.flatMap((accepted) => accepted ? Effect.void : Effect.fail(new StoppedError())))
|
|
512
|
+
};
|
|
513
|
+
const sendAcknowledged = logic.execution?._tag !== "Compiled"
|
|
514
|
+
? undefined
|
|
515
|
+
: (event) => Effect.uninterruptibleMask((restore) => Deferred.make().pipe(Effect.flatMap((deferred) => Queue.offer(queue, {
|
|
516
|
+
[AcknowledgedMessageTypeId]: true,
|
|
517
|
+
event,
|
|
518
|
+
deferred
|
|
519
|
+
}).pipe(Effect.flatMap((accepted) => accepted
|
|
520
|
+
? restore(Deferred.await(deferred))
|
|
521
|
+
: Effect.fail(new StoppedError())))), Effect.map((delivery) => delivery)));
|
|
522
|
+
let { changes: childChanges, close: closeChildren, get: getChild, owned: ownedChildren, sendTo, spawn, stop: stopChild } = childlessRuntime;
|
|
523
|
+
if (!executionIsChildless(logic.execution)) {
|
|
524
|
+
;
|
|
525
|
+
({
|
|
526
|
+
changes: childChanges,
|
|
527
|
+
close: closeChildren,
|
|
528
|
+
get: getChild,
|
|
529
|
+
owned: ownedChildren,
|
|
530
|
+
sendTo,
|
|
531
|
+
spawn,
|
|
532
|
+
stop: stopChild
|
|
533
|
+
} = yield* makeChildRuntime(self, runtime));
|
|
534
|
+
}
|
|
535
|
+
const cleanupStartupFailure = (exit) => Exit.isFailure(exit)
|
|
536
|
+
? closeChildren(exit)
|
|
537
|
+
: Effect.void;
|
|
538
|
+
const cleanup = onStopSync === undefined ? onStop ?? Effect.void : Effect.sync(onStopSync);
|
|
539
|
+
const sendParent = overrideSendParent ?? (parent === undefined ? noParentSend : parent.send);
|
|
540
|
+
const scope = {
|
|
541
|
+
self,
|
|
542
|
+
parent,
|
|
543
|
+
spawn,
|
|
544
|
+
sendParent,
|
|
545
|
+
sendTo,
|
|
546
|
+
stopChild,
|
|
547
|
+
failCause: (cause) => Deferred.succeed(termination, {
|
|
548
|
+
_tag: "Failure",
|
|
549
|
+
cause: cause
|
|
550
|
+
}).pipe(Effect.asVoid)
|
|
551
|
+
};
|
|
552
|
+
const initial = yield* logic.initial(scope).pipe(Effect.onExit(cleanupStartupFailure), Effect.ensuring(Effect.sync(() => {
|
|
553
|
+
initializing = false;
|
|
554
|
+
})));
|
|
555
|
+
const current = yield* SynchronizedRef.make({
|
|
556
|
+
revision: 0,
|
|
557
|
+
terminalizing: false,
|
|
558
|
+
changes: undefined,
|
|
559
|
+
snapshot: {
|
|
560
|
+
status: "active",
|
|
561
|
+
state: initial
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
const publishSnapshot = onSnapshot === undefined
|
|
565
|
+
? (snapshot) => snapshot.changes === undefined
|
|
566
|
+
? Effect.succeed(snapshot)
|
|
567
|
+
: PubSub.publish(snapshot.changes, [snapshot]).pipe(Effect.as(snapshot))
|
|
568
|
+
: (snapshot) => {
|
|
569
|
+
const publish = snapshot.changes === undefined
|
|
570
|
+
? Effect.succeed(snapshot)
|
|
571
|
+
: PubSub.publish(snapshot.changes, [snapshot]).pipe(Effect.as(snapshot));
|
|
572
|
+
const runtimeSnapshot = snapshot.snapshot;
|
|
573
|
+
return runtimeSnapshot.status !== "active"
|
|
574
|
+
? publish
|
|
575
|
+
: publish.pipe(Effect.tap(() => notifyActiveSnapshot(onSnapshot, runtimeSnapshot)));
|
|
576
|
+
};
|
|
577
|
+
const completeChanges = (snapshot) => snapshot.changes === undefined
|
|
578
|
+
? Effect.void
|
|
579
|
+
: PubSub.publish(snapshot.changes, Exit.succeed(undefined)).pipe(Effect.asVoid);
|
|
580
|
+
const completeIfTerminal = (snapshot) => {
|
|
581
|
+
if (snapshot.snapshot.status === "active") {
|
|
582
|
+
return Effect.succeed(snapshot);
|
|
583
|
+
}
|
|
584
|
+
return completeChanges(snapshot).pipe(Effect.as(snapshot));
|
|
585
|
+
};
|
|
586
|
+
const publishIfCurrent = (snapshot) => SynchronizedRef.get(current).pipe(Effect.flatMap((currentSnapshot) => currentSnapshot.revision === snapshot.revision
|
|
587
|
+
? publishSnapshot(snapshot).pipe(Effect.flatMap(completeIfTerminal))
|
|
588
|
+
: Effect.succeed(undefined)));
|
|
589
|
+
const updateSnapshot = (f) => SynchronizedRef.modifyEffect(current, (current) => current.terminalizing
|
|
590
|
+
? Effect.succeed([undefined, current])
|
|
591
|
+
: Effect.map(f(current.snapshot), (next) => {
|
|
592
|
+
if (next === undefined) {
|
|
593
|
+
return [undefined, current];
|
|
594
|
+
}
|
|
595
|
+
const versioned = {
|
|
596
|
+
revision: current.revision + 1,
|
|
597
|
+
snapshot: next,
|
|
598
|
+
terminalizing: false,
|
|
599
|
+
changes: current.changes
|
|
600
|
+
};
|
|
601
|
+
return [versioned, versioned];
|
|
602
|
+
})).pipe(Effect.flatMap((versioned) => versioned === undefined ? Effect.succeed(undefined) : publishIfCurrent(versioned)), Effect.map((published) => published?.snapshot));
|
|
603
|
+
const reserveTerminalSnapshot = (f) => SynchronizedRef.modify(current, (current) => {
|
|
604
|
+
if (current.terminalizing || current.snapshot.status !== "active") {
|
|
605
|
+
return [undefined, current];
|
|
606
|
+
}
|
|
607
|
+
return [
|
|
608
|
+
{
|
|
609
|
+
revision: current.revision + 1,
|
|
610
|
+
snapshot: f(current.snapshot),
|
|
611
|
+
terminalizing: true,
|
|
612
|
+
changes: current.changes
|
|
613
|
+
},
|
|
614
|
+
{ ...current, terminalizing: true }
|
|
615
|
+
];
|
|
616
|
+
}).pipe(Effect.map((versioned) => versioned?.snapshot));
|
|
617
|
+
const setAndPublishSnapshot = (snapshot) => SynchronizedRef.updateAndGet(current, (current) => ({
|
|
618
|
+
revision: current.revision + 1,
|
|
619
|
+
snapshot,
|
|
620
|
+
terminalizing: true,
|
|
621
|
+
changes: current.changes
|
|
622
|
+
})).pipe(Effect.flatMap(publishSnapshot), Effect.flatMap(completeIfTerminal), Effect.asVoid);
|
|
623
|
+
const setActiveState = (state) => updateSnapshot((snapshot) => Effect.succeed(snapshot.status === "active"
|
|
624
|
+
? {
|
|
625
|
+
status: "active",
|
|
626
|
+
state
|
|
627
|
+
}
|
|
628
|
+
: undefined)).pipe(Effect.asVoid);
|
|
629
|
+
const terminalizeWith = (snapshot, exit, completeDone) => {
|
|
630
|
+
const notifyOutcome = onOutcome === undefined || (snapshot.status === "stopped" && options.skipStoppedOutcome === true)
|
|
631
|
+
? Effect.void
|
|
632
|
+
: Effect.suspend(() => onOutcome(classifyOutcome(snapshot))).pipe(Effect.exit, Effect.asVoid);
|
|
633
|
+
return Effect.uninterruptible(Effect.sync(() => {
|
|
634
|
+
while (true) {
|
|
635
|
+
const pending = Queue.takeUnsafe(queue);
|
|
636
|
+
if (pending === undefined || Exit.isFailure(pending))
|
|
637
|
+
break;
|
|
638
|
+
stopAcknowledgedMessage(pending.value);
|
|
639
|
+
}
|
|
640
|
+
}).pipe(Effect.andThen(Queue.shutdown(queue)), Effect.andThen(closeChildren(exit)), Effect.andThen(setAndPublishSnapshot(snapshot)), Effect.andThen(Effect.sync(() => {
|
|
641
|
+
if (Exit.isFailure(exit)) {
|
|
642
|
+
failAcknowledgedMessage(inFlightMessage, exit.cause);
|
|
643
|
+
}
|
|
644
|
+
else {
|
|
645
|
+
stopAcknowledgedMessage(inFlightMessage);
|
|
646
|
+
}
|
|
647
|
+
inFlightMessage = undefined;
|
|
648
|
+
})), Effect.andThen(notifyOutcome), Effect.andThen(cleanup), Effect.andThen(completeDone)));
|
|
649
|
+
};
|
|
650
|
+
const reserveStoppedSnapshot = reserveTerminalSnapshot((snapshot) => ({
|
|
651
|
+
status: "stopped",
|
|
652
|
+
state: snapshot.state
|
|
653
|
+
}));
|
|
654
|
+
const reserveFailureSnapshot = (cause) => reserveTerminalSnapshot((snapshot) => ({
|
|
655
|
+
status: "error",
|
|
656
|
+
state: snapshot.state,
|
|
657
|
+
cause
|
|
658
|
+
}));
|
|
659
|
+
const reserveSuccessSnapshot = (output) => reserveTerminalSnapshot((snapshot) => ({
|
|
660
|
+
status: "done",
|
|
661
|
+
state: snapshot.state,
|
|
662
|
+
output
|
|
663
|
+
}));
|
|
664
|
+
const terminalizeReservedStop = (snapshot) => {
|
|
665
|
+
const exit = Exit.void;
|
|
666
|
+
return terminalizeWith(snapshot, exit, Deferred.fail(done, new StoppedError()));
|
|
667
|
+
};
|
|
668
|
+
const terminalizeReservedFailure = (snapshot, cause) => {
|
|
669
|
+
const exit = Exit.failCause(cause);
|
|
670
|
+
return terminalizeWith(snapshot, exit, Deferred.failCause(done, cause));
|
|
671
|
+
};
|
|
672
|
+
const terminalizeReservedSuccess = (snapshot, output) => {
|
|
673
|
+
const exit = Exit.succeed(output);
|
|
674
|
+
return terminalizeWith(snapshot, exit, Deferred.succeed(done, output));
|
|
675
|
+
};
|
|
676
|
+
const stop = Effect.uninterruptible(requestStop.pipe(Effect.andThen(awaitCompletion)));
|
|
677
|
+
const acknowledgedContext = logic.execution?._tag !== "Compiled" ? undefined : {
|
|
678
|
+
receiveMessage: Queue.take(queue).pipe(Effect.tap((message) => Effect.sync(() => {
|
|
679
|
+
inFlightMessage = isAcknowledgedMessage(message) ? message : undefined;
|
|
680
|
+
}))),
|
|
681
|
+
pollMessage: Queue.poll(queue).pipe(Effect.tap((message) => Effect.sync(() => {
|
|
682
|
+
if (Option.isSome(message)) {
|
|
683
|
+
inFlightMessage = isAcknowledgedMessage(message.value) ? message.value : undefined;
|
|
684
|
+
}
|
|
685
|
+
}))),
|
|
686
|
+
completeMessage: (delivery) => {
|
|
687
|
+
succeedAcknowledgedMessage(inFlightMessage, delivery);
|
|
688
|
+
inFlightMessage = undefined;
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
const context = {
|
|
692
|
+
...scope,
|
|
693
|
+
...(logic.execution?._tag === "Compiled" && !logic.execution.childless ? { ownedChildren } : undefined),
|
|
694
|
+
...acknowledgedContext,
|
|
695
|
+
receive: Queue.take(queue).pipe(Effect.map(messageEvent)),
|
|
696
|
+
poll: Queue.poll(queue).pipe(Effect.map(Option.map(messageEvent))),
|
|
697
|
+
state: SynchronizedRef.get(current).pipe(Effect.map((current) => current.snapshot.state)),
|
|
698
|
+
setState: setActiveState,
|
|
699
|
+
updateState: (f) => updateSnapshot((snapshot) => snapshot.status === "active"
|
|
700
|
+
? f(snapshot.state).pipe(Effect.map((state) => ({
|
|
701
|
+
status: "active",
|
|
702
|
+
state
|
|
703
|
+
})))
|
|
704
|
+
: Effect.succeed(undefined)).pipe(Effect.asVoid)
|
|
705
|
+
};
|
|
706
|
+
const getOrCreateChanges = SynchronizedRef.modifyEffect(current, (current) => {
|
|
707
|
+
if (current.snapshot.status !== "active") {
|
|
708
|
+
return Effect.succeed([undefined, current]);
|
|
709
|
+
}
|
|
710
|
+
if (current.changes !== undefined) {
|
|
711
|
+
return Effect.succeed([current.changes, current]);
|
|
712
|
+
}
|
|
713
|
+
return PubSub.unbounded({ replay: 1 }).pipe(Effect.map((changes) => [changes, { ...current, changes }]));
|
|
714
|
+
});
|
|
715
|
+
const changesStream = Stream.unwrap(Effect.gen(function* () {
|
|
716
|
+
const changes = yield* getOrCreateChanges;
|
|
717
|
+
if (changes === undefined) {
|
|
718
|
+
return Stream.succeed((yield* SynchronizedRef.get(current)).snapshot);
|
|
719
|
+
}
|
|
720
|
+
const subscription = yield* PubSub.subscribe(changes);
|
|
721
|
+
const captured = yield* SynchronizedRef.get(current);
|
|
722
|
+
if (captured.snapshot.status !== "active") {
|
|
723
|
+
return Stream.succeed(captured.snapshot);
|
|
724
|
+
}
|
|
725
|
+
return Stream.succeed(captured.snapshot).pipe(Stream.concat(Stream.fromChannel(Channel.fromEffectTake(PubSub.take(subscription))).pipe(Stream.filter((next) => next.revision > captured.revision), Stream.map((next) => next.snapshot))));
|
|
726
|
+
}));
|
|
727
|
+
const ref = {
|
|
728
|
+
id,
|
|
729
|
+
sessionId,
|
|
730
|
+
state: SynchronizedRef.get(current).pipe(Effect.map((current) => current.snapshot.state)),
|
|
731
|
+
snapshot: SynchronizedRef.get(current).pipe(Effect.map((current) => current.snapshot)),
|
|
732
|
+
changes: changesStream,
|
|
733
|
+
join: Deferred.await(done),
|
|
734
|
+
stop,
|
|
735
|
+
send: self.send,
|
|
736
|
+
...(sendAcknowledged === undefined ? undefined : { [acknowledgedSend]: sendAcknowledged }),
|
|
737
|
+
child: getChild,
|
|
738
|
+
childChanges
|
|
739
|
+
};
|
|
740
|
+
if (onReadySync !== undefined && !onReadySync(ref)) {
|
|
741
|
+
yield* requestStop;
|
|
742
|
+
}
|
|
743
|
+
else if (onReady !== undefined) {
|
|
744
|
+
yield* onReady(ref, requestStop);
|
|
745
|
+
}
|
|
746
|
+
if (onSnapshot !== undefined) {
|
|
747
|
+
yield* notifyActiveSnapshot(onSnapshot, { status: "active", state: initial });
|
|
748
|
+
}
|
|
749
|
+
const reserveTermination = (termination) => {
|
|
750
|
+
switch (termination._tag) {
|
|
751
|
+
case "Stopped":
|
|
752
|
+
return reserveStoppedSnapshot;
|
|
753
|
+
case "Done":
|
|
754
|
+
return reserveSuccessSnapshot(termination.output);
|
|
755
|
+
case "Failure":
|
|
756
|
+
return reserveFailureSnapshot(termination.cause);
|
|
757
|
+
}
|
|
758
|
+
};
|
|
759
|
+
const completeTermination = (termination, snapshot) => {
|
|
760
|
+
switch (termination._tag) {
|
|
761
|
+
case "Stopped":
|
|
762
|
+
return terminalizeReservedStop(snapshot);
|
|
763
|
+
case "Done":
|
|
764
|
+
return terminalizeReservedSuccess(snapshot, termination.output);
|
|
765
|
+
case "Failure":
|
|
766
|
+
return terminalizeReservedFailure(snapshot, termination.cause);
|
|
767
|
+
}
|
|
768
|
+
};
|
|
769
|
+
const forkRuntime = (effect) => detached === true
|
|
770
|
+
? Effect.forkDetach(effect)
|
|
771
|
+
: Effect.forkChild(effect);
|
|
772
|
+
const pendingTermination = yield* Deferred.poll(termination);
|
|
773
|
+
const worker = Option.isNone(pendingTermination)
|
|
774
|
+
? yield* Effect.uninterruptibleMask((restore) => restore(Effect.suspend(() => logic.run(context))).pipe(Effect.exit, Effect.flatMap((exit) => Deferred.succeed(termination, Exit.isFailure(exit)
|
|
775
|
+
? { _tag: "Failure", cause: exit.cause }
|
|
776
|
+
: { _tag: "Done", output: exit.value })))).pipe(forkRuntime)
|
|
777
|
+
: undefined;
|
|
778
|
+
// One Deferred arbitrates all terminal causes. The supervisor reserves the
|
|
779
|
+
// terminal snapshot before interrupting the worker, so worker finalizers
|
|
780
|
+
// cannot mutate the frozen state. It then waits for those finalizers before
|
|
781
|
+
// publishing and completing `join` / `stop`.
|
|
782
|
+
const runFiber = Effect.uninterruptibleMask((restore) => Effect.gen(function* () {
|
|
783
|
+
const requested = Option.isSome(pendingTermination)
|
|
784
|
+
? yield* pendingTermination.value
|
|
785
|
+
: yield* restore(Deferred.await(termination));
|
|
786
|
+
const snapshot = yield* reserveTermination(requested);
|
|
787
|
+
if (worker !== undefined) {
|
|
788
|
+
yield* Fiber.interrupt(worker);
|
|
789
|
+
}
|
|
790
|
+
if (snapshot === undefined) {
|
|
791
|
+
return yield* awaitCompletion;
|
|
792
|
+
}
|
|
793
|
+
return yield* completeTermination(requested, snapshot);
|
|
794
|
+
}));
|
|
795
|
+
yield* forkRuntime(runFiber);
|
|
796
|
+
yield* Effect.yieldNow;
|
|
797
|
+
return ref;
|
|
798
|
+
});
|
|
799
|
+
// Stopping is commonly used only for resource cleanup. Keep that path free of
|
|
800
|
+
// Error stack capture and materialize the typed join failure only if observed.
|
|
801
|
+
const CompiledStoppedCompletion = Symbol("effect/Machine/CompiledStoppedCompletion");
|
|
802
|
+
/**
|
|
803
|
+
* Compact runtime for compiled statecharts.
|
|
804
|
+
*
|
|
805
|
+
* All long-lived state is stored directly on this object. Operations are
|
|
806
|
+
* implemented by shared prototype methods, and public Effect / Stream values
|
|
807
|
+
* are materialized only when accessed. Arbitrary process logic continues to
|
|
808
|
+
* use the general runtime above.
|
|
809
|
+
*/
|
|
810
|
+
class CompiledProcess {
|
|
811
|
+
logic;
|
|
812
|
+
options;
|
|
813
|
+
services;
|
|
814
|
+
id;
|
|
815
|
+
sessionId;
|
|
816
|
+
send;
|
|
817
|
+
[acknowledgedSend](event) {
|
|
818
|
+
return this.sendAcknowledgedEffect(event);
|
|
819
|
+
}
|
|
820
|
+
mailbox = {
|
|
821
|
+
items: undefined,
|
|
822
|
+
index: 0,
|
|
823
|
+
closed: false
|
|
824
|
+
};
|
|
825
|
+
address;
|
|
826
|
+
childRuntime = childlessRuntime;
|
|
827
|
+
processScope;
|
|
828
|
+
processContext;
|
|
829
|
+
compiledContext;
|
|
830
|
+
current;
|
|
831
|
+
/**
|
|
832
|
+
* `Active` has no terminal payload, `TerminationRequested` owns
|
|
833
|
+
* `termination` and its optional reserved snapshot, and `Completed` owns
|
|
834
|
+
* `completion`. `runState` independently tracks worker activity.
|
|
835
|
+
*/
|
|
836
|
+
lifecycle = "Active";
|
|
837
|
+
runState = "Initializing";
|
|
838
|
+
completion;
|
|
839
|
+
waiter;
|
|
840
|
+
termination;
|
|
841
|
+
terminationSnapshot;
|
|
842
|
+
worker;
|
|
843
|
+
interruptRequested = false;
|
|
844
|
+
offerRevision = 0;
|
|
845
|
+
inFlightMessage;
|
|
846
|
+
constructor(logic, options, services, sessionId) {
|
|
847
|
+
this.logic = logic;
|
|
848
|
+
this.options = options;
|
|
849
|
+
this.services = services;
|
|
850
|
+
this.sessionId = sessionId;
|
|
851
|
+
this.id = options.id ?? sessionId;
|
|
852
|
+
this.send = (event) => this.offerMessage(event);
|
|
853
|
+
this.address = {
|
|
854
|
+
id: this.id,
|
|
855
|
+
sessionId,
|
|
856
|
+
stop: Effect.suspend(() => this.stopFromProcess()),
|
|
857
|
+
send: this.send
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
get execution() {
|
|
861
|
+
return this.logic.execution;
|
|
862
|
+
}
|
|
863
|
+
initializeCompiledSync() {
|
|
864
|
+
if (!this.execution.childless) {
|
|
865
|
+
this.childRuntime = makeChildRuntimeSync(this.address, this.options.runtime, this.services);
|
|
866
|
+
}
|
|
867
|
+
const parent = this.options.parent;
|
|
868
|
+
const sendParent = this.options.sendParent ?? (parent === undefined ? noParentSend : parent.send);
|
|
869
|
+
this.processScope = {
|
|
870
|
+
self: this.address,
|
|
871
|
+
parent,
|
|
872
|
+
spawn: this.childRuntime.spawn,
|
|
873
|
+
sendParent,
|
|
874
|
+
sendTo: this.childRuntime.sendTo,
|
|
875
|
+
stopChild: this.childRuntime.stop,
|
|
876
|
+
failCause: (cause) => this.failCause(cause)
|
|
877
|
+
};
|
|
878
|
+
const compiledInitial = this.execution.initialSync;
|
|
879
|
+
let initialized;
|
|
880
|
+
try {
|
|
881
|
+
initialized = compiledInitial(this.processScope);
|
|
882
|
+
}
|
|
883
|
+
catch (error) {
|
|
884
|
+
this.runState = "Idle";
|
|
885
|
+
return Effect.fail(error);
|
|
886
|
+
}
|
|
887
|
+
this.runState = "Idle";
|
|
888
|
+
this.current = {
|
|
889
|
+
revision: 0,
|
|
890
|
+
terminalizing: false,
|
|
891
|
+
changes: undefined,
|
|
892
|
+
snapshot: { status: "active", state: initialized.state }
|
|
893
|
+
};
|
|
894
|
+
this.compiledContext = new CompiledProcessContextImpl(this.processScope, this.childRuntime.owned, this);
|
|
895
|
+
if ("executionState" in initialized) {
|
|
896
|
+
this.compiledContext.executionState = initialized.executionState;
|
|
897
|
+
}
|
|
898
|
+
if (this.options.onReadySync !== undefined && !this.options.onReadySync(this)) {
|
|
899
|
+
this.requestTerminationSync({ _tag: "Stopped" });
|
|
900
|
+
}
|
|
901
|
+
if (initialized.done === true && this.lifecycle === "Active") {
|
|
902
|
+
this.requestTerminationSync({ _tag: "Done", output: initialized.output });
|
|
903
|
+
}
|
|
904
|
+
if (initialized.done === false && this.execution.childless &&
|
|
905
|
+
this.lifecycle === "Active" &&
|
|
906
|
+
this.mailbox.items === undefined) {
|
|
907
|
+
return Effect.succeed(this);
|
|
908
|
+
}
|
|
909
|
+
this.runState = "Draining";
|
|
910
|
+
return Effect.provideContext(this.drainRuntime(), this.services).pipe(Effect.as(this));
|
|
911
|
+
}
|
|
912
|
+
initialize() {
|
|
913
|
+
const self = this;
|
|
914
|
+
return Effect.gen(function* () {
|
|
915
|
+
if (!self.execution.childless) {
|
|
916
|
+
self.childRuntime = yield* makeChildRuntime(self.address, self.options.runtime, self.services);
|
|
917
|
+
}
|
|
918
|
+
const parent = self.options.parent;
|
|
919
|
+
const sendParent = self.options.sendParent ?? (parent === undefined ? noParentSend : parent.send);
|
|
920
|
+
self.processScope = {
|
|
921
|
+
self: self.address,
|
|
922
|
+
parent,
|
|
923
|
+
spawn: self.childRuntime.spawn,
|
|
924
|
+
sendParent,
|
|
925
|
+
sendTo: self.childRuntime.sendTo,
|
|
926
|
+
stopChild: self.childRuntime.stop,
|
|
927
|
+
failCause: (cause) => self.failCause(cause)
|
|
928
|
+
};
|
|
929
|
+
const cleanupStartupFailure = (exit) => Exit.isFailure(exit) ? self.childRuntime.close(exit) : Effect.void;
|
|
930
|
+
const compiledInitial = self.execution.initial;
|
|
931
|
+
const initializeEffect = compiledInitial === undefined
|
|
932
|
+
? self.logic.initial(self.processScope).pipe(Effect.map((state) => ({ state, done: undefined, output: undefined })))
|
|
933
|
+
: compiledInitial(self.processScope);
|
|
934
|
+
const initialized = yield* initializeEffect.pipe(Effect.onExit(cleanupStartupFailure), Effect.ensuring(Effect.sync(() => {
|
|
935
|
+
self.runState = "Idle";
|
|
936
|
+
})));
|
|
937
|
+
const initial = initialized.state;
|
|
938
|
+
self.current = {
|
|
939
|
+
revision: 0,
|
|
940
|
+
terminalizing: false,
|
|
941
|
+
changes: undefined,
|
|
942
|
+
snapshot: { status: "active", state: initial }
|
|
943
|
+
};
|
|
944
|
+
if (self.execution.drain._tag === "Process") {
|
|
945
|
+
self.processContext = {
|
|
946
|
+
...self.processScope,
|
|
947
|
+
receive: Effect.never,
|
|
948
|
+
poll: Effect.sync(() => Option.map(pollCompactMailbox(self.mailbox), messageEvent)),
|
|
949
|
+
receiveMessage: Effect.never,
|
|
950
|
+
pollMessage: Effect.sync(() => self.pollCompiledMessage()),
|
|
951
|
+
completeMessage: (delivery) => self.completeCompiledMessage(delivery),
|
|
952
|
+
state: Effect.sync(() => self.current.snapshot.state),
|
|
953
|
+
setState: (state) => self.setActiveState(state),
|
|
954
|
+
updateState: (f) => self.updateState(f)
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
else {
|
|
958
|
+
self.compiledContext = new CompiledProcessContextImpl(self.processScope, self.childRuntime.owned, self);
|
|
959
|
+
if ("executionState" in initialized) {
|
|
960
|
+
self.compiledContext.executionState = initialized.executionState;
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
if (self.options.onReadySync !== undefined && !self.options.onReadySync(self)) {
|
|
964
|
+
yield* self.requestTermination({ _tag: "Stopped" });
|
|
965
|
+
}
|
|
966
|
+
else if (self.options.onReady !== undefined) {
|
|
967
|
+
yield* self.options.onReady(self, self.requestTermination({ _tag: "Stopped" }).pipe(Effect.asVoid));
|
|
968
|
+
}
|
|
969
|
+
if (self.options.onSnapshot !== undefined) {
|
|
970
|
+
yield* notifyActiveSnapshot(self.options.onSnapshot, { status: "active", state: initial });
|
|
971
|
+
}
|
|
972
|
+
if (initialized.done === true && self.lifecycle === "Active") {
|
|
973
|
+
yield* self.requestTermination({ _tag: "Done", output: initialized.output });
|
|
974
|
+
}
|
|
975
|
+
// A compiled machine startup plan has already settled entry actions,
|
|
976
|
+
// raised events, and eventless transitions. If it is known active and
|
|
977
|
+
// neither startup hooks nor emitted work queued an event, there is no
|
|
978
|
+
// first drain to perform. Future sends observe an idle run state and
|
|
979
|
+
// schedule the ordinary compiled worker.
|
|
980
|
+
if (initialized.done === false && self.execution.childless &&
|
|
981
|
+
self.lifecycle === "Active" && self.mailbox.items === undefined) {
|
|
982
|
+
return self;
|
|
983
|
+
}
|
|
984
|
+
self.runState = "Draining";
|
|
985
|
+
yield* self.drainRuntime();
|
|
986
|
+
return self;
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
get state() {
|
|
990
|
+
return Effect.sync(() => this.current.snapshot.state);
|
|
991
|
+
}
|
|
992
|
+
get snapshot() {
|
|
993
|
+
return Effect.sync(() => this.current.snapshot);
|
|
994
|
+
}
|
|
995
|
+
get changes() {
|
|
996
|
+
return this.changesStream();
|
|
997
|
+
}
|
|
998
|
+
get join() {
|
|
999
|
+
return Effect.suspend(() => {
|
|
1000
|
+
if (this.lifecycle === "Completed") {
|
|
1001
|
+
return this.resolveCompletion(this.completion);
|
|
1002
|
+
}
|
|
1003
|
+
this.waiter ??= Deferred.makeUnsafe();
|
|
1004
|
+
return Deferred.await(this.waiter);
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
1007
|
+
get stop() {
|
|
1008
|
+
return Effect.uninterruptible(this.stopEffect());
|
|
1009
|
+
}
|
|
1010
|
+
child(child) {
|
|
1011
|
+
return this.childRuntime.get(child);
|
|
1012
|
+
}
|
|
1013
|
+
childChanges(child) {
|
|
1014
|
+
return this.childRuntime.changes(child);
|
|
1015
|
+
}
|
|
1016
|
+
requestTermination(requested) {
|
|
1017
|
+
return Effect.sync(() => this.requestTerminationSync(requested));
|
|
1018
|
+
}
|
|
1019
|
+
hasTerminationRequest() {
|
|
1020
|
+
return this.lifecycle === "TerminationRequested";
|
|
1021
|
+
}
|
|
1022
|
+
requestTerminationSync(requested) {
|
|
1023
|
+
if (this.lifecycle !== "Active") {
|
|
1024
|
+
return false;
|
|
1025
|
+
}
|
|
1026
|
+
this.lifecycle = "TerminationRequested";
|
|
1027
|
+
this.termination = requested;
|
|
1028
|
+
this.terminationSnapshot = this.reserveTermination(requested);
|
|
1029
|
+
return true;
|
|
1030
|
+
}
|
|
1031
|
+
reserveTermination(requested) {
|
|
1032
|
+
const latest = this.current;
|
|
1033
|
+
if (latest === undefined || latest.terminalizing || latest.snapshot.status !== "active") {
|
|
1034
|
+
return undefined;
|
|
1035
|
+
}
|
|
1036
|
+
const snapshot = requested._tag === "Stopped"
|
|
1037
|
+
? { status: "stopped", state: latest.snapshot.state }
|
|
1038
|
+
: requested._tag === "Done"
|
|
1039
|
+
? { status: "done", state: latest.snapshot.state, output: requested.output }
|
|
1040
|
+
: { status: "error", state: latest.snapshot.state, cause: requested.cause };
|
|
1041
|
+
this.current = { ...latest, terminalizing: true };
|
|
1042
|
+
return snapshot;
|
|
1043
|
+
}
|
|
1044
|
+
stopFromProcess() {
|
|
1045
|
+
const request = this.requestTermination({ _tag: "Stopped" }).pipe(Effect.asVoid);
|
|
1046
|
+
return this.runState === "Initializing" ? request : request.pipe(Effect.andThen(Effect.interrupt));
|
|
1047
|
+
}
|
|
1048
|
+
failCause(cause) {
|
|
1049
|
+
const requested = { _tag: "Failure", cause };
|
|
1050
|
+
return this.requestTermination(requested).pipe(Effect.flatMap((accepted) => accepted
|
|
1051
|
+
? Effect.forkDetach(this.settleRequestedTermination()).pipe(Effect.asVoid)
|
|
1052
|
+
: Effect.void));
|
|
1053
|
+
}
|
|
1054
|
+
sendAcknowledgedEffect(event) {
|
|
1055
|
+
return Effect.uninterruptibleMask((restore) => Deferred.make().pipe(Effect.flatMap((deferred) => this.offerMessage({
|
|
1056
|
+
[AcknowledgedMessageTypeId]: true,
|
|
1057
|
+
event,
|
|
1058
|
+
deferred
|
|
1059
|
+
}).pipe(Effect.andThen(restore(Deferred.await(deferred)))))));
|
|
1060
|
+
}
|
|
1061
|
+
offerMessage(message) {
|
|
1062
|
+
return Effect.uninterruptible(Effect.suspend(() => {
|
|
1063
|
+
if (this.mailbox.closed || this.lifecycle !== "Active") {
|
|
1064
|
+
return Effect.fail(new StoppedError());
|
|
1065
|
+
}
|
|
1066
|
+
offerCompactMailbox(this.mailbox, message);
|
|
1067
|
+
this.offerRevision += 1;
|
|
1068
|
+
if (this.runState === "Draining") {
|
|
1069
|
+
return Effect.void;
|
|
1070
|
+
}
|
|
1071
|
+
this.runState = "Draining";
|
|
1072
|
+
const scheduled = Effect.yieldNow.pipe(Effect.andThen(Effect.provideContext(this.drainRuntime(), this.services)));
|
|
1073
|
+
const fork = this.options.detached === true
|
|
1074
|
+
? Effect.forkDetach(scheduled, { startImmediately: true })
|
|
1075
|
+
: Effect.forkChild(scheduled, { startImmediately: true });
|
|
1076
|
+
return fork.pipe(Effect.flatMap((fiber) => Effect.sync(() => {
|
|
1077
|
+
this.worker = fiber;
|
|
1078
|
+
if (!this.interruptRequested) {
|
|
1079
|
+
return false;
|
|
1080
|
+
}
|
|
1081
|
+
this.interruptRequested = false;
|
|
1082
|
+
return true;
|
|
1083
|
+
}).pipe(Effect.flatMap((interrupt) => interrupt ? this.interruptAndFinish(fiber) : Effect.void))), Effect.asVoid);
|
|
1084
|
+
}));
|
|
1085
|
+
}
|
|
1086
|
+
stopEffect() {
|
|
1087
|
+
return Effect.suspend(() => {
|
|
1088
|
+
if (this.lifecycle === "Completed") {
|
|
1089
|
+
return Effect.void;
|
|
1090
|
+
}
|
|
1091
|
+
if (this.finishIdleChildlessStop()) {
|
|
1092
|
+
return Effect.void;
|
|
1093
|
+
}
|
|
1094
|
+
const requested = { _tag: "Stopped" };
|
|
1095
|
+
return this.requestTermination(requested).pipe(Effect.flatMap((accepted) => accepted
|
|
1096
|
+
? this.settleRequestedTermination()
|
|
1097
|
+
: this.awaitCompletion()));
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
finishIdleChildlessStop() {
|
|
1101
|
+
if (!this.execution.childless || this.runState !== "Idle" || this.worker !== undefined ||
|
|
1102
|
+
this.lifecycle !== "Active" ||
|
|
1103
|
+
(this.options.onOutcome !== undefined && this.options.skipStoppedOutcome !== true) ||
|
|
1104
|
+
this.options.onStop !== undefined ||
|
|
1105
|
+
this.current.changes !== undefined ||
|
|
1106
|
+
this.current.terminalizing || this.current.snapshot.status !== "active") {
|
|
1107
|
+
return false;
|
|
1108
|
+
}
|
|
1109
|
+
const snapshot = { status: "stopped", state: this.current.snapshot.state };
|
|
1110
|
+
this.lifecycle = "TerminationRequested";
|
|
1111
|
+
this.termination = { _tag: "Stopped" };
|
|
1112
|
+
this.terminationSnapshot = snapshot;
|
|
1113
|
+
closeCompactMailbox(this.mailbox);
|
|
1114
|
+
this.current = {
|
|
1115
|
+
revision: this.current.revision + 1,
|
|
1116
|
+
terminalizing: true,
|
|
1117
|
+
changes: undefined,
|
|
1118
|
+
snapshot
|
|
1119
|
+
};
|
|
1120
|
+
stopAcknowledgedMessage(this.inFlightMessage);
|
|
1121
|
+
this.inFlightMessage = undefined;
|
|
1122
|
+
this.interruptRequested = false;
|
|
1123
|
+
if (this.compiledContext !== undefined) {
|
|
1124
|
+
this.compiledContext.executionState = undefined;
|
|
1125
|
+
}
|
|
1126
|
+
this.options.onStopSync?.();
|
|
1127
|
+
this.completion = CompiledStoppedCompletion;
|
|
1128
|
+
this.lifecycle = "Completed";
|
|
1129
|
+
if (this.waiter !== undefined) {
|
|
1130
|
+
Deferred.doneUnsafe(this.waiter, this.resolveCompletion(CompiledStoppedCompletion));
|
|
1131
|
+
this.waiter = undefined;
|
|
1132
|
+
}
|
|
1133
|
+
return true;
|
|
1134
|
+
}
|
|
1135
|
+
settleRequestedTermination() {
|
|
1136
|
+
return Effect.suspend(() => {
|
|
1137
|
+
if (this.runState !== "Draining") {
|
|
1138
|
+
return this.finishRequestedTermination();
|
|
1139
|
+
}
|
|
1140
|
+
if (this.worker === undefined) {
|
|
1141
|
+
this.interruptRequested = true;
|
|
1142
|
+
return this.awaitCompletion();
|
|
1143
|
+
}
|
|
1144
|
+
return this.interruptAndFinish(this.worker).pipe(Effect.andThen(this.awaitCompletion()));
|
|
1145
|
+
});
|
|
1146
|
+
}
|
|
1147
|
+
interruptAndFinish(worker) {
|
|
1148
|
+
return Fiber.interrupt(worker).pipe(Effect.andThen(Effect.suspend(() => this.lifecycle !== "Completed"
|
|
1149
|
+
? this.finishRequestedTermination()
|
|
1150
|
+
: Effect.void)));
|
|
1151
|
+
}
|
|
1152
|
+
awaitCompletion() {
|
|
1153
|
+
return this.lifecycle !== "Completed"
|
|
1154
|
+
? this.join.pipe(Effect.exit, Effect.asVoid)
|
|
1155
|
+
: Effect.void;
|
|
1156
|
+
}
|
|
1157
|
+
resolveCompletion(completion) {
|
|
1158
|
+
if (completion !== CompiledStoppedCompletion) {
|
|
1159
|
+
return completion;
|
|
1160
|
+
}
|
|
1161
|
+
const stopped = Effect.fail(new StoppedError());
|
|
1162
|
+
this.completion = stopped;
|
|
1163
|
+
return stopped;
|
|
1164
|
+
}
|
|
1165
|
+
drainRuntime() {
|
|
1166
|
+
const self = this;
|
|
1167
|
+
return Effect.uninterruptibleMask((restore) => Effect.gen(function* () {
|
|
1168
|
+
let observedRevision = self.offerRevision;
|
|
1169
|
+
while (true) {
|
|
1170
|
+
if (self.hasTerminationRequest()) {
|
|
1171
|
+
return yield* self.finishRequestedTermination();
|
|
1172
|
+
}
|
|
1173
|
+
const exit = yield* restore(Effect.suspend(() => {
|
|
1174
|
+
const drain = self.execution.drain;
|
|
1175
|
+
return drain._tag === "Process"
|
|
1176
|
+
? drain.run(self.processContext)
|
|
1177
|
+
: drain.run(self.compiledContext);
|
|
1178
|
+
})).pipe(Effect.exit);
|
|
1179
|
+
self.flushPendingChanges();
|
|
1180
|
+
if (Exit.isFailure(exit)) {
|
|
1181
|
+
if (self.lifecycle === "Active") {
|
|
1182
|
+
yield* self.requestTermination({ _tag: "Failure", cause: exit.cause });
|
|
1183
|
+
}
|
|
1184
|
+
return yield* self.finishRequestedTermination();
|
|
1185
|
+
}
|
|
1186
|
+
if (Option.isSome(exit.value)) {
|
|
1187
|
+
yield* self.requestTermination({ _tag: "Done", output: exit.value.value });
|
|
1188
|
+
return yield* self.finishRequestedTermination();
|
|
1189
|
+
}
|
|
1190
|
+
if (self.hasTerminationRequest()) {
|
|
1191
|
+
return yield* self.finishRequestedTermination();
|
|
1192
|
+
}
|
|
1193
|
+
if (self.offerRevision !== observedRevision) {
|
|
1194
|
+
observedRevision = self.offerRevision;
|
|
1195
|
+
continue;
|
|
1196
|
+
}
|
|
1197
|
+
self.runState = "Idle";
|
|
1198
|
+
self.worker = undefined;
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
}));
|
|
1202
|
+
}
|
|
1203
|
+
finishRequestedTermination() {
|
|
1204
|
+
return Effect.suspend(() => {
|
|
1205
|
+
if (this.lifecycle !== "TerminationRequested") {
|
|
1206
|
+
return Effect.void;
|
|
1207
|
+
}
|
|
1208
|
+
const requested = this.termination;
|
|
1209
|
+
if (requested === undefined) {
|
|
1210
|
+
return Effect.void;
|
|
1211
|
+
}
|
|
1212
|
+
const snapshot = this.terminationSnapshot ?? this.reserveTermination(requested);
|
|
1213
|
+
if (snapshot === undefined) {
|
|
1214
|
+
return this.awaitCompletion();
|
|
1215
|
+
}
|
|
1216
|
+
const exit = requested._tag === "Stopped"
|
|
1217
|
+
? Exit.void
|
|
1218
|
+
: requested._tag === "Done"
|
|
1219
|
+
? Exit.succeed(requested.output)
|
|
1220
|
+
: Exit.failCause(requested.cause);
|
|
1221
|
+
const completion = requested._tag === "Stopped"
|
|
1222
|
+
? CompiledStoppedCompletion
|
|
1223
|
+
: requested._tag === "Done"
|
|
1224
|
+
? Effect.succeed(requested.output)
|
|
1225
|
+
: Effect.failCause(requested.cause);
|
|
1226
|
+
const notifyOutcome = this.options.onOutcome === undefined ||
|
|
1227
|
+
(requested._tag === "Stopped" && this.options.skipStoppedOutcome === true)
|
|
1228
|
+
? Effect.void
|
|
1229
|
+
: Effect.suspend(() => this.options.onOutcome(classifyOutcome(snapshot))).pipe(Effect.exit, Effect.asVoid);
|
|
1230
|
+
return Effect.uninterruptible(Effect.sync(() => {
|
|
1231
|
+
closeCompactMailbox(this.mailbox);
|
|
1232
|
+
}).pipe(Effect.andThen(this.childRuntime.close(exit)), Effect.andThen(this.setAndPublishSnapshot(snapshot)), Effect.andThen(Effect.sync(() => {
|
|
1233
|
+
if (requested._tag === "Failure") {
|
|
1234
|
+
failAcknowledgedMessage(this.inFlightMessage, requested.cause);
|
|
1235
|
+
}
|
|
1236
|
+
else {
|
|
1237
|
+
stopAcknowledgedMessage(this.inFlightMessage);
|
|
1238
|
+
}
|
|
1239
|
+
this.inFlightMessage = undefined;
|
|
1240
|
+
})), Effect.andThen(notifyOutcome), Effect.andThen(this.options.onStop ?? Effect.void), Effect.andThen(Effect.sync(() => {
|
|
1241
|
+
this.options.onStopSync?.();
|
|
1242
|
+
this.runState = "Idle";
|
|
1243
|
+
this.worker = undefined;
|
|
1244
|
+
this.interruptRequested = false;
|
|
1245
|
+
if (this.compiledContext !== undefined) {
|
|
1246
|
+
this.compiledContext.executionState = undefined;
|
|
1247
|
+
}
|
|
1248
|
+
this.completion = completion;
|
|
1249
|
+
this.lifecycle = "Completed";
|
|
1250
|
+
if (this.waiter !== undefined) {
|
|
1251
|
+
Deferred.doneUnsafe(this.waiter, this.resolveCompletion(completion));
|
|
1252
|
+
this.waiter = undefined;
|
|
1253
|
+
}
|
|
1254
|
+
}))));
|
|
1255
|
+
});
|
|
1256
|
+
}
|
|
1257
|
+
publishSnapshot(snapshot) {
|
|
1258
|
+
const publish = snapshot.changes === undefined
|
|
1259
|
+
? Effect.succeed(snapshot)
|
|
1260
|
+
: PubSub.publish(snapshot.changes, [snapshot]).pipe(Effect.as(snapshot));
|
|
1261
|
+
const current = snapshot.snapshot;
|
|
1262
|
+
return this.options.onSnapshot === undefined || current.status !== "active"
|
|
1263
|
+
? publish
|
|
1264
|
+
: publish.pipe(Effect.tap(() => notifyActiveSnapshot(this.options.onSnapshot, current)));
|
|
1265
|
+
}
|
|
1266
|
+
completeChanges(snapshot) {
|
|
1267
|
+
return snapshot.changes === undefined
|
|
1268
|
+
? Effect.void
|
|
1269
|
+
: PubSub.publish(snapshot.changes, Exit.succeed(undefined)).pipe(Effect.asVoid);
|
|
1270
|
+
}
|
|
1271
|
+
setAndPublishSnapshot(snapshot) {
|
|
1272
|
+
return Effect.suspend(() => {
|
|
1273
|
+
this.flushPendingChanges();
|
|
1274
|
+
const versioned = {
|
|
1275
|
+
revision: this.current.revision + 1,
|
|
1276
|
+
snapshot,
|
|
1277
|
+
terminalizing: true,
|
|
1278
|
+
changes: this.current.changes
|
|
1279
|
+
};
|
|
1280
|
+
this.current = versioned;
|
|
1281
|
+
return this.publishSnapshot(versioned).pipe(Effect.flatMap((published) => this.completeChanges(published)), Effect.asVoid);
|
|
1282
|
+
});
|
|
1283
|
+
}
|
|
1284
|
+
setActiveState(state) {
|
|
1285
|
+
return Effect.suspend(() => this.commitActiveState(state) ?? Effect.void);
|
|
1286
|
+
}
|
|
1287
|
+
pollCompiledMessage() {
|
|
1288
|
+
const message = pollCompactMailbox(this.mailbox);
|
|
1289
|
+
if (Option.isSome(message)) {
|
|
1290
|
+
this.inFlightMessage = isAcknowledgedMessage(message.value) ? message.value : undefined;
|
|
1291
|
+
}
|
|
1292
|
+
return message;
|
|
1293
|
+
}
|
|
1294
|
+
completeCompiledMessage(delivery) {
|
|
1295
|
+
succeedAcknowledgedMessage(this.inFlightMessage, delivery);
|
|
1296
|
+
this.inFlightMessage = undefined;
|
|
1297
|
+
}
|
|
1298
|
+
compiledState() {
|
|
1299
|
+
return this.current.snapshot.state;
|
|
1300
|
+
}
|
|
1301
|
+
commitCompiledState(state) {
|
|
1302
|
+
return this.commitActiveState(state, true);
|
|
1303
|
+
}
|
|
1304
|
+
commitActiveState(state, batchChanges = false) {
|
|
1305
|
+
const latest = this.current;
|
|
1306
|
+
if (latest.terminalizing || latest.snapshot.status !== "active") {
|
|
1307
|
+
return undefined;
|
|
1308
|
+
}
|
|
1309
|
+
const pendingChanges = latest.pendingChanges;
|
|
1310
|
+
if (pendingChanges !== undefined) {
|
|
1311
|
+
latest.pendingChanges = undefined;
|
|
1312
|
+
}
|
|
1313
|
+
const activeSnapshot = { status: "active", state };
|
|
1314
|
+
const versioned = {
|
|
1315
|
+
revision: latest.revision + 1,
|
|
1316
|
+
snapshot: activeSnapshot,
|
|
1317
|
+
terminalizing: false,
|
|
1318
|
+
changes: latest.changes
|
|
1319
|
+
};
|
|
1320
|
+
this.current = versioned;
|
|
1321
|
+
if (versioned.changes !== undefined) {
|
|
1322
|
+
if (pendingChanges === undefined) {
|
|
1323
|
+
if (batchChanges) {
|
|
1324
|
+
versioned.pendingChanges = [versioned];
|
|
1325
|
+
}
|
|
1326
|
+
else {
|
|
1327
|
+
PubSub.publishUnsafe(versioned.changes, [versioned]);
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
else {
|
|
1331
|
+
pendingChanges.push(versioned);
|
|
1332
|
+
if (batchChanges) {
|
|
1333
|
+
versioned.pendingChanges = pendingChanges;
|
|
1334
|
+
}
|
|
1335
|
+
else {
|
|
1336
|
+
PubSub.publishUnsafe(versioned.changes, pendingChanges);
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
return this.options.onSnapshot === undefined
|
|
1341
|
+
? undefined
|
|
1342
|
+
: notifyActiveSnapshot(this.options.onSnapshot, activeSnapshot);
|
|
1343
|
+
}
|
|
1344
|
+
flushPendingChanges() {
|
|
1345
|
+
const current = this.current;
|
|
1346
|
+
const pendingChanges = current?.pendingChanges;
|
|
1347
|
+
if (pendingChanges === undefined || current.changes === undefined) {
|
|
1348
|
+
return;
|
|
1349
|
+
}
|
|
1350
|
+
current.pendingChanges = undefined;
|
|
1351
|
+
PubSub.publishUnsafe(current.changes, pendingChanges);
|
|
1352
|
+
}
|
|
1353
|
+
updateState(f) {
|
|
1354
|
+
return Effect.suspend(() => {
|
|
1355
|
+
const observed = this.current;
|
|
1356
|
+
if (observed.terminalizing || observed.snapshot.status !== "active") {
|
|
1357
|
+
return Effect.void;
|
|
1358
|
+
}
|
|
1359
|
+
return f(observed.snapshot.state).pipe(Effect.flatMap((state) => {
|
|
1360
|
+
const latest = this.current;
|
|
1361
|
+
return latest.terminalizing || latest.revision !== observed.revision
|
|
1362
|
+
? Effect.void
|
|
1363
|
+
: this.setActiveState(state);
|
|
1364
|
+
}));
|
|
1365
|
+
});
|
|
1366
|
+
}
|
|
1367
|
+
getOrCreateChanges() {
|
|
1368
|
+
return Effect.suspend(() => {
|
|
1369
|
+
const observed = this.current;
|
|
1370
|
+
if (observed.snapshot.status !== "active") {
|
|
1371
|
+
return Effect.succeed(undefined);
|
|
1372
|
+
}
|
|
1373
|
+
if (observed.changes !== undefined) {
|
|
1374
|
+
return Effect.succeed(observed.changes);
|
|
1375
|
+
}
|
|
1376
|
+
return PubSub.unbounded({ replay: 1 }).pipe(Effect.flatMap((candidate) => Effect.sync(() => {
|
|
1377
|
+
const latest = this.current;
|
|
1378
|
+
if (latest.snapshot.status !== "active") {
|
|
1379
|
+
return [undefined, true];
|
|
1380
|
+
}
|
|
1381
|
+
if (latest.changes !== undefined) {
|
|
1382
|
+
return [latest.changes, true];
|
|
1383
|
+
}
|
|
1384
|
+
this.current = { ...latest, changes: candidate };
|
|
1385
|
+
return [candidate, false];
|
|
1386
|
+
}).pipe(Effect.flatMap(([changes, discard]) => discard ? PubSub.shutdown(candidate).pipe(Effect.as(changes)) : Effect.succeed(changes)))));
|
|
1387
|
+
});
|
|
1388
|
+
}
|
|
1389
|
+
changesStream() {
|
|
1390
|
+
const self = this;
|
|
1391
|
+
return Stream.unwrap(Effect.gen(function* () {
|
|
1392
|
+
const changes = yield* self.getOrCreateChanges();
|
|
1393
|
+
if (changes === undefined) {
|
|
1394
|
+
return Stream.succeed(self.current.snapshot);
|
|
1395
|
+
}
|
|
1396
|
+
const subscription = yield* PubSub.subscribe(changes);
|
|
1397
|
+
const captured = self.current;
|
|
1398
|
+
if (captured.snapshot.status !== "active") {
|
|
1399
|
+
return Stream.succeed(captured.snapshot);
|
|
1400
|
+
}
|
|
1401
|
+
return Stream.succeed(captured.snapshot).pipe(Stream.concat(Stream.fromChannel(Channel.fromEffectTake(PubSub.take(subscription))).pipe(Stream.filter((next) => next.revision > captured.revision), Stream.map((next) => next.snapshot))));
|
|
1402
|
+
}));
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
class CompiledProcessContextImpl {
|
|
1406
|
+
scope;
|
|
1407
|
+
ownedChildren;
|
|
1408
|
+
process;
|
|
1409
|
+
executionState;
|
|
1410
|
+
constructor(scope, ownedChildren, process) {
|
|
1411
|
+
this.scope = scope;
|
|
1412
|
+
this.ownedChildren = ownedChildren;
|
|
1413
|
+
this.process = process;
|
|
1414
|
+
}
|
|
1415
|
+
poll() {
|
|
1416
|
+
return Option.map(this.process.pollCompiledMessage(), messageEvent);
|
|
1417
|
+
}
|
|
1418
|
+
pollMessage() {
|
|
1419
|
+
return this.process.pollCompiledMessage();
|
|
1420
|
+
}
|
|
1421
|
+
state() {
|
|
1422
|
+
return this.process.compiledState();
|
|
1423
|
+
}
|
|
1424
|
+
completeMessage(delivery) {
|
|
1425
|
+
this.process.completeCompiledMessage(delivery);
|
|
1426
|
+
}
|
|
1427
|
+
commit(state) {
|
|
1428
|
+
return this.process.commitCompiledState(state);
|
|
1429
|
+
}
|
|
1430
|
+
runAfterChanges(effect) {
|
|
1431
|
+
this.process.flushPendingChanges();
|
|
1432
|
+
return effect;
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
const startCompactCompiledInternal = Effect.fnUntraced(function* (logic, options) {
|
|
1436
|
+
const sessionId = yield* options.runtime.nextSessionId;
|
|
1437
|
+
const services = yield* Effect.context();
|
|
1438
|
+
const execution = logic.execution;
|
|
1439
|
+
const process = new CompiledProcess(logic, options, services, sessionId);
|
|
1440
|
+
// A compiled initializer is synchronous by construction. Only startup
|
|
1441
|
+
// callbacks that themselves return Effects need the generic initialization
|
|
1442
|
+
// program; the compiled drain is still provided the complete service context.
|
|
1443
|
+
return yield* execution.initialSync !== undefined &&
|
|
1444
|
+
options.onReady === undefined && options.onSnapshot === undefined
|
|
1445
|
+
? process.initializeCompiledSync()
|
|
1446
|
+
: process.initialize();
|
|
1447
|
+
});
|
|
1448
|
+
const startLogicInternal = ((logic, options) => logic.execution?._tag === "Compiled"
|
|
1449
|
+
? startCompactCompiledInternal(logic, options)
|
|
1450
|
+
: startGenericInternal(logic, options));
|
|
1451
|
+
const startProcessWithStrategy = Effect.fnUntraced(function* (logic, strategy, options) {
|
|
1452
|
+
const runtime = yield* makeProcessRuntime;
|
|
1453
|
+
const internalOptions = options === undefined
|
|
1454
|
+
? {
|
|
1455
|
+
detached: true,
|
|
1456
|
+
runtime
|
|
1457
|
+
}
|
|
1458
|
+
: {
|
|
1459
|
+
...options,
|
|
1460
|
+
detached: true,
|
|
1461
|
+
runtime
|
|
1462
|
+
};
|
|
1463
|
+
if (strategy === "generic") {
|
|
1464
|
+
return yield* startGenericInternal(logic, internalOptions);
|
|
1465
|
+
}
|
|
1466
|
+
if (strategy === "compiled") {
|
|
1467
|
+
if (logic.execution?._tag !== "Compiled") {
|
|
1468
|
+
return yield* Effect.die(new Error("Machine cannot force the compiled runtime for generic process logic"));
|
|
1469
|
+
}
|
|
1470
|
+
return yield* startCompactCompiledInternal(logic, internalOptions);
|
|
1471
|
+
}
|
|
1472
|
+
return yield* startLogicInternal(logic, internalOptions);
|
|
1473
|
+
});
|
|
1474
|
+
/** @internal Test-only startup strategy selection. */
|
|
1475
|
+
export const startProcessWithStrategyForTesting = (logic, strategy, options) => startProcessWithStrategy(logic, strategy, options);
|
|
1476
|
+
export const startProcess = Effect.fnUntraced(function* (logic, options) {
|
|
1477
|
+
const runtime = yield* makeProcessRuntime;
|
|
1478
|
+
return yield* startLogicInternal(logic, options === undefined
|
|
1479
|
+
? {
|
|
1480
|
+
detached: true,
|
|
1481
|
+
runtime
|
|
1482
|
+
}
|
|
1483
|
+
: {
|
|
1484
|
+
...options,
|
|
1485
|
+
detached: true,
|
|
1486
|
+
runtime
|
|
1487
|
+
});
|
|
1488
|
+
});
|
|
1489
|
+
//# sourceMappingURL=runtime.js.map
|