@xstate/effect 0.1.0-alpha.5 → 0.1.0-alpha.7

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.
package/README.md CHANGED
@@ -7,14 +7,14 @@ Combine XState's event-driven workflows with Effect services, scopes, clocks and
7
7
  - Provide services once for the actor and its child logic.
8
8
  - Keep actor lifetime inside your Effect application.
9
9
 
10
- This package is experimental. It targets XState v6 alpha and Effect 4 RC.
10
+ This package is experimental. It targets XState v6 alpha and Effect 4.
11
11
 
12
12
  ## Installation
13
13
 
14
14
  <!-- package name and peer dependencies from package.json -->
15
15
 
16
16
  ```bash
17
- npm install @xstate/effect@alpha xstate@alpha effect@rc
17
+ npm install @xstate/effect@alpha xstate@alpha effect@^4
18
18
  ```
19
19
 
20
20
  ## XState Effect: Quick start
@@ -256,7 +256,7 @@ See [atoms and React](docs/atoms-and-react.md) for complete examples with import
256
256
  - Timed waits fail with Effect's `Cause.TimeoutError`.
257
257
  - `join` preserves a task's typed failure. Unexpected machine errors have type `unknown`.
258
258
 
259
- See [testing and errors](docs/testing-and-errors.md) for executable deadline, retry and failure examples. Persisted snapshots record actor state; restoration into a new Effect interpreter is not supported yet.
259
+ See [testing and errors](docs/testing-and-errors.md) for executable deadline, retry and failure examples. Persisted snapshots record actor state; pass one to `createEffectActor(logic, { snapshot })` to resume the actor. See [persisting and restoring](docs/actors.md#persisting-and-restoring).
260
260
 
261
261
  ## Tracing
262
262
 
@@ -1,4 +1,4 @@
1
- import { AsyncResult, Atom } from 'effect/unstable/reactivity';
1
+ import { AsyncResult, Atom } from 'effect/reactivity';
2
2
  import type { AnyActorLogic, ErrorFrom, EventFromLogic, SnapshotFrom } from 'xstate';
3
3
  import { type EffectActorOptionsArgs } from "./createEffectActor.js";
4
4
  import type { EffectActor } from "./effectActor.js";
@@ -8,7 +8,7 @@ import type { RequirementsFrom } from "./types.js";
8
8
  export { NotReadyError } from "./errors.js";
9
9
  /**
10
10
  * Atoms that expose one actor to a reactive UI through
11
- * `effect/unstable/reactivity`. Read them with the atom bindings for your
11
+ * `effect/reactivity`. Read them with the atom bindings for your
12
12
  * framework, such as `@effect/atom-react`.
13
13
  */
14
14
  export interface ActorAtoms<TLogic extends AnyActorLogic, ER = never> {
@@ -1,14 +1,24 @@
1
1
  import { Effect, Scope } from 'effect';
2
- import { type AnyActorLogic, type InputFrom } from 'xstate';
2
+ import { type ActorOptions, type AnyActorLogic, type InputFrom, type RequiredActorOptionsFor, type RequiredActorOptionsKeys } from 'xstate';
3
3
  import { EffectActor } from "./effectActor.js";
4
4
  import type { RequirementsFrom } from "./types.js";
5
5
  /** Options for {@link createEffectActor}. */
6
- export type EffectActorOptions<TLogic extends AnyActorLogic> = undefined extends InputFrom<TLogic> ? {
6
+ export type EffectActorOptions<TLogic extends AnyActorLogic> = {
7
7
  readonly input?: InputFrom<TLogic>;
8
- } : {
9
- readonly input: InputFrom<TLogic>;
10
- };
11
- export type EffectActorOptionsArgs<TLogic extends AnyActorLogic> = undefined extends InputFrom<TLogic> ? [options?: EffectActorOptions<TLogic>] : [options: EffectActorOptions<TLogic>];
8
+ /**
9
+ * A snapshot from `actor.getPersistedSnapshot()`. The actor resumes in that
10
+ * state without re-running entry actions, and pending timers keep their
11
+ * original deadlines, as with XState's `createActor(logic, { snapshot })`.
12
+ * State-machine children resume their persisted state and completed
13
+ * children stay done. Effect tasks and streams that were running, at the
14
+ * root or as children, start again from the beginning. A restored actor
15
+ * does not need `input`.
16
+ */
17
+ readonly snapshot?: ActorOptions<TLogic>['snapshot'];
18
+ } & RequiredActorOptionsFor<TLogic>;
19
+ export type EffectActorOptionsArgs<TLogic extends AnyActorLogic> = [
20
+ RequiredActorOptionsKeys<TLogic>
21
+ ] extends [never] ? [options?: EffectActorOptions<TLogic>] : [options: EffectActorOptions<TLogic>];
12
22
  /**
13
23
  * Creates and starts an actor as an Effect interpreter over pure transitions.
14
24
  *
@@ -1,5 +1,5 @@
1
- import { Queue, Effect, Fiber, Scope, Exit, Cause, Context, Duration, Data } from 'effect';
2
- import { stopActor, terminateActor, deliverEvent } from 'xstate';
1
+ import { Queue, Effect, Fiber, Scope, Exit, Cause, Context, Clock, Duration, Data } from 'effect';
2
+ import { stopActor, terminateActor, deliverEvent, isMachineSnapshot } from 'xstate';
3
3
  import { createDurable } from 'xstate/durable';
4
4
 
5
5
  /** A mailbox item that reports a failed fire-and-forget action. */
@@ -416,6 +416,7 @@ function withActorScope(effect) {
416
416
  }
417
417
 
418
418
  const XSTATE_TIMER = 'xstate.timer';
419
+ const XSTATE_INIT = '@xstate.init';
419
420
 
420
421
  /** Options for {@link createEffectActor}. */
421
422
 
@@ -456,7 +457,11 @@ function createEffectActor(logic, ...[options]) {
456
457
  const timerKey = (source, id) => `${source.sessionId}:${id}`;
457
458
  const inspectors = new Set();
458
459
  let rootAnnounced = false;
460
+ const clock = Context.get(context, Clock.Clock);
459
461
  const durable = createDurable(logic, {
462
+ // Timer deadlines are measured on the same clock the timers sleep
463
+ // on, so a restored timer resumes with its remaining delay.
464
+ now: () => clock.currentTimeMillisUnsafe(),
460
465
  executeAction: (action, _metadata, runtime) => {
461
466
  // Fire-and-forget: the action starts now and the loop continues.
462
467
  // A rejection reaches the machine as an execution error.
@@ -590,10 +595,29 @@ function createEffectActor(logic, ...[options]) {
590
595
  closeEffectHost(host);
591
596
  };
592
597
 
593
- // The first transition runs here so the handle is ready when this
594
- // Effect succeeds, and the initial actions start before any send.
595
- let [snapshot, effects] = durable.initialTransition(options?.input);
598
+ // The first transition (or the restore) runs here so the handle is
599
+ // ready when this Effect succeeds, and the initial actions start before
600
+ // any send. Restoring runs no entry actions; its effects resume
601
+ // machine children, restart running Effect children and re-arm pending
602
+ // timers.
603
+ let [snapshot, effects] = options?.snapshot ? durable.restore(options.snapshot) : durable.initialTransition(options?.input);
604
+ const initial = snapshot;
605
+ if (options?.snapshot && !isMachineSnapshot(initial) && initial.status === 'active') {
606
+ // A restored machine resumes through the effects above. Other logic
607
+ // (an Effect task or stream at the root) reattaches its active work by
608
+ // replaying its init event, as its restored `start` does under
609
+ // `createActor`.
610
+ const [started, startEffects] = durable.transition(snapshot, {
611
+ type: XSTATE_INIT,
612
+ input: 'input' in initial ? initial.input : undefined
613
+ });
614
+ snapshot = started;
615
+ effects = [...effects, ...startEffects];
616
+ }
596
617
  const root = durable.getActorRef(snapshot);
618
+ // Restored children are created by the snapshot, not spawned through
619
+ // the adapter; they find this host through their parent chain.
620
+ bindEffectHost(root, host);
597
621
  // The root exists from here on; later announcements are step
598
622
  // re-materializations, not new actors.
599
623
  rootAnnounced = true;
@@ -1,13 +1,13 @@
1
1
  import { Cause } from 'effect';
2
- import { Atom, AsyncResult } from 'effect/unstable/reactivity';
3
- import { c as createEffectActor, N as NotReadyError, t as taggedState } from './state-14a1fecf.js';
4
- export { N as NotReadyError } from './state-14a1fecf.js';
2
+ import { Atom, AsyncResult } from 'effect/reactivity';
3
+ import { c as createEffectActor, N as NotReadyError, t as taggedState } from './state-b3d9b3e2.js';
4
+ export { N as NotReadyError } from './state-b3d9b3e2.js';
5
5
  import 'xstate';
6
6
  import 'xstate/durable';
7
7
 
8
8
  /**
9
9
  * Atoms that expose one actor to a reactive UI through
10
- * `effect/unstable/reactivity`. Read them with the atom bindings for your
10
+ * `effect/reactivity`. Read them with the atom bindings for your
11
11
  * framework, such as `@effect/atom-react`.
12
12
  */
13
13
 
@@ -1,7 +1,7 @@
1
1
  import { Schema, Effect, Stream, Exit, Cause, Queue } from 'effect';
2
2
  import { createLogic, setup } from 'xstate';
3
- import { s as startHostedEffect, r as relayToParent, E as EffectInterruptedError, A as ActorStoppedError, a as runHostedEffect } from './state-14a1fecf.js';
4
- export { d as ActorScope, A as ActorStoppedError, b as EffectActor, E as EffectInterruptedError, c as createEffectActor, t as taggedState, w as withActorScope } from './state-14a1fecf.js';
3
+ import { s as startHostedEffect, r as relayToParent, E as EffectInterruptedError, A as ActorStoppedError, a as runHostedEffect } from './state-b3d9b3e2.js';
4
+ export { d as ActorScope, A as ActorStoppedError, b as EffectActor, E as EffectInterruptedError, c as createEffectActor, t as taggedState, w as withActorScope } from './state-b3d9b3e2.js';
5
5
  import { dual } from 'effect/Function';
6
6
  import 'xstate/durable';
7
7
 
@@ -658,6 +658,7 @@ function wrapActions(actions) {
658
658
  for (const key of Object.keys(actions)) {
659
659
  const action = actions[key];
660
660
  wrapped[key] = args => {
661
+ // machine.provide can replace Effect actions with plain actions.
661
662
  const result = action(args);
662
663
  return Effect.isEffect(result) ? runHostedEffect(args.self, result, `action.${key}`) : result;
663
664
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xstate/effect",
3
- "version": "0.1.0-alpha.5",
3
+ "version": "0.1.0-alpha.7",
4
4
  "type": "module",
5
5
  "description": "Effect integration for XState",
6
6
  "keywords": [
@@ -29,12 +29,13 @@
29
29
  "url": "https://github.com/statelyai/xstate/issues"
30
30
  },
31
31
  "peerDependencies": {
32
- "effect": "^4.0.0-rc.115",
33
- "xstate": "^6.0.0-alpha.62"
32
+ "effect": "^4.0.0",
33
+ "xstate": "^6.0.0-alpha.65"
34
34
  },
35
35
  "devDependencies": {
36
- "effect": "4.0.0-rc.115",
37
- "xstate": "^6.0.0-alpha.62"
36
+ "effect": "4.0.0",
37
+ "fast-check": "4.10.2",
38
+ "xstate": "^6.0.0-alpha.65"
38
39
  },
39
40
  "preconstruct": {
40
41
  "entrypoints": [