@typeonce/effect-machine 0.19.1 → 0.20.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 (32) hide show
  1. package/README.md +49 -14
  2. package/dist/Machine.d.ts +93 -2
  3. package/dist/Machine.d.ts.map +1 -1
  4. package/dist/Machine.js +11 -0
  5. package/dist/Machine.js.map +1 -1
  6. package/dist/internal/machine/atom.d.ts.map +1 -1
  7. package/dist/internal/machine/atom.js +18 -4
  8. package/dist/internal/machine/atom.js.map +1 -1
  9. package/dist/internal/machine/invocation.d.ts.map +1 -1
  10. package/dist/internal/machine/invocation.js +7 -0
  11. package/dist/internal/machine/invocation.js.map +1 -1
  12. package/dist/internal/machine/machine.d.ts +1 -0
  13. package/dist/internal/machine/machine.d.ts.map +1 -1
  14. package/dist/internal/machine/machine.js +11 -4
  15. package/dist/internal/machine/machine.js.map +1 -1
  16. package/dist/internal/machine/runtime.d.ts +4 -3
  17. package/dist/internal/machine/runtime.d.ts.map +1 -1
  18. package/dist/internal/machine/runtime.js +12 -1
  19. package/dist/internal/machine/runtime.js.map +1 -1
  20. package/dist/unstable/reactivity/AtomMachine.d.ts +10 -8
  21. package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -1
  22. package/dist/unstable/reactivity/AtomMachine.js +2 -2
  23. package/dist/unstable/reactivity/AtomMachine.js.map +1 -1
  24. package/docs/agent-guide.md +31 -0
  25. package/docs/effect-atom-react.md +28 -0
  26. package/package.json +1 -1
  27. package/src/Machine.ts +141 -2
  28. package/src/internal/machine/atom.ts +26 -18
  29. package/src/internal/machine/invocation.ts +13 -1
  30. package/src/internal/machine/machine.ts +18 -6
  31. package/src/internal/machine/runtime.ts +48 -19
  32. package/src/unstable/reactivity/AtomMachine.ts +10 -8
@@ -315,6 +315,37 @@ machine.
315
315
  Do not start a promise inside a transition callback. A transition has no
316
316
  lifetime in which to own that work. A state does.
317
317
 
318
+ ### Choose state-owned or process-owned children
319
+
320
+ Use `from.child(...)` when the child belongs to one state and must stop when
321
+ that state exits. Use a child family and `children.spawn(...)` when runtime
322
+ events determine the ids or cardinality and the children must survive owner
323
+ state changes:
324
+
325
+ ```ts
326
+ const Worker = Machine.childFamily(workerMachine)
327
+
328
+ Commissioning: {
329
+ invoke: (from) =>
330
+ from.effect("start-workers", ({ children, state }) =>
331
+ Effect.forEach(
332
+ state.workers,
333
+ (input) => children.spawn(Worker(input.id), { input }),
334
+ { discard: true }
335
+ )
336
+ )
337
+ .onDone((to) => to.full.Running())
338
+ .onFailure((to) => to.full.Failed())
339
+ }
340
+ ```
341
+
342
+ The Effect owns the startup attempt. The machine process owns every child that
343
+ starts successfully. Leaving `Commissioning` does not stop those children.
344
+ Stop one with `children.stop(Worker(id))` inside an Effect or
345
+ `enqueue.stop(Worker(id))` inside a transition. A duplicate active id fails
346
+ instead of replacing the existing child, and a partially successful group is
347
+ not rolled back automatically.
348
+
318
349
  ## Keep transition decisions synchronous
319
350
 
320
351
  A transition should choose the next state from the current snapshot and event.
@@ -200,3 +200,31 @@ the inferred dialog events.
200
200
 
201
201
  Use `dialogId` in atom labels for diagnostics. Do not pass it into
202
202
  `dialogMachine` as unused fake input.
