effect-machine 0.10.0 → 0.12.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.
- package/README.md +65 -62
- package/dist/actor.d.ts +35 -97
- package/dist/actor.js +69 -98
- package/dist/cluster/adapters/in-memory.d.ts +28 -0
- package/dist/cluster/adapters/in-memory.js +79 -0
- package/dist/cluster/entity-actor-ref.d.ts +56 -0
- package/dist/cluster/entity-actor-ref.js +33 -0
- package/dist/cluster/entity-machine.d.ts +31 -49
- package/dist/cluster/entity-machine.js +167 -52
- package/dist/cluster/index.d.ts +5 -2
- package/dist/cluster/index.js +4 -1
- package/dist/cluster/persistence.d.ts +49 -0
- package/dist/cluster/persistence.js +18 -0
- package/dist/cluster/to-entity.d.ts +9 -3
- package/dist/cluster/to-entity.js +16 -4
- package/dist/errors.d.ts +12 -1
- package/dist/errors.js +8 -1
- package/dist/index.d.ts +5 -8
- package/dist/index.js +1 -6
- package/dist/internal/brands.d.ts +14 -1
- package/dist/internal/runtime.d.ts +67 -0
- package/dist/internal/runtime.js +248 -0
- package/dist/internal/transition.d.ts +6 -1
- package/dist/internal/transition.js +16 -4
- package/dist/internal/utils.d.ts +42 -6
- package/dist/internal/utils.js +28 -6
- package/dist/machine.d.ts +55 -46
- package/dist/machine.js +74 -13
- package/dist/schema.d.ts +35 -34
- package/dist/schema.js +32 -3
- package/dist/testing.js +4 -2
- package/package.json +4 -4
- package/v3/dist/actor.d.ts +29 -96
- package/v3/dist/actor.js +52 -97
- package/v3/dist/cluster/adapters/in-memory.d.ts +15 -0
- package/v3/dist/cluster/adapters/in-memory.js +62 -0
- package/v3/dist/cluster/entity-actor-ref.d.ts +49 -0
- package/v3/dist/cluster/entity-actor-ref.js +19 -0
- package/v3/dist/cluster/entity-machine.d.ts +34 -49
- package/v3/dist/cluster/entity-machine.js +134 -50
- package/v3/dist/cluster/index.d.ts +5 -2
- package/v3/dist/cluster/index.js +4 -1
- package/v3/dist/cluster/persistence.d.ts +48 -0
- package/v3/dist/cluster/persistence.js +14 -0
- package/v3/dist/cluster/to-entity.d.ts +5 -2
- package/v3/dist/cluster/to-entity.js +12 -4
- package/v3/dist/errors.d.ts +16 -1
- package/v3/dist/errors.js +8 -1
- package/v3/dist/index.d.ts +5 -8
- package/v3/dist/index.js +1 -6
- package/v3/dist/internal/brands.d.ts +15 -1
- package/v3/dist/internal/runtime.d.ts +65 -0
- package/v3/dist/internal/runtime.js +236 -0
- package/v3/dist/internal/transition.d.ts +5 -0
- package/v3/dist/internal/transition.js +15 -3
- package/v3/dist/internal/utils.d.ts +42 -6
- package/v3/dist/internal/utils.js +28 -6
- package/v3/dist/machine.d.ts +48 -46
- package/v3/dist/machine.js +71 -13
- package/v3/dist/schema.d.ts +35 -34
- package/v3/dist/schema.js +29 -3
- package/dist/persistence/adapter.d.ts +0 -135
- package/dist/persistence/adapter.js +0 -25
- package/dist/persistence/adapters/in-memory.d.ts +0 -32
- package/dist/persistence/adapters/in-memory.js +0 -174
- package/dist/persistence/index.d.ts +0 -5
- package/dist/persistence/index.js +0 -5
- package/dist/persistence/persistent-actor.d.ts +0 -50
- package/dist/persistence/persistent-actor.js +0 -404
- package/dist/persistence/persistent-machine.d.ts +0 -105
- package/dist/persistence/persistent-machine.js +0 -22
- package/v3/dist/persistence/adapter.d.ts +0 -138
- package/v3/dist/persistence/adapter.js +0 -25
- package/v3/dist/persistence/adapters/in-memory.d.ts +0 -32
- package/v3/dist/persistence/adapters/in-memory.js +0 -174
- package/v3/dist/persistence/index.d.ts +0 -5
- package/v3/dist/persistence/index.js +0 -5
- package/v3/dist/persistence/persistent-actor.d.ts +0 -50
- package/v3/dist/persistence/persistent-actor.js +0 -404
- package/v3/dist/persistence/persistent-machine.d.ts +0 -105
- package/v3/dist/persistence/persistent-machine.js +0 -22
package/dist/machine.js
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
import { __exportAll } from "./_virtual/_rolldown/runtime.js";
|
|
2
2
|
import { Inspector } from "./inspection.js";
|
|
3
|
-
import { getTag } from "./internal/utils.js";
|
|
3
|
+
import { getTag, makeDeferReply, makeReply, stubSystem } from "./internal/utils.js";
|
|
4
4
|
import { ProvisionValidationError, SlotProvisionError } from "./errors.js";
|
|
5
|
-
import { persist } from "./persistence/persistent-machine.js";
|
|
6
5
|
import { emitWithTimestamp } from "./internal/inspection.js";
|
|
7
6
|
import { MachineContextTag } from "./slot.js";
|
|
8
|
-
import { findTransitions, invalidateIndex } from "./internal/transition.js";
|
|
7
|
+
import { findTransitions, invalidateIndex, resolveTransition, runTransitionHandler, shouldPostpone } from "./internal/transition.js";
|
|
9
8
|
import { createActor } from "./actor.js";
|
|
10
9
|
import { Cause, Effect, Exit, Option, Scope } from "effect";
|
|
11
10
|
//#region src/machine.ts
|
|
12
11
|
var machine_exports = /* @__PURE__ */ __exportAll({
|
|
13
12
|
BuiltMachine: () => BuiltMachine,
|
|
14
13
|
Machine: () => Machine,
|
|
14
|
+
deferReply: () => deferReply,
|
|
15
15
|
findTransitions: () => findTransitions,
|
|
16
16
|
make: () => make,
|
|
17
|
+
replay: () => replay,
|
|
18
|
+
reply: () => reply,
|
|
17
19
|
spawn: () => spawn
|
|
18
20
|
});
|
|
19
21
|
const emitTaskInspection = (input) => Effect.flatMap(Effect.serviceOption(Inspector), (inspector) => Option.isNone(inspector) ? Effect.void : emitWithTimestamp(inspector.value, (timestamp) => ({
|
|
@@ -42,9 +44,6 @@ var BuiltMachine = class {
|
|
|
42
44
|
get initial() {
|
|
43
45
|
return this._inner.initial;
|
|
44
46
|
}
|
|
45
|
-
persist(config) {
|
|
46
|
-
return this._inner.persist(config);
|
|
47
|
-
}
|
|
48
47
|
};
|
|
49
48
|
/**
|
|
50
49
|
* Machine definition with fluent builder API.
|
|
@@ -72,6 +71,7 @@ var Machine = class Machine {
|
|
|
72
71
|
/** @internal */ _slots;
|
|
73
72
|
stateSchema;
|
|
74
73
|
eventSchema;
|
|
74
|
+
/** @internal */ _replySchemas;
|
|
75
75
|
/**
|
|
76
76
|
* Context tag for accessing machine state/event/self in slot handlers.
|
|
77
77
|
* Uses shared module-level tag for all machines.
|
|
@@ -98,6 +98,9 @@ var Machine = class Machine {
|
|
|
98
98
|
get effectsSchema() {
|
|
99
99
|
return this._effectsSchema;
|
|
100
100
|
}
|
|
101
|
+
get replySchemas() {
|
|
102
|
+
return this._replySchemas;
|
|
103
|
+
}
|
|
101
104
|
/** @internal */
|
|
102
105
|
constructor(initial, stateSchema, eventSchema, guardsSchema, effectsSchema) {
|
|
103
106
|
this.initial = initial;
|
|
@@ -108,6 +111,7 @@ var Machine = class Machine {
|
|
|
108
111
|
this._postponeRules = [];
|
|
109
112
|
this._guardsSchema = guardsSchema;
|
|
110
113
|
this._effectsSchema = effectsSchema;
|
|
114
|
+
this._replySchemas = eventSchema?._replySchemas ?? /* @__PURE__ */ new Map();
|
|
111
115
|
this._guardHandlers = /* @__PURE__ */ new Map();
|
|
112
116
|
this._effectHandlers = /* @__PURE__ */ new Map();
|
|
113
117
|
this.stateSchema = stateSchema;
|
|
@@ -378,6 +382,7 @@ var Machine = class Machine {
|
|
|
378
382
|
result._spawnEffects = [...this._spawnEffects];
|
|
379
383
|
result._backgroundEffects = [...this._backgroundEffects];
|
|
380
384
|
result._postponeRules = [...this._postponeRules];
|
|
385
|
+
result._replySchemas = this._replySchemas;
|
|
381
386
|
const anyHandlers = handlers;
|
|
382
387
|
if (this._guardsSchema !== void 0) for (const name of Object.keys(this._guardsSchema.definitions)) result._guardHandlers.set(name, anyHandlers[name]);
|
|
383
388
|
if (this._effectsSchema !== void 0) for (const name of Object.keys(this._effectsSchema.definitions)) result._effectHandlers.set(name, anyHandlers[name]);
|
|
@@ -385,10 +390,6 @@ var Machine = class Machine {
|
|
|
385
390
|
}
|
|
386
391
|
return new BuiltMachine(this);
|
|
387
392
|
}
|
|
388
|
-
/** @internal Persist from raw Machine — prefer BuiltMachine.persist() */
|
|
389
|
-
persist(config) {
|
|
390
|
-
return persist(config)(this);
|
|
391
|
-
}
|
|
392
393
|
static make(config) {
|
|
393
394
|
return new Machine(config.initial, config.state, config.event, config.guards, config.effects);
|
|
394
395
|
}
|
|
@@ -408,11 +409,71 @@ var TransitionScope = class {
|
|
|
408
409
|
}
|
|
409
410
|
};
|
|
410
411
|
const make = Machine.make;
|
|
411
|
-
|
|
412
|
-
|
|
412
|
+
/**
|
|
413
|
+
* Spawn an actor from a built machine.
|
|
414
|
+
*
|
|
415
|
+
* Options:
|
|
416
|
+
* - `id` — custom actor ID (default: random)
|
|
417
|
+
* - `hydrate` — restore from a previously-saved state snapshot.
|
|
418
|
+
* The actor starts in the hydrated state and re-runs spawn effects
|
|
419
|
+
* for that state (timers, scoped resources, etc.). Transition history
|
|
420
|
+
* is not replayed — only the current state's entry effects run.
|
|
421
|
+
*
|
|
422
|
+
* Persistence is composed in userland by observing `actor.changes`
|
|
423
|
+
* and saving snapshots to your own storage.
|
|
424
|
+
*/
|
|
425
|
+
const spawn = Effect.fn("effect-machine.spawn")(function* (built, idOrOptions) {
|
|
426
|
+
const opts = typeof idOrOptions === "string" ? { id: idOrOptions } : idOrOptions;
|
|
427
|
+
const actor = yield* createActor(opts?.id ?? `actor-${Math.random().toString(36).slice(2)}`, built._inner, { initialState: opts?.hydrate });
|
|
413
428
|
const maybeScope = yield* Effect.serviceOption(Scope.Scope);
|
|
414
429
|
if (Option.isSome(maybeScope)) yield* Scope.addFinalizer(maybeScope.value, actor.stop);
|
|
415
430
|
return actor;
|
|
416
431
|
});
|
|
432
|
+
const replay = Effect.fn("effect-machine.replay")(function* (built, events, options) {
|
|
433
|
+
const machine = built._inner;
|
|
434
|
+
let state = options?.from ?? machine.initial;
|
|
435
|
+
const hasPostponeRules = machine.postponeRules.length > 0;
|
|
436
|
+
const postponed = [];
|
|
437
|
+
const dummySend = Effect.fn("effect-machine.replay.send")((_event) => Effect.void);
|
|
438
|
+
const self = {
|
|
439
|
+
send: dummySend,
|
|
440
|
+
cast: dummySend,
|
|
441
|
+
spawn: () => Effect.die("spawn not supported in replay"),
|
|
442
|
+
reply: () => Effect.succeed(false)
|
|
443
|
+
};
|
|
444
|
+
for (const event of events) {
|
|
445
|
+
if (machine.finalStates.has(state._tag)) break;
|
|
446
|
+
if (hasPostponeRules && shouldPostpone(machine, state._tag, event._tag)) {
|
|
447
|
+
postponed.push(event);
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
const transition = resolveTransition(machine, state, event);
|
|
451
|
+
if (transition !== void 0) {
|
|
452
|
+
const result = yield* runTransitionHandler(machine, transition, state, event, self, stubSystem, "replay");
|
|
453
|
+
const previousTag = state._tag;
|
|
454
|
+
state = result.newState;
|
|
455
|
+
if ((state._tag !== previousTag || transition.reenter === true) && postponed.length > 0) {
|
|
456
|
+
let drainTag = previousTag;
|
|
457
|
+
while (state._tag !== drainTag && postponed.length > 0) {
|
|
458
|
+
if (machine.finalStates.has(state._tag)) break;
|
|
459
|
+
drainTag = state._tag;
|
|
460
|
+
const drained = postponed.splice(0);
|
|
461
|
+
for (const postponedEvent of drained) {
|
|
462
|
+
if (machine.finalStates.has(state._tag)) break;
|
|
463
|
+
if (shouldPostpone(machine, state._tag, postponedEvent._tag)) {
|
|
464
|
+
postponed.push(postponedEvent);
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
const pTransition = resolveTransition(machine, state, postponedEvent);
|
|
468
|
+
if (pTransition !== void 0) state = (yield* runTransitionHandler(machine, pTransition, state, postponedEvent, self, stubSystem, "replay")).newState;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
return state;
|
|
475
|
+
});
|
|
476
|
+
const reply = makeReply;
|
|
477
|
+
const deferReply = makeDeferReply;
|
|
417
478
|
//#endregion
|
|
418
|
-
export { BuiltMachine, Machine, findTransitions, machine_exports, make, spawn };
|
|
479
|
+
export { BuiltMachine, Machine, deferReply, findTransitions, machine_exports, make, replay, reply, spawn };
|
package/dist/schema.d.ts
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
|
-
import { FullEventBrand, FullStateBrand } from "./internal/brands.js";
|
|
1
|
+
import { FullEventBrand, FullStateBrand, ReplyTypeBrand } from "./internal/brands.js";
|
|
2
2
|
import { Schema } from "effect";
|
|
3
3
|
|
|
4
4
|
//#region src/schema.d.ts
|
|
5
|
+
declare const ReplySchemaSymbol: unique symbol;
|
|
6
|
+
type ReplySchemaSymbol = typeof ReplySchemaSymbol;
|
|
7
|
+
/**
|
|
8
|
+
* Fields annotated with a reply schema.
|
|
9
|
+
* Structurally identical to Schema.Struct.Fields at runtime,
|
|
10
|
+
* but carries the reply schema type at compile time.
|
|
11
|
+
*/
|
|
12
|
+
type ReplyFields<F extends Schema.Struct.Fields, RS extends Schema.Schema<unknown>> = F & {
|
|
13
|
+
readonly [ReplySchemaSymbol]: RS;
|
|
14
|
+
};
|
|
5
15
|
/**
|
|
6
16
|
* Extract the TypeScript type from a TaggedStruct schema
|
|
7
17
|
*/
|
|
@@ -12,20 +22,23 @@ type TaggedStructType<Tag extends string, Fields extends Schema.Struct.Fields> =
|
|
|
12
22
|
type VariantSchemas<D extends Record<string, Schema.Struct.Fields>> = { readonly [K in keyof D & string]: Schema.TaggedStruct<K, D[K]> };
|
|
13
23
|
/**
|
|
14
24
|
* Build union type from variant schemas.
|
|
15
|
-
*
|
|
25
|
+
* Reply-bearing variants carry ReplyTypeBrand<R> for ask() inference.
|
|
16
26
|
*/
|
|
17
|
-
type VariantsUnion<D extends Record<string, Schema.Struct.Fields>> = { [K in keyof D & string]: TaggedStructType<K, D[K]>
|
|
27
|
+
type VariantsUnion<D extends Record<string, Schema.Struct.Fields>> = { [K in keyof D & string]: TaggedStructType<K, D[K]> & (D[K] extends {
|
|
28
|
+
readonly [ReplySchemaSymbol]: Schema.Schema<infer R>;
|
|
29
|
+
} ? ReplyTypeBrand<R> : unknown) }[keyof D & string];
|
|
18
30
|
/**
|
|
19
|
-
* Check if fields are empty (no required properties)
|
|
31
|
+
* Check if fields are empty (no required string properties).
|
|
32
|
+
* Symbol keys (like ReplySchemaSymbol) are metadata, not payload fields.
|
|
20
33
|
*/
|
|
21
|
-
type IsEmptyFields<Fields extends Schema.Struct.Fields> = keyof Fields extends never ? true : false;
|
|
34
|
+
type IsEmptyFields<Fields extends Schema.Struct.Fields> = string & keyof Fields extends never ? true : false;
|
|
22
35
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* Non-empty structs require args: `State.Loading({ url })`
|
|
26
|
-
*
|
|
27
|
-
* Each variant also has a `derive` method for constructing from a source object.
|
|
36
|
+
* Resolve the reply brand for a variant's fields.
|
|
37
|
+
* If fields carry ReplySchemaSymbol, adds ReplyTypeBrand<R>.
|
|
28
38
|
*/
|
|
39
|
+
type VariantReplyBrand<Fields extends Schema.Struct.Fields> = Fields extends {
|
|
40
|
+
readonly [ReplySchemaSymbol]: Schema.Schema<infer R>;
|
|
41
|
+
} ? ReplyTypeBrand<R> : unknown;
|
|
29
42
|
/**
|
|
30
43
|
* Constructor functions for each variant.
|
|
31
44
|
* Empty structs: plain values with `_tag`: `State.Idle`
|
|
@@ -33,10 +46,11 @@ type IsEmptyFields<Fields extends Schema.Struct.Fields> = keyof Fields extends n
|
|
|
33
46
|
*
|
|
34
47
|
* Each variant also has a `derive` method for constructing from a source object.
|
|
35
48
|
* The source type uses `object` to accept branded state types without index signature issues.
|
|
49
|
+
* Reply-bearing variants carry ReplyTypeBrand<R> for ask() type inference.
|
|
36
50
|
*/
|
|
37
|
-
type VariantConstructors<D extends Record<string, Schema.Struct.Fields>, Brand> = { readonly [K in keyof D & string]: IsEmptyFields<D[K]> extends true ? TaggedStructType<K, D[K]> & Brand & {
|
|
51
|
+
type VariantConstructors<D extends Record<string, Schema.Struct.Fields>, Brand> = { readonly [K in keyof D & string]: IsEmptyFields<D[K]> extends true ? TaggedStructType<K, D[K]> & Brand & VariantReplyBrand<D[K]> & {
|
|
38
52
|
readonly derive: (source: object) => TaggedStructType<K, D[K]> & Brand;
|
|
39
|
-
} : ((args: Schema.Struct.Type<D[K]>) => TaggedStructType<K, D[K]> & Brand) & {
|
|
53
|
+
} : ((args: Schema.Struct.Type<D[K]>) => TaggedStructType<K, D[K]> & Brand & VariantReplyBrand<D[K]>) & {
|
|
40
54
|
readonly derive: (source: object, partial?: Partial<Schema.Struct.Type<D[K]>>) => TaggedStructType<K, D[K]> & Brand;
|
|
41
55
|
readonly _tag: K;
|
|
42
56
|
} };
|
|
@@ -67,6 +81,11 @@ interface MachineSchemaBase<D extends Record<string, Schema.Struct.Fields>, Bran
|
|
|
67
81
|
<R>(cases: MatchCases<D, R>): (value: VariantsUnion<D> & Brand) => R;
|
|
68
82
|
<R>(value: VariantsUnion<D> & Brand, cases: MatchCases<D, R>): R;
|
|
69
83
|
};
|
|
84
|
+
/**
|
|
85
|
+
* Reply schemas per variant tag. Only populated for event schemas
|
|
86
|
+
* with variants defined via `Event.reply()`.
|
|
87
|
+
*/
|
|
88
|
+
readonly _replySchemas: ReadonlyMap<string, Schema.Decoder<unknown>>;
|
|
70
89
|
}
|
|
71
90
|
/**
|
|
72
91
|
* Schema-first state definition that provides:
|
|
@@ -116,26 +135,8 @@ type MachineEventSchema<D extends Record<string, Schema.Struct.Fields>> = Schema
|
|
|
116
135
|
* ```
|
|
117
136
|
*/
|
|
118
137
|
declare const State: <const D extends Record<string, Schema.Struct.Fields>>(definition: D) => MachineStateSchema<D>;
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
* The schema's definition type D creates a unique brand, preventing
|
|
123
|
-
* accidental use of constructors from different event schemas
|
|
124
|
-
* (unless they have identical definitions).
|
|
125
|
-
*
|
|
126
|
-
* @example
|
|
127
|
-
* ```ts
|
|
128
|
-
* const OrderEvent = MachineSchema.Event({
|
|
129
|
-
* Ship: { trackingId: Schema.String },
|
|
130
|
-
* Cancel: {},
|
|
131
|
-
* })
|
|
132
|
-
*
|
|
133
|
-
* type OrderEvent = typeof OrderEvent.Type
|
|
134
|
-
*
|
|
135
|
-
* // Construct
|
|
136
|
-
* const e = OrderEvent.Ship({ trackingId: "abc" })
|
|
137
|
-
* ```
|
|
138
|
-
*/
|
|
139
|
-
declare const Event: <const D extends Record<string, Schema.Struct.Fields>>(definition: D) => MachineEventSchema<D>;
|
|
138
|
+
declare const Event: (<const D extends Record<string, Schema.Struct.Fields>>(definition: D) => MachineEventSchema<D>) & {
|
|
139
|
+
reply: <F extends Schema.Struct.Fields, RS extends Schema.Schema<unknown>>(fields: F, replySchema: RS) => ReplyFields<F, RS>;
|
|
140
|
+
};
|
|
140
141
|
//#endregion
|
|
141
|
-
export { Event, MachineEventSchema, MachineStateSchema, State, VariantsUnion };
|
|
142
|
+
export { Event, MachineEventSchema, MachineStateSchema, ReplyFields, ReplySchemaSymbol, State, VariantsUnion };
|
package/dist/schema.js
CHANGED
|
@@ -38,6 +38,7 @@ import { Schema } from "effect";
|
|
|
38
38
|
*
|
|
39
39
|
* @module
|
|
40
40
|
*/
|
|
41
|
+
const ReplySchemaSymbol = Symbol.for("effect-machine/ReplySchema");
|
|
41
42
|
/**
|
|
42
43
|
* Build a schema-first definition from a record of tag -> fields
|
|
43
44
|
*/
|
|
@@ -45,9 +46,14 @@ const RESERVED_DERIVE_KEYS = new Set(["_tag"]);
|
|
|
45
46
|
const buildMachineSchema = (definition) => {
|
|
46
47
|
const variants = {};
|
|
47
48
|
const constructors = {};
|
|
49
|
+
const replySchemas = /* @__PURE__ */ new Map();
|
|
48
50
|
for (const tag of Object.keys(definition)) {
|
|
49
51
|
const fields = definition[tag];
|
|
50
52
|
if (fields === void 0) continue;
|
|
53
|
+
if (ReplySchemaSymbol in fields) {
|
|
54
|
+
const rs = fields[ReplySchemaSymbol];
|
|
55
|
+
if (rs !== void 0) replySchemas.set(tag, rs);
|
|
56
|
+
}
|
|
51
57
|
variants[tag] = Schema.TaggedStruct(tag, fields);
|
|
52
58
|
const fieldNames = new Set(Object.keys(fields));
|
|
53
59
|
if (fieldNames.size > 0) {
|
|
@@ -94,6 +100,7 @@ const buildMachineSchema = (definition) => {
|
|
|
94
100
|
variants,
|
|
95
101
|
constructors,
|
|
96
102
|
_definition: definition,
|
|
103
|
+
replySchemas,
|
|
97
104
|
$is,
|
|
98
105
|
$match
|
|
99
106
|
};
|
|
@@ -103,10 +110,11 @@ const buildMachineSchema = (definition) => {
|
|
|
103
110
|
* Builds the schema object with variants, constructors, $is, and $match.
|
|
104
111
|
*/
|
|
105
112
|
const createMachineSchema = (definition) => {
|
|
106
|
-
const { schema, variants, constructors, _definition, $is, $match } = buildMachineSchema(definition);
|
|
113
|
+
const { schema, variants, constructors, _definition, replySchemas, $is, $match } = buildMachineSchema(definition);
|
|
107
114
|
return Object.assign(Object.create(schema), {
|
|
108
115
|
variants,
|
|
109
116
|
_definition,
|
|
117
|
+
_replySchemas: replySchemas,
|
|
110
118
|
$is,
|
|
111
119
|
$match,
|
|
112
120
|
...constructors
|
|
@@ -149,19 +157,40 @@ const State = (definition) => createMachineSchema(definition);
|
|
|
149
157
|
* accidental use of constructors from different event schemas
|
|
150
158
|
* (unless they have identical definitions).
|
|
151
159
|
*
|
|
160
|
+
* Use `Event.reply(fields, replySchema)` to define events that support
|
|
161
|
+
* typed `ask()` replies.
|
|
162
|
+
*
|
|
152
163
|
* @example
|
|
153
164
|
* ```ts
|
|
154
|
-
* const OrderEvent =
|
|
165
|
+
* const OrderEvent = Event({
|
|
155
166
|
* Ship: { trackingId: Schema.String },
|
|
156
167
|
* Cancel: {},
|
|
168
|
+
* GetTotal: Event.reply({}, Schema.Number),
|
|
157
169
|
* })
|
|
158
170
|
*
|
|
159
171
|
* type OrderEvent = typeof OrderEvent.Type
|
|
160
172
|
*
|
|
161
173
|
* // Construct
|
|
162
174
|
* const e = OrderEvent.Ship({ trackingId: "abc" })
|
|
175
|
+
*
|
|
176
|
+
* // Typed ask
|
|
177
|
+
* const total = yield* actor.ask(OrderEvent.GetTotal) // number
|
|
163
178
|
* ```
|
|
164
179
|
*/
|
|
165
|
-
const
|
|
180
|
+
const EventImpl = (definition) => createMachineSchema(definition);
|
|
181
|
+
/**
|
|
182
|
+
* Annotate event fields with a reply schema.
|
|
183
|
+
* Events defined with `Event.reply(fields, replySchema)` enable typed `ask()`.
|
|
184
|
+
*/
|
|
185
|
+
const replyFieldsFn = (fields, replySchema) => {
|
|
186
|
+
const annotated = { ...fields };
|
|
187
|
+
Object.defineProperty(annotated, ReplySchemaSymbol, {
|
|
188
|
+
value: replySchema,
|
|
189
|
+
enumerable: false,
|
|
190
|
+
writable: false
|
|
191
|
+
});
|
|
192
|
+
return annotated;
|
|
193
|
+
};
|
|
194
|
+
const Event = Object.assign(EventImpl, { reply: replyFieldsFn });
|
|
166
195
|
//#endregion
|
|
167
196
|
export { Event, State };
|
package/dist/testing.js
CHANGED
|
@@ -30,7 +30,8 @@ const simulate = Effect.fn("effect-machine.simulate")(function* (input, events)
|
|
|
30
30
|
const dummySelf = {
|
|
31
31
|
send: dummySend,
|
|
32
32
|
cast: dummySend,
|
|
33
|
-
spawn: () => Effect.die("spawn not supported in simulation")
|
|
33
|
+
spawn: () => Effect.die("spawn not supported in simulation"),
|
|
34
|
+
reply: () => Effect.succeed(false)
|
|
34
35
|
};
|
|
35
36
|
let currentState = machine.initial;
|
|
36
37
|
const states = [currentState];
|
|
@@ -143,7 +144,8 @@ const createTestHarness = Effect.fn("effect-machine.createTestHarness")(function
|
|
|
143
144
|
const dummySelf = {
|
|
144
145
|
send: dummySend,
|
|
145
146
|
cast: dummySend,
|
|
146
|
-
spawn: () => Effect.die("spawn not supported in test harness")
|
|
147
|
+
spawn: () => Effect.die("spawn not supported in test harness"),
|
|
148
|
+
reply: () => Effect.succeed(false)
|
|
147
149
|
};
|
|
148
150
|
const stateRef = yield* SubscriptionRef.make(machine.initial);
|
|
149
151
|
const hasPostponeRules = machine.postponeRules.length > 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "effect-machine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/cevr/effect-machine.git"
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"release": "bun run build && changeset publish"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"effect": "4.0.0-beta.
|
|
59
|
+
"effect": "4.0.0-beta.42"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"@changesets/changelog-github": "^0.6.0",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"@effect/language-service": "^0.82.0",
|
|
65
65
|
"@types/bun": "1.3.11",
|
|
66
66
|
"concurrently": "^9.2.1",
|
|
67
|
-
"effect-bun-test": "0.
|
|
67
|
+
"effect-bun-test": "0.3.0",
|
|
68
68
|
"effect-v3": "npm:effect@^3.21.0",
|
|
69
69
|
"lefthook": "^2.1.4",
|
|
70
70
|
"oxfmt": "^0.41.0",
|
|
@@ -81,6 +81,6 @@
|
|
|
81
81
|
}
|
|
82
82
|
},
|
|
83
83
|
"overrides": {
|
|
84
|
-
"effect": "4.0.0-beta.
|
|
84
|
+
"effect": "4.0.0-beta.42"
|
|
85
85
|
}
|
|
86
86
|
}
|
package/v3/dist/actor.d.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { EffectsDef, GuardsDef, MachineContext } from "./slot.js";
|
|
2
|
-
import {
|
|
2
|
+
import { ExtractReply, ReplyTypeBrand } from "./internal/brands.js";
|
|
3
3
|
import { ActorStoppedError, DuplicateActorError, NoReplyError } from "./errors.js";
|
|
4
4
|
import { ProcessEventError, ProcessEventHooks, ProcessEventResult, processEventCore, resolveTransition, runSpawnEffects } from "./internal/transition.js";
|
|
5
|
-
import { PersistentActorRef } from "./persistence/persistent-actor.js";
|
|
6
|
-
import { ActorMetadata, PersistenceAdapterTag, PersistenceError, RestoreResult, VersionConflictError } from "./persistence/adapter.js";
|
|
7
5
|
import { BuiltMachine, Machine, MachineRef } from "./machine.js";
|
|
8
|
-
import { Context, Deferred, Effect, Layer, Option, Queue, Ref, Scope, Stream, SubscriptionRef } from "effect";
|
|
6
|
+
import { Context, Deferred, Effect, Layer, Option, PubSub, Queue, Ref, Scope, Stream, SubscriptionRef } from "effect";
|
|
9
7
|
import * as effect_dist_dts_Tracer_js0 from "effect/dist/dts/Tracer.js";
|
|
10
8
|
|
|
11
9
|
//#region src/actor.d.ts
|
|
@@ -39,6 +37,15 @@ interface ActorRefSync<State extends {
|
|
|
39
37
|
readonly matches: (tag: State["_tag"]) => boolean;
|
|
40
38
|
readonly can: (event: Event) => boolean;
|
|
41
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Information about a successful transition.
|
|
42
|
+
* Emitted on the `transitions` stream after each accepted event.
|
|
43
|
+
*/
|
|
44
|
+
interface TransitionInfo<State, Event> {
|
|
45
|
+
readonly fromState: State;
|
|
46
|
+
readonly toState: State;
|
|
47
|
+
readonly event: Event;
|
|
48
|
+
}
|
|
42
49
|
interface ActorRef<State extends {
|
|
43
50
|
readonly _tag: string;
|
|
44
51
|
}, Event> {
|
|
@@ -53,11 +60,11 @@ interface ActorRef<State extends {
|
|
|
53
60
|
*/
|
|
54
61
|
readonly call: (event: Event) => Effect.Effect<ProcessEventResult<State>>;
|
|
55
62
|
/**
|
|
56
|
-
* Typed request-reply.
|
|
57
|
-
*
|
|
63
|
+
* Typed request-reply. Accepts only events with a reply schema
|
|
64
|
+
* (defined via `Event.reply()`). Return type is inferred from the schema.
|
|
58
65
|
* Fails with NoReplyError if the handler doesn't provide a reply.
|
|
59
66
|
*/
|
|
60
|
-
readonly ask: <
|
|
67
|
+
readonly ask: <E extends Event & ReplyTypeBrand<unknown>>(event: E) => Effect.Effect<ExtractReply<E>, NoReplyError | ActorStoppedError>;
|
|
61
68
|
/** Observable state. */
|
|
62
69
|
readonly state: SubscriptionRef.SubscriptionRef<State>;
|
|
63
70
|
/** Stop the actor gracefully. */
|
|
@@ -70,6 +77,14 @@ interface ActorRef<State extends {
|
|
|
70
77
|
readonly can: (event: Event) => Effect.Effect<boolean>;
|
|
71
78
|
/** Stream of state changes. */
|
|
72
79
|
readonly changes: Stream.Stream<State>;
|
|
80
|
+
/**
|
|
81
|
+
* Stream of accepted transitions (edge stream).
|
|
82
|
+
*
|
|
83
|
+
* Emits `{ fromState, toState, event }` on every successful transition,
|
|
84
|
+
* including same-state reenters. PubSub-backed — late subscribers miss
|
|
85
|
+
* past edges. This is observational, not a durability guarantee.
|
|
86
|
+
*/
|
|
87
|
+
readonly transitions: Stream.Stream<TransitionInfo<State, Event>>;
|
|
73
88
|
/** Wait for a state matching predicate or variant (includes current snapshot). */
|
|
74
89
|
readonly waitFor: {
|
|
75
90
|
(predicate: (state: State) => boolean): Effect.Effect<State>;
|
|
@@ -123,54 +138,17 @@ interface ActorSystem {
|
|
|
123
138
|
/**
|
|
124
139
|
* Spawn a new actor with the given machine.
|
|
125
140
|
*
|
|
126
|
-
* For regular machines, returns ActorRef.
|
|
127
|
-
* For persistent machines (created with Machine.persist), returns PersistentActorRef.
|
|
128
|
-
*
|
|
129
|
-
* All effect slots must be provided via `.build()` before spawning.
|
|
130
|
-
*
|
|
131
141
|
* @example
|
|
132
142
|
* ```ts
|
|
133
|
-
* // Regular machine (built)
|
|
134
143
|
* const built = machine.build({ fetchData: ... })
|
|
135
144
|
* const actor = yield* system.spawn("my-actor", built);
|
|
136
|
-
*
|
|
137
|
-
* // Persistent machine (auto-detected)
|
|
138
|
-
* const persistentActor = yield* system.spawn("my-actor", persistentMachine);
|
|
139
|
-
* persistentActor.persist; // available
|
|
140
|
-
* persistentActor.version; // available
|
|
141
|
-
* ```
|
|
142
|
-
*/
|
|
143
|
-
readonly spawn: {
|
|
144
|
-
<S extends {
|
|
145
|
-
readonly _tag: string;
|
|
146
|
-
}, E extends {
|
|
147
|
-
readonly _tag: string;
|
|
148
|
-
}, R>(id: string, machine: BuiltMachine<S, E, R>): Effect.Effect<ActorRef<S, E>, DuplicateActorError, R>;
|
|
149
|
-
<S extends {
|
|
150
|
-
readonly _tag: string;
|
|
151
|
-
}, E extends {
|
|
152
|
-
readonly _tag: string;
|
|
153
|
-
}, R>(id: string, machine: PersistentMachine<S, E, R>): Effect.Effect<PersistentActorRef<S, E, R>, PersistenceError | VersionConflictError | DuplicateActorError, R | PersistenceAdapterTag>;
|
|
154
|
-
};
|
|
155
|
-
/**
|
|
156
|
-
* Restore an actor from persistence.
|
|
157
|
-
* Returns None if no persisted state exists for the given ID.
|
|
158
|
-
*
|
|
159
|
-
* @example
|
|
160
|
-
* ```ts
|
|
161
|
-
* const maybeActor = yield* system.restore("order-1", persistentMachine);
|
|
162
|
-
* if (Option.isSome(maybeActor)) {
|
|
163
|
-
* const actor = maybeActor.value;
|
|
164
|
-
* const state = yield* actor.snapshot;
|
|
165
|
-
* console.log(`Restored to state: ${state._tag}`);
|
|
166
|
-
* }
|
|
167
145
|
* ```
|
|
168
146
|
*/
|
|
169
|
-
readonly
|
|
147
|
+
readonly spawn: <S extends {
|
|
170
148
|
readonly _tag: string;
|
|
171
149
|
}, E extends {
|
|
172
150
|
readonly _tag: string;
|
|
173
|
-
}, R>(id: string, machine:
|
|
151
|
+
}, R>(id: string, machine: BuiltMachine<S, E, R>) => Effect.Effect<ActorRef<S, E>, DuplicateActorError, R>;
|
|
174
152
|
/**
|
|
175
153
|
* Get an existing actor by ID
|
|
176
154
|
*/
|
|
@@ -194,53 +172,6 @@ interface ActorSystem {
|
|
|
194
172
|
* Returns an unsubscribe function.
|
|
195
173
|
*/
|
|
196
174
|
readonly subscribe: (fn: SystemEventListener) => () => void;
|
|
197
|
-
/**
|
|
198
|
-
* List all persisted actor metadata.
|
|
199
|
-
* Returns empty array if adapter doesn't support registry.
|
|
200
|
-
*
|
|
201
|
-
* @example
|
|
202
|
-
* ```ts
|
|
203
|
-
* const actors = yield* system.listPersisted();
|
|
204
|
-
* for (const meta of actors) {
|
|
205
|
-
* console.log(`${meta.id}: ${meta.stateTag} (v${meta.version})`);
|
|
206
|
-
* }
|
|
207
|
-
* ```
|
|
208
|
-
*/
|
|
209
|
-
readonly listPersisted: () => Effect.Effect<ReadonlyArray<ActorMetadata>, PersistenceError, PersistenceAdapterTag>;
|
|
210
|
-
/**
|
|
211
|
-
* Restore multiple actors by ID.
|
|
212
|
-
* Returns both successfully restored actors and failures.
|
|
213
|
-
*
|
|
214
|
-
* @example
|
|
215
|
-
* ```ts
|
|
216
|
-
* const result = yield* system.restoreMany(["order-1", "order-2"], orderMachine);
|
|
217
|
-
* console.log(`Restored: ${result.restored.length}, Failed: ${result.failed.length}`);
|
|
218
|
-
* ```
|
|
219
|
-
*/
|
|
220
|
-
readonly restoreMany: <S extends {
|
|
221
|
-
readonly _tag: string;
|
|
222
|
-
}, E extends {
|
|
223
|
-
readonly _tag: string;
|
|
224
|
-
}, R>(ids: ReadonlyArray<string>, machine: PersistentMachine<S, E, R>) => Effect.Effect<RestoreResult<S, E, R>, never, R | PersistenceAdapterTag>;
|
|
225
|
-
/**
|
|
226
|
-
* Restore all persisted actors for a machine type.
|
|
227
|
-
* Uses adapter registry if available, otherwise returns empty result.
|
|
228
|
-
*
|
|
229
|
-
* @example
|
|
230
|
-
* ```ts
|
|
231
|
-
* const result = yield* system.restoreAll(orderMachine, {
|
|
232
|
-
* filter: (meta) => meta.stateTag !== "Done"
|
|
233
|
-
* });
|
|
234
|
-
* console.log(`Restored ${result.restored.length} active orders`);
|
|
235
|
-
* ```
|
|
236
|
-
*/
|
|
237
|
-
readonly restoreAll: <S extends {
|
|
238
|
-
readonly _tag: string;
|
|
239
|
-
}, E extends {
|
|
240
|
-
readonly _tag: string;
|
|
241
|
-
}, R>(machine: PersistentMachine<S, E, R>, options?: {
|
|
242
|
-
filter?: (meta: ActorMetadata) => boolean;
|
|
243
|
-
}) => Effect.Effect<RestoreResult<S, E, R>, PersistenceError, R | PersistenceAdapterTag>;
|
|
244
175
|
}
|
|
245
176
|
/**
|
|
246
177
|
* ActorSystem service tag
|
|
@@ -259,7 +190,7 @@ declare const buildActorRefCore: <S extends {
|
|
|
259
190
|
readonly _tag: string;
|
|
260
191
|
}, E extends {
|
|
261
192
|
readonly _tag: string;
|
|
262
|
-
}, R, GD extends GuardsDef, EFD extends EffectsDef>(id: string, machine: Machine<S, E, R, any, any, GD, EFD>, stateRef: SubscriptionRef.SubscriptionRef<S>, eventQueue: Queue.Queue<QueuedEvent<E>>, stoppedRef: Ref.Ref<boolean>, listeners: Listeners<S>, stop: Effect.Effect<void>, system: ActorSystem, childrenMap: ReadonlyMap<string, ActorRef<AnyState, unknown>>, pendingReplies: Set<Deferred.Deferred<unknown, unknown>>) => ActorRef<S, E>;
|
|
193
|
+
}, R, GD extends GuardsDef, EFD extends EffectsDef>(id: string, machine: Machine<S, E, R, any, any, GD, EFD>, stateRef: SubscriptionRef.SubscriptionRef<S>, eventQueue: Queue.Queue<QueuedEvent<E>>, stoppedRef: Ref.Ref<boolean>, listeners: Listeners<S>, stop: Effect.Effect<void>, system: ActorSystem, childrenMap: ReadonlyMap<string, ActorRef<AnyState, unknown>>, pendingReplies: Set<Deferred.Deferred<unknown, unknown>>, transitionsPubSub?: PubSub.PubSub<TransitionInfo<S, E>>) => ActorRef<S, E>;
|
|
263
194
|
/**
|
|
264
195
|
* Create and start an actor for a machine
|
|
265
196
|
*/
|
|
@@ -267,7 +198,9 @@ declare const createActor: <S extends {
|
|
|
267
198
|
readonly _tag: string;
|
|
268
199
|
}, E extends {
|
|
269
200
|
readonly _tag: string;
|
|
270
|
-
}, R, GD extends GuardsDef, EFD extends EffectsDef>(id: string, machine: Machine<S, E, R, Record<string, never>, Record<string, never>, GD, EFD
|
|
201
|
+
}, R, GD extends GuardsDef, EFD extends EffectsDef>(id: string, machine: Machine<S, E, R, Record<string, never>, Record<string, never>, GD, EFD>, options?: {
|
|
202
|
+
initialState?: S;
|
|
203
|
+
} | undefined) => Effect.Effect<ActorRef<S, E>, never, Exclude<R, MachineContext<S, E, MachineRef<E>>> | Exclude<Exclude<R, MachineContext<S, E, MachineRef<E>>>, effect_dist_dts_Tracer_js0.ParentSpan> | Exclude<Exclude<R, MachineContext<S, E, MachineRef<E>>>, Scope.Scope> | Exclude<Exclude<Exclude<R, MachineContext<S, E, MachineRef<E>>>, Scope.Scope>, effect_dist_dts_Tracer_js0.ParentSpan>>;
|
|
271
204
|
/** Fail all pending call/ask Deferreds with ActorStoppedError. Safe to call multiple times. */
|
|
272
205
|
declare const settlePendingReplies: (pendingReplies: Set<Deferred.Deferred<unknown, unknown>>, actorId: string) => Effect.Effect<void, never, never>;
|
|
273
206
|
/**
|
|
@@ -275,4 +208,4 @@ declare const settlePendingReplies: (pendingReplies: Set<Deferred.Deferred<unkno
|
|
|
275
208
|
*/
|
|
276
209
|
declare const Default: Layer.Layer<ActorSystem, never, never>;
|
|
277
210
|
//#endregion
|
|
278
|
-
export { ActorRef, ActorRefSync, ActorSystem, Default, Listeners, type ProcessEventError, type ProcessEventHooks, type ProcessEventResult, QueuedEvent, SystemEvent, SystemEventListener, buildActorRefCore, createActor, notifyListeners, processEventCore, resolveTransition, runSpawnEffects, settlePendingReplies };
|
|
211
|
+
export { ActorRef, ActorRefSync, ActorSystem, Default, Listeners, type ProcessEventError, type ProcessEventHooks, type ProcessEventResult, QueuedEvent, SystemEvent, SystemEventListener, TransitionInfo, buildActorRefCore, createActor, notifyListeners, processEventCore, resolveTransition, runSpawnEffects, settlePendingReplies };
|