@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 +19 -13
- package/dist/declarations/src/createEffectActor.d.ts +16 -6
- package/dist/declarations/src/effectActor.d.ts +1 -2
- package/dist/declarations/src/{fromEffect.d.ts → effectLogic.d.ts} +21 -21
- package/dist/declarations/src/index.d.ts +1 -1
- package/dist/declarations/src/schema.d.ts +2 -2
- package/dist/declarations/src/types.d.ts +1 -1
- package/dist/{state-14a1fecf.js → state-ea62b88e.js} +36 -13
- package/dist/xstate-effect-atom.js +2 -2
- package/dist/xstate-effect.js +25 -25
- package/package.json +4 -3
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
<!--
|
|
188
|
+
<!-- createEffectLogic, createEffectStreamLogic, createEffectEventStreamLogic from src/createEffectLogic.ts -->
|
|
187
189
|
|
|
188
190
|
| Function | Behavior |
|
|
189
191
|
| --- | --- |
|
|
190
|
-
| `
|
|
191
|
-
| `
|
|
192
|
-
| `
|
|
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 {
|
|
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 =
|
|
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 `
|
|
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;
|
|
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/
|
|
269
|
+
<!-- span names and attributes from src/internal.ts and src/createEffectLogic.ts -->
|
|
264
270
|
|
|
265
|
-
Hosted work uses spans named `
|
|
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> =
|
|
6
|
+
export type EffectActorOptions<TLogic extends AnyActorLogic> = {
|
|
7
7
|
readonly input?: InputFrom<TLogic>;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
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 `
|
|
8
|
-
* `
|
|
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 `
|
|
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 `
|
|
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 `
|
|
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 `
|
|
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 `
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
128
|
-
export declare function
|
|
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 `
|
|
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
|
|
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
|
|
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
|
|
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 `
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
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`, `
|
|
5
|
-
* `
|
|
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 "./
|
|
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
|
-
|
|
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,
|
|
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,
|
|
512
|
-
const key = timerKey(source,
|
|
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
|
-
|
|
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,
|
|
529
|
-
const key = timerKey(source,
|
|
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
|
|
594
|
-
// Effect succeeds, and the initial actions start before
|
|
595
|
-
|
|
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-
|
|
4
|
-
export { N as NotReadyError } from './state-
|
|
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
|
|
package/dist/xstate-effect.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { Schema, Effect, Stream, Exit, Cause, Queue } from 'effect';
|
|
2
|
-
import {
|
|
3
|
-
import { s as startHostedEffect, r as relayToParent, E as EffectInterruptedError, A as ActorStoppedError, a as runHostedEffect } from './state-
|
|
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-
|
|
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`, `
|
|
10
|
-
* `
|
|
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 = '
|
|
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 `
|
|
86
|
-
* `
|
|
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 `
|
|
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 `
|
|
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 `
|
|
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 `
|
|
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 `
|
|
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
|
|
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 =
|
|
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), '
|
|
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 `
|
|
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
|
|
266
|
+
function createEffectStreamLogic(sourceOrConfig) {
|
|
267
267
|
const config = resolveStreamConfig(sourceOrConfig);
|
|
268
268
|
const stream = config.stream;
|
|
269
|
-
const logic =
|
|
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), '
|
|
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 `
|
|
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
|
|
338
|
+
function createEffectEventStreamLogic(sourceOrConfig) {
|
|
339
339
|
const config = resolveStreamConfig(sourceOrConfig);
|
|
340
340
|
const stream = config.stream;
|
|
341
|
-
const logic =
|
|
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), '
|
|
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 {
|
|
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": "
|
|
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.
|
|
33
|
+
"xstate": "^6.0.0-alpha.68"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"effect": "4.0.0",
|
|
37
|
-
"
|
|
37
|
+
"fast-check": "4.10.2",
|
|
38
|
+
"xstate": "^6.0.0-alpha.68"
|
|
38
39
|
},
|
|
39
40
|
"preconstruct": {
|
|
40
41
|
"entrypoints": [
|