@xstate/effect 0.1.0-alpha.6 → 1.0.0-alpha.8

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
@@ -9,6 +9,8 @@ Combine XState's event-driven workflows with Effect services, scopes, clocks and
9
9
 
10
10
  This package is experimental. It targets XState v6 alpha and Effect 4.
11
11
 
12
+ In these docs, "transition effects" are the executable effect descriptors (`ExecutableEffect`) that an XState transition returns. "Effect" means an Effect.ts program.
13
+
12
14
  ## Installation
13
15
 
14
16
  <!-- package name and peer dependencies from package.json -->
@@ -29,7 +31,7 @@ A release waits for approval, expires after 30 seconds, and allows cancellation
29
31
  import { Context, Effect, Schema } from 'effect';
30
32
  import {
31
33
  createEffectActor,
32
- fromEffect,
34
+ createEffectLogic,
33
35
  send,
34
36
  setupEffect,
35
37
  waitFor
@@ -40,7 +42,7 @@ export class Deployments extends Context.Service<
40
42
  { readonly deploy: (release: string) => Effect.Effect<string, Error> }
41
43
  >()('@app/Deployments') {}
42
44
 
43
- const deploy = fromEffect({
45
+ const deploy = createEffectLogic({
44
46
  schemas: { input: Schema.Struct({ release: Schema.String }) },
45
47
  effect: ({ input }) => Deployments.use((api) => api.deploy(input.release))
46
48
  });
@@ -134,7 +136,7 @@ Start Effect-backed logic with `createEffectActor` inside an Effect scope.
134
136
  | [Atoms and React](docs/atoms-and-react.md) | Registry ownership, approval controls and useSelector. |
135
137
  | [Testing and errors](docs/testing-and-errors.md) | TestClock, task retries, supervision and typed failures. |
136
138
 
137
- Runnable copies live in [examples/effect-workflows](../../examples/effect-workflows). Their tests verify the docs use the same code.
139
+ Try the runnable examples in [examples/effect-workflows](../../examples/effect-workflows).
138
140
 
139
141
  ## Actors
140
142
 
@@ -183,13 +185,13 @@ The actor's later errors are observed through `join` or snapshots. Invalid input
183
185
 
184
186
  ## Effect actor logic
185
187
 
186
- <!-- fromEffect, fromEffectStream, fromEffectEventStream from src/fromEffect.ts -->
188
+ <!-- createEffectLogic, createEffectStreamLogic, createEffectEventStreamLogic from src/createEffectLogic.ts -->
187
189
 
188
190
  | Function | Behavior |
189
191
  | --- | --- |
190
- | `fromEffect` | Run a task; its success value is the actor's output. |
191
- | `fromEffectStream` | Expose the latest stream item as snapshot context. |
192
- | `fromEffectEventStream` | Relay stream items to the parent as events. |
192
+ | `createEffectLogic` | Run a task; its success value is the actor's output. |
193
+ | `createEffectStreamLogic` | Expose the latest stream item as snapshot context. |
194
+ | `createEffectEventStreamLogic` | Relay stream items to the parent as events. |
193
195
 
194
196
  For example, a progress stream can feed an upload UI:
195
197
 
@@ -197,10 +199,14 @@ For example, a progress stream can feed an upload UI:
197
199
 
198
200
  ```ts
199
201
  import { Effect, Option, Stream } from 'effect';
200
- import { createEffectActor, fromEffectStream, snapshots } from '@xstate/effect';
202
+ import {
203
+ createEffectActor,
204
+ createEffectStreamLogic,
205
+ snapshots
206
+ } from '@xstate/effect';
201
207
 
202
208
  // Demo upload progress. Replace with your upload SDK's progress stream.
203
- const uploadProgress = fromEffectStream(Stream.make(0, 25, 60, 100));
209
+ const uploadProgress = createEffectStreamLogic(Stream.make(0, 25, 60, 100));
204
210
 
205
211
  const program = Effect.gen(function* () {
206
212
  const actor = yield* createEffectActor(uploadProgress);
@@ -229,7 +235,7 @@ See [Effect actor logic](docs/effect-logic.md) for tasks and a stream that drive
229
235
  <!-- effect action contract from src/setupEffect.ts; spawn guard from src/internal.ts -->
230
236
 
231
237
  - Register background Effect actions in `setupEffect({ actions })`; enqueue them with explicit arguments.
232
- - Invoke `fromEffect` logic when the machine needs to wait for a result.
238
+ - Invoke `createEffectLogic` logic when the machine needs to wait for a result.
233
239
  - Register spawned Effect logic in `actors`, then use `enq.spawn(args.actors.worker)`.
234
240
 
235
241
  See [schemas and actions](docs/schemas-and-actions.md) for a complete audit example, runtime schema constraints and inline-source pitfalls.
@@ -256,10 +262,10 @@ See [atoms and React](docs/atoms-and-react.md) for complete examples with import
256
262
  - Timed waits fail with Effect's `Cause.TimeoutError`.
257
263
  - `join` preserves a task's typed failure. Unexpected machine errors have type `unknown`.
258
264
 
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.
265
+ 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
266
 
261
267
  ## Tracing
262
268
 
263
- <!-- span names and attributes from src/internal.ts and src/fromEffect.ts -->
269
+ <!-- span names and attributes from src/internal.ts and src/createEffectLogic.ts -->
264
270
 
265
- Hosted work uses spans named `fromEffect`, `fromEffectStream`, `fromEffectEventStream` and `action.<name>`. Each carries `xstate.actor.id` and `xstate.actor.address`. Provide a `Tracer` to record and export them.
271
+ Hosted work uses spans named `createEffectLogic`, `createEffectStreamLogic`, `createEffectEventStreamLogic` and `action.<name>`. Each carries `xstate.actor.id` and `xstate.actor.address`. Provide a `Tracer` to record and export them.
@@ -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
  *
@@ -53,8 +53,7 @@ export declare class EffectActor<TLogic extends AnyActorLogic> implements ActorR
53
53
  inspect(observer: Observer<InspectionEvent> | ((event: InspectionEvent) => void)): Subscription;
54
54
  [symbolObservable](): this;
55
55
  toJSON(): {
56
- xstate$$type: number;
57
- id: string;
56
+ $actor: string;
58
57
  };
59
58
  }
60
59
  export {};
@@ -4,8 +4,8 @@ import type { EffectRequirements } from "./types.js";
4
4
  import { type EffectSchemaLike, type ToStandardSchema, type ValidateEffectActorSchemas } from "./schema.js";
5
5
  declare const effectLogicBrand: unique symbol;
6
6
  /**
7
- * Type-level marker attached to logic created by `fromEffect`,
8
- * `fromEffectStream` and `fromEffectEventStream`. It carries the logic's
7
+ * Type-level marker attached to logic created by `createEffectLogic`,
8
+ * `createEffectStreamLogic` and `createEffectEventStreamLogic`. It carries the logic's
9
9
  * failure type and its Effect service requirements, which is how
10
10
  * `RequirementsFrom` collects the `R` channel of `createEffectActor` without
11
11
  * inspecting the logic at runtime.
@@ -17,7 +17,7 @@ export interface EffectLogicBrand<TError, TRequirements> {
17
17
  };
18
18
  }
19
19
  /**
20
- * Snapshot of a `fromEffect` actor. The actor holds no context of its own: it
20
+ * Snapshot of a `createEffectLogic` actor. The actor holds no context of its own: it
21
21
  * is `active` while the Effect runs, `done` with the Effect's success value as
22
22
  * `output`, or `error` with the Effect's failure, defect or
23
23
  * `EffectInterruptedError` as `error`.
@@ -41,7 +41,7 @@ export type EffectSourceArgs<TInput> = {
41
41
  readonly emit: (event: AnyEventObject) => void;
42
42
  };
43
43
  /**
44
- * What `fromEffect` accepts as its Effect: either an Effect value, or a
44
+ * What `createEffectLogic` accepts as its Effect: either an Effect value, or a
45
45
  * function of {@link EffectSourceArgs} returning one. The function form is
46
46
  * called once per actor start, so it sees that actor's input.
47
47
  */
@@ -67,7 +67,7 @@ type EffectSchemaValidationConfig<TSchemas extends EffectActorSchemas> = {
67
67
  readonly schemas: TSchemas;
68
68
  };
69
69
  /**
70
- * Actor logic produced by `fromEffect` and `fromEffectEventStream`. It is
70
+ * Actor logic produced by `createEffectLogic` and `createEffectEventStreamLogic`. It is
71
71
  * ordinary XState actor logic carrying {@link EffectLogicBrand}, so a machine
72
72
  * can invoke or spawn it, and `createEffectActor` can collect its
73
73
  * requirements.
@@ -76,7 +76,7 @@ export type EffectActorLogic<TOutput, TError, TInput, TRequirements> = ActorLogi
76
76
  readonly id?: string;
77
77
  } & EffectLogicBrand<TError, TRequirements>;
78
78
  /**
79
- * Snapshot of a `fromEffectStream` actor. `context` holds the most recent
79
+ * Snapshot of a `createEffectStreamLogic` actor. `context` holds the most recent
80
80
  * stream item, or `undefined` before the first one. The actor reaches `done`
81
81
  * with no output when the stream completes, and `error` when it fails.
82
82
  */
@@ -86,7 +86,7 @@ export type EffectStreamSnapshot<TItem, TError, TInput> = Snapshot<undefined> &
86
86
  readonly error: TError | undefined;
87
87
  };
88
88
  /**
89
- * Actor logic produced by `fromEffectStream`. Its snapshot is an
89
+ * Actor logic produced by `createEffectStreamLogic`. Its snapshot is an
90
90
  * {@link EffectStreamSnapshot}, so the parent reads the latest item from the
91
91
  * child's `context` rather than from events.
92
92
  */
@@ -105,11 +105,11 @@ export type EffectStreamActorLogic<TItem, TError, TInput, TRequirements> = Actor
105
105
  * interruption. Use `withActorScope` to retain a resource until the owning
106
106
  * Effect actor stops.
107
107
  */
108
- export declare function fromEffect<const TInputSchema extends EffectSchemaLike, const TOutputSchema extends EffectSchemaLike, TError = unknown, TRequirements = never>(config: Omit<EffectSourceConfig<SchemaOutput<TOutputSchema>, TError, SchemaOutput<TInputSchema>, TRequirements>, 'schemas' | 'validator'> & EffectSchemaValidationConfig<{
108
+ export declare function createEffectLogic<const TInputSchema extends EffectSchemaLike, const TOutputSchema extends EffectSchemaLike, TError = unknown, TRequirements = never>(config: Omit<EffectSourceConfig<SchemaOutput<TOutputSchema>, TError, SchemaOutput<TInputSchema>, TRequirements>, 'schemas' | 'validator'> & EffectSchemaValidationConfig<{
109
109
  input: TInputSchema;
110
110
  output: TOutputSchema;
111
111
  }>): EffectActorLogic<SchemaOutput<TOutputSchema>, TError, SchemaOutput<TInputSchema>, TRequirements>;
112
- export declare function fromEffect<const TInputSchema extends EffectSchemaLike, TEffect extends Effect.Effect<any, any, any>>(config: Omit<EffectSourceConfig<EffectSuccess<TEffect>, EffectFailure<TEffect>, SchemaOutput<TInputSchema>, EffectRequirements<TEffect>, {
112
+ export declare function createEffectLogic<const TInputSchema extends EffectSchemaLike, TEffect extends Effect.Effect<any, any, any>>(config: Omit<EffectSourceConfig<EffectSuccess<TEffect>, EffectFailure<TEffect>, SchemaOutput<TInputSchema>, EffectRequirements<TEffect>, {
113
113
  input: TInputSchema;
114
114
  }>, 'effect' | 'schemas' | 'validator'> & EffectSchemaValidationConfig<{
115
115
  input: TInputSchema;
@@ -117,17 +117,17 @@ export declare function fromEffect<const TInputSchema extends EffectSchemaLike,
117
117
  }> & {
118
118
  effect: TEffect | ((args: EffectSourceArgs<SchemaOutput<TInputSchema>>) => TEffect);
119
119
  }): EffectActorLogic<EffectSuccess<TEffect>, EffectFailure<TEffect>, SchemaOutput<TInputSchema>, EffectRequirements<TEffect>>;
120
- export declare function fromEffect<const TOutputSchema extends EffectSchemaLike, TError = unknown, TInput = undefined, TRequirements = never>(config: Omit<EffectSourceConfig<SchemaOutput<TOutputSchema>, TError, TInput, TRequirements>, 'schemas' | 'validator'> & EffectSchemaValidationConfig<{
120
+ export declare function createEffectLogic<const TOutputSchema extends EffectSchemaLike, TError = unknown, TInput = undefined, TRequirements = never>(config: Omit<EffectSourceConfig<SchemaOutput<TOutputSchema>, TError, TInput, TRequirements>, 'schemas' | 'validator'> & EffectSchemaValidationConfig<{
121
121
  input?: never;
122
122
  output: TOutputSchema;
123
123
  }>): EffectActorLogic<SchemaOutput<TOutputSchema>, TError, TInput, TRequirements>;
124
- export declare function fromEffect<TOutput, TError = unknown, TInput = undefined, TRequirements = never>(config: EffectSourceConfig<TOutput, TError, TInput, TRequirements> & {
124
+ export declare function createEffectLogic<TOutput, TError = unknown, TInput = undefined, TRequirements = never>(config: EffectSourceConfig<TOutput, TError, TInput, TRequirements> & {
125
125
  schemas?: undefined;
126
126
  }): EffectActorLogic<TOutput, TError, TInput, TRequirements>;
127
- export declare function fromEffect<TOutput, TError = unknown, TRequirements = never>(effect: Effect.Effect<TOutput, TError, TRequirements>): EffectActorLogic<TOutput, TError, undefined, TRequirements>;
128
- export declare function fromEffect<TOutput, TError = unknown, TInput = undefined, TRequirements = never>(effect: (args: EffectSourceArgs<TInput>) => Effect.Effect<TOutput, TError, TRequirements>): EffectActorLogic<TOutput, TError, TInput, TRequirements>;
127
+ export declare function createEffectLogic<TOutput, TError = unknown, TRequirements = never>(effect: Effect.Effect<TOutput, TError, TRequirements>): EffectActorLogic<TOutput, TError, undefined, TRequirements>;
128
+ export declare function createEffectLogic<TOutput, TError = unknown, TInput = undefined, TRequirements = never>(effect: (args: EffectSourceArgs<TInput>) => Effect.Effect<TOutput, TError, TRequirements>): EffectActorLogic<TOutput, TError, TInput, TRequirements>;
129
129
  /**
130
- * What `fromEffectStream` and `fromEffectEventStream` accept as their stream:
130
+ * What `createEffectStreamLogic` and `createEffectEventStreamLogic` accept as their stream:
131
131
  * either a Stream value, or a function of {@link EffectSourceArgs} returning
132
132
  * one. The function form is called once per actor start.
133
133
  */
@@ -153,29 +153,29 @@ type StreamRequirements<T> = T extends Stream.Stream<any, any, infer R> ? R : ne
153
153
  * `schemas`, `validator` and `stream`. Its resources close when the stream
154
154
  * completes, fails or is interrupted.
155
155
  */
156
- export declare function fromEffectStream<const TInputSchema extends EffectSchemaLike, TStream extends Stream.Stream<any, any, any>>(config: Omit<EffectStreamConfig<StreamItem<TStream>, StreamError<TStream>, SchemaOutput<TInputSchema>, StreamRequirements<TStream>>, 'stream' | 'schemas' | 'validator'> & EffectSchemaValidationConfig<{
156
+ export declare function createEffectStreamLogic<const TInputSchema extends EffectSchemaLike, TStream extends Stream.Stream<any, any, any>>(config: Omit<EffectStreamConfig<StreamItem<TStream>, StreamError<TStream>, SchemaOutput<TInputSchema>, StreamRequirements<TStream>>, 'stream' | 'schemas' | 'validator'> & EffectSchemaValidationConfig<{
157
157
  input: TInputSchema;
158
158
  }> & {
159
159
  stream: TStream | ((args: EffectSourceArgs<SchemaOutput<TInputSchema>>) => TStream);
160
160
  }): EffectStreamActorLogic<StreamItem<TStream>, StreamError<TStream>, SchemaOutput<TInputSchema>, StreamRequirements<TStream>>;
161
- export declare function fromEffectStream<TItem, TError = unknown, TInput = undefined, TRequirements = never>(config: EffectStreamConfig<TItem, TError, TInput, TRequirements> & {
161
+ export declare function createEffectStreamLogic<TItem, TError = unknown, TInput = undefined, TRequirements = never>(config: EffectStreamConfig<TItem, TError, TInput, TRequirements> & {
162
162
  schemas?: undefined;
163
163
  }): EffectStreamActorLogic<TItem, TError, TInput, TRequirements>;
164
- export declare function fromEffectStream<TItem, TError = unknown, TInput = undefined, TRequirements = never>(stream: EffectStreamSource<TItem, TError, TInput, TRequirements>): EffectStreamActorLogic<TItem, TError, TInput, TRequirements>;
164
+ export declare function createEffectStreamLogic<TItem, TError = unknown, TInput = undefined, TRequirements = never>(stream: EffectStreamSource<TItem, TError, TInput, TRequirements>): EffectStreamActorLogic<TItem, TError, TInput, TRequirements>;
165
165
  /**
166
166
  * Creates actor logic that runs a Stream of events and relays each item to the
167
167
  * parent machine as an event, the way `fromEventObservable` does. The actor
168
168
  * has no output: it reaches `done` when the stream completes and `error` when
169
- * it fails. Accepts the same forms as `fromEffectStream`. Its resources close
169
+ * it fails. Accepts the same forms as `createEffectStreamLogic`. Its resources close
170
170
  * when the stream completes, fails or is interrupted.
171
171
  */
172
- export declare function fromEffectEventStream<const TInputSchema extends EffectSchemaLike, TStream extends Stream.Stream<EventObject, any, any>>(config: Omit<EffectStreamConfig<StreamItem<TStream>, StreamError<TStream>, SchemaOutput<TInputSchema>, StreamRequirements<TStream>>, 'stream' | 'schemas' | 'validator'> & EffectSchemaValidationConfig<{
172
+ export declare function createEffectEventStreamLogic<const TInputSchema extends EffectSchemaLike, TStream extends Stream.Stream<EventObject, any, any>>(config: Omit<EffectStreamConfig<StreamItem<TStream>, StreamError<TStream>, SchemaOutput<TInputSchema>, StreamRequirements<TStream>>, 'stream' | 'schemas' | 'validator'> & EffectSchemaValidationConfig<{
173
173
  input: TInputSchema;
174
174
  }> & {
175
175
  stream: TStream | ((args: EffectSourceArgs<SchemaOutput<TInputSchema>>) => TStream);
176
176
  }): EffectActorLogic<undefined, StreamError<TStream>, SchemaOutput<TInputSchema>, StreamRequirements<TStream>>;
177
- export declare function fromEffectEventStream<TEvent extends EventObject, TError = unknown, TInput = undefined, TRequirements = never>(config: EffectStreamConfig<TEvent, TError, TInput, TRequirements> & {
177
+ export declare function createEffectEventStreamLogic<TEvent extends EventObject, TError = unknown, TInput = undefined, TRequirements = never>(config: EffectStreamConfig<TEvent, TError, TInput, TRequirements> & {
178
178
  schemas?: undefined;
179
179
  }): EffectActorLogic<undefined, TError, TInput, TRequirements>;
180
- export declare function fromEffectEventStream<TEvent extends EventObject, TError = unknown, TInput = undefined, TRequirements = never>(stream: EffectStreamSource<TEvent, TError, TInput, TRequirements>): EffectActorLogic<undefined, TError, TInput, TRequirements>;
180
+ export declare function createEffectEventStreamLogic<TEvent extends EventObject, TError = unknown, TInput = undefined, TRequirements = never>(stream: EffectStreamSource<TEvent, TError, TInput, TRequirements>): EffectActorLogic<undefined, TError, TInput, TRequirements>;
181
181
  export {};
@@ -1,4 +1,4 @@
1
- export { fromEffect, fromEffectEventStream, fromEffectStream, type EffectActorLogic, type EffectLogicBrand, type EffectSnapshot, type EffectSource, type EffectSourceArgs, type EffectStreamSource, type EffectStreamActorLogic, type EffectStreamSnapshot } from "./fromEffect.js";
1
+ export { createEffectLogic, createEffectEventStreamLogic, createEffectStreamLogic, type EffectActorLogic, type EffectLogicBrand, type EffectSnapshot, type EffectSource, type EffectSourceArgs, type EffectStreamSource, type EffectStreamActorLogic, type EffectStreamSnapshot } from "./effectLogic.js";
2
2
  export { createEffectActor, type EffectActorOptions } from "./createEffectActor.js";
3
3
  export { EffectActor } from "./effectActor.js";
4
4
  export { ActorScope, withActorScope } from "./actorScope.js";
@@ -1,8 +1,8 @@
1
1
  import { Schema } from 'effect';
2
2
  import type { SetupSchemas, SetupStateSchema, SetupStateSchemas, StandardSchemaV1 } from 'xstate';
3
3
  /**
4
- * An Effect `Schema` accepted by this package. `setupEffect`, `fromEffect`,
5
- * `fromEffectStream` and `fromEffectEventStream` convert it to a Standard
4
+ * An Effect `Schema` accepted by this package. `setupEffect`, `createEffectLogic`,
5
+ * `createEffectStreamLogic` and `createEffectEventStreamLogic` convert it to a Standard
6
6
  * Schema, so XState infers the decoded `Schema.Type` without an explicit call
7
7
  * to `Schema.toStandardSchemaV1`.
8
8
  */
@@ -1,5 +1,5 @@
1
1
  import type { Effect, Scope } from 'effect';
2
- import type { EffectLogicBrand } from "./fromEffect.js";
2
+ import type { EffectLogicBrand } from "./effectLogic.js";
3
3
  import { effectActionBrand } from "./brands.js";
4
4
  import type { ActorScope } from "./actorScope.js";
5
5
  /** The `R` channel of an Effect type, or `never` for anything else. */
@@ -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. */
@@ -146,8 +146,7 @@ class EffectActor {
146
146
  }
147
147
  toJSON() {
148
148
  return {
149
- xstate$$type: 1,
150
- id: this.id
149
+ $actor: this.address
151
150
  };
152
151
  }
153
152
 
@@ -416,6 +415,7 @@ function withActorScope(effect) {
416
415
  }
417
416
 
418
417
  const XSTATE_TIMER = 'xstate.timer';
418
+ const XSTATE_INIT = 'xstate.init';
419
419
 
420
420
  /** Options for {@link createEffectActor}. */
421
421
 
@@ -453,10 +453,14 @@ function createEffectActor(logic, ...[options]) {
453
453
  Queue.offerUnsafe(mailbox, item);
454
454
  }
455
455
  };
456
- const timerKey = (source, id) => `${source.sessionId}:${id}`;
456
+ const timerKey = (source, timer) => `${source.sessionId}:${JSON.stringify(timer)}`;
457
457
  const inspectors = new Set();
458
458
  let rootAnnounced = false;
459
+ const clock = Context.get(context, Clock.Clock);
459
460
  const durable = createDurable(logic, {
461
+ // Timer deadlines are measured on the same clock the timers sleep
462
+ // on, so a restored timer resumes with its remaining delay.
463
+ now: () => clock.currentTimeMillisUnsafe(),
460
464
  executeAction: (action, _metadata, runtime) => {
461
465
  // Fire-and-forget: the action starts now and the loop continues.
462
466
  // A rejection reaches the machine as an execution error.
@@ -508,14 +512,14 @@ function createEffectActor(logic, ...[options]) {
508
512
  }
509
513
  source._emit(event);
510
514
  },
511
- scheduleTimer: (source, id, delay) => {
512
- const key = timerKey(source, id);
515
+ scheduleTimer: (source, timer, delay) => {
516
+ const key = timerKey(source, timer);
513
517
  timers.get(key)?.interruptUnsafe();
514
518
  const fiber = Fiber.runIn(runFork(Effect.andThen(Effect.sleep(Duration.millis(delay)), Effect.sync(() => {
515
519
  timers.delete(key);
516
520
  const timerEvent = {
517
521
  type: XSTATE_TIMER,
518
- id
522
+ ...timer
519
523
  };
520
524
  if (isRoot(source)) {
521
525
  offer(timerEvent);
@@ -525,8 +529,8 @@ function createEffectActor(logic, ...[options]) {
525
529
  }))), actorScope);
526
530
  timers.set(key, fiber);
527
531
  },
528
- cancelTimer: (source, id) => {
529
- const key = timerKey(source, id);
532
+ cancelTimer: (source, timer) => {
533
+ const key = timerKey(source, timer);
530
534
  timers.get(key)?.interruptUnsafe();
531
535
  timers.delete(key);
532
536
  },
@@ -590,10 +594,29 @@ function createEffectActor(logic, ...[options]) {
590
594
  closeEffectHost(host);
591
595
  };
592
596
 
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);
597
+ // The first transition (or the restore) runs here so the handle is
598
+ // ready when this Effect succeeds, and the initial actions start before
599
+ // any send. Restoring runs no entry actions; its effects resume
600
+ // machine children, restart running Effect children and re-arm pending
601
+ // timers.
602
+ let [snapshot, effects] = options?.snapshot ? durable.restore(options.snapshot) : durable.initialTransition(options?.input);
603
+ const initial = snapshot;
604
+ if (options?.snapshot && !isMachineSnapshot(initial) && initial.status === 'active') {
605
+ // A restored machine resumes through the effects above. Other logic
606
+ // (an Effect task or stream at the root) reattaches its active work by
607
+ // replaying its init event, as its restored `start` does under
608
+ // `createActor`.
609
+ const [started, startEffects] = durable.transition(snapshot, {
610
+ type: XSTATE_INIT,
611
+ input: 'input' in initial ? initial.input : undefined
612
+ });
613
+ snapshot = started;
614
+ effects = [...effects, ...startEffects];
615
+ }
596
616
  const root = durable.getActorRef(snapshot);
617
+ // Restored children are created by the snapshot, not spawned through
618
+ // the adapter; they find this host through their parent chain.
619
+ bindEffectHost(root, host);
597
620
  // The root exists from here on; later announcements are step
598
621
  // re-materializations, not new actors.
599
622
  rootAnnounced = true;
@@ -1,7 +1,7 @@
1
1
  import { Cause } from 'effect';
2
2
  import { Atom, AsyncResult } from 'effect/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';
3
+ import { c as createEffectActor, N as NotReadyError, t as taggedState } from './state-ea62b88e.js';
4
+ export { N as NotReadyError } from './state-ea62b88e.js';
5
5
  import 'xstate';
6
6
  import 'xstate/durable';
7
7
 
@@ -1,13 +1,13 @@
1
1
  import { Schema, Effect, Stream, Exit, Cause, Queue } from 'effect';
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';
2
+ import { createStepLogic, setup } from 'xstate';
3
+ import { s as startHostedEffect, r as relayToParent, E as EffectInterruptedError, A as ActorStoppedError, a as runHostedEffect } from './state-ea62b88e.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-ea62b88e.js';
5
5
  import { dual } from 'effect/Function';
6
6
  import 'xstate/durable';
7
7
 
8
8
  /**
9
- * An Effect `Schema` accepted by this package. `setupEffect`, `fromEffect`,
10
- * `fromEffectStream` and `fromEffectEventStream` convert it to a Standard
9
+ * An Effect `Schema` accepted by this package. `setupEffect`, `createEffectLogic`,
10
+ * `createEffectStreamLogic` and `createEffectEventStreamLogic` convert it to a Standard
11
11
  * Schema, so XState infers the decoded `Schema.Type` without an explicit call
12
12
  * to `Schema.toStandardSchemaV1`.
13
13
  */
@@ -74,7 +74,7 @@ function toStandardSetupStates(states) {
74
74
  }));
75
75
  }
76
76
 
77
- const EFFECT_INIT = '@xstate.init';
77
+ const EFFECT_INIT = 'xstate.init';
78
78
  const EFFECT_RESOLVE = 'xstate.effect.resolve';
79
79
  const EFFECT_REJECT = 'xstate.effect.reject';
80
80
  const EFFECT_NEXT = 'xstate.effect.next';
@@ -82,15 +82,15 @@ const EFFECT_COMPLETE = 'xstate.effect.complete';
82
82
  const effectLogicBrand = Symbol.for('@xstate/effect/logic');
83
83
 
84
84
  /**
85
- * Type-level marker attached to logic created by `fromEffect`,
86
- * `fromEffectStream` and `fromEffectEventStream`. It carries the logic's
85
+ * Type-level marker attached to logic created by `createEffectLogic`,
86
+ * `createEffectStreamLogic` and `createEffectEventStreamLogic`. It carries the logic's
87
87
  * failure type and its Effect service requirements, which is how
88
88
  * `RequirementsFrom` collects the `R` channel of `createEffectActor` without
89
89
  * inspecting the logic at runtime.
90
90
  */
91
91
 
92
92
  /**
93
- * Snapshot of a `fromEffect` actor. The actor holds no context of its own: it
93
+ * Snapshot of a `createEffectLogic` actor. The actor holds no context of its own: it
94
94
  * is `active` while the Effect runs, `done` with the Effect's success value as
95
95
  * `output`, or `error` with the Effect's failure, defect or
96
96
  * `EffectInterruptedError` as `error`.
@@ -104,26 +104,26 @@ const effectLogicBrand = Symbol.for('@xstate/effect/logic');
104
104
  */
105
105
 
106
106
  /**
107
- * What `fromEffect` accepts as its Effect: either an Effect value, or a
107
+ * What `createEffectLogic` accepts as its Effect: either an Effect value, or a
108
108
  * function of {@link EffectSourceArgs} returning one. The function form is
109
109
  * called once per actor start, so it sees that actor's input.
110
110
  */
111
111
 
112
112
  /**
113
- * Actor logic produced by `fromEffect` and `fromEffectEventStream`. It is
113
+ * Actor logic produced by `createEffectLogic` and `createEffectEventStreamLogic`. It is
114
114
  * ordinary XState actor logic carrying {@link EffectLogicBrand}, so a machine
115
115
  * can invoke or spawn it, and `createEffectActor` can collect its
116
116
  * requirements.
117
117
  */
118
118
 
119
119
  /**
120
- * Snapshot of a `fromEffectStream` actor. `context` holds the most recent
120
+ * Snapshot of a `createEffectStreamLogic` actor. `context` holds the most recent
121
121
  * stream item, or `undefined` before the first one. The actor reaches `done`
122
122
  * with no output when the stream completes, and `error` when it fails.
123
123
  */
124
124
 
125
125
  /**
126
- * Actor logic produced by `fromEffectStream`. Its snapshot is an
126
+ * Actor logic produced by `createEffectStreamLogic`. Its snapshot is an
127
127
  * {@link EffectStreamSnapshot}, so the parent reads the latest item from the
128
128
  * child's `context` rather than from events.
129
129
  */
@@ -174,7 +174,7 @@ function brandLogic(logic, error, requirements) {
174
174
  * Effect actor stops.
175
175
  */
176
176
 
177
- function fromEffect(sourceOrConfig) {
177
+ function createEffectLogic(sourceOrConfig) {
178
178
  const config = typeof sourceOrConfig === 'function' || Effect.isEffect(sourceOrConfig) ? {
179
179
  effect: sourceOrConfig
180
180
  } : sourceOrConfig;
@@ -187,7 +187,7 @@ function fromEffect(sourceOrConfig) {
187
187
  output: toStandardSchema(config.schemas.output)
188
188
  } : {})
189
189
  } : undefined;
190
- const logic = createLogic({
190
+ const logic = createStepLogic({
191
191
  id: config.id,
192
192
  validator: config.validator,
193
193
  schemas,
@@ -216,7 +216,7 @@ function fromEffect(sourceOrConfig) {
216
216
  return;
217
217
  }
218
218
  const effect = typeof source === 'function' ? source(createSourceArgs(input, self, system)) : source;
219
- enq.effect(() => startHostedEffect(self, Effect.scoped(effect), 'fromEffect', exit => {
219
+ enq.effect(() => startHostedEffect(self, Effect.scoped(effect), 'createEffectLogic', exit => {
220
220
  if (self.getSnapshot().status !== 'active') {
221
221
  return;
222
222
  }
@@ -238,7 +238,7 @@ function fromEffect(sourceOrConfig) {
238
238
  }
239
239
 
240
240
  /**
241
- * What `fromEffectStream` and `fromEffectEventStream` accept as their stream:
241
+ * What `createEffectStreamLogic` and `createEffectEventStreamLogic` accept as their stream:
242
242
  * either a Stream value, or a function of {@link EffectSourceArgs} returning
243
243
  * one. The function form is called once per actor start.
244
244
  */
@@ -263,10 +263,10 @@ function toLogicSchemas(schemas) {
263
263
  * completes, fails or is interrupted.
264
264
  */
265
265
 
266
- function fromEffectStream(sourceOrConfig) {
266
+ function createEffectStreamLogic(sourceOrConfig) {
267
267
  const config = resolveStreamConfig(sourceOrConfig);
268
268
  const stream = config.stream;
269
- const logic = createLogic({
269
+ const logic = createStepLogic({
270
270
  id: config.id,
271
271
  validator: config.validator,
272
272
  schemas: toLogicSchemas(config.schemas),
@@ -307,7 +307,7 @@ function fromEffectStream(sourceOrConfig) {
307
307
  });
308
308
  }
309
309
  }));
310
- enq.effect(() => startHostedEffect(self, Effect.scoped(consume), 'fromEffectStream', exit => {
310
+ enq.effect(() => startHostedEffect(self, Effect.scoped(consume), 'createEffectStreamLogic', exit => {
311
311
  if (self.getSnapshot().status !== 'active') {
312
312
  return;
313
313
  }
@@ -331,14 +331,14 @@ function fromEffectStream(sourceOrConfig) {
331
331
  * Creates actor logic that runs a Stream of events and relays each item to the
332
332
  * parent machine as an event, the way `fromEventObservable` does. The actor
333
333
  * has no output: it reaches `done` when the stream completes and `error` when
334
- * it fails. Accepts the same forms as `fromEffectStream`. Its resources close
334
+ * it fails. Accepts the same forms as `createEffectStreamLogic`. Its resources close
335
335
  * when the stream completes, fails or is interrupted.
336
336
  */
337
337
 
338
- function fromEffectEventStream(sourceOrConfig) {
338
+ function createEffectEventStreamLogic(sourceOrConfig) {
339
339
  const config = resolveStreamConfig(sourceOrConfig);
340
340
  const stream = config.stream;
341
- const logic = createLogic({
341
+ const logic = createStepLogic({
342
342
  id: config.id,
343
343
  validator: config.validator,
344
344
  schemas: toLogicSchemas(config.schemas),
@@ -367,7 +367,7 @@ function fromEffectEventStream(sourceOrConfig) {
367
367
  }
368
368
  const streamValue = typeof stream === 'function' ? stream(createSourceArgs(input, self, system)) : stream;
369
369
  const consume = Stream.runForEach(streamValue, value => Effect.sync(() => relayToParent(self, value)));
370
- enq.effect(() => startHostedEffect(self, Effect.scoped(consume), 'fromEffectEventStream', exit => {
370
+ enq.effect(() => startHostedEffect(self, Effect.scoped(consume), 'createEffectEventStreamLogic', exit => {
371
371
  if (self.getSnapshot().status !== 'active') {
372
372
  return;
373
373
  }
@@ -710,4 +710,4 @@ function setupEffect(config = {}) {
710
710
  }));
711
711
  }
712
712
 
713
- export { deadLetters, emitted, fromEffect, fromEffectEventStream, fromEffectStream, inspect, join, send, setupEffect, snapshots, waitFor };
713
+ export { createEffectEventStreamLogic, createEffectLogic, createEffectStreamLogic, deadLetters, emitted, inspect, join, send, setupEffect, snapshots, waitFor };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xstate/effect",
3
- "version": "0.1.0-alpha.6",
3
+ "version": "1.0.0-alpha.8",
4
4
  "type": "module",
5
5
  "description": "Effect integration for XState",
6
6
  "keywords": [
@@ -30,11 +30,12 @@
30
30
  },
31
31
  "peerDependencies": {
32
32
  "effect": "^4.0.0",
33
- "xstate": "^6.0.0-alpha.63"
33
+ "xstate": "^6.0.0-alpha.68"
34
34
  },
35
35
  "devDependencies": {
36
36
  "effect": "4.0.0",
37
- "xstate": "^6.0.0-alpha.63"
37
+ "fast-check": "4.10.2",
38
+ "xstate": "^6.0.0-alpha.68"
38
39
  },
39
40
  "preconstruct": {
40
41
  "entrypoints": [