@typeonce/effect-machine 0.6.1 → 0.7.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.
@@ -357,6 +357,15 @@ States.getSnapshot(snapshot, "Route.Ready")
357
357
  States.matches(snapshot, "Route.Ready.Saving")
358
358
  ```
359
359
 
360
+ Snapshots returned by `getSnapshot` can be queried again with `get`,
361
+ `getSnapshot`, or `matches`. Paths remain absolute and are restricted to the
362
+ extracted snapshot and its descendants:
363
+
364
+ ```ts
365
+ const ready = Option.getOrThrow(States.getSnapshot(snapshot, "Route.Ready"))
366
+ States.matches(ready, "Route.Ready.Saving")
367
+ ```
368
+
360
369
  All paths are checked against the definition. `context.parent` is the immediate
361
370
  typed parent (`undefined` at a root). Use `parents` when another ancestor is
362
371
  needed:
@@ -642,11 +651,16 @@ the `DefinedStates` object:
642
651
 
643
652
  ```ts
644
653
  AtomMachine.select(machineAtom, "Ready")
654
+ AtomMachine.selectSnapshot(machineAtom, "Ready")
645
655
  AtomMachine.matches(machineAtom, "Ready.Saving")
646
656
  AtomMachine.selectChild(childAtom, "Editing")
657
+ AtomMachine.selectSnapshotChild(childAtom, "Editing")
647
658
  AtomMachine.matchesChild(childAtom, "Editing")
