@typeonce/effect-machine 0.19.0 → 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 +50 -15
  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 +302 -1449
  25. package/docs/effect-atom-react.md +230 -0
  26. package/package.json +4 -4
  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
package/README.md CHANGED
@@ -34,7 +34,7 @@ Cluster and are exposed only through explicit integration boundaries.
34
34
  ## Install
35
35
 
36
36
  ```sh
37
- pnpm add @typeonce/effect-machine effect@4.0.0-rc.110
37
+ pnpm add @typeonce/effect-machine effect@4.0.0-rc.111
38
38
  ```
39
39
 
40
40
  `effect` is an exact peer dependency. Install the version above and upgrade it
@@ -582,6 +582,46 @@ entered. Use an Effect containing `Effect.sleep(...)` for generic work, while
582
582
  `from.timer(...)` keeps timer intent explicit and makes static durations visible
583
583
  through activity inspection.
584
584
 
585
+ ### Spawn dynamic child machines
586
+
587
+ Use `from.child(...)` when a state owns a fixed child lifecycle. Use the
588
+ `children` context inside an invoked Effect when the machine process owns an
589
+ open set of children that must survive state changes:
590
+
591
+ ```ts
592
+ const Plant = Machine.childFamily(plantMachine)
593
+
594
+ const central = Machine.make({
595
+ events: Machine.events(ResourcesOffered, PlantBroken)
596
+ // ...
597
+ }).handle({
598
+ Commissioning: {
599
+ invoke: (from) =>
600
+ from.effect("commission-wave", ({ children, state }) =>
601
+ Effect.forEach(
602
+ state.plants,
603
+ (input) => children.spawn(Plant(input.id), { input }),
604
+ { discard: true }
605
+ ))
606
+ .onDone((to) => to.full.Operating())
607
+ .onFailure((to) => to.full.CommissioningFailed())
608
+ }
609
+ })
610
+ ```
611
+
612
+ `children.spawn` completes after initialization. The new child remains owned
613
+ by the machine process after the commissioning Effect completes or its state
614
+ exits. `children.sendTo` and `children.stop` address one active child from an
615
+ Effect; transition resolvers use `enqueue.sendTo` and `enqueue.stop` with the
616
+ same descriptor. Duplicate active ids fail with `ChildAlreadyExistsError` and
617
+ do not replace the existing child. Earlier successful spawns remain active if
618
+ a later spawn in the same wave fails.
619
+
620
+ The child machine's declared `Machine.parent(...)` events must be accepted by
621
+ the owner. This is checked at each spawn call even though ids and cardinality
622
+ remain dynamic. `scope.spawn(child, { input })` provides the same descriptor
623
+ form for lower-level process logic, where the process event protocol is known.
624
+
585
625
  ## Reactivity
586
626
 
587
627
  `AtomMachine` runs one lazy machine instance per `AtomRegistry`:
@@ -603,6 +643,15 @@ The bridge exposes `ref`, `snapshot`, `state`, fail-aware `result`, writable
603
643
  equality-aware derivations. React applications using `@effect/atom-react` need
604
644
  a `RegistryProvider`.
605
645
 
646
+ Descriptors reconstructed from a `Machine.childFamily` resolve the same child
647
+ bridge by machine identity and id:
648
+
649
+ ```ts
650
+ const Plant = Machine.childFamily(plantMachine)
651
+ const plantAtom = centralAtom.child(Plant(selectedPlantId))
652
+ const brokenAtom = AtomMachine.matchesChild(plantAtom, "Broken")
653
+ ```
654
+
606
655
  Emissions stay streams rather than becoming retained atom state:
607
656
 