203
+
204
+ ## 4. Selecting process-owned child machines
205
+
206
+ Bind a machine definition once when a parent owns a runtime-sized set of child
207
+ machines:
208
+
209
+ ```ts
210
+ const Plant = Machine.childFamily(plantMachine)
211
+
212
+ export const centralMachineAtom = machineAtoms.make(centralMachine)
213
+
214
+ export const plantScopeFamily = Atom.family((plantId: string) => {
215
+ const plant = centralMachineAtom.child(Plant(plantId))
216
+
217
+ return {
218
+ stateAtom: plant.state,
219
+ isBrokenAtom: AtomMachine.matchesChild(plant, "Broken"),
220
+ sendAtom: plant.send,
221
+ stopAtom: plant.stop
222
+ }
223
+ })
224
+ ```
225
+
226
+ `Plant(plantId)` may be reconstructed wherever the id is available. Child
227
+ lookup and bridge reuse match by machine identity and id, not descriptor object
228
+ identity. Before the parent spawns that child, selectors contain `Option.none`
229
+ and `matchesChild` is `false`. They follow the child after startup and return to
230
+ the inactive values after it stops.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typeonce/effect-machine",
3
- "version": "0.19.1",
3
+ "version": "0.20.0",
4
4
  "description": "Schema-first state machines and statecharts for Effect",
5
5
  "author": "Sandro Maglione",
