@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.
Files changed (170) hide show
  1. package/README.md +410 -17
  2. package/dist/Machine.d.ts +1191 -510
  3. package/dist/Machine.d.ts.map +1 -1
  4. package/dist/Machine.js +371 -565
  5. package/dist/Machine.js.map +1 -1
  6. package/dist/internal/machine/activities.d.ts +57 -0
  7. package/dist/internal/machine/activities.d.ts.map +1 -0
  8. package/dist/internal/machine/activities.js +49 -0
  9. package/dist/internal/machine/activities.js.map +1 -0
  10. package/dist/internal/machine/atom.d.ts +69 -0
  11. package/dist/internal/machine/atom.d.ts.map +1 -0
  12. package/dist/{AtomMachine.js → internal/machine/atom.js} +20 -95
  13. package/dist/internal/machine/atom.js.map +1 -0
  14. package/dist/internal/machine/cluster.d.ts +63 -0
  15. package/dist/internal/machine/cluster.d.ts.map +1 -0
  16. package/dist/{ClusterMachine.js → internal/machine/cluster.js} +15 -119
  17. package/dist/internal/machine/cluster.js.map +1 -0
  18. package/dist/internal/machine/command.d.ts +16 -0
  19. package/dist/internal/machine/command.d.ts.map +1 -0
  20. package/dist/internal/machine/command.js +45 -0
  21. package/dist/internal/machine/command.js.map +1 -0
  22. package/dist/internal/machine/commandRuntime.d.ts +13 -0
  23. package/dist/internal/machine/commandRuntime.d.ts.map +1 -0
  24. package/dist/internal/machine/commandRuntime.js +16 -0
  25. package/dist/internal/machine/commandRuntime.js.map +1 -0
  26. package/dist/internal/{machineModel.d.ts → machine/configuration.d.ts} +12 -61
  27. package/dist/internal/machine/configuration.d.ts.map +1 -0
  28. package/dist/internal/{machineModel.js → machine/configuration.js} +250 -545
  29. package/dist/internal/machine/configuration.js.map +1 -0
  30. package/dist/internal/{machineErrors.d.ts → machine/errors.d.ts} +8 -8
  31. package/dist/internal/machine/errors.d.ts.map +1 -0
  32. package/dist/internal/{machineErrors.js → machine/errors.js} +8 -8
  33. package/dist/internal/machine/errors.js.map +1 -0
  34. package/dist/internal/machine/executionPlan.d.ts +49 -0
  35. package/dist/internal/machine/executionPlan.d.ts.map +1 -0
  36. package/dist/internal/machine/executionPlan.js +676 -0
  37. package/dist/internal/machine/executionPlan.js.map +1 -0
  38. package/dist/internal/machine/invocation.d.ts +23 -0
  39. package/dist/internal/machine/invocation.d.ts.map +1 -0
  40. package/dist/internal/machine/invocation.js +77 -0
  41. package/dist/internal/machine/invocation.js.map +1 -0
  42. package/dist/internal/machine/machine.d.ts +183 -0
  43. package/dist/internal/machine/machine.d.ts.map +1 -0
  44. package/dist/internal/machine/machine.js +552 -0
  45. package/dist/internal/machine/machine.js.map +1 -0
  46. package/dist/internal/machine/planner.d.ts +182 -0
  47. package/dist/internal/machine/planner.d.ts.map +1 -0
  48. package/dist/internal/machine/planner.js +1082 -0
  49. package/dist/internal/machine/planner.js.map +1 -0
  50. package/dist/internal/{machineProcess.d.ts → machine/process.d.ts} +11 -5
  51. package/dist/internal/machine/process.d.ts.map +1 -0
  52. package/dist/internal/machine/process.js +498 -0
  53. package/dist/internal/machine/process.js.map +1 -0
  54. package/dist/internal/machine/protocol.d.ts +34 -0
  55. package/dist/internal/machine/protocol.d.ts.map +1 -0
  56. package/dist/internal/machine/protocol.js +182 -0
  57. package/dist/internal/machine/protocol.js.map +1 -0
  58. package/dist/internal/machine/readiness.d.ts +4 -0
  59. package/dist/internal/machine/readiness.d.ts.map +1 -0
  60. package/dist/internal/machine/readiness.js +2 -0
  61. package/dist/internal/machine/readiness.js.map +1 -0
  62. package/dist/internal/machine/runtime.d.ts +255 -0
  63. package/dist/internal/machine/runtime.d.ts.map +1 -0
  64. package/dist/internal/machine/runtime.js +1489 -0
  65. package/dist/internal/machine/runtime.js.map +1 -0
  66. package/dist/internal/machine/serialization.d.ts +14 -0
  67. package/dist/internal/machine/serialization.d.ts.map +1 -0
  68. package/dist/internal/machine/serialization.js +338 -0
  69. package/dist/internal/machine/serialization.js.map +1 -0
  70. package/dist/internal/machine/stateDefinition.d.ts +15 -0
  71. package/dist/internal/machine/stateDefinition.d.ts.map +1 -0
  72. package/dist/internal/machine/stateDefinition.js +210 -0
  73. package/dist/internal/machine/stateDefinition.js.map +1 -0
  74. package/dist/internal/machine/symbols.d.ts +3 -0
  75. package/dist/internal/machine/symbols.d.ts.map +1 -0
  76. package/dist/internal/machine/symbols.js +3 -0
  77. package/dist/internal/machine/symbols.js.map +1 -0
  78. package/dist/internal/machine/topology.d.ts +98 -0
  79. package/dist/internal/machine/topology.d.ts.map +1 -0
  80. package/dist/internal/machine/topology.js +347 -0
  81. package/dist/internal/machine/topology.js.map +1 -0
  82. package/dist/internal/testing/machine/arbitrary.d.ts +36 -0
  83. package/dist/internal/testing/machine/arbitrary.d.ts.map +1 -0
  84. package/dist/internal/testing/machine/arbitrary.js +68 -0
  85. package/dist/internal/testing/machine/arbitrary.js.map +1 -0
  86. package/dist/internal/testing/machine/exploration.d.ts +30 -0
  87. package/dist/internal/testing/machine/exploration.d.ts.map +1 -0
  88. package/dist/internal/testing/machine/exploration.js +237 -0
  89. package/dist/internal/testing/machine/exploration.js.map +1 -0
  90. package/dist/internal/testing/machine/finiteModel.d.ts +322 -0
  91. package/dist/internal/testing/machine/finiteModel.d.ts.map +1 -0
  92. package/dist/internal/testing/machine/finiteModel.js +976 -0
  93. package/dist/internal/testing/machine/finiteModel.js.map +1 -0
  94. package/dist/internal/testing/machine/invariant.d.ts +31 -0
  95. package/dist/internal/testing/machine/invariant.d.ts.map +1 -0
  96. package/dist/internal/testing/machine/invariant.js +214 -0
  97. package/dist/internal/testing/machine/invariant.js.map +1 -0
  98. package/dist/internal/testing/machine/probe.d.ts +26 -0
  99. package/dist/internal/testing/machine/probe.d.ts.map +1 -0
  100. package/dist/internal/testing/machine/probe.js +45 -0
  101. package/dist/internal/testing/machine/probe.js.map +1 -0
  102. package/dist/internal/testing/machine/referenceModel.d.ts +203 -0
  103. package/dist/internal/testing/machine/referenceModel.d.ts.map +1 -0
  104. package/dist/internal/testing/machine/referenceModel.js +1012 -0
  105. package/dist/internal/testing/machine/referenceModel.js.map +1 -0
  106. package/dist/internal/testing/machine/runtime.d.ts +631 -0
  107. package/dist/internal/testing/machine/runtime.d.ts.map +1 -0
  108. package/dist/internal/testing/machine/runtime.js +726 -0
  109. package/dist/internal/testing/machine/runtime.js.map +1 -0
  110. package/dist/internal/testing/machine/runtimeInvariant.d.ts +32 -0
  111. package/dist/internal/testing/machine/runtimeInvariant.d.ts.map +1 -0
  112. package/dist/internal/testing/machine/runtimeInvariant.js +334 -0
  113. package/dist/internal/testing/machine/runtimeInvariant.js.map +1 -0
  114. package/dist/internal/testing/machine/trace.d.ts +17 -0
  115. package/dist/internal/testing/machine/trace.d.ts.map +1 -0
  116. package/dist/internal/testing/machine/trace.js +94 -0
  117. package/dist/internal/testing/machine/trace.js.map +1 -0
  118. package/dist/internal/testing/machine/verification.d.ts +36 -0
  119. package/dist/internal/testing/machine/verification.d.ts.map +1 -0
  120. package/dist/internal/testing/machine/verification.js +1375 -0
  121. package/dist/internal/testing/machine/verification.js.map +1 -0
  122. package/dist/testing/MachineTest.d.ts +1588 -0
  123. package/dist/testing/MachineTest.d.ts.map +1 -0
  124. package/dist/testing/MachineTest.js +406 -0
  125. package/dist/testing/MachineTest.js.map +1 -0
  126. package/dist/testing/index.d.ts +7 -0
  127. package/dist/testing/index.d.ts.map +1 -0
  128. package/dist/testing/index.js +7 -0
  129. package/dist/testing/index.js.map +1 -0
  130. package/dist/{ClusterMachine.d.ts → unstable/cluster/ClusterMachine.d.ts} +56 -45
  131. package/dist/unstable/cluster/ClusterMachine.d.ts.map +1 -0
  132. package/dist/unstable/cluster/ClusterMachine.js +144 -0
  133. package/dist/unstable/cluster/ClusterMachine.js.map +1 -0
  134. package/dist/{cluster.js → unstable/cluster/index.d.ts} +1 -1
  135. package/dist/unstable/cluster/index.d.ts.map +1 -0
  136. package/dist/{cluster.d.ts → unstable/cluster/index.js} +1 -1
  137. package/dist/unstable/cluster/index.js.map +1 -0
  138. package/dist/{AtomMachine.d.ts → unstable/reactivity/AtomMachine.d.ts} +116 -50
  139. package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -0
  140. package/dist/unstable/reactivity/AtomMachine.js +164 -0
  141. package/dist/unstable/reactivity/AtomMachine.js.map +1 -0
  142. package/dist/{reactivity.js → unstable/reactivity/index.d.ts} +1 -1
  143. package/dist/unstable/reactivity/index.d.ts.map +1 -0
  144. package/dist/{reactivity.d.ts → unstable/reactivity/index.js} +1 -1
  145. package/dist/unstable/reactivity/index.js.map +1 -0
  146. package/docs/agent-guide.md +316 -31
  147. package/package.json +28 -10
  148. package/dist/AtomMachine.d.ts.map +0 -1
  149. package/dist/AtomMachine.js.map +0 -1
  150. package/dist/ClusterMachine.d.ts.map +0 -1
  151. package/dist/ClusterMachine.js.map +0 -1
  152. package/dist/cluster.d.ts.map +0 -1
  153. package/dist/cluster.js.map +0 -1
  154. package/dist/internal/machineErrors.d.ts.map +0 -1
  155. package/dist/internal/machineErrors.js.map +0 -1
  156. package/dist/internal/machineModel.d.ts.map +0 -1
  157. package/dist/internal/machineModel.js.map +0 -1
  158. package/dist/internal/machinePlanner.d.ts +0 -74
  159. package/dist/internal/machinePlanner.d.ts.map +0 -1
  160. package/dist/internal/machinePlanner.js +0 -797
  161. package/dist/internal/machinePlanner.js.map +0 -1
  162. package/dist/internal/machineProcess.d.ts.map +0 -1
  163. package/dist/internal/machineProcess.js +0 -181
  164. package/dist/internal/machineProcess.js.map +0 -1
  165. package/dist/internal/machineRuntime.d.ts +0 -125
  166. package/dist/internal/machineRuntime.d.ts.map +0 -1
  167. package/dist/internal/machineRuntime.js +0 -365
  168. package/dist/internal/machineRuntime.js.map +0 -1
  169. package/dist/reactivity.d.ts.map +0 -1
  170. 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