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/v3/dist/machine.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
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, 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
|
|
@@ -14,6 +13,8 @@ var machine_exports = /* @__PURE__ */ __exportAll({
|
|
|
14
13
|
Machine: () => Machine,
|
|
15
14
|
findTransitions: () => findTransitions,
|
|
16
15
|
make: () => make,
|
|
16
|
+
replay: () => replay,
|
|
17
|
+
reply: () => reply,
|
|
17
18
|
spawn: () => spawn
|
|
18
19
|
});
|
|
19
20
|
const emitTaskInspection = (input) => Effect.flatMap(Effect.serviceOptional(Inspector).pipe(Effect.option), (inspector) => Option.isNone(inspector) ? Effect.void : emitWithTimestamp(inspector.value, (timestamp) => ({
|
|
@@ -42,9 +43,6 @@ var BuiltMachine = class {
|
|
|
42
43
|
get initial() {
|
|
43
44
|
return this._inner.initial;
|
|
44
45
|
}
|
|
45
|
-
persist(config) {
|
|
46
|
-
return this._inner.persist(config);
|
|
47
|
-
}
|
|
48
46
|
};
|
|
49
47
|
/**
|
|
50
48
|
* Machine definition with fluent builder API.
|
|
@@ -72,6 +70,7 @@ var Machine = class Machine {
|
|
|
72
70
|
/** @internal */ _slots;
|
|
73
71
|
stateSchema;
|
|
74
72
|
eventSchema;
|
|
73
|
+
/** @internal */ _replySchemas;
|
|
75
74
|
/**
|
|
76
75
|
* Context tag for accessing machine state/event/self in slot handlers.
|
|
77
76
|
* Uses shared module-level tag for all machines.
|
|
@@ -98,6 +97,9 @@ var Machine = class Machine {
|
|
|
98
97
|
get effectsSchema() {
|
|
99
98
|
return this._effectsSchema;
|
|
100
99
|
}
|
|
100
|
+
get replySchemas() {
|
|
101
|
+
return this._replySchemas;
|
|
102
|
+
}
|
|
101
103
|
/** @internal */
|
|
102
104
|
constructor(initial, stateSchema, eventSchema, guardsSchema, effectsSchema) {
|
|
103
105
|
this.initial = initial;
|
|
@@ -112,6 +114,7 @@ var Machine = class Machine {
|
|
|
112
114
|
this._effectHandlers = /* @__PURE__ */ new Map();
|
|
113
115
|
this.stateSchema = stateSchema;
|
|
114
116
|
this.eventSchema = eventSchema;
|
|
117
|
+
this._replySchemas = eventSchema?._replySchemas ?? /* @__PURE__ */ new Map();
|
|
115
118
|
this._slots = {
|
|
116
119
|
guards: this._guardsSchema !== void 0 ? this._guardsSchema._createSlots((name, params) => Effect.flatMap(Effect.serviceOptional(this.Context).pipe(Effect.orDie), (ctx) => {
|
|
117
120
|
const handler = this._guardHandlers.get(name);
|
|
@@ -374,6 +377,7 @@ var Machine = class Machine {
|
|
|
374
377
|
result._spawnEffects = [...this._spawnEffects];
|
|
375
378
|
result._backgroundEffects = [...this._backgroundEffects];
|
|
376
379
|
result._postponeRules = [...this._postponeRules];
|
|
380
|
+
result._replySchemas = this._replySchemas;
|
|
377
381
|
const anyHandlers = handlers;
|
|
378
382
|
if (this._guardsSchema !== void 0) for (const name of Object.keys(this._guardsSchema.definitions)) result._guardHandlers.set(name, anyHandlers[name]);
|
|
379
383
|
if (this._effectsSchema !== void 0) for (const name of Object.keys(this._effectsSchema.definitions)) result._effectHandlers.set(name, anyHandlers[name]);
|
|
@@ -381,10 +385,6 @@ var Machine = class Machine {
|
|
|
381
385
|
}
|
|
382
386
|
return new BuiltMachine(this);
|
|
383
387
|
}
|
|
384
|
-
/** @internal Persist from raw Machine — prefer BuiltMachine.persist() */
|
|
385
|
-
persist(config) {
|
|
386
|
-
return persist(config)(this);
|
|
387
|
-
}
|
|
388
388
|
static make(config) {
|
|
389
389
|
return new Machine(config.initial, config.state, config.event, config.guards, config.effects);
|
|
390
390
|
}
|
|
@@ -404,11 +404,69 @@ var TransitionScope = class {
|
|
|
404
404
|
}
|
|
405
405
|
};
|
|
406
406
|
const make = Machine.make;
|
|
407
|
-
|
|
408
|
-
|
|
407
|
+
/**
|
|
408
|
+
* Spawn an actor from a built machine.
|
|
409
|
+
*
|
|
410
|
+
* Options:
|
|
411
|
+
* - `id` — custom actor ID (default: random)
|
|
412
|
+
* - `hydrate` — restore from a previously-saved state snapshot.
|
|
413
|
+
* The actor starts in the hydrated state and re-runs spawn effects
|
|
414
|
+
* for that state (timers, scoped resources, etc.). Transition history
|
|
415
|
+
* is not replayed — only the current state's entry effects run.
|
|
416
|
+
*
|
|
417
|
+
* Persistence is composed in userland by observing `actor.changes`
|
|
418
|
+
* and saving snapshots to your own storage.
|
|
419
|
+
*/
|
|
420
|
+
const spawn = Effect.fn("effect-machine.spawn")(function* (built, idOrOptions) {
|
|
421
|
+
const opts = typeof idOrOptions === "string" ? { id: idOrOptions } : idOrOptions;
|
|
422
|
+
const actor = yield* createActor(opts?.id ?? `actor-${Math.random().toString(36).slice(2)}`, built._inner, { initialState: opts?.hydrate });
|
|
409
423
|
const maybeScope = yield* Effect.serviceOption(Scope.Scope);
|
|
410
424
|
if (Option.isSome(maybeScope)) yield* Scope.addFinalizer(maybeScope.value, actor.stop);
|
|
411
425
|
return actor;
|
|
412
426
|
});
|
|
427
|
+
const replay = Effect.fn("effect-machine.replay")(function* (built, events, options) {
|
|
428
|
+
const machine = built._inner;
|
|
429
|
+
let state = options?.from ?? machine.initial;
|
|
430
|
+
const hasPostponeRules = machine.postponeRules.length > 0;
|
|
431
|
+
const postponed = [];
|
|
432
|
+
const dummySend = Effect.fn("effect-machine.replay.send")((_event) => Effect.void);
|
|
433
|
+
const self = {
|
|
434
|
+
send: dummySend,
|
|
435
|
+
cast: dummySend,
|
|
436
|
+
spawn: () => Effect.die("spawn not supported in replay")
|
|
437
|
+
};
|
|
438
|
+
for (const event of events) {
|
|
439
|
+
if (machine.finalStates.has(state._tag)) break;
|
|
440
|
+
if (hasPostponeRules && shouldPostpone(machine, state._tag, event._tag)) {
|
|
441
|
+
postponed.push(event);
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
const transition = resolveTransition(machine, state, event);
|
|
445
|
+
if (transition !== void 0) {
|
|
446
|
+
const result = yield* runTransitionHandler(machine, transition, state, event, self, stubSystem, "replay");
|
|
447
|
+
const previousTag = state._tag;
|
|
448
|
+
state = result.newState;
|
|
449
|
+
if ((state._tag !== previousTag || transition.reenter === true) && postponed.length > 0) {
|
|
450
|
+
let drainTag = previousTag;
|
|
451
|
+
while (state._tag !== drainTag && postponed.length > 0) {
|
|
452
|
+
if (machine.finalStates.has(state._tag)) break;
|
|
453
|
+
drainTag = state._tag;
|
|
454
|
+
const drained = postponed.splice(0);
|
|
455
|
+
for (const postponedEvent of drained) {
|
|
456
|
+
if (machine.finalStates.has(state._tag)) break;
|
|
457
|
+
if (shouldPostpone(machine, state._tag, postponedEvent._tag)) {
|
|
458
|
+
postponed.push(postponedEvent);
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
const pTransition = resolveTransition(machine, state, postponedEvent);
|
|
462
|
+
if (pTransition !== void 0) state = (yield* runTransitionHandler(machine, pTransition, state, postponedEvent, self, stubSystem, "replay")).newState;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
return state;
|
|
469
|
+
});
|
|
470
|
+
const reply = makeReply;
|
|
413
471
|
//#endregion
|
|
414
|
-
export { BuiltMachine, Machine, findTransitions, machine_exports, make, spawn };
|
|
472
|
+
export { BuiltMachine, Machine, findTransitions, machine_exports, make, replay, reply, spawn };
|
package/v3/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.Any> = 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, infer _I, infer _RR>;
|
|
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, infer _I, infer _RR>;
|
|
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.Constructor<D[K]>) => TaggedStructType<K, D[K]> & Brand) & {
|
|
53
|
+
} : ((args: Schema.Struct.Constructor<D[K]>) => TaggedStructType<K, D[K]> & Brand & VariantReplyBrand<D[K]>) & {
|
|
40
54
|
readonly derive: (source: object, partial?: Partial<Schema.Struct.Constructor<D[K]>>) => TaggedStructType<K, D[K]> & Brand;
|
|
41
55
|
readonly _tag: K;
|
|
42
56
|
} };
|
|
@@ -56,6 +70,11 @@ interface MachineSchemaBase<D extends Record<string, Schema.Struct.Fields>, Bran
|
|
|
56
70
|
* Per-variant schemas for fine-grained operations
|
|
57
71
|
*/
|
|
58
72
|
readonly variants: VariantSchemas<D>;
|
|
73
|
+
/**
|
|
74
|
+
* Reply schemas per variant tag. Only populated for event schemas
|
|
75
|
+
* with variants defined via `Event.reply()`.
|
|
76
|
+
*/
|
|
77
|
+
readonly _replySchemas: ReadonlyMap<string, Schema.Schema.Any>;
|
|
59
78
|
/**
|
|
60
79
|
* Type guard: `OrderState.$is("Pending")(value)`
|
|
61
80
|
*/
|
|
@@ -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.Any>(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/v3/dist/schema.js
CHANGED
|
@@ -38,15 +38,21 @@ 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
|
*/
|
|
44
45
|
const buildMachineSchema = (definition) => {
|
|
45
46
|
const variants = {};
|
|
46
47
|
const constructors = {};
|
|
48
|
+
const replySchemas = /* @__PURE__ */ new Map();
|
|
47
49
|
for (const tag of Object.keys(definition)) {
|
|
48
50
|
const fields = definition[tag];
|
|
49
51
|
if (fields === void 0) continue;
|
|
52
|
+
if (ReplySchemaSymbol in fields) {
|
|
53
|
+
const rs = fields[ReplySchemaSymbol];
|
|
54
|
+
if (rs !== void 0) replySchemas.set(tag, rs);
|
|
55
|
+
}
|
|
50
56
|
variants[tag] = Schema.TaggedStruct(tag, fields);
|
|
51
57
|
const fieldNames = new Set(Object.keys(fields));
|
|
52
58
|
if (fieldNames.size > 0) {
|
|
@@ -93,6 +99,7 @@ const buildMachineSchema = (definition) => {
|
|
|
93
99
|
variants,
|
|
94
100
|
constructors,
|
|
95
101
|
_definition: definition,
|
|
102
|
+
_replySchemas: replySchemas,
|
|
96
103
|
$is,
|
|
97
104
|
$match
|
|
98
105
|
};
|
|
@@ -102,10 +109,11 @@ const buildMachineSchema = (definition) => {
|
|
|
102
109
|
* Builds the schema object with variants, constructors, $is, and $match.
|
|
103
110
|
*/
|
|
104
111
|
const createMachineSchema = (definition) => {
|
|
105
|
-
const { schema, variants, constructors, _definition, $is, $match } = buildMachineSchema(definition);
|
|
112
|
+
const { schema, variants, constructors, _definition, _replySchemas, $is, $match } = buildMachineSchema(definition);
|
|
106
113
|
return Object.assign(Object.create(schema), {
|
|
107
114
|
variants,
|
|
108
115
|
_definition,
|
|
116
|
+
_replySchemas,
|
|
109
117
|
$is,
|
|
110
118
|
$match,
|
|
111
119
|
...constructors
|
|
@@ -148,11 +156,15 @@ const State = (definition) => createMachineSchema(definition);
|
|
|
148
156
|
* accidental use of constructors from different event schemas
|
|
149
157
|
* (unless they have identical definitions).
|
|
150
158
|
*
|
|
159
|
+
* Use `Event.reply(fields, replySchema)` to define events that support
|
|
160
|
+
* typed `ask()` replies.
|
|
161
|
+
*
|
|
151
162
|
* @example
|
|
152
163
|
* ```ts
|
|
153
|
-
* const OrderEvent =
|
|
164
|
+
* const OrderEvent = Event({
|
|
154
165
|
* Ship: { trackingId: Schema.String },
|
|
155
166
|
* Cancel: {},
|
|
167
|
+
* GetTotal: Event.reply({}, Schema.Number),
|
|
156
168
|
* })
|
|
157
169
|
*
|
|
158
170
|
* type OrderEvent = typeof OrderEvent.Type
|
|
@@ -161,6 +173,20 @@ const State = (definition) => createMachineSchema(definition);
|
|
|
161
173
|
* const e = OrderEvent.Ship({ trackingId: "abc" })
|
|
162
174
|
* ```
|
|
163
175
|
*/
|
|
164
|
-
const
|
|
176
|
+
const EventImpl = (definition) => createMachineSchema(definition);
|
|
177
|
+
/**
|
|
178
|
+
* Annotate event fields with a reply schema.
|
|
179
|
+
* Events defined with `Event.reply(fields, replySchema)` enable typed `ask()`.
|
|
180
|
+
*/
|
|
181
|
+
const replyFieldsFn = (fields, replySchema) => {
|
|
182
|
+
const annotated = { ...fields };
|
|
183
|
+
Object.defineProperty(annotated, ReplySchemaSymbol, {
|
|
184
|
+
value: replySchema,
|
|
185
|
+
enumerable: false,
|
|
186
|
+
writable: false
|
|
187
|
+
});
|
|
188
|
+
return annotated;
|
|
189
|
+
};
|
|
190
|
+
const Event = Object.assign(EventImpl, { reply: replyFieldsFn });
|
|
165
191
|
//#endregion
|
|
166
192
|
export { Event, State };
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import { DuplicateActorError } from "../errors.js";
|
|
2
|
-
import { PersistentActorRef } from "./persistent-actor.js";
|
|
3
|
-
import { Effect, Option, Schema, ServiceMap } from "effect";
|
|
4
|
-
import * as effect_Cause0 from "effect/Cause";
|
|
5
|
-
|
|
6
|
-
//#region src/persistence/adapter.d.ts
|
|
7
|
-
/**
|
|
8
|
-
* Metadata for a persisted actor.
|
|
9
|
-
* Used for discovery and filtering during bulk restore.
|
|
10
|
-
*/
|
|
11
|
-
interface ActorMetadata {
|
|
12
|
-
readonly id: string;
|
|
13
|
-
/** User-provided identifier for the machine type */
|
|
14
|
-
readonly machineType: string;
|
|
15
|
-
readonly createdAt: number;
|
|
16
|
-
readonly lastActivityAt: number;
|
|
17
|
-
readonly version: number;
|
|
18
|
-
/** Current state _tag value */
|
|
19
|
-
readonly stateTag: string;
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Result of a bulk restore operation.
|
|
23
|
-
* Contains both successfully restored actors and failures.
|
|
24
|
-
*/
|
|
25
|
-
interface RestoreResult<S extends {
|
|
26
|
-
readonly _tag: string;
|
|
27
|
-
}, E extends {
|
|
28
|
-
readonly _tag: string;
|
|
29
|
-
}, R = never> {
|
|
30
|
-
readonly restored: ReadonlyArray<PersistentActorRef<S, E, R>>;
|
|
31
|
-
readonly failed: ReadonlyArray<RestoreFailure>;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* A single restore failure with actor ID and error details.
|
|
35
|
-
*/
|
|
36
|
-
interface RestoreFailure {
|
|
37
|
-
readonly id: string;
|
|
38
|
-
readonly error: PersistenceError | DuplicateActorError;
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Snapshot of actor state at a point in time
|
|
42
|
-
*/
|
|
43
|
-
interface Snapshot<S> {
|
|
44
|
-
readonly state: S;
|
|
45
|
-
readonly version: number;
|
|
46
|
-
readonly timestamp: number;
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Persisted event with metadata
|
|
50
|
-
*/
|
|
51
|
-
interface PersistedEvent<E> {
|
|
52
|
-
readonly event: E;
|
|
53
|
-
readonly version: number;
|
|
54
|
-
readonly timestamp: number;
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Adapter for persisting actor state and events.
|
|
58
|
-
*
|
|
59
|
-
* Implementations handle serialization and storage of snapshots and event journals.
|
|
60
|
-
* Schema parameters ensure type-safe serialization/deserialization.
|
|
61
|
-
* Schemas must have no context requirements (use Schema<S, SI, never>).
|
|
62
|
-
*/
|
|
63
|
-
interface PersistenceAdapter {
|
|
64
|
-
/**
|
|
65
|
-
* Save a snapshot of actor state.
|
|
66
|
-
* Implementations should use optimistic locking — fail if version mismatch.
|
|
67
|
-
*/
|
|
68
|
-
readonly saveSnapshot: <S>(id: string, snapshot: Snapshot<S>, schema: Schema.Codec<S, unknown, never, never>) => Effect.Effect<void, PersistenceError | VersionConflictError>;
|
|
69
|
-
/**
|
|
70
|
-
* Load the latest snapshot for an actor.
|
|
71
|
-
* Returns None if no snapshot exists.
|
|
72
|
-
*/
|
|
73
|
-
readonly loadSnapshot: <S>(id: string, schema: Schema.Codec<S, unknown, never, never>) => Effect.Effect<Option.Option<Snapshot<S>>, PersistenceError>;
|
|
74
|
-
/**
|
|
75
|
-
* Append an event to the actor's event journal.
|
|
76
|
-
*/
|
|
77
|
-
readonly appendEvent: <E>(id: string, event: PersistedEvent<E>, schema: Schema.Codec<E, unknown, never, never>) => Effect.Effect<void, PersistenceError>;
|
|
78
|
-
/**
|
|
79
|
-
* Load events from the journal, optionally after a specific version.
|
|
80
|
-
*/
|
|
81
|
-
readonly loadEvents: <E>(id: string, schema: Schema.Codec<E, unknown, never, never>, afterVersion?: number) => Effect.Effect<ReadonlyArray<PersistedEvent<E>>, PersistenceError>;
|
|
82
|
-
/**
|
|
83
|
-
* Delete all persisted data for an actor (snapshot + events).
|
|
84
|
-
*/
|
|
85
|
-
readonly deleteActor: (id: string) => Effect.Effect<void, PersistenceError>;
|
|
86
|
-
/**
|
|
87
|
-
* List all persisted actor metadata.
|
|
88
|
-
* Optional — adapters without registry support can omit this.
|
|
89
|
-
*/
|
|
90
|
-
readonly listActors?: () => Effect.Effect<ReadonlyArray<ActorMetadata>, PersistenceError>;
|
|
91
|
-
/**
|
|
92
|
-
* Save or update actor metadata.
|
|
93
|
-
* Called on spawn and state transitions.
|
|
94
|
-
* Optional — adapters without registry support can omit this.
|
|
95
|
-
*/
|
|
96
|
-
readonly saveMetadata?: (metadata: ActorMetadata) => Effect.Effect<void, PersistenceError>;
|
|
97
|
-
/**
|
|
98
|
-
* Delete actor metadata.
|
|
99
|
-
* Called when actor is deleted.
|
|
100
|
-
* Optional — adapters without registry support can omit this.
|
|
101
|
-
*/
|
|
102
|
-
readonly deleteMetadata?: (id: string) => Effect.Effect<void, PersistenceError>;
|
|
103
|
-
/**
|
|
104
|
-
* Load metadata for a specific actor by ID.
|
|
105
|
-
* Returns None if no metadata exists.
|
|
106
|
-
* Optional — adapters without registry support can omit this.
|
|
107
|
-
*/
|
|
108
|
-
readonly loadMetadata?: (id: string) => Effect.Effect<Option.Option<ActorMetadata>, PersistenceError>;
|
|
109
|
-
}
|
|
110
|
-
declare const PersistenceError_base: Schema.ErrorClass<PersistenceError, Schema.TaggedStruct<"PersistenceError", {
|
|
111
|
-
readonly operation: Schema.String;
|
|
112
|
-
readonly actorId: Schema.String;
|
|
113
|
-
readonly cause: Schema.optional<Schema.Unknown>;
|
|
114
|
-
readonly message: Schema.optional<Schema.String>;
|
|
115
|
-
}>, effect_Cause0.YieldableError>;
|
|
116
|
-
/**
|
|
117
|
-
* Error type for persistence operations
|
|
118
|
-
*/
|
|
119
|
-
declare class PersistenceError extends PersistenceError_base {}
|
|
120
|
-
declare const VersionConflictError_base: Schema.ErrorClass<VersionConflictError, Schema.TaggedStruct<"VersionConflictError", {
|
|
121
|
-
readonly actorId: Schema.String;
|
|
122
|
-
readonly expectedVersion: Schema.Number;
|
|
123
|
-
readonly actualVersion: Schema.Number;
|
|
124
|
-
}>, effect_Cause0.YieldableError>;
|
|
125
|
-
/**
|
|
126
|
-
* Version conflict error — snapshot version doesn't match expected
|
|
127
|
-
*/
|
|
128
|
-
declare class VersionConflictError extends VersionConflictError_base {}
|
|
129
|
-
declare const PersistenceAdapterTag_base: ServiceMap.ServiceClass<PersistenceAdapterTag, "effect-machine/src/persistence/adapter/PersistenceAdapterTag", PersistenceAdapter>;
|
|
130
|
-
/**
|
|
131
|
-
* PersistenceAdapter service tag
|
|
132
|
-
*/
|
|
133
|
-
declare class PersistenceAdapterTag extends PersistenceAdapterTag_base {}
|
|
134
|
-
//#endregion
|
|
135
|
-
export { ActorMetadata, PersistedEvent, PersistenceAdapter, PersistenceAdapterTag, PersistenceError, RestoreFailure, RestoreResult, Snapshot, VersionConflictError };
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { Schema, ServiceMap } from "effect";
|
|
2
|
-
//#region src/persistence/adapter.ts
|
|
3
|
-
/**
|
|
4
|
-
* Error type for persistence operations
|
|
5
|
-
*/
|
|
6
|
-
var PersistenceError = class extends Schema.TaggedErrorClass()("PersistenceError", {
|
|
7
|
-
operation: Schema.String,
|
|
8
|
-
actorId: Schema.String,
|
|
9
|
-
cause: Schema.optional(Schema.Unknown),
|
|
10
|
-
message: Schema.optional(Schema.String)
|
|
11
|
-
}) {};
|
|
12
|
-
/**
|
|
13
|
-
* Version conflict error — snapshot version doesn't match expected
|
|
14
|
-
*/
|
|
15
|
-
var VersionConflictError = class extends Schema.TaggedErrorClass()("VersionConflictError", {
|
|
16
|
-
actorId: Schema.String,
|
|
17
|
-
expectedVersion: Schema.Number,
|
|
18
|
-
actualVersion: Schema.Number
|
|
19
|
-
}) {};
|
|
20
|
-
/**
|
|
21
|
-
* PersistenceAdapter service tag
|
|
22
|
-
*/
|
|
23
|
-
var PersistenceAdapterTag = class extends ServiceMap.Service()("effect-machine/src/persistence/adapter/PersistenceAdapterTag") {};
|
|
24
|
-
//#endregion
|
|
25
|
-
export { PersistenceAdapterTag, PersistenceError, VersionConflictError };
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { PersistenceAdapter, PersistenceAdapterTag } from "../adapter.js";
|
|
2
|
-
import { Effect, Layer } from "effect";
|
|
3
|
-
|
|
4
|
-
//#region src/persistence/adapters/in-memory.d.ts
|
|
5
|
-
/**
|
|
6
|
-
* Create an in-memory persistence adapter effect.
|
|
7
|
-
* Returns the adapter directly for custom layer composition.
|
|
8
|
-
*/
|
|
9
|
-
declare const makeInMemoryPersistenceAdapter: Effect.Effect<PersistenceAdapter, never, never>;
|
|
10
|
-
/**
|
|
11
|
-
* In-memory persistence adapter layer.
|
|
12
|
-
* Data is not persisted across process restarts.
|
|
13
|
-
*
|
|
14
|
-
* NOTE: Each `Effect.provide(InMemoryPersistenceAdapter)` creates a NEW adapter
|
|
15
|
-
* with empty storage. For tests that need persistent storage across multiple
|
|
16
|
-
* runPromise calls, use `makeInMemoryPersistenceAdapter` with a shared scope.
|
|
17
|
-
*
|
|
18
|
-
* @example
|
|
19
|
-
* ```ts
|
|
20
|
-
* const program = Effect.gen(function* () {
|
|
21
|
-
* const system = yield* ActorSystemService;
|
|
22
|
-
* const actor = yield* system.spawn("my-actor", persistentMachine);
|
|
23
|
-
* // ...
|
|
24
|
-
* }).pipe(
|
|
25
|
-
* Effect.provide(InMemoryPersistenceAdapter),
|
|
26
|
-
* Effect.provide(ActorSystemDefault),
|
|
27
|
-
* );
|
|
28
|
-
* ```
|
|
29
|
-
*/
|
|
30
|
-
declare const InMemoryPersistenceAdapter: Layer.Layer<PersistenceAdapterTag>;
|
|
31
|
-
//#endregion
|
|
32
|
-
export { InMemoryPersistenceAdapter, makeInMemoryPersistenceAdapter };
|