6
6
  "repository": {
package/src/Machine.ts CHANGED
@@ -2265,7 +2265,15 @@ export declare namespace Logic {
2265
2265
  * @category models
2266
2266
  * @since 0.4.0
2267
2267
  */
2268
- export interface Spawn {
2268
+ export interface Spawn<OwnerEvent = unknown> {
2269
+ <const Child extends ChildMachine.Any>(
2270
+ child: Child & ChildMachine.Executable<Child> & ChildMachine.ParentCompatibility<Child, OwnerEvent>,
2271
+ ...options: ChildMachine.SpawnArgs<Child>
2272
+ ): Effect.Effect<
2273
+ ChildMachine.Ref<Child>,
2274
+ ChildAlreadyExistsError | ChildMachine.StartError<Child>,
2275
+ ChildMachine.StartRequirements<Child>
2276
+ >
2269
2277
  <ChildState, ChildEvent, ChildError, ChildRequirements, ChildOutput, ChildInitialError = never>(
2270
2278
  logic: Logic<ChildState, ChildEvent, ChildError, ChildRequirements, ChildOutput, ChildInitialError>
2271
2279
  ): Effect.Effect<
@@ -2305,7 +2313,7 @@ export declare namespace Logic {
2305
2313
  readonly parent: Address<unknown> | undefined
2306
2314
 
2307
2315
  /** Starts a child process owned by this scope. */
2308
- readonly spawn: Spawn
2316
+ readonly spawn: Spawn<Event>
2309
2317
 
2310
2318
  /** Sends an event to a machine target or typed parent-local child address. */
2311
2319
  readonly sendTo: {
@@ -2345,6 +2353,7 @@ export declare namespace Logic {
2345
2353
 
2346
2354
  const ChildAddressTypeId = "~effect/Machine/ChildAddress"
2347
2355
  const ChildAddressCompatibilityErrorTypeId = "~effect/Machine/ChildAddressCompatibilityError"
2356
+ const ChildParentCompatibilityErrorTypeId = "~effect/Machine/ChildParentCompatibilityError"
2348
2357
  const ChildMachineTypeId = "~effect/Machine/ChildMachine"
2349
2358
  type InvokeLifecycleId = string & { readonly [ChildAddressTypeId]?: never }
2350
2359
 
@@ -2389,6 +2398,95 @@ export declare namespace ChildMachine {
2389
2398
  */
2390
2399
  export type Any = ChildMachine<string, Machine.Any>
2391
2400
 
2401
+ /**
2402
+ * Bound constructor for an open family of child descriptors that share one
2403
+ * machine definition.
2404
+ *
2405
+ * @category models
2406
+ * @since 0.20.0
2407
+ */
2408
+ export interface Family<M extends Machine.Any> {
2409
+ <const Id extends string>(id: Id): ChildMachine<Id, M>
2410
+ }
2411
+
2412
+ /**
2413
+ * Ensures a child machine's declared owner protocol is accepted by the
2414
+ * process that will own it.
2415
+ *
2416
+ * @category utility types
2417
+ * @since 0.20.0
2418
+ */
2419
+ export type ParentCompatibility<Child extends Any, OwnerEvent> = Child extends ChildMachine<string, infer M> ?
2420
+ Machine.Any extends M ? {
2421
+ readonly [ChildParentCompatibilityErrorTypeId]: {
2422
+ readonly child: unknown
2423
+ readonly owner: OwnerEvent
2424
+ }
2425
+ }
2426
+ : [Machine.EventOf<Machine.ParentEvents<M>>] extends [OwnerEvent] ? unknown :
2427
+ {
2428
+ readonly [ChildParentCompatibilityErrorTypeId]: {
2429
+ readonly child: Machine.EventOf<Machine.ParentEvents<M>>
2430
+ readonly owner: OwnerEvent
2431
+ }
2432
+ }
2433
+ : never
2434
+
2435
+ /**
2436
+ * Ensures the selected child machine has complete handlers and outputs.
2437
+ *
2438
+ * @category utility types
2439
+ * @since 0.20.0
2440
+ */
2441
+ export type Executable<Child extends Any> = Child["machine"] extends EnsureExecutable<
2442
+ Machine.States<Child["machine"]>,
2443
+ Machine.UnhandledStates<Child["machine"]>,
2444
+ Machine.OutputStates<Child["machine"]>
2445
+ > ? unknown
2446
+ : never
2447
+
2448
+ /**
2449
+ * Startup arguments accepted while spawning a child machine.
2450
+ *
2451
+ * @category utility types
2452
+ * @since 0.20.0
2453
+ */
2454
+ export type SpawnArgs<Child extends Any> = Machine.InputSchema<Child["machine"]> extends typeof Schema.Void ?
2455
+ [options?: { readonly input?: never }]
2456
+ : [options: { readonly input: Machine.Input<Child["machine"]> }]
2457
+
2458
+ /**
2459
+ * Typed failures that may occur before a spawned child becomes active.
2460
+ *
2461
+ * @category utility types
2462
+ * @since 0.20.0
2463
+ */
2464
+ export type StartError<Child extends Any> = Child extends ChildMachine<string, infer M> ?
2465
+ | Machine.InitialError<M>
2466
+ | Machine.Error<M>
2467
+ | ActionError<Machine.InitialServices<M> | Machine.Services<M>>
2468
+ | InfiniteTransitionError
2469
+ | MachineSchemaDecodeError
2470
+ | StartupError
2471
+ | StoppedError
2472
+ : never
2473
+
2474
+ /**
2475
+ * Services needed to initialize a spawned child machine.
2476
+ *
2477
+ * @category utility types
2478
+ * @since 0.20.0
2479
+ */
2480
+ export type StartRequirements<Child extends Any> = Child extends ChildMachine<string, infer M> ? Exclude<
2481
+ ExcludeCompatibleRuntime<
2482
+ Exclude<ExecutionServices<Machine.InitialServices<M> | Machine.Services<M>>, MachineRuntimeRequirement>,
2483
+ Machine.Event<M>,
2484
+ Machine.Emit<M>
2485
+ >,
2486
+ Scope.Scope
2487
+ >
2488
+ : never
2489
+
2392
2490
  /**
2393
2491
  * Running machine reference selected by a child descriptor.
2394
2492
  *
@@ -2418,6 +2516,34 @@ export declare namespace ChildMachine {
2418
2516
  : never
2419
2517
  }
2420
2518
 
2519
+ /**
2520
+ * Effectful operations for child machines owned directly by the current
2521
+ * machine process.
2522
+ *
2523
+ * @category models
2524
+ * @since 0.20.0
2525
+ */
2526
+ export interface ChildOwner<OwnerEvent> {
2527
+ /** Starts a process-owned child and returns once initialization succeeds. */
2528
+ readonly spawn: <const Child extends ChildMachine.Any>(
2529
+ child: Child & ChildMachine.Executable<Child> & ChildMachine.ParentCompatibility<Child, OwnerEvent>,
2530
+ ...options: ChildMachine.SpawnArgs<Child>
2531
+ ) => Effect.Effect<
2532
+ ChildMachine.Ref<Child>,
2533
+ ChildAlreadyExistsError | ChildMachine.StartError<Child>,
2534
+ ChildMachine.StartRequirements<Child>
2535
+ >
2536
+
2537
+ /** Sends an event to one active child. Missing children are ignored. */
2538
+ readonly sendTo: <Child extends ChildMachine.Any>(
2539
+ child: Child,
2540
+ event: ChildMachine.Event<Child>
2541
+ ) => Effect.Effect<void, StoppedError>
2542
+
2543
+ /** Stops one active child. Missing children are ignored. */
2544
+ readonly stop: <Child extends ChildMachine.Any>(child: Child) => Effect.Effect<void>
2545
+ }
2546
+
2421
2547
  /**
2422
2548
  * Parent-local address for a child process that can receive events.
2423
2549
  *
@@ -4799,6 +4925,8 @@ export declare namespace Machine {
4799
4925
  InputEvents extends ReadonlyArray<TaggedSchema> = Events,
4800
4926
  ParentEvents extends ReadonlyArray<TaggedSchema> = readonly []
4801
4927
  > = MachineReferences<InputEvents, ParentEvents> & {
4928
+ /** Process-owned child operations for dynamic child machine lifecycles. */
4929
+ readonly children: ChildOwner<EventOf<InputEvents>>
4802
4930
  /** Value owned by the state that owns this invocation. */
4803
4931
  readonly state: StateByIdentifier<States, StateId>
4804
4932
  /** Value owned by the nearest schema-backed ancestor, when one exists. */
@@ -8917,6 +9045,17 @@ export const logic: <
8917
9045
  export const child: <const Id extends string, M extends Machine.Any>(id: Id, machine: M) => ChildMachine<Id, M> =
8918
9046
  internal.child
8919
9047
 
9048
+ /**
9049
+ * Binds one machine definition to an open family of runtime child ids.
9050
+ *
9051
+ * Descriptors created by the returned function are interchangeable with
9052
+ * {@link child} descriptors for the same id and machine definition.
9053
+ *
9054
+ * @category constructors
9055
+ * @since 0.20.0
9056
+ */
9057
+ export const childFamily: <M extends Machine.Any>(machine: M) => ChildMachine.Family<M> = internal.childFamily
9058
+
8920
9059
  /**
8921
9060
  * Creates a typed parent-local address for lower-level child process logic.
8922
9061
  *
@@ -342,15 +342,7 @@ const makeChildFromRefAtom = <Child extends Machine.ChildMachine.Any, StartError
342
342
  }
343
343
  )
344
344
 
345
- const childFamily = Atom.family((nested: Machine.ChildMachine.Any) =>
346
- makeChildFromRefAtom(
347
- makeChildRefAtom(ref as any, nested),
348
- nested
349
- )
350
- )
351
- const child = <Nested extends Machine.ChildMachine.Any>(
352
- nested: Nested
353
- ): ChildMachineAtom<Nested, StartError> => childFamily(nested) as ChildMachineAtom<Nested, StartError>
345
+ const child = makeChildSelector<StartError>(ref as any)
354
346
 
355
347
  return {
356
348
  ref,
@@ -363,6 +355,30 @@ const makeChildFromRefAtom = <Child extends Machine.ChildMachine.Any, StartError
363
355
  }
364
356
  }
365
357
 
358
+ const makeChildSelector = <StartError>(
359
+ parentRef: Atom.Atom<
360
+ AsyncResult.AsyncResult<Option.Option<Machine.MachineRef<any, any, any, any, any>>, StartError>
361
+ >
362
+ ) => {
363
+ const byMachine = new WeakMap<object, (id: string) => ChildMachineAtom<Machine.ChildMachine.Any, StartError>>()
364
+ return <Child extends Machine.ChildMachine.Any>(descriptor: Child): ChildMachineAtom<Child, StartError> => {
365
+ let family = byMachine.get(descriptor.machine)
366
+ if (family === undefined) {
367
+ const machine = descriptor.machine
368
+ const atoms = Atom.family((id: string) => {
369
+ const child = internalMachine.child(id, machine)
370
+ return makeChildFromRefAtom(
371
+ makeChildRefAtom(parentRef as any, child),
372
+ child
373
+ )
374
+ })
375
+ family = (id) => atoms(id) as ChildMachineAtom<Machine.ChildMachine.Any, StartError>
376
+ byMachine.set(machine, family)
377
+ }
378
+ return family(descriptor.id) as ChildMachineAtom<Child, StartError>
379
+ }
380
+ }
381
+
366
382
  const makeFromRefAtom = <State, Event, Error, Output, StartError, Emitted>(
367
383
  ref: Atom.Atom<AsyncResult.AsyncResult<Machine.MachineRef<State, Event, Error, Output, Emitted>, StartError>>
368
384
  ): MachineAtom<State, Event, Error, Output, StartError, Emitted> => {
@@ -438,15 +454,7 @@ const makeFromRefAtom = <State, Event, Error, Output, StartError, Emitted>(
438
454
  )
439
455
 
440
456
  const optionalRef = Atom.mapResult(ref, Option.some)
441
- const childFamily = Atom.family((descriptor: Machine.ChildMachine.Any) =>
442
- makeChildFromRefAtom(
443
- makeChildRefAtom(optionalRef as any, descriptor),
444
- descriptor
445
- )
446
- )
447
- const child = <Child extends Machine.ChildMachine.Any>(
448
- descriptor: Child
449
- ): ChildMachineAtom<Child, StartError> => childFamily(descriptor) as ChildMachineAtom<Child, StartError>
457
+ const child = makeChildSelector<StartError>(optionalRef as any)
450
458
 
451
459
  return {
452
460
  ref,
@@ -7,7 +7,7 @@
7
7
  import * as Cause from "effect/Cause"
8
8
  import * as Effect from "effect/Effect"
9
9
  import * as Stream from "effect/Stream"
10
- import type { ChildMachine, Inspection, Logic, Machine } from "../../Machine.js"
10
+ import type { ChildMachine, ChildOwner, Inspection, Logic, Machine } from "../../Machine.js"
11
11
  import * as Configuration from "./configuration.js"
12
12
  import { InfiniteTransitionError, MachineSchemaDecodeError, StoppedError } from "./errors.js"
13
13
  import * as InvocationEvent from "./invocationEvent.js"
@@ -63,6 +63,16 @@ const streamLogic = (
63
63
  const resolveValue = (value: unknown, context: Machine.InvokeContext<any, any, any, any>): unknown =>
64
64
  typeof value === "function" ? value(context) : value
65
65
 
66
+ const makeChildOwner = (scope: Runtime.ProcessScope<any>): ChildOwner<any> => ({
67
+ spawn:
68
+ ((descriptor: ChildMachine.Any, options?: { readonly input?: unknown }) =>
69
+ (scope.spawn as any)(descriptor, options)) as ChildOwner<any>["spawn"],
70
+ sendTo: ((descriptor: ChildMachine.Any, event: unknown) => scope.sendTo(descriptor, event)) as ChildOwner<
71
+ any
72
+ >["sendTo"],
73
+ stop: ((descriptor: ChildMachine.Any) => scope.stopChild(descriptor)) as ChildOwner<any>["stop"]
74
+ })
75
+
66
76
  const resolveOne = (
67
77
  raw: Record<PropertyKey, any>,
68
78
  context: Machine.InvokeContext<any, any, any, any>,
@@ -291,11 +301,13 @@ export const startAll = (
291
301
  paths: ReadonlyArray<string>,
292
302
  event: Machine.LifecycleEvent<any>
293
303
  ): Effect.Effect<void, any, any> | undefined => {
304
+ const children = makeChildOwner(scope)
294
305
  const effects = Planner.sortEntryPaths(machine, paths)
295
306
  .filter((path) => configuration.active.has(path))
296
307
  .flatMap((path) => {
297
308
  const context = {
298
309
  ...(Configuration.getMachineReferences(configuration) ?? { self: scope.self, parent: scope.parent }),
310
+ children,
299
311
  state: configuration.values.get(path),
300
312
  containingState: Configuration.getParentValue(machine, configuration, path),
301
313
  ancestors: Configuration.getParentValues(machine, configuration, path),
@@ -2124,15 +2124,30 @@ export const transition = <State, Event, Error = never, Requirements = never>(
2124
2124
  export const child = <const Id extends string, M extends Machine.Any>(
2125
2125
  id: Id,
2126
2126
  machine: M
2127
+ ): ChildMachine<Id, M> =>
2128
+ makeChild(id, machine, (input) =>
2129
+ machine.input === undefined
2130
+ ? (internalProcess.toProcessLogic as any)(machine)
2131
+ : (internalProcess.toProcessLogic as any)(machine, input))
2132
+
2133
+ const makeChild = <const Id extends string, M extends Machine.Any>(
2134
+ id: Id,
2135
+ machine: M,
2136
+ makeLogic: (input?: unknown) => Logic<any, any, any, any, any, any>
2127
2137
  ): ChildMachine<Id, M> => ({
2128
2138
  [ChildMachineTypeId]: ChildMachineTypeId,
2129
2139
  id,
2130
2140
  machine,
2131
- [ChildMachineLogicTypeId]: (input) =>
2141
+ [ChildMachineLogicTypeId]: makeLogic
2142
+ })
2143
+
2144
+ export const childFamily = <M extends Machine.Any>(machine: M): ChildMachine.Family<M> => {
2145
+ const makeLogic = (input?: unknown): Logic<any, any, any, any, any, any> =>
2132
2146
  machine.input === undefined
2133
2147
  ? (internalProcess.toProcessLogic as any)(machine)
2134
2148
  : (internalProcess.toProcessLogic as any)(machine, input)
2135
- })
2149
+ return (id) => makeChild(id, machine, makeLogic)
2150
+ }
2136
2151
 
2137
2152
  export const childAddress = <Event = never>(id: string): ChildAddress<Event> => id as ChildAddress<Event>
2138
2153
 
@@ -2174,10 +2189,7 @@ export const spawn: {
2174
2189
  SpawnError<Options>,
2175
2190
  ChildInitialError
2176
2191
  >
2177
- } = ((
2178
- logic: Logic<any, any, any, any, any, any>,
2179
- options?: SpawnOptions
2180
- ) =>
2192
+ } = ((logic: Logic<any, any, any, any, any, any>, options?: SpawnOptions) =>
2181
2193
  Effect.flatMap(
2182
2194
  internalRuntime.MachineRuntime,
2183
2195
  (runtime) => options === undefined ? runtime.spawn(logic) : (runtime.spawn as any)(logic, options)
@@ -18,9 +18,10 @@ import * as Scope from "effect/Scope"
18
18
  import * as Stream from "effect/Stream"
19
19
  import * as SynchronizedRef from "effect/SynchronizedRef"
20
20
  import type * as Take from "effect/Take"
21
- import type { Inspection, Machine as MachineDefinition, MachineTarget } from "../../Machine.js"
21
+ import type { ChildMachine, Inspection, Machine as MachineDefinition, MachineTarget } from "../../Machine.js"
22
22
  import { ChildAlreadyExistsError, StoppedError } from "./errors.js"
23
23
  import * as InspectionRuntime from "./inspectionRuntime.js"
24
+ import { ChildMachineLogicTypeId } from "./symbols.js"
24
25
 
25
26
  type ChildDescriptor = {
26
27
  readonly id: string
@@ -376,7 +377,7 @@ const sendMachineTarget = (
376
377
  export interface ProcessScope<Event> {
377
378
  readonly self: ProcessAddress<Event>
378
379
  readonly parent: ProcessAddress<unknown> | undefined
379
- readonly spawn: ProcessSpawn
380
+ readonly spawn: ProcessSpawn<Event>
380
381
  readonly sendParent: (event: unknown) => Effect.Effect<void, StoppedError>
381
382
  readonly emit: (event: unknown) => Effect.Effect<void>
382
383
  readonly sendTo: {
@@ -552,7 +553,15 @@ export interface ProcessLogic<
552
553
  run(context: ProcessContext<State, Event>): Effect.Effect<Output, Error, Requirements>
553
554
  }
554
555
 
555
- export interface ProcessSpawn {
556
+ export interface ProcessSpawn<OwnerEvent = unknown> {
557
+ <const Child extends ChildMachine.Any>(
558
+ child: Child & ChildMachine.Executable<Child> & ChildMachine.ParentCompatibility<Child, OwnerEvent>,
559
+ ...options: ChildMachine.SpawnArgs<Child>
560
+ ): Effect.Effect<
561
+ ChildMachine.Ref<Child>,
562
+ ChildAlreadyExistsError | ChildMachine.StartError<Child>,
563
+ ChildMachine.StartRequirements<Child>
564
+ >
556
565
  <ChildState, ChildEvent, ChildError, ChildRequirements, ChildOutput, ChildInitialError = never>(
557
566
  logic: ProcessLogic<ChildState, ChildEvent, ChildError, ChildRequirements, ChildOutput, ChildInitialError>
558
567
  ): Effect.Effect<
@@ -1207,6 +1216,14 @@ const makeChildRuntimeSync = (
1207
1216
  })
1208
1217
  }
1209
1218
 
1219
+ function spawn<const Child extends ChildMachine.Any>(
1220
+ child: Child & ChildMachine.Executable<Child> & ChildMachine.ParentCompatibility<Child, unknown>,
1221
+ ...options: ChildMachine.SpawnArgs<Child>
1222
+ ): Effect.Effect<
1223
+ ChildMachine.Ref<Child>,
1224
+ ChildAlreadyExistsError | ChildMachine.StartError<Child>,
1225
+ ChildMachine.StartRequirements<Child>
1226
+ >
1210
1227
  function spawn<ChildState, ChildEvent, ChildError, ChildRequirements, ChildOutput, ChildInitialError = never>(
1211
1228
  logic: ProcessLogic<ChildState, ChildEvent, ChildError, ChildRequirements, ChildOutput, ChildInitialError>
1212
1229
  ): Effect.Effect<
@@ -1232,32 +1249,44 @@ const makeChildRuntimeSync = (
1232
1249
  ChildAlreadyExistsError | ChildInitialError,
1233
1250
  Exclude<ChildRequirements, Scope.Scope>
1234
1251
  >
1235
- function spawn<ChildState, ChildEvent, ChildError, ChildRequirements, ChildOutput, ChildInitialError = never>(
1236
- logic: ProcessLogic<ChildState, ChildEvent, ChildError, ChildRequirements, ChildOutput, ChildInitialError>,
1237
- spawnOptions?: {
1252
+ function spawn(
1253
+ logicOrChild: ProcessLogic<any, any, any, any, any, any> | ChildMachine.Any,
1254
+ options?: {
1238
1255
  readonly id: string
1239
1256
  readonly descriptor?: ChildDescriptor
1240
1257
  readonly onOutcome?: (
1241
- outcome: RuntimeOutcome<ChildState, ChildError, ChildOutput>
1258
+ outcome: RuntimeOutcome<any, any, any>
1242
1259
  ) => Effect.Effect<void>
1243
1260
  readonly [activeSnapshotObserver]?: (
1244
- snapshot: Extract<RuntimeSnapshot<ChildState, ChildError, ChildOutput>, { readonly status: "active" }>
1261
+ snapshot: Extract<RuntimeSnapshot<any, any, any>, { readonly status: "active" }>
1245
1262
  ) => Effect.Effect<void>
1246
1263
  readonly [sendParentOverride]?: (event: unknown) => Effect.Effect<void, StoppedError>
1247
- }
1248
- ): Effect.Effect<
1249
- MachineRef<ChildState, ChildEvent, ChildError, ChildOutput>,
1250
- ChildAlreadyExistsError | ChildInitialError,
1251
- Exclude<ChildRequirements, Scope.Scope>
1252
- > {
1264
+ } | { readonly input?: unknown }
1265
+ ): Effect.Effect<MachineRef<any, any, any, any>, any, any> {
1266
+ const descriptor = typeof logicOrChild === "object" && logicOrChild !== null &&
1267
+ ChildMachineLogicTypeId in logicOrChild
1268
+ ? logicOrChild as ChildMachine.Any
1269
+ : undefined
1270
+ const logic = descriptor === undefined
1271
+ ? logicOrChild as ProcessLogic<any, any, any, any, any, any>
1272
+ : descriptor[ChildMachineLogicTypeId](
1273
+ (options as { readonly input?: unknown } | undefined)?.input
1274
+ ) as unknown as ProcessLogic<any, any, any, any, any, any>
1275
+ const spawnOptions = descriptor === undefined
1276
+ ? options as {
1277
+ readonly id: string
1278
+ readonly descriptor?: ChildDescriptor
1279
+ readonly onOutcome?: (outcome: RuntimeOutcome<any, any, any>) => Effect.Effect<void>
1280
+ readonly [activeSnapshotObserver]?: (
1281
+ snapshot: Extract<RuntimeSnapshot<any, any, any>, { readonly status: "active" }>
1282
+ ) => Effect.Effect<void>
1283
+ readonly [sendParentOverride]?: (event: unknown) => Effect.Effect<void, StoppedError>
1284
+ } | undefined
1285
+ : { id: descriptor.id, descriptor }
1253
1286
  const token = Symbol()
1254
1287
  const key = spawnOptions?.id ?? token
1255
1288
  let startedChild: MachineRef<any, any, any, any> | undefined
1256
- return Effect.suspend((): Effect.Effect<
1257
- MachineRef<ChildState, ChildEvent, ChildError, ChildOutput>,
1258
- ChildAlreadyExistsError | ChildInitialError,
1259
- Exclude<ChildRequirements, Scope.Scope>
1260
- > => {
1289
+ return Effect.suspend(() => {
1261
1290
  if (registry.closed) {
1262
1291
  return Effect.interrupt
1263
1292
  }
@@ -148,9 +148,9 @@ export interface MachineAtom<State, Event, Error = never, Output = never, StartE
148
148
  readonly stop: Atom.Writable<AsyncResult.AsyncResult<void, StartError | NotReadyError>, void>
149
149
 
150
150
  /**
151
- * Creates a reactive bridge for a directly invoked child machine.
152
- * Reusing the same descriptor returns the same live bridge while it remains
153
- * referenced.
151
+ * Creates a reactive bridge for a directly owned child machine.
152
+ * Descriptors with the same id and machine definition return the same live
153
+ * bridge while it remains referenced.
154
154
  *
155
155
  * @since 0.4.0
156
156
  */
@@ -206,7 +206,8 @@ export const childEmissions: <Child extends Machine.ChildMachine.Any, StartError
206
206
  > = internal.childEmissions
207
207
 
208
208
  /**
209
- * Reactive access to one invoked child machine selected by its descriptor.
209
+ * Reactive access to one directly owned child machine selected by its
210
+ * descriptor.
210
211
  *
211
212
  * **Details**
212
213
  *
@@ -295,8 +296,9 @@ export interface ChildMachineAtom<Child extends Machine.ChildMachine.Any, StartE
295
296
  void
296
297
  >
297
298
  /**
298
- * Creates a reactive bridge for a directly owned nested child. Reusing the
299
- * same descriptor returns the same live bridge while it remains referenced.
299
+ * Creates a reactive bridge for a directly owned nested child. Descriptors
300
+ * with the same id and machine definition return the same live bridge while
301
+ * it remains referenced.
300
302
  *
301
303
  * @since 0.4.0
302
304
  */
@@ -418,7 +420,7 @@ export const selectSnapshot: <
418
420
  > = internal.selectSnapshot
419
421
 
420
422
  /**
421
- * Selects the typed value for an active state path in an invoked child.
423
+ * Selects the typed value for an active state path in a directly owned child.
422
424
  *
423
425
  * Valid paths and their selected value types are inferred from the child
424
426
  * bridge. An inactive child produces `Option.none()`. Keep the returned atom
@@ -513,7 +515,7 @@ export const matches: <
513
515
  ) => Atom.Atom<AsyncResult.AsyncResult<boolean, StartError | Error>> = internal.matches
514
516
 
515
517
  /**
516
- * Returns whether a state path is active in an invoked child.
518
+ * Returns whether a state path is active in a directly owned child.
517
519
  *
518
520
  * Valid paths are inferred from the child bridge snapshot.
519
521
  * An inactive child produces `false`. Keep the returned atom stable when