648
659
  ```
649
660
 
661
+ `select` returns only the decoded state value. Use `selectSnapshot` when a
662
+ component needs the selected node's compound or parallel child topology.
663
+
650
664
  Like ordinary Effect Atom combinators, each selector call returns a derived
651
665
  atom. Define it at a stable composition boundary or memoize it when constructing
652
666
  it inside a component.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typeonce/effect-machine",
3
- "version": "0.6.1",
3
+ "version": "0.7.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
@@ -2341,14 +2341,26 @@ export declare namespace Machine {
2341
2341
  readonly initial: InitialBuilder<States>
2342
2342
 
2343
2343
  /**
2344
- * Returns the decoded value for an active state path.
2344
+ * Returns the decoded value for an active state path. The supplied
2345
+ * snapshot may be a complete root snapshot or a snapshot previously
2346
+ * extracted from this definition. Extracted snapshots accept only their
2347
+ * own absolute path and descendant paths.
2345
2348
  *
2346
2349
  * @since 0.4.0
2347
2350
  */
2348
- readonly get: <Path extends StateIdentifier<States>>(
2349
- snapshot: Snapshot<States>,
2350
- path: Path
2351
- ) => Option.Option<StateByIdentifier<States, Path>>
2351
+ readonly get: {
2352
+ <Path extends StateIdentifier<States>>(
2353
+ snapshot: Snapshot<States>,
2354
+ path: Path
2355
+ ): Option.Option<StateByIdentifier<States, Path>>
2356
+ <
2357
+ const From extends StateIdentifier<States>,
2358
+ const Path extends StateIdentifier<States>
2359
+ >(
2360
+ snapshot: SnapshotByIdentifier<States, From>,
2361
+ path: Path & (Path extends NoInfer<From> | `${NoInfer<From>}.${string}` ? unknown : never)
2362
+ ): Option.Option<StateByIdentifier<States, Path>>
2363
+ }
2352
2364
 
2353
2365
  /**
2354
2366
  * Returns the decoded value for an active state path together with all of
@@ -2366,24 +2378,48 @@ export declare namespace Machine {
2366
2378
  ) => Option.Option<StateWithParents<States, Path>>
2367
2379
 
2368
2380
  /**
2369
- * Returns the snapshot for an active state path.
2381
+ * Returns the snapshot for an active state path. The supplied snapshot may
2382
+ * be a complete root snapshot or a snapshot previously extracted from this
2383
+ * definition. Extracted snapshots accept only their own absolute path and
2384
+ * descendant paths.
2370
2385
  *
2371
2386
  * @since 0.4.0
2372
2387
  */
2373
- readonly getSnapshot: <Path extends StateIdentifier<States>>(
2374
- snapshot: Snapshot<States>,
2375
- path: Path
2376
- ) => Option.Option<SnapshotByIdentifier<States, Path>>
2388
+ readonly getSnapshot: {
2389
+ <Path extends StateIdentifier<States>>(
2390
+ snapshot: Snapshot<States>,
2391
+ path: Path
2392
+ ): Option.Option<SnapshotByIdentifier<States, Path>>
2393
+ <
2394
+ const From extends StateIdentifier<States>,
2395
+ const Path extends StateIdentifier<States>
2396
+ >(
2397
+ snapshot: SnapshotByIdentifier<States, From>,
2398
+ path: Path & (Path extends NoInfer<From> | `${NoInfer<From>}.${string}` ? unknown : never)
2399
+ ): Option.Option<SnapshotByIdentifier<States, Path>>
2400
+ }
2377
2401
 
2378
2402
  /**
2379
- * Returns whether a state path is active in the snapshot.
2403
+ * Returns whether a state path is active in the snapshot. The supplied
2404
+ * snapshot may be a complete root snapshot or a snapshot previously
2405
+ * extracted from this definition. Extracted snapshots accept only their
2406
+ * own absolute path and descendant paths.
2380
2407
  *
2381
2408
  * @since 0.4.0
2382
2409
  */
2383
- readonly matches: <Path extends StateIdentifier<States>>(
2384
- snapshot: Snapshot<States>,
2385
- path: Path
2386
- ) => boolean
2410
+ readonly matches: {
2411
+ <Path extends StateIdentifier<States>>(
2412
+ snapshot: Snapshot<States>,
2413
+ path: Path
2414
+ ): boolean
2415
+ <
2416
+ const From extends StateIdentifier<States>,
2417
+ const Path extends StateIdentifier<States>
2418
+ >(
2419
+ snapshot: SnapshotByIdentifier<States, From>,
2420
+ path: Path & (Path extends NoInfer<From> | `${NoInfer<From>}.${string}` ? unknown : never)
2421
+ ): boolean
2422
+ }
2387
2423
  }
2388
2424
 
2389
2425
  /**
@@ -435,9 +435,13 @@ type SnapshotValueByIdentifier<State, Path extends SnapshotIdentifier<State>> =
435
435
  Node extends { readonly path: Path; readonly value: infer Value } ? Value : never
436
436
  : never
437
437
 
438
+ type SnapshotByIdentifier<State, Path extends SnapshotIdentifier<State>> = SnapshotNode<State> extends infer Node ?
439
+ Node extends { readonly path: Path } ? Node : never
440
+ : never
441
+
438
442
  type ChildState<Child extends Machine.ChildMachine.Any> = RefState<Machine.ChildMachine.Ref<Child>>
439
443
 
440
- const selectSnapshot = <
444
+ const selectValueByPath = <
441
445
  State extends Machine.Machine.AtomicSnapshot<string, unknown>,
442
446
  Path extends SnapshotIdentifier<State>
443
447
  >(
@@ -448,6 +452,15 @@ const selectSnapshot = <
448
452
  Option.map((snapshot) => snapshot.value)
449
453
  ) as Option.Option<SnapshotValueByIdentifier<State, Path>>
450
454
 
455
+ const selectSnapshotByPath = <
456
+ State extends Machine.Machine.AtomicSnapshot<string, unknown>,
457
+ Path extends SnapshotIdentifier<State>
458
+ >(
459
+ snapshot: State,
460
+ path: Path
461
+ ): Option.Option<SnapshotByIdentifier<State, Path>> =>
462
+ Topology.getSnapshotByPath(snapshot, path) as Option.Option<SnapshotByIdentifier<State, Path>>
463
+
451
464
  export const select = <
452
465
  State extends Machine.Machine.AtomicSnapshot<string, unknown>,
453
466
  Event,
@@ -461,7 +474,24 @@ export const select = <
461
474
  ): Atom.Atom<
462
475
  AsyncResult.AsyncResult<Option.Option<SnapshotValueByIdentifier<State, Path>>, StartError | Error>
463
476
  > =>
464
- Atom.mapResult(self.result, (snapshot) => selectSnapshot(snapshot, path)).pipe(
477
+ Atom.mapResult(self.result, (snapshot) => selectValueByPath(snapshot, path)).pipe(
478
+ Atom.withEquality(Equal.equals)
479
+ )
480
+
481
+ export const selectSnapshot = <
482
+ State extends Machine.Machine.AtomicSnapshot<string, unknown>,
483
+ Event,
484
+ Error,
485
+ Output,
486
+ StartError,
487
+ const Path extends SnapshotIdentifier<State>
488
+ >(
489
+ self: MachineAtom<State, Event, Error, Output, StartError>,
490
+ path: Path
491
+ ): Atom.Atom<
492
+ AsyncResult.AsyncResult<Option.Option<SnapshotByIdentifier<State, Path>>, StartError | Error>
493
+ > =>
494
+ Atom.mapResult(self.result, (snapshot) => selectSnapshotByPath(snapshot, path)).pipe(
465
495
  Atom.withEquality(Equal.equals)
466
496
  )
467
497
 
@@ -480,7 +510,25 @@ export const selectChild = <
480
510
  > =>
481
511
  Atom.mapResult(
482
512
  self.result,
483
- Option.flatMap((snapshot) => selectSnapshot(snapshot, path))
513
+ Option.flatMap((snapshot) => selectValueByPath(snapshot, path))
514
+ ).pipe(Atom.withEquality(Equal.equals))
515
+
516
+ export const selectSnapshotChild = <
517
+ Child extends Machine.ChildMachine.Any,
518
+ StartError,
519
+ const Path extends SnapshotIdentifier<ChildState<Child>>
520
+ >(
521
+ self: ChildMachineAtom<Child, StartError>,
522
+ path: Path
523
+ ): Atom.Atom<
524
+ AsyncResult.AsyncResult<
525
+ Option.Option<SnapshotByIdentifier<ChildState<Child>, Path>>,
526
+ StartError | RefError<Machine.ChildMachine.Ref<Child>>
527
+ >
528
+ > =>
529
+ Atom.mapResult(
530
+ self.result,
531
+ Option.flatMap((snapshot) => selectSnapshotByPath(snapshot, path))
484
532
  ).pipe(Atom.withEquality(Equal.equals))
485
533
 
486
534
  export const matches = <
@@ -713,10 +713,11 @@ export const defineStates: DefineStates = (<const States extends Machine.StateSc
713
713
  return {
714
714
  states,
715
715
  initial: makeSnapshotBuilder(states, { mode: "initial", prefix: "" }) as Machine.InitialBuilder<States>,
716
- get: ((snapshot, path) =>
717
- Topology.getSnapshotByPath(snapshot, path).pipe(
718
- Option.map((snapshot) => snapshot.value)
719
- )) as Machine.DefinedStates<States>["get"],
716
+ get:
717
+ ((snapshot: Machine.AtomicSnapshot<string, unknown>, path: string) =>
718
+ Topology.getSnapshotByPath(snapshot, path).pipe(
719
+ Option.map((snapshot) => snapshot.value)
720
+ )) as Machine.DefinedStates<States>["get"],
720
721
  getWithParents: ((snapshot, path) => {
721
722
  const parents: Record<string, unknown> = {}
722
723
  return Topology.getSnapshotByPath(snapshot, path, parents).pipe(
@@ -724,7 +725,9 @@ export const defineStates: DefineStates = (<const States extends Machine.StateSc
724
725
  )
725
726
  }) as Machine.DefinedStates<States>["getWithParents"],
726
727
  getSnapshot: Topology.getSnapshotByPath as unknown as Machine.DefinedStates<States>["getSnapshot"],
727
- matches: (snapshot, path) => Option.isSome(Topology.getSnapshotByPath(snapshot, path))
728
+ matches:
729
+ ((snapshot: Machine.AtomicSnapshot<string, unknown>, path: string) =>
730
+ Option.isSome(Topology.getSnapshotByPath(snapshot, path))) as Machine.DefinedStates<States>["matches"]
728
731
  }
729
732
  }) as DefineStates
730
733
 
@@ -295,6 +295,10 @@ type SnapshotValueByIdentifier<State, Path extends SnapshotIdentifier<State>> =
295
295
  Node extends { readonly path: Path; readonly value: infer Value } ? Value : never
296
296
  : never
297
297
 
298
+ type SnapshotByIdentifier<State, Path extends SnapshotIdentifier<State>> = SnapshotNode<State> extends infer Node ?
299
+ Node extends { readonly path: Path } ? Node : never
300
+ : never
301
+
298
302
  type ChildState<Child extends Machine.ChildMachine.Any> = RefState<Machine.ChildMachine.Ref<Child>>
299
303
 
300
304
  /**
@@ -339,6 +343,27 @@ export const select: <
339
343
  AsyncResult.AsyncResult<Option.Option<SnapshotValueByIdentifier<State, Path>>, StartError | Error>
340
344
  > = internal.select
341
345
 
346
+ /**
347
+ * Selects the typed logical snapshot for an active state path.
348
+ *
349
+ * Unlike {@link select}, the selected value retains its child snapshot
350
+ * topology. The derived atom suppresses structurally equal updates. Keep the
351
+ * returned atom stable when constructing it inside a component.
352
+ *
353
+ * @category combinators
354
+ * @since 0.7.0
355
+ */
356
+ export const selectSnapshot: <
357
+ State extends Machine.Machine.AtomicSnapshot<string, unknown>,
358
+ Event,
359
+ Error,
360
+ Output,
361
+ StartError,
362
+ const Path extends SnapshotIdentifier<State>
363
+ >(self: MachineAtom<State, Event, Error, Output, StartError>, path: Path) => Atom.Atom<
364
+ AsyncResult.AsyncResult<Option.Option<SnapshotByIdentifier<State, Path>>, StartError | Error>
365
+ > = internal.selectSnapshot
366
+
342
367
  /**
343
368
  * Selects the typed value for an active state path in an invoked child.
344
369
  *
@@ -367,6 +392,28 @@ export const selectChild: <
367
392
  >
368
393
  > = internal.selectChild
369
394
 
395
+ /**
396
+ * Selects the typed logical snapshot for an active state path in an invoked
397
+ * child.
398
+ *
399
+ * An inactive child or state path produces `Option.none()`. Unlike
400
+ * {@link selectChild}, the selected value retains its child snapshot topology.
401
+ * The derived atom suppresses structurally equal updates.
402
+ *
403
+ * @category combinators
404
+ * @since 0.7.0
405
+ */
406
+ export const selectSnapshotChild: <
407
+ Child extends Machine.ChildMachine.Any,
408
+ StartError,
409
+ const Path extends SnapshotIdentifier<ChildState<Child>>
410
+ >(self: ChildMachineAtom<Child, StartError>, path: Path) => Atom.Atom<
411
+ AsyncResult.AsyncResult<
412
+ Option.Option<SnapshotByIdentifier<ChildState<Child>, Path>>,
413
+ StartError | RefError<Machine.ChildMachine.Ref<Child>>
414
+ >
415
+ > = internal.selectSnapshotChild
416
+
370
417
  /**
371
418
  * Returns whether a state path is active.
372
419
  *