effect-machine 0.1.0 → 0.2.1

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.
@@ -23,26 +23,29 @@ const make = Effect.gen(function* () {
23
23
  const storage = yield* Ref.make(new Map<string, ActorStorage>());
24
24
  const registry = yield* Ref.make(new Map<string, ActorMetadata>());
25
25
 
26
- const getOrCreateStorage = (id: string): Effect.Effect<ActorStorage> =>
27
- Ref.modify(storage, (map) => {
28
- const existing = map.get(id);
29
- if (existing !== undefined) {
30
- return [existing, map];
31
- }
32
- const newStorage: ActorStorage = {
33
- snapshot: Option.none(),
34
- events: [],
35
- };
36
- const newMap = new Map(map);
37
- newMap.set(id, newStorage);
38
- return [newStorage, newMap];
39
- });
26
+ const getOrCreateStorage = Effect.fn("effect-machine.persistence.inMemory.getOrCreateStorage")(
27
+ function* (id: string) {
28
+ return yield* Ref.modify(storage, (map) => {
29
+ const existing = map.get(id);
30
+ if (existing !== undefined) {
31
+ return [existing, map];
32
+ }
33
+ const newStorage: ActorStorage = {
34
+ snapshot: Option.none(),
35
+ events: [],
36
+ };
37
+ const newMap = new Map(map);
38
+ newMap.set(id, newStorage);
39
+ return [newStorage, newMap];
40
+ });
41
+ },
42
+ );
40
43
 
41
- const updateStorage = (
44
+ const updateStorage = Effect.fn("effect-machine.persistence.inMemory.updateStorage")(function* (
42
45
  id: string,
43
46
  update: (storage: ActorStorage) => ActorStorage,
44
- ): Effect.Effect<void> =>
45
- Ref.update(storage, (map) => {
47
+ ) {
48
+ yield* Ref.update(storage, (map) => {
46
49
  const existing = map.get(id);
47
50
  if (existing === undefined) {
48
51
  return map;
@@ -51,197 +54,213 @@ const make = Effect.gen(function* () {
51
54
  newMap.set(id, update(existing));
52
55
  return newMap;
53
56
  });
57
+ });
54
58
 
55
- const adapter: PersistenceAdapter = {
56
- saveSnapshot: <S, SI>(
57
- id: string,
58
- snapshot: Snapshot<S>,
59
- schema: Schema.Schema<S, SI, never>,
60
- ): Effect.Effect<void, PersistenceError | VersionConflictError> =>
61
- Effect.gen(function* () {
62
- const actorStorage = yield* getOrCreateStorage(id);
59
+ const saveSnapshot = Effect.fn("effect-machine.persistence.inMemory.saveSnapshot")(function* <
60
+ S,
61
+ SI,
62
+ >(id: string, snapshot: Snapshot<S>, schema: Schema.Schema<S, SI, never>) {
63
+ const actorStorage = yield* getOrCreateStorage(id);
63
64
 
64
- // Optimistic locking: check version
65
- // Reject only if trying to save an older version (strict <)
66
- // Same-version saves are idempotent (allow retries/multiple callers)
67
- if (Option.isSome(actorStorage.snapshot)) {
68
- const existingVersion = actorStorage.snapshot.value.version;
69
- if (snapshot.version < existingVersion) {
70
- return yield* new VersionConflictError({
71
- actorId: id,
72
- expectedVersion: existingVersion,
73
- actualVersion: snapshot.version,
74
- });
75
- }
76
- }
77
-
78
- // Encode state using schema
79
- const encoded = yield* Schema.encode(schema)(snapshot.state).pipe(
80
- Effect.mapError(
81
- (cause) =>
82
- new PersistenceError({
83
- operation: "saveSnapshot",
84
- actorId: id,
85
- cause,
86
- message: "Failed to encode state",
87
- }),
88
- ),
89
- );
65
+ // Optimistic locking: check version
66
+ // Reject only if trying to save an older version (strict <)
67
+ // Same-version saves are idempotent (allow retries/multiple callers)
68
+ if (Option.isSome(actorStorage.snapshot)) {
69
+ const existingVersion = actorStorage.snapshot.value.version;
70
+ if (snapshot.version < existingVersion) {
71
+ return yield* new VersionConflictError({
72
+ actorId: id,
73
+ expectedVersion: existingVersion,
74
+ actualVersion: snapshot.version,
75
+ });
76
+ }
77
+ }
90
78
 
91
- yield* updateStorage(id, (s) => ({
92
- ...s,
93
- snapshot: Option.some({
94
- data: encoded,
95
- version: snapshot.version,
96
- timestamp: snapshot.timestamp,
79
+ // Encode state using schema
80
+ const encoded = yield* Schema.encode(schema)(snapshot.state).pipe(
81
+ Effect.mapError(
82
+ (cause) =>
83
+ new PersistenceError({
84
+ operation: "saveSnapshot",
85
+ actorId: id,
86
+ cause,
87
+ message: "Failed to encode state",
97
88
  }),
98
- }));
89
+ ),
90
+ );
91
+
92
+ yield* updateStorage(id, (s) => ({
93
+ ...s,
94
+ snapshot: Option.some({
95
+ data: encoded,
96
+ version: snapshot.version,
97
+ timestamp: snapshot.timestamp,
99
98
  }),
99
+ }));
100
+ });
100
101
 
101
- loadSnapshot: <S, SI>(
102
- id: string,
103
- schema: Schema.Schema<S, SI, never>,
104
- ): Effect.Effect<Option.Option<Snapshot<S>>, PersistenceError> =>
105
- Effect.gen(function* () {
106
- const actorStorage = yield* getOrCreateStorage(id);
102
+ const loadSnapshot = Effect.fn("effect-machine.persistence.inMemory.loadSnapshot")(function* <
103
+ S,
104
+ SI,
105
+ >(id: string, schema: Schema.Schema<S, SI, never>) {
106
+ const actorStorage = yield* getOrCreateStorage(id);
107
107
 
108
- if (Option.isNone(actorStorage.snapshot)) {
109
- return Option.none();
110
- }
108
+ if (Option.isNone(actorStorage.snapshot)) {
109
+ return Option.none();
110
+ }
111
111
 
112
- const stored = actorStorage.snapshot.value;
112
+ const stored = actorStorage.snapshot.value;
113
113
 
114
- // Decode state using schema
115
- const decoded = yield* Schema.decode(schema)(stored.data as SI).pipe(
116
- Effect.mapError(
117
- (cause) =>
118
- new PersistenceError({
119
- operation: "loadSnapshot",
120
- actorId: id,
121
- cause,
122
- message: "Failed to decode state",
123
- }),
124
- ),
125
- );
114
+ // Decode state using schema
115
+ const decoded = yield* Schema.decode(schema)(stored.data as SI).pipe(
116
+ Effect.mapError(
117
+ (cause) =>
118
+ new PersistenceError({
119
+ operation: "loadSnapshot",
120
+ actorId: id,
121
+ cause,
122
+ message: "Failed to decode state",
123
+ }),
124
+ ),
125
+ );
126
126
 
127
- return Option.some({
128
- state: decoded,
129
- version: stored.version,
130
- timestamp: stored.timestamp,
131
- });
132
- }),
127
+ return Option.some({
128
+ state: decoded,
129
+ version: stored.version,
130
+ timestamp: stored.timestamp,
131
+ });
132
+ });
133
133
 
134
- appendEvent: <E, EI>(
135
- id: string,
136
- event: PersistedEvent<E>,
137
- schema: Schema.Schema<E, EI, never>,
138
- ): Effect.Effect<void, PersistenceError> =>
139
- Effect.gen(function* () {
140
- yield* getOrCreateStorage(id);
134
+ const appendEvent = Effect.fn("effect-machine.persistence.inMemory.appendEvent")(function* <
135
+ E,
136
+ EI,
137
+ >(id: string, event: PersistedEvent<E>, schema: Schema.Schema<E, EI, never>) {
138
+ yield* getOrCreateStorage(id);
141
139
 
142
- // Encode event using schema
143
- const encoded = yield* Schema.encode(schema)(event.event).pipe(
144
- Effect.mapError(
145
- (cause) =>
146
- new PersistenceError({
147
- operation: "appendEvent",
148
- actorId: id,
149
- cause,
150
- message: "Failed to encode event",
151
- }),
152
- ),
153
- );
140
+ // Encode event using schema
141
+ const encoded = yield* Schema.encode(schema)(event.event).pipe(
142
+ Effect.mapError(
143
+ (cause) =>
144
+ new PersistenceError({
145
+ operation: "appendEvent",
146
+ actorId: id,
147
+ cause,
148
+ message: "Failed to encode event",
149
+ }),
150
+ ),
151
+ );
154
152
 
155
- yield* updateStorage(id, (s) => ({
156
- ...s,
157
- events: [
158
- ...s.events,
159
- {
160
- data: encoded,
161
- version: event.version,
162
- timestamp: event.timestamp,
163
- },
164
- ],
165
- }));
166
- }),
153
+ yield* updateStorage(id, (s) => ({
154
+ ...s,
155
+ events: [
156
+ ...s.events,
157
+ {
158
+ data: encoded,
159
+ version: event.version,
160
+ timestamp: event.timestamp,
161
+ },
162
+ ],
163
+ }));
164
+ });
167
165
 
168
- loadEvents: <E, EI>(
169
- id: string,
170
- schema: Schema.Schema<E, EI, never>,
171
- afterVersion?: number,
172
- ): Effect.Effect<ReadonlyArray<PersistedEvent<E>>, PersistenceError> =>
173
- Effect.gen(function* () {
174
- const actorStorage = yield* getOrCreateStorage(id);
166
+ const loadEvents = Effect.fn("effect-machine.persistence.inMemory.loadEvents")(function* <E, EI>(
167
+ id: string,
168
+ schema: Schema.Schema<E, EI, never>,
169
+ afterVersion?: number,
170
+ ) {
171
+ const actorStorage = yield* getOrCreateStorage(id);
175
172
 
176
- // Single pass - skip filtered events inline instead of creating intermediate array
177
- const decoded: PersistedEvent<E>[] = [];
178
- for (const stored of actorStorage.events) {
179
- if (afterVersion !== undefined && stored.version <= afterVersion) continue;
173
+ // Single pass - skip filtered events inline instead of creating intermediate array
174
+ const decoded: PersistedEvent<E>[] = [];
175
+ for (const stored of actorStorage.events) {
176
+ if (afterVersion !== undefined && stored.version <= afterVersion) continue;
180
177
 
181
- const event = yield* Schema.decode(schema)(stored.data as EI).pipe(
182
- Effect.mapError(
183
- (cause) =>
184
- new PersistenceError({
185
- operation: "loadEvents",
186
- actorId: id,
187
- cause,
188
- message: "Failed to decode event",
189
- }),
190
- ),
191
- );
192
- decoded.push({
193
- event,
194
- version: stored.version,
195
- timestamp: stored.timestamp,
196
- });
197
- }
178
+ const event = yield* Schema.decode(schema)(stored.data as EI).pipe(
179
+ Effect.mapError(
180
+ (cause) =>
181
+ new PersistenceError({
182
+ operation: "loadEvents",
183
+ actorId: id,
184
+ cause,
185
+ message: "Failed to decode event",
186
+ }),
187
+ ),
188
+ );
189
+ decoded.push({
190
+ event,
191
+ version: stored.version,
192
+ timestamp: stored.timestamp,
193
+ });
194
+ }
198
195
 
199
- return decoded;
200
- }),
196
+ return decoded;
197
+ });
201
198
 
202
- deleteActor: (id: string): Effect.Effect<void, PersistenceError> =>
203
- Effect.gen(function* () {
204
- yield* Ref.update(storage, (map) => {
205
- const newMap = new Map(map);
206
- newMap.delete(id);
207
- return newMap;
208
- });
209
- // Also delete metadata
210
- yield* Ref.update(registry, (map) => {
211
- const newMap = new Map(map);
212
- newMap.delete(id);
213
- return newMap;
214
- });
215
- }),
199
+ const deleteActor = Effect.fn("effect-machine.persistence.inMemory.deleteActor")(function* (
200
+ id: string,
201
+ ) {
202
+ yield* Ref.update(storage, (map) => {
203
+ const newMap = new Map(map);
204
+ newMap.delete(id);
205
+ return newMap;
206
+ });
207
+ // Also delete metadata
208
+ yield* Ref.update(registry, (map) => {
209
+ const newMap = new Map(map);
210
+ newMap.delete(id);
211
+ return newMap;
212
+ });
213
+ });
216
214
 
217
- // Registry methods for actor discovery
215
+ const listActors = Effect.fn("effect-machine.persistence.inMemory.listActors")(function* () {
216
+ const map = yield* Ref.get(registry);
217
+ return Array.from(map.values());
218
+ });
218
219
 
219
- listActors: (): Effect.Effect<ReadonlyArray<ActorMetadata>, PersistenceError> =>
220
- Effect.map(Ref.get(registry), (map) => Array.from(map.values())),
220
+ const saveMetadata = Effect.fn("effect-machine.persistence.inMemory.saveMetadata")(function* (
221
+ metadata: ActorMetadata,
222
+ ) {
223
+ yield* Ref.update(registry, (map) => {
224
+ const newMap = new Map(map);
225
+ newMap.set(metadata.id, metadata);
226
+ return newMap;
227
+ });
228
+ });
221
229
 
222
- saveMetadata: (metadata: ActorMetadata): Effect.Effect<void, PersistenceError> =>
223
- Ref.update(registry, (map) => {
224
- const newMap = new Map(map);
225
- newMap.set(metadata.id, metadata);
226
- return newMap;
227
- }),
230
+ const deleteMetadata = Effect.fn("effect-machine.persistence.inMemory.deleteMetadata")(function* (
231
+ id: string,
232
+ ) {
233
+ yield* Ref.update(registry, (map) => {
234
+ const newMap = new Map(map);
235
+ newMap.delete(id);
236
+ return newMap;
237
+ });
238
+ });
228
239
 
229
- deleteMetadata: (id: string): Effect.Effect<void, PersistenceError> =>
230
- Ref.update(registry, (map) => {
231
- const newMap = new Map(map);
232
- newMap.delete(id);
233
- return newMap;
234
- }),
240
+ const loadMetadata = Effect.fn("effect-machine.persistence.inMemory.loadMetadata")(function* (
241
+ id: string,
242
+ ) {
243
+ const map = yield* Ref.get(registry);
244
+ const meta = map.get(id);
245
+ return meta !== undefined ? Option.some(meta) : Option.none();
246
+ });
235
247
 
236
- loadMetadata: (id: string): Effect.Effect<Option.Option<ActorMetadata>, PersistenceError> =>
237
- Effect.map(Ref.get(registry), (map) => {
238
- const meta = map.get(id);
239
- return meta !== undefined ? Option.some(meta) : Option.none();
240
- }),
248
+ const adapter: PersistenceAdapter = {
249
+ saveSnapshot,
250
+ loadSnapshot,
251
+ appendEvent,
252
+ loadEvents,
253
+ deleteActor,
254
+
255
+ // Registry methods for actor discovery
256
+ listActors,
257
+ saveMetadata,
258
+ deleteMetadata,
259
+ loadMetadata,
241
260
  };
242
261
 
243
262
  return adapter;
244
- });
263
+ }).pipe(Effect.withSpan("effect-machine.persistence.inMemory.make"));
245
264
 
246
265
  /**
247
266
  * Create an in-memory persistence adapter effect.