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.
Files changed (81) hide show
  1. package/README.md +65 -62
  2. package/dist/actor.d.ts +35 -97
  3. package/dist/actor.js +69 -98
  4. package/dist/cluster/adapters/in-memory.d.ts +28 -0
  5. package/dist/cluster/adapters/in-memory.js +79 -0
  6. package/dist/cluster/entity-actor-ref.d.ts +56 -0
  7. package/dist/cluster/entity-actor-ref.js +33 -0
  8. package/dist/cluster/entity-machine.d.ts +31 -49
  9. package/dist/cluster/entity-machine.js +167 -52
  10. package/dist/cluster/index.d.ts +5 -2
  11. package/dist/cluster/index.js +4 -1
  12. package/dist/cluster/persistence.d.ts +49 -0
  13. package/dist/cluster/persistence.js +18 -0
  14. package/dist/cluster/to-entity.d.ts +9 -3
  15. package/dist/cluster/to-entity.js +16 -4
  16. package/dist/errors.d.ts +12 -1
  17. package/dist/errors.js +8 -1
  18. package/dist/index.d.ts +5 -8
  19. package/dist/index.js +1 -6
  20. package/dist/internal/brands.d.ts +14 -1
  21. package/dist/internal/runtime.d.ts +67 -0
  22. package/dist/internal/runtime.js +248 -0
  23. package/dist/internal/transition.d.ts +6 -1
  24. package/dist/internal/transition.js +16 -4
  25. package/dist/internal/utils.d.ts +42 -6
  26. package/dist/internal/utils.js +28 -6
  27. package/dist/machine.d.ts +55 -46
  28. package/dist/machine.js +74 -13
  29. package/dist/schema.d.ts +35 -34
  30. package/dist/schema.js +32 -3
  31. package/dist/testing.js +4 -2
  32. package/package.json +4 -4
  33. package/v3/dist/actor.d.ts +29 -96
  34. package/v3/dist/actor.js +52 -97
  35. package/v3/dist/cluster/adapters/in-memory.d.ts +15 -0
  36. package/v3/dist/cluster/adapters/in-memory.js +62 -0
  37. package/v3/dist/cluster/entity-actor-ref.d.ts +49 -0
  38. package/v3/dist/cluster/entity-actor-ref.js +19 -0
  39. package/v3/dist/cluster/entity-machine.d.ts +34 -49
  40. package/v3/dist/cluster/entity-machine.js +134 -50
  41. package/v3/dist/cluster/index.d.ts +5 -2
  42. package/v3/dist/cluster/index.js +4 -1
  43. package/v3/dist/cluster/persistence.d.ts +48 -0
  44. package/v3/dist/cluster/persistence.js +14 -0
  45. package/v3/dist/cluster/to-entity.d.ts +5 -2
  46. package/v3/dist/cluster/to-entity.js +12 -4
  47. package/v3/dist/errors.d.ts +16 -1
  48. package/v3/dist/errors.js +8 -1
  49. package/v3/dist/index.d.ts +5 -8
  50. package/v3/dist/index.js +1 -6
  51. package/v3/dist/internal/brands.d.ts +15 -1
  52. package/v3/dist/internal/runtime.d.ts +65 -0
  53. package/v3/dist/internal/runtime.js +236 -0
  54. package/v3/dist/internal/transition.d.ts +5 -0
  55. package/v3/dist/internal/transition.js +15 -3
  56. package/v3/dist/internal/utils.d.ts +42 -6
  57. package/v3/dist/internal/utils.js +28 -6
  58. package/v3/dist/machine.d.ts +48 -46
  59. package/v3/dist/machine.js +71 -13
  60. package/v3/dist/schema.d.ts +35 -34
  61. package/v3/dist/schema.js +29 -3
  62. package/dist/persistence/adapter.d.ts +0 -135
  63. package/dist/persistence/adapter.js +0 -25
  64. package/dist/persistence/adapters/in-memory.d.ts +0 -32
  65. package/dist/persistence/adapters/in-memory.js +0 -174
  66. package/dist/persistence/index.d.ts +0 -5
  67. package/dist/persistence/index.js +0 -5
  68. package/dist/persistence/persistent-actor.d.ts +0 -50
  69. package/dist/persistence/persistent-actor.js +0 -404
  70. package/dist/persistence/persistent-machine.d.ts +0 -105
  71. package/dist/persistence/persistent-machine.js +0 -22
  72. package/v3/dist/persistence/adapter.d.ts +0 -138
  73. package/v3/dist/persistence/adapter.js +0 -25
  74. package/v3/dist/persistence/adapters/in-memory.d.ts +0 -32
  75. package/v3/dist/persistence/adapters/in-memory.js +0 -174
  76. package/v3/dist/persistence/index.d.ts +0 -5
  77. package/v3/dist/persistence/index.js +0 -5
  78. package/v3/dist/persistence/persistent-actor.d.ts +0 -50
  79. package/v3/dist/persistence/persistent-actor.js +0 -404
  80. package/v3/dist/persistence/persistent-machine.d.ts +0 -105
  81. package/v3/dist/persistence/persistent-machine.js +0 -22
@@ -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
- const spawn = Effect.fn("effect-machine.spawn")(function* (built, id) {
408
- const actor = yield* createActor(id ?? `actor-${Math.random().toString(36).slice(2)}`, built._inner);
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 };
@@ -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
- * Used for constraining fluent method type params.
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]> }[keyof D & string];
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
- * Constructor functions for each variant.
24
- * Empty structs: plain values with `_tag`: `State.Idle`
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
- * Create a schema-first Event definition.
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 = MachineSchema.Event({
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 Event = (definition) => createMachineSchema(definition);
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 };