608
657
  ```ts
@@ -679,20 +728,6 @@ import { MachineTest } from "@typeonce/effect-machine/testing"
679
728
 
680
729
  Each ESM entrypoint is independent and tree-shakeable.
681
730
 
682
- ## Examples
683
-
684
- Every package directly under [`examples/`](./examples) has its own lockfile and
685
- `check` script.
686
-
687
- | Example | What it demonstrates |
688
- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
689
- | [Playground](./examples/playground) | Five focused React examples: atomic turnstile commands, state-scoped traffic-light timers, hierarchical microwave safety, a resource-owned media player, and a worker-hosted machine synchronized across tabs |
690
- | [Pokémon](./examples/pokemon) | Compound workflow states, invoked child machines, typed emissions, Atom reactivity, and a live Effect service |
691
- | [Platformer](./examples/platformer) | Nested parallel statecharts, typed deep history, raised events, state-scoped timers, deterministic model tests, and a playable SVG adapter |
692
-
693
- The playground is the shortest path from one concept to working code. The
694
- standalone examples show larger composition and ownership boundaries.
695
-
696
731
  ## Reference and development
697
732
 
698
733
  - [API reference](https://effect-machine.typeonce.dev)
package/dist/Machine.d.ts CHANGED
@@ -1258,7 +1258,8 @@ export declare namespace Logic {
1258
1258
  * @category models
1259
1259
  * @since 0.4.0
1260
1260
  */
1261
- interface Spawn {
1261
+ interface Spawn<OwnerEvent = unknown> {
1262
+ <const Child extends ChildMachine.Any>(child: Child & ChildMachine.Executable<Child> & ChildMachine.ParentCompatibility<Child, OwnerEvent>, ...options: ChildMachine.SpawnArgs<Child>): Effect.Effect<ChildMachine.Ref<Child>, ChildAlreadyExistsError | ChildMachine.StartError<Child>, ChildMachine.StartRequirements<Child>>;
1262
1263
  <ChildState, ChildEvent, ChildError, ChildRequirements, ChildOutput, ChildInitialError = never>(logic: Logic<ChildState, ChildEvent, ChildError, ChildRequirements, ChildOutput, ChildInitialError>): Effect.Effect<MachineRef<ChildState, ChildEvent, ChildError, ChildOutput>, ChildInitialError, Exclude<ChildRequirements, Scope.Scope>>;
1263
1264
  <ChildState, ChildEvent, ChildError, ChildRequirements, ChildOutput, Options extends SpawnOptions, ChildInitialError = never>(logic: Logic<ChildState, ChildEvent, ChildError, ChildRequirements, ChildOutput, ChildInitialError>, options: Options & ChildAddress.OptionsCompatibility<Options, ChildEvent>): Effect.Effect<MachineRef<ChildState, ChildEvent, ChildError, ChildOutput>, SpawnIdError<Options> | ChildInitialError, Exclude<ChildRequirements, Scope.Scope>>;
1264
1265
  }
@@ -1274,7 +1275,7 @@ export declare namespace Logic {
1274
1275
  /** Address of the owning process, when one exists. */
1275
1276
  readonly parent: Address<unknown> | undefined;
1276
1277
  /** Starts a child process owned by this scope. */
1277
- readonly spawn: Spawn;
1278
+ readonly spawn: Spawn<Event>;
1278
1279
  /** Sends an event to a machine target or typed parent-local child address. */
1279
1280
  readonly sendTo: {
1280
1281
  <TargetEvent>(target: MachineTarget<TargetEvent>, event: TargetEvent): Effect.Effect<void, StoppedError>;
@@ -1302,6 +1303,7 @@ export declare namespace Logic {
1302
1303
  }
1303
1304
  declare const ChildAddressTypeId = "~effect/Machine/ChildAddress";
1304
1305
  declare const ChildAddressCompatibilityErrorTypeId = "~effect/Machine/ChildAddressCompatibilityError";
1306
+ declare const ChildParentCompatibilityErrorTypeId = "~effect/Machine/ChildParentCompatibilityError";
1305
1307
  declare const ChildMachineTypeId = "~effect/Machine/ChildMachine";
1306
1308
  type InvokeLifecycleId = string & {
1307
1309
  readonly [ChildAddressTypeId]?: never;
@@ -1342,6 +1344,68 @@ export declare namespace ChildMachine {
1342
1344
  * @since 0.4.0
1343
1345
  */
1344
1346
  type Any = ChildMachine<string, Machine.Any>;
1347
+ /**
1348
+ * Bound constructor for an open family of child descriptors that share one
1349
+ * machine definition.
1350
+ *
1351
+ * @category models
1352
+ * @since 0.20.0
1353
+ */
1354
+ interface Family<M extends Machine.Any> {
1355
+ <const Id extends string>(id: Id): ChildMachine<Id, M>;
1356
+ }
1357
+ /**
1358
+ * Ensures a child machine's declared owner protocol is accepted by the
1359
+ * process that will own it.
1360
+ *
1361
+ * @category utility types
1362
+ * @since 0.20.0
1363
+ */
1364
+ type ParentCompatibility<Child extends Any, OwnerEvent> = Child extends ChildMachine<string, infer M> ? Machine.Any extends M ? {
1365
+ readonly [ChildParentCompatibilityErrorTypeId]: {
1366
+ readonly child: unknown;
1367
+ readonly owner: OwnerEvent;
1368
+ };
1369
+ } : [Machine.EventOf<Machine.ParentEvents<M>>] extends [OwnerEvent] ? unknown : {
1370
+ readonly [ChildParentCompatibilityErrorTypeId]: {
1371
+ readonly child: Machine.EventOf<Machine.ParentEvents<M>>;
1372
+ readonly owner: OwnerEvent;
1373
+ };
1374
+ } : never;
1375
+ /**
1376
+ * Ensures the selected child machine has complete handlers and outputs.
1377
+ *
1378
+ * @category utility types
1379
+ * @since 0.20.0
1380
+ */
1381
+ type Executable<Child extends Any> = Child["machine"] extends EnsureExecutable<Machine.States<Child["machine"]>, Machine.UnhandledStates<Child["machine"]>, Machine.OutputStates<Child["machine"]>> ? unknown : never;
1382
+ /**
1383
+ * Startup arguments accepted while spawning a child machine.
1384
+ *
1385
+ * @category utility types
1386
+ * @since 0.20.0
1387
+ */
1388
+ type SpawnArgs<Child extends Any> = Machine.InputSchema<Child["machine"]> extends typeof Schema.Void ? [
1389
+ options?: {
1390
+ readonly input?: never;
1391
+ }
1392
+ ] : [options: {
1393
+ readonly input: Machine.Input<Child["machine"]>;
1394
+ }];
1395
+ /**
1396
+ * Typed failures that may occur before a spawned child becomes active.
1397
+ *
1398
+ * @category utility types
1399
+ * @since 0.20.0
1400
+ */
1401
+ type StartError<Child extends Any> = Child extends ChildMachine<string, infer M> ? Machine.InitialError<M> | Machine.Error<M> | ActionError<Machine.InitialServices<M> | Machine.Services<M>> | InfiniteTransitionError | MachineSchemaDecodeError | StartupError | StoppedError : never;
1402
+ /**
1403
+ * Services needed to initialize a spawned child machine.
1404
+ *
1405
+ * @category utility types
1406
+ * @since 0.20.0
1407
+ */
1408
+ type StartRequirements<Child extends Any> = Child extends ChildMachine<string, infer M> ? Exclude<ExcludeCompatibleRuntime<Exclude<ExecutionServices<Machine.InitialServices<M> | Machine.Services<M>>, MachineRuntimeRequirement>, Machine.Event<M>, Machine.Emit<M>>, Scope.Scope> : never;
1345
1409
  /**
1346
1410
  * Running machine reference selected by a child descriptor.
1347
1411
  *
@@ -1357,6 +1421,21 @@ export declare namespace ChildMachine {
1357
1421
  */
1358
1422
  type Event<Child> = Child extends ChildMachine<string, infer M> ? Machine.EventInput<Machine.InputEvent<M>> : never;
1359
1423
  }
1424
+ /**
1425
+ * Effectful operations for child machines owned directly by the current
1426
+ * machine process.
1427
+ *
1428
+ * @category models
1429
+ * @since 0.20.0
1430
+ */
1431
+ export interface ChildOwner<OwnerEvent> {
1432
+ /** Starts a process-owned child and returns once initialization succeeds. */
1433
+ readonly spawn: <const Child extends ChildMachine.Any>(child: Child & ChildMachine.Executable<Child> & ChildMachine.ParentCompatibility<Child, OwnerEvent>, ...options: ChildMachine.SpawnArgs<Child>) => Effect.Effect<ChildMachine.Ref<Child>, ChildAlreadyExistsError | ChildMachine.StartError<Child>, ChildMachine.StartRequirements<Child>>;
1434
+ /** Sends an event to one active child. Missing children are ignored. */
1435
+ readonly sendTo: <Child extends ChildMachine.Any>(child: Child, event: ChildMachine.Event<Child>) => Effect.Effect<void, StoppedError>;
1436
+ /** Stops one active child. Missing children are ignored. */
1437
+ readonly stop: <Child extends ChildMachine.Any>(child: Child) => Effect.Effect<void>;
1438
+ }
1360
1439
  /**
1361
1440
  * Parent-local address for a child process that can receive events.
1362
1441
  *
@@ -3060,6 +3139,8 @@ export declare namespace Machine {
3060
3139
  * @since 0.4.0
3061
3140
  */
3062
3141
  type InvokeContext<States extends StateSchemas, Events extends ReadonlyArray<TaggedSchema>, Emits extends ReadonlyArray<TaggedSchema>, StateId extends StateIdentifier<States>, InputEvents extends ReadonlyArray<TaggedSchema> = Events, ParentEvents extends ReadonlyArray<TaggedSchema> = readonly []> = MachineReferences<InputEvents, ParentEvents> & {
3142
+ /** Process-owned child operations for dynamic child machine lifecycles. */
3143
+ readonly children: ChildOwner<EventOf<InputEvents>>;
3063
3144
  /** Value owned by the state that owns this invocation. */
3064
3145
  readonly state: StateByIdentifier<States, StateId>;
3065
3146
  /** Value owned by the nearest schema-backed ancestor, when one exists. */
@@ -4989,6 +5070,16 @@ export declare const logic: <State, Event = never, Output = void, Error = never,
4989
5070
  * @since 0.4.0
4990
5071
  */
4991
5072
  export declare const child: <const Id extends string, M extends Machine.Any>(id: Id, machine: M) => ChildMachine<Id, M>;
5073
+ /**
5074
+ * Binds one machine definition to an open family of runtime child ids.
5075
+ *
5076
+ * Descriptors created by the returned function are interchangeable with
5077
+ * {@link child} descriptors for the same id and machine definition.
5078
+ *
5079
+ * @category constructors
5080
+ * @since 0.20.0
5081
+ */
5082
+ export declare const childFamily: <M extends Machine.Any>(machine: M) => ChildMachine.Family<M>;
4992
5083
  /**
4993
5084
  * Creates a typed parent-local address for lower-level child process logic.
4994
5085
  *