@typeonce/effect-machine 0.1.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 (48) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +5 -0
  3. package/README.md +117 -0
  4. package/dist/AtomMachine.d.ts +141 -0
  5. package/dist/AtomMachine.d.ts.map +1 -0
  6. package/dist/AtomMachine.js +191 -0
  7. package/dist/AtomMachine.js.map +1 -0
  8. package/dist/ClusterMachine.d.ts +250 -0
  9. package/dist/ClusterMachine.d.ts.map +1 -0
  10. package/dist/ClusterMachine.js +280 -0
  11. package/dist/ClusterMachine.js.map +1 -0
  12. package/dist/Machine.d.ts +2587 -0
  13. package/dist/Machine.d.ts.map +1 -0
  14. package/dist/Machine.js +923 -0
  15. package/dist/Machine.js.map +1 -0
  16. package/dist/cluster.d.ts +2 -0
  17. package/dist/cluster.d.ts.map +1 -0
  18. package/dist/cluster.js +2 -0
  19. package/dist/cluster.js.map +1 -0
  20. package/dist/index.d.ts +2 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +2 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/internal/machineErrors.d.ts +109 -0
  25. package/dist/internal/machineErrors.d.ts.map +1 -0
  26. package/dist/internal/machineErrors.js +65 -0
  27. package/dist/internal/machineErrors.js.map +1 -0
  28. package/dist/internal/machineModel.d.ts +88 -0
  29. package/dist/internal/machineModel.d.ts.map +1 -0
  30. package/dist/internal/machineModel.js +713 -0
  31. package/dist/internal/machineModel.js.map +1 -0
  32. package/dist/internal/machinePlanner.d.ts +64 -0
  33. package/dist/internal/machinePlanner.d.ts.map +1 -0
  34. package/dist/internal/machinePlanner.js +594 -0
  35. package/dist/internal/machinePlanner.js.map +1 -0
  36. package/dist/internal/machineProcess.d.ts +16 -0
  37. package/dist/internal/machineProcess.d.ts.map +1 -0
  38. package/dist/internal/machineProcess.js +170 -0
  39. package/dist/internal/machineProcess.js.map +1 -0
  40. package/dist/internal/machineRuntime.d.ts +119 -0
  41. package/dist/internal/machineRuntime.d.ts.map +1 -0
  42. package/dist/internal/machineRuntime.js +354 -0
  43. package/dist/internal/machineRuntime.js.map +1 -0
  44. package/dist/reactivity.d.ts +2 -0
  45. package/dist/reactivity.d.ts.map +1 -0
  46. package/dist/reactivity.js +2 -0
  47. package/dist/reactivity.js.map +1 -0
  48. package/package.json +76 -0
@@ -0,0 +1,923 @@
1
+ /**
2
+ * Schema-first machine definitions.
3
+ *
4
+ * @since 4.0.0
5
+ */
6
+ import * as Effect from "effect/Effect";
7
+ import * as Inspectable from "effect/Inspectable";
8
+ import * as Option from "effect/Option";
9
+ import { Prototype as PipeablePrototype } from "effect/Pipeable";
10
+ import { hasProperty } from "effect/Predicate";
11
+ import { ProcessLocalError as ProcessLocalErrorValue } from "./internal/machineErrors.js";
12
+ import * as Model from "./internal/machineModel.js";
13
+ import * as internalPlanner from "./internal/machinePlanner.js";
14
+ import * as internalProcess from "./internal/machineProcess.js";
15
+ import * as internalRuntime from "./internal/machineRuntime.js";
16
+ /**
17
+ * Runtime type identifier attached to `Machine` values.
18
+ *
19
+ * @category type IDs
20
+ * @since 4.0.0
21
+ */
22
+ export const TypeId = "~effect/Machine";
23
+ /**
24
+ * Type identifier used for the synthetic event passed to startup lifecycle
25
+ * actions.
26
+ *
27
+ * @category type IDs
28
+ * @since 4.0.0
29
+ */
30
+ export const InitialEventTypeId = internalPlanner.InitialEventTypeId;
31
+ /**
32
+ * Synthetic event value used while the machine settles its initial state.
33
+ *
34
+ * @category constructors
35
+ * @since 4.0.0
36
+ */
37
+ export const InitialEvent = internalPlanner.InitialEvent;
38
+ /**
39
+ * Returns `true` if a value is the synthetic machine initial event.
40
+ *
41
+ * @category guards
42
+ * @since 4.0.0
43
+ */
44
+ export const isInitialEvent = (u) => hasProperty(u, "_tag") && u._tag === InitialEventTypeId;
45
+ export {
46
+ /**
47
+ * Error returned by `spawn` when a child process with the same id already
48
+ * exists for the current machine.
49
+ *
50
+ * @category errors
51
+ * @since 4.0.0
52
+ */
53
+ ChildAlreadyExistsError,
54
+ /**
55
+ * Error returned when a machine does not stabilize within the maximum
56
+ * number of macrostep iterations.
57
+ *
58
+ * @category errors
59
+ * @since 4.0.0
60
+ */
61
+ InfiniteTransitionError,
62
+ /**
63
+ * Error returned when a machine contract value does not match the schema or
64
+ * structural configuration declared for a machine boundary.
65
+ *
66
+ * @category errors
67
+ * @since 4.0.0
68
+ */
69
+ MachineSchemaDecodeError,
70
+ /**
71
+ * Error returned when a decoded machine snapshot cannot be encoded through
72
+ * its declared state or output schemas.
73
+ *
74
+ * @category errors
75
+ * @since 4.0.0
76
+ */
77
+ MachineSchemaEncodeError,
78
+ /**
79
+ * Error returned when standalone action execution attempts an operation that
80
+ * requires a managed machine process.
81
+ *
82
+ * @category errors
83
+ * @since 4.0.0
84
+ */
85
+ ProcessLocalError,
86
+ /**
87
+ * Error returned when a machine fails while running startup lifecycle
88
+ * logic after the initial state has been computed.
89
+ *
90
+ * @category errors
91
+ * @since 4.0.0
92
+ */
93
+ StartupError,
94
+ /**
95
+ * Error returned by `join` when a running machine is stopped before
96
+ * producing an output.
97
+ *
98
+ * @category errors
99
+ * @since 4.0.0
100
+ */
101
+ StoppedError } from "./internal/machineErrors.js";
102
+ const RuntimeRequirementTypeId = "~effect/Machine/RuntimeRequirement";
103
+ const ActionRequirementTypeId = "~effect/Machine/ActionRequirement";
104
+ const RuntimeCompatibilityErrorTypeId = "~effect/Machine/RuntimeCompatibilityError";
105
+ const InvokeTypeId = Symbol.for("effect/Machine/Invoke");
106
+ const SnapshotBuilderStateTypeId = Symbol("effect/Machine/SnapshotBuilderState");
107
+ const ChildAddressTypeId = "~effect/Machine/ChildAddress";
108
+ const ChildAddressCompatibilityErrorTypeId = "~effect/Machine/ChildAddressCompatibilityError";
109
+ const ChildMachineTypeId = "~effect/Machine/ChildMachine";
110
+ const Proto = {
111
+ ...Inspectable.BaseProto,
112
+ ...PipeablePrototype,
113
+ [TypeId]: TypeId,
114
+ toJSON() {
115
+ return {
116
+ _id: "Machine"
117
+ };
118
+ }
119
+ };
120
+ const cloneWithHandlers = (self, handlers) => {
121
+ const machine = Object.create(Proto);
122
+ machine.states = self.states;
123
+ machine.events = self.events;
124
+ machine.emits = self.emits;
125
+ machine.input = self.input;
126
+ machine.id = self.id;
127
+ machine.initial = self.initial;
128
+ machine.stateNodes = self.stateNodes;
129
+ machine.makeTargetBuilder = self.makeTargetBuilder;
130
+ machine.handlers = handlers;
131
+ machine.handle = makeHandle(machine);
132
+ return machine;
133
+ };
134
+ const flattenHandlers = (handlers, states, prefix, config) => {
135
+ for (const key of Object.keys(config)) {
136
+ const path = prefix === "" ? key : `${prefix}.${key}`;
137
+ if (!hasProperty(states, key)) {
138
+ throw new Error(`Machine received handler for unknown state "${path}"`);
139
+ }
140
+ const nodeConfig = config[key];
141
+ if (typeof nodeConfig !== "object" || nodeConfig === null) {
142
+ throw new Error(`Machine expected state "${path}" handler to be an object`);
143
+ }
144
+ const { states: childConfig, ...stateConfig } = nodeConfig;
145
+ handlers[path] = stateConfig;
146
+ if (childConfig !== undefined) {
147
+ const node = Model.getStateNodeDefinition(path, states[key]);
148
+ if (node.states === undefined) {
149
+ throw new Error(`Machine expected state "${path}" to declare child states`);
150
+ }
151
+ if (typeof childConfig !== "object" || childConfig === null) {
152
+ throw new Error(`Machine expected state "${path}" child handlers to be an object`);
153
+ }
154
+ flattenHandlers(handlers, node.states, path, childConfig);
155
+ }
156
+ }
157
+ };
158
+ const makeHandle = (self) => ((config) => {
159
+ const handlers = Object.assign(Object.create(null), self.handlers);
160
+ flattenHandlers(handlers, self.states, "", config);
161
+ return cloneWithHandlers(self, handlers);
162
+ });
163
+ /**
164
+ * Returns `true` if a value is a `Machine`.
165
+ *
166
+ * @category guards
167
+ * @since 4.0.0
168
+ */
169
+ export const isMachine = (u) => hasProperty(u, TypeId);
170
+ /**
171
+ * Returns `true` if a state snapshot is final for a machine.
172
+ *
173
+ * @category guards
174
+ * @since 4.0.0
175
+ */
176
+ export const isFinal = (machine, state) => internalPlanner.isFinal(machine, state);
177
+ const makeSnapshotBuilder = (states, options) => {
178
+ const builder = {};
179
+ for (const key of Object.keys(states)) {
180
+ builder[key] = (value, selector) => makeSnapshotForNode(states[key], key, value, selector, options);
181
+ }
182
+ return builder;
183
+ };
184
+ const makeParallelSnapshotBuilder = (states, options, regions) => {
185
+ const builder = {};
186
+ Object.defineProperty(builder, SnapshotBuilderStateTypeId, {
187
+ value: regions,
188
+ enumerable: false
189
+ });
190
+ for (const key of Object.keys(states)) {
191
+ if (hasProperty(regions, key)) {
192
+ continue;
193
+ }
194
+ builder[key] = (value, selector) => {
195
+ const nextRegions = {};
196
+ for (const regionKey of Object.keys(regions)) {
197
+ nextRegions[regionKey] = regions[regionKey];
198
+ }
199
+ nextRegions[key] = makeSnapshotForNode(states[key], key, value, selector, options);
200
+ return makeParallelSnapshotBuilder(states, options, nextRegions);
201
+ };
202
+ }
203
+ return builder;
204
+ };
205
+ const getParallelSnapshotBuilderRegions = (path, states, builder) => {
206
+ if (typeof builder !== "object" || builder === null || !hasProperty(builder, SnapshotBuilderStateTypeId)) {
207
+ throw new Error(`Machine expected parallel state "${path}" builder callback to return a builder`);
208
+ }
209
+ const regions = builder[SnapshotBuilderStateTypeId];
210
+ for (const key of Object.keys(states)) {
211
+ if (!hasProperty(regions, key)) {
212
+ throw new Error(`Machine expected parallel state "${path}" builder callback to provide region "${key}"`);
213
+ }
214
+ }
215
+ return regions;
216
+ };
217
+ const makeSnapshotForNode = (definition, key, value, selector, options) => {
218
+ const path = options.prefix === "" ? key : `${options.prefix}.${key}`;
219
+ const node = Model.getStateNodeDefinition(path, definition);
220
+ const snapshot = {
221
+ path,
222
+ value
223
+ };
224
+ if (node.states === undefined) {
225
+ return snapshot;
226
+ }
227
+ if (selector === undefined) {
228
+ throw new Error(`Machine expected state "${path}" builder to provide active child states`);
229
+ }
230
+ if (node.type === "parallel") {
231
+ const builder = makeParallelSnapshotBuilder(node.states, { ...options, prefix: path }, {});
232
+ snapshot.states = getParallelSnapshotBuilderRegions(path, node.states, selector(builder));
233
+ return snapshot;
234
+ }
235
+ const childStates = options.mode === "initial" && node.initial !== undefined
236
+ ? { [node.initial]: node.states[node.initial] }
237
+ : node.states;
238
+ snapshot.state = selector(makeSnapshotBuilder(childStates, { ...options, prefix: path }));
239
+ return snapshot;
240
+ };
241
+ const getTargetBuilderNode = (stateNodes, path) => {
242
+ const node = stateNodes.byPath.get(path);
243
+ if (node === undefined) {
244
+ throw new Error(`Machine expected state path "${path}" to exist`);
245
+ }
246
+ return node;
247
+ };
248
+ const getLocalTargetScope = (stateNodes, source) => {
249
+ let current = source;
250
+ while (current !== undefined) {
251
+ const node = stateNodes.byPath.get(current);
252
+ if (node === undefined) {
253
+ return undefined;
254
+ }
255
+ if (node.type === "compound") {
256
+ return node.path;
257
+ }
258
+ current = node.parent;
259
+ }
260
+ return undefined;
261
+ };
262
+ const hasTargetValues = (values) => values !== undefined && Object.keys(values).length > 0;
263
+ const makeTargetWithValues = (path, value, values) => hasTargetValues(values)
264
+ ? Model.makeTarget(path, value, { values: values })
265
+ : Model.makeTarget(path, value);
266
+ const extendTargetValues = (values, path, value) => {
267
+ const next = {};
268
+ if (values !== undefined) {
269
+ for (const key of Object.keys(values)) {
270
+ next[key] = values[key];
271
+ }
272
+ }
273
+ next[path] = value;
274
+ return next;
275
+ };
276
+ const makeLocalTargetChildBuilder = (stateNodes, parentPath, values) => {
277
+ const parent = getTargetBuilderNode(stateNodes, parentPath);
278
+ const builder = {};
279
+ for (const childPath of parent.children) {
280
+ const child = getTargetBuilderNode(stateNodes, childPath);
281
+ builder[child.key] = (value, selector) => {
282
+ if (child.type === "atomic" || child.type === "final") {
283
+ return makeTargetWithValues(child.path, value, values);
284
+ }
285
+ if (selector === undefined) {
286
+ throw new Error(`Machine expected target "${child.path}" builder to provide an active child state`);
287
+ }
288
+ return selector(makeLocalTargetChildBuilder(stateNodes, child.path, extendTargetValues(values, child.path, value)));
289
+ };
290
+ }
291
+ return builder;
292
+ };
293
+ const makeLocalTargetBuilder = (stateNodes, source) => {
294
+ const scope = getLocalTargetScope(stateNodes, source);
295
+ if (scope === undefined) {
296
+ return {};
297
+ }
298
+ const builder = makeLocalTargetChildBuilder(stateNodes, scope, undefined);
299
+ builder.with = (value, selector) => {
300
+ if (selector === undefined) {
301
+ throw new Error(`Machine expected target "${scope}" builder to provide an active child state`);
302
+ }
303
+ return selector(makeLocalTargetChildBuilder(stateNodes, scope, { [scope]: value }));
304
+ };
305
+ return builder;
306
+ };
307
+ const addBranchTargetChildren = (builder, stateNodes, parentPath, values) => {
308
+ const parent = getTargetBuilderNode(stateNodes, parentPath);
309
+ for (const childPath of parent.children) {
310
+ const child = getTargetBuilderNode(stateNodes, childPath);
311
+ builder[child.key] = makeBranchTargetNodeBuilder(stateNodes, child.path, values);
312
+ }
313
+ };
314
+ const makeBranchTargetNodeBuilder = (stateNodes, path, values) => {
315
+ const node = getTargetBuilderNode(stateNodes, path);
316
+ if (node.type === "atomic" || node.type === "final") {
317
+ return (value) => makeTargetWithValues(node.path, value, values);
318
+ }
319
+ const builder = ((value, selector) => {
320
+ if (selector === undefined) {
321
+ throw new Error(`Machine expected target "${node.path}" builder to provide an active child state`);
322
+ }
323
+ const nextBuilder = {};
324
+ addBranchTargetChildren(nextBuilder, stateNodes, node.path, extendTargetValues(values, node.path, value));
325
+ return selector(nextBuilder);
326
+ });
327
+ addBranchTargetChildren(builder, stateNodes, node.path, values);
328
+ return builder;
329
+ };
330
+ const makeBranchTargetBuilder = (stateNodes, source) => {
331
+ const rootPath = source.split(".")[0];
332
+ const root = getTargetBuilderNode(stateNodes, rootPath);
333
+ return {
334
+ [root.key]: makeBranchTargetNodeBuilder(stateNodes, root.path, undefined)
335
+ };
336
+ };
337
+ const makeTargetBuilder = (states, stateNodes) => {
338
+ const full = makeSnapshotBuilder(states, { mode: "full", prefix: "" });
339
+ return (source) => ({
340
+ local: makeLocalTargetBuilder(stateNodes, source),
341
+ branch: makeBranchTargetBuilder(stateNodes, source),
342
+ full
343
+ });
344
+ };
345
+ const getSnapshotByPath = (snapshot, path, parents) => {
346
+ if (snapshot.path === path) {
347
+ return Option.some(snapshot);
348
+ }
349
+ if (!path.startsWith(`${snapshot.path}.`)) {
350
+ return Option.none();
351
+ }
352
+ if (parents !== undefined) {
353
+ parents[snapshot.path] = snapshot.value;
354
+ }
355
+ if (hasProperty(snapshot, "state") && Model.isSnapshot(snapshot.state)) {
356
+ return getSnapshotByPath(snapshot.state, path, parents);
357
+ }
358
+ if (hasProperty(snapshot, "states") && typeof snapshot.states === "object" && snapshot.states !== null) {
359
+ for (const child of Object.values(snapshot.states)) {
360
+ if (Model.isSnapshot(child)) {
361
+ const result = getSnapshotByPath(child, path, parents);
362
+ if (Option.isSome(result)) {
363
+ return result;
364
+ }
365
+ }
366
+ }
367
+ }
368
+ return Option.none();
369
+ };
370
+ /**
371
+ * Defines a state tree while preserving literal state paths.
372
+ *
373
+ * **When to use**
374
+ *
375
+ * Use when you want to pass a state tree to `make` and also get typed
376
+ * snapshot builders for initial states and tests.
377
+ *
378
+ * **Details**
379
+ *
380
+ * The returned `states` property is the same object passed to `defineStates`.
381
+ * The returned `initial` builder creates snapshots without user-authored path
382
+ * strings and enforces compound and parallel initial-state rules.
383
+ *
384
+ * **Example** (Atomic initial snapshot)
385
+ *
386
+ * ```ts
387
+ * import { Schema } from "effect"
388
+ * import { Machine } from "effect/unstable/machine"
389
+ *
390
+ * class Idle extends Schema.TaggedClass<Idle>("Idle")("Idle", {}) {}
391
+ *
392
+ * const States = Machine.defineStates({ idle: Idle })
393
+ *
394
+ * Machine.make({
395
+ * states: States.states,
396
+ * events: [],
397
+ * initial: () => States.initial.idle(new Idle({}))
398
+ * })
399
+ * ```
400
+ *
401
+ * @category constructors
402
+ * @since 4.0.0
403
+ */
404
+ export const defineStates = (states, ..._validation) => ({
405
+ states: states,
406
+ initial: makeSnapshotBuilder(states, { mode: "initial", prefix: "" }),
407
+ get: ((snapshot, path) => getSnapshotByPath(snapshot, path).pipe(Option.map((snapshot) => snapshot.value))),
408
+ getWithParents: ((snapshot, path) => {
409
+ const parents = {};
410
+ return getSnapshotByPath(snapshot, path, parents).pipe(Option.map((snapshot) => ({ value: snapshot.value, parents })));
411
+ }),
412
+ getSnapshot: getSnapshotByPath,
413
+ matches: (snapshot, path) => Option.isSome(getSnapshotByPath(snapshot, path))
414
+ });
415
+ /**
416
+ * Creates a schema-first machine definition.
417
+ *
418
+ * **Details**
419
+ *
420
+ * State and event schemas provide runtime boundary validation while their
421
+ * decoded types drive handler, state, event, target, error, and service
422
+ * inference. State-tree validation is applied whether `states` comes from
423
+ * `defineStates` or is passed inline. Call `handle` on the returned definition
424
+ * to implement state behavior with ordinary TypeScript control flow.
425
+ *
426
+ * **Example** (Typed counter machine)
427
+ *
428
+ * ```ts
429
+ * import { Schema } from "effect"
430
+ * import { Machine } from "effect/unstable/machine"
431
+ *
432
+ * class Count extends Schema.TaggedClass<Count>("Count")("Count", {
433
+ * value: Schema.Number
434
+ * }) {}
435
+ *
436
+ * class Increment extends Schema.TaggedClass<Increment>("Increment")("Increment", {
437
+ * by: Schema.Number
438
+ * }) {}
439
+ *
440
+ * const States = Machine.defineStates({ Count })
441
+ *
442
+ * const counter = Machine.make({
443
+ * states: States.states,
444
+ * events: [Increment],
445
+ * initial: () => States.initial.Count(new Count({ value: 0 }))
446
+ * }).handle({
447
+ * Count: {
448
+ * on: {
449
+ * Increment: ({ event, state }) =>
450
+ * States.initial.Count(new Count({ value: state.value + event.by }))
451
+ * }
452
+ * }
453
+ * })
454
+ * ```
455
+ *
456
+ * @see {@link defineStates} for typed initial snapshot builders.
457
+ * @category constructors
458
+ * @since 4.0.0
459
+ */
460
+ export const make = (config, ..._validation) => {
461
+ const self = Object.create(Proto);
462
+ self.states = config.states;
463
+ self.events = config.events;
464
+ self.emits = config.emits ?? [];
465
+ self.input = config.input;
466
+ self.id = config.id;
467
+ self.initial = config.initial;
468
+ self.stateNodes = Model.compileStateNodes(config.states);
469
+ self.makeTargetBuilder = makeTargetBuilder(config.states, self.stateNodes);
470
+ self.handlers = Object.create(null);
471
+ self.handle = makeHandle(self);
472
+ return self;
473
+ };
474
+ /**
475
+ * Encodes a decoded machine snapshot into a normalized data representation.
476
+ *
477
+ * **When to use**
478
+ *
479
+ * Use when you need to store or transport a statechart snapshot independently
480
+ * of its local machine runtime.
481
+ *
482
+ * **Details**
483
+ *
484
+ * Each active state value and completed output is encoded with the schema
485
+ * declared for its state path. The result contains no process-local runtime
486
+ * state.
487
+ *
488
+ * **Gotchas**
489
+ *
490
+ * The encoded snapshot does not contain the machine definition, machine
491
+ * version, running children, invoked process state, services, or subscriptions.
492
+ * Store machine identity and migration metadata alongside the result when the
493
+ * snapshot crosses deployment versions. Schema encoding does not by itself
494
+ * guarantee JSON-compatible values; schemas used with JSON-backed storage must
495
+ * have JSON-compatible encoded representations.
496
+ *
497
+ * @see {@link decodeSnapshot} for restoring an encoded snapshot.
498
+ * @category encoding
499
+ * @since 4.0.0
500
+ */
501
+ export const encodeSnapshot = Model.encodeSnapshot;
502
+ /**
503
+ * Decodes a normalized data representation into a validated machine snapshot.
504
+ *
505
+ * **When to use**
506
+ *
507
+ * Use when you need to resume planning from a snapshot loaded from storage or
508
+ * received over a transport boundary.
509
+ *
510
+ * **Details**
511
+ *
512
+ * Decoding resolves every path against the supplied machine, decodes values
513
+ * with their state and output schemas, validates compound and parallel state
514
+ * relationships, and rebuilds the recursive in-memory snapshot.
515
+ *
516
+ * **Gotchas**
517
+ *
518
+ * Decoding restores logical statechart data only. It does not restart invoked
519
+ * processes, recreate spawned children, or restore a previous `MachineRef`.
520
+ *
521
+ * @see {@link encodeSnapshot} for creating the normalized representation.
522
+ * @category decoding
523
+ * @since 4.0.0
524
+ */
525
+ export const decodeSnapshot = Model.decodeSnapshot;
526
+ /**
527
+ * Creates an invoked child process configuration for an active state.
528
+ *
529
+ * **When to use**
530
+ *
531
+ * Use to run a child process while a machine remains in a state. Successful
532
+ * outputs are sent directly to the parent machine as events; `void` sends
533
+ * nothing. Unrecovered child failures fail the owning machine. Active
534
+ * snapshots can optionally be mapped to progress events.
535
+ *
536
+ * **Gotchas**
537
+ *
538
+ * Invoked child processes run while their owning state is active and are
539
+ * stopped before the state exits. An unrecovered child failure fails the owning
540
+ * machine; recover inside the child Effect when failure should become an event.
541
+ * The `src` callback is intentionally independent from its parent state. When
542
+ * construction depends on the typed state, lifecycle event, or runtime, use
543
+ * the state config factory form `invoke: (context) => Machine.invoke(...)` and
544
+ * close over that context from `src`.
545
+ *
546
+ * **Example** (Effect output as a parent event)
547
+ *
548
+ * ```ts
549
+ * import { Effect, Schema } from "effect"
550
+ * import { Machine } from "effect/unstable/machine"
551
+ *
552
+ * class Loaded extends Schema.TaggedClass<Loaded>("Loaded")("Loaded", {
553
+ * value: Schema.String
554
+ * }) {}
555
+ *
556
+ * const load = Machine.invoke({
557
+ * id: "load",
558
+ * src: () => Machine.effect(Effect.succeed(new Loaded({ value: "ready" })))
559
+ * })
560
+ * ```
561
+ *
562
+ * @see {@link effect} for one-shot child effects.
563
+ * @see {@link spawn} for children whose lifetime is controlled by actions.
564
+ * @category constructors
565
+ * @since 4.0.0
566
+ */
567
+ export const invoke = (config) => ({ ...config, [InvokeTypeId]: undefined });
568
+ /**
569
+ * Creates an invoked child process from a complete statechart machine.
570
+ *
571
+ * **When to use**
572
+ *
573
+ * Use when a state should own another statechart machine and communicate with
574
+ * it through typed child events, emissions, snapshots, or terminal output.
575
+ *
576
+ * **Details**
577
+ *
578
+ * Child emissions are delivered directly to the parent as events. Active
579
+ * snapshots and terminal output can be mapped to parent events. The owning
580
+ * state controls the child lifetime.
581
+ *
582
+ * **Gotchas**
583
+ *
584
+ * Active invoked machines must have unique child addresses. A machine starts
585
+ * after its owning state's entry actions, so those actions cannot send events
586
+ * to a newly entered child. Unrecovered child failures fail the parent.
587
+ *
588
+ * @see {@link invoke} for invoking lower-level process logic.
589
+ * @see {@link sendTo} for sending events to the invoked machine.
590
+ * @category constructors
591
+ * @since 4.0.0
592
+ */
593
+ export const invokeMachine = ((config) => {
594
+ const machine = config.child.machine;
595
+ return {
596
+ id: config.child.id,
597
+ addressable: true,
598
+ src: () => machine.input === undefined
599
+ ? internalProcess.toProcessLogic(machine)
600
+ : internalProcess.toProcessLogic(machine, config.input),
601
+ snapshot: config.snapshot,
602
+ onDone: config.onDone,
603
+ [InvokeTypeId]: undefined
604
+ };
605
+ });
606
+ /**
607
+ * Plans the initial state for a machine without running deferred actions.
608
+ *
609
+ * **Details**
610
+ *
611
+ * The returned plan contains the settled initial snapshot, staged actions,
612
+ * emitted events, and optional final output. Planning may evaluate transition
613
+ * logic and follow completion, eventless, and raised-event steps, but it does
614
+ * not execute effects passed to `action`.
615
+ *
616
+ * **Gotchas**
617
+ *
618
+ * Callers that execute a plan manually must run actions sequentially before
619
+ * publishing its state or delivering its emitted events. `start` performs this
620
+ * protocol automatically.
621
+ *
622
+ * @see {@link plan} for planning a received event.
623
+ * @see {@link start} for the managed runtime protocol.
624
+ * @category constructors
625
+ * @since 4.0.0
626
+ */
627
+ export const planInitial = internalPlanner.planInitial;
628
+ /**
629
+ * Returns the event tags handled by the current state snapshot.
630
+ *
631
+ * @category getters
632
+ * @since 4.0.0
633
+ */
634
+ export const enabled = (machine, state) => internalPlanner.enabled(machine, state);
635
+ /**
636
+ * Plans the next state snapshot without running deferred actions.
637
+ *
638
+ * **Details**
639
+ *
640
+ * Planning selects child transitions before conflicting ancestors, permits
641
+ * non-conflicting transitions in parallel regions, processes completion and
642
+ * eventless transitions, and drains raised events in FIFO order. Exit paths
643
+ * are deepest-first and entry paths are parent-first.
644
+ *
645
+ * **Gotchas**
646
+ *
647
+ * `plan` returns data; it does not implement the runtime commit protocol. Run
648
+ * actions sequentially, publish `next` only after they succeed, and then
649
+ * deliver `emittedEvents`. A failed action must retain the previously
650
+ * published state and suppress emissions. Events with no enabled transition
651
+ * are ignored and produce an unchanged plan.
652
+ *
653
+ * @see {@link planInitial} for planning machine startup.
654
+ * @see {@link start} for managed execution and lifecycle observation.
655
+ * @category combinators
656
+ * @since 4.0.0
657
+ */
658
+ export const plan = internalPlanner.plan;
659
+ /**
660
+ * Defers an effectful action until the current machine step is planned.
661
+ *
662
+ * **Details**
663
+ *
664
+ * The action's error and service requirements are retained in the machine
665
+ * type without becoming requirements of `plan` or `planInitial`. The managed
666
+ * runtime executes staged actions sequentially before publishing the planned
667
+ * state.
668
+ *
669
+ * **Example** (Typed staged action)
670
+ *
671
+ * ```ts
672
+ * import { Context, Effect } from "effect"
673
+ * import { Machine } from "effect/unstable/machine"
674
+ *
675
+ * class Audit extends Context.Service<Audit, {
676
+ * readonly write: Effect.Effect<void, "AuditError">
677
+ * }>()("example/Audit") {}
678
+ *
679
+ * const writeAudit = Machine.action(
680
+ * Effect.flatMap(Audit, (audit) => audit.write)
681
+ * )
682
+ * ```
683
+ *
684
+ * @see {@link plan} for inspecting staged actions without executing them.
685
+ * @category combinators
686
+ * @since 4.0.0
687
+ */
688
+ export const action = (effect) => internalPlanner.action(effect);
689
+ const processLocal = (operation) => Effect.die(new ProcessLocalErrorValue({ operation }));
690
+ const standaloneProcessRuntime = internalRuntime.MachineRuntime.of({
691
+ self: {
692
+ id: "Machine.runActions",
693
+ sessionId: "Machine.runActions",
694
+ stop: processLocal("stop self"),
695
+ send: () => processLocal("send to self")
696
+ },
697
+ parent: undefined,
698
+ spawn: () => processLocal("spawn"),
699
+ sendParent: () => processLocal("send to parent"),
700
+ sendTo: () => processLocal("send to child"),
701
+ stopChild: () => processLocal("stop child"),
702
+ failCause: () => processLocal("fail process")
703
+ });
704
+ /**
705
+ * Runs staged machine actions sequentially with the supplied runtime.
706
+ *
707
+ * **When to use**
708
+ *
709
+ * Use when you implement a commit protocol around `plan` or `planInitial` and
710
+ * need to execute their staged actions before publishing the planned snapshot.
711
+ *
712
+ * **Gotchas**
713
+ *
714
+ * This function only runs actions. The caller remains responsible for
715
+ * publishing the planned state and delivering planned emitted events after all
716
+ * actions succeed. Process-local operations such as `spawn`, `sendTo`, and
717
+ * `stopChild` fail with `ProcessLocalError` because no managed machine process
718
+ * owns the actions.
719
+ *
720
+ * @see {@link plan} for creating a transition plan.
721
+ * @see {@link planInitial} for creating an initial plan.
722
+ * @category running
723
+ * @since 4.0.0
724
+ */
725
+ export const runActions = (actions, runtime) => internalRuntime.provideMachineRuntime(internalPlanner.runActions(actions, runtime), standaloneProcessRuntime).pipe(Effect.catchDefect((defect) => defect instanceof ProcessLocalErrorValue
726
+ ? Effect.fail(defect)
727
+ : Effect.die(defect)));
728
+ /**
729
+ * Returns the typed runtime capability for the current machine.
730
+ *
731
+ * @category combinators
732
+ * @since 4.0.0
733
+ */
734
+ export const runtime = () => internalPlanner.runtime();
735
+ /**
736
+ * Creates a one-shot child process from an Effect.
737
+ *
738
+ * **When to use**
739
+ *
740
+ * Use when you need side effects that produce one typed output or error.
741
+ *
742
+ * **Details**
743
+ *
744
+ * The Effect may run arbitrary side effects. Its success value is the process
745
+ * output, its typed error is preserved, and its services are inferred. When
746
+ * invoked, the output is sent to the owning machine as an event unless it is
747
+ * `void`.
748
+ *
749
+ * **Gotchas**
750
+ *
751
+ * This process has no incoming event protocol. Its Effect runs once. Use
752
+ * `transition` for a process that receives events over time and `logic` for
753
+ * direct machine-local communication or intermediate snapshots.
754
+ *
755
+ * **Example** (Recover a child failure as output)
756
+ *
757
+ * ```ts
758
+ * import { Effect, Schema } from "effect"
759
+ * import { Machine } from "effect/unstable/machine"
760
+ *
761
+ * class LoadFailed extends Schema.TaggedClass<LoadFailed>("LoadFailed")("LoadFailed", {
762
+ * reason: Schema.String
763
+ * }) {}
764
+ *
765
+ * const load = Machine.effect(
766
+ * Effect.fail("unavailable").pipe(
767
+ * Effect.catch((reason) => Effect.succeed(new LoadFailed({ reason })))
768
+ * )
769
+ * )
770
+ * ```
771
+ *
772
+ * @see {@link transition} for event-driven state.
773
+ * @see {@link logic} for direct control over intermediate snapshots.
774
+ * @category constructors
775
+ * @since 4.0.0
776
+ */
777
+ export const effect = (effect) => ({
778
+ initial: () => Effect.void,
779
+ run: () => effect
780
+ });
781
+ /**
782
+ * Creates advanced stateful process logic from explicit initialization and
783
+ * execution methods.
784
+ *
785
+ * **When to use**
786
+ *
787
+ * Use when you need a machine-scoped process to publish intermediate snapshots
788
+ * directly.
789
+ *
790
+ * **Details**
791
+ *
792
+ * Initialization produces the first state before `run` starts. The running
793
+ * context receives events, reads or updates state, manages child processes,
794
+ * and can communicate with its owning machine. Errors and service requirements
795
+ * from both phases remain in the returned `Logic` type.
796
+ *
797
+ * **Gotchas**
798
+ *
799
+ * This is the low-level process constructor. Parent messages sent directly
800
+ * through its scope are intentionally `unknown` because the logic does not know
801
+ * which machine will eventually own it. Prefer typed output, typed child
802
+ * addresses, or invoke snapshot mapping when possible.
803
+ *
804
+ * @see {@link effect} for one-shot work.
805
+ * @see {@link transition} for event-driven state.
806
+ * @category constructors
807
+ * @since 4.0.0
808
+ */
809
+ export const logic = (options) => ({
810
+ initial: (scope) => typeof options.initial === "function"
811
+ ? options.initial(scope)
812
+ : Effect.succeed(options.initial),
813
+ run: options.run
814
+ });
815
+ /**
816
+ * Creates child process logic from an initial state and a transition function.
817
+ *
818
+ * **When to use**
819
+ *
820
+ * Use when a child process only needs sequential event-driven state updates and
821
+ * does not need direct control over intermediate snapshots or child ownership.
822
+ *
823
+ * **Details**
824
+ *
825
+ * Each received event runs the transition Effect against the latest state. The
826
+ * resulting state is published before the next queued event is processed.
827
+ *
828
+ * @see {@link effect} for one-shot work.
829
+ * @see {@link logic} for direct process lifecycle control.
830
+ * @category constructors
831
+ * @since 4.0.0
832
+ */
833
+ export const transition = (initial, transition) => logic({
834
+ initial,
835
+ run: ({ receive, updateState }) => receive.pipe(Effect.flatMap((event) => updateState((state) => transition(state, event))), Effect.forever)
836
+ });
837
+ /**
838
+ * Creates a typed parent-local child address or complete machine descriptor.
839
+ *
840
+ * **When to use**
841
+ *
842
+ * Use with a complete machine to create the descriptor shared by
843
+ * `invokeMachine`, `sendTo`, and child lookup APIs. The one-argument form
844
+ * creates an event-only address for lower-level process logic.
845
+ *
846
+ * @category constructors
847
+ * @since 4.0.0
848
+ */
849
+ export const child = ((id, machine) => machine === undefined
850
+ ? id
851
+ : { [ChildMachineTypeId]: ChildMachineTypeId, id, machine });
852
+ /**
853
+ * Spawns a child process owned by the currently running machine.
854
+ *
855
+ * **When to use**
856
+ *
857
+ * Use to create child processes from machine actions when the child
858
+ * should be addressed or stopped by the owning machine instead of tied to a
859
+ * single state's `invoke` lifecycle.
860
+ *
861
+ * **Gotchas**
862
+ *
863
+ * This effect requires the machine runtime, so it only runs from machine
864
+ * actions. A named child id must be unique for the current parent machine until
865
+ * that child stops.
866
+ *
867
+ * @see {@link invoke} for children that start and stop with a state.
868
+ * @see {@link sendTo} for sending events to named children.
869
+ * @category runtime
870
+ * @since 4.0.0
871
+ */
872
+ export const spawn = ((logic, options) => Effect.flatMap(internalRuntime.MachineRuntime, (runtime) => options === undefined ? runtime.spawn(logic) : runtime.spawn(logic, options)));
873
+ /**
874
+ * Sends an event to a named child process of the running machine.
875
+ *
876
+ * @category runtime
877
+ * @since 4.0.0
878
+ */
879
+ export const sendTo = ((child, event) => Effect.flatMap(internalRuntime.MachineRuntime, (runtime) => runtime.sendTo(typeof child === "string" ? child : child.id, event)));
880
+ /**
881
+ * Stops a named child process of the running machine.
882
+ *
883
+ * @category runtime
884
+ * @since 4.0.0
885
+ */
886
+ export const stopChild = ((child) => Effect.flatMap(internalRuntime.MachineRuntime, (runtime) => runtime.stopChild(typeof child === "string" ? child : child.id)));
887
+ /**
888
+ * Returns a stream of terminal lifecycle outcomes for a running machine.
889
+ *
890
+ * @category combinators
891
+ * @since 4.0.0
892
+ */
893
+ export const watch = (ref) => internalRuntime.watch(ref);
894
+ /**
895
+ * Starts a machine.
896
+ *
897
+ * **When to use**
898
+ *
899
+ * Use when you want asynchronous event delivery, lifecycle snapshots, `join`,
900
+ * and machine-owned spawned or invoked children.
901
+ *
902
+ * **Details**
903
+ *
904
+ * For each accepted event the runtime plans the complete macrostep, runs staged
905
+ * actions sequentially, stops invokes for exited states, publishes the new
906
+ * state, delivers emitted events, and then starts invokes for entered states.
907
+ * If an action fails, the previous published state is retained and emissions
908
+ * from that plan are suppressed.
909
+ *
910
+ * **Gotchas**
911
+ *
912
+ * The returned handle's `send` operation only enqueues events. Transition
913
+ * failures are reported through the runtime snapshot, `changes`, and `join`
914
+ * rather than being returned by `send`. Sending after the machine reaches any
915
+ * terminal state fails immediately with `StoppedError`.
916
+ *
917
+ * @see {@link plan} for inspecting the same transition plan without executing it.
918
+ * @see {@link watch} for classified terminal outcomes.
919
+ * @category constructors
920
+ * @since 4.0.0
921
+ */
922
+ export const start = internalProcess.start;
923
+ //# sourceMappingURL=Machine.js.map