@rotorsoft/act 0.3.0 → 0.4.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.
Files changed (87) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/{act-builder.d.ts → @types/act-builder.d.ts} +17 -2
  3. package/dist/@types/act-builder.d.ts.map +1 -0
  4. package/dist/@types/act.d.ts +75 -0
  5. package/dist/@types/act.d.ts.map +1 -0
  6. package/dist/{adapters → @types/adapters}/InMemoryStore.d.ts +1 -1
  7. package/dist/@types/adapters/InMemoryStore.d.ts.map +1 -0
  8. package/dist/{config.d.ts → @types/config.d.ts} +4 -0
  9. package/dist/@types/config.d.ts.map +1 -0
  10. package/dist/@types/event-sourcing.d.ts +41 -0
  11. package/dist/@types/event-sourcing.d.ts.map +1 -0
  12. package/dist/@types/index.d.ts +13 -0
  13. package/dist/@types/index.d.ts.map +1 -0
  14. package/dist/{ports.d.ts → @types/ports.d.ts} +5 -1
  15. package/dist/@types/ports.d.ts.map +1 -0
  16. package/dist/{state-builder.d.ts → @types/state-builder.d.ts} +1 -1
  17. package/dist/@types/state-builder.d.ts.map +1 -0
  18. package/dist/{types → @types/types}/action.d.ts +1 -1
  19. package/dist/@types/types/action.d.ts.map +1 -0
  20. package/dist/{types → @types/types}/errors.d.ts +1 -1
  21. package/dist/@types/types/errors.d.ts.map +1 -0
  22. package/dist/{types → @types/types}/index.d.ts +6 -6
  23. package/dist/@types/types/index.d.ts.map +1 -0
  24. package/dist/{types → @types/types}/ports.d.ts +2 -2
  25. package/dist/@types/types/ports.d.ts.map +1 -0
  26. package/dist/{types → @types/types}/reaction.d.ts +1 -2
  27. package/dist/@types/types/reaction.d.ts.map +1 -0
  28. package/dist/{types → @types/types}/registry.d.ts +2 -2
  29. package/dist/@types/types/registry.d.ts.map +1 -0
  30. package/dist/{types → @types/types}/schemas.d.ts +4 -4
  31. package/dist/@types/types/schemas.d.ts.map +1 -0
  32. package/dist/{utils.d.ts → @types/utils.d.ts} +4 -1
  33. package/dist/@types/utils.d.ts.map +1 -0
  34. package/dist/index.cjs +928 -0
  35. package/dist/index.cjs.map +1 -0
  36. package/dist/index.js +854 -19
  37. package/dist/index.js.map +1 -1
  38. package/package.json +17 -5
  39. package/dist/act-builder.d.ts.map +0 -1
  40. package/dist/act-builder.js +0 -69
  41. package/dist/act-builder.js.map +0 -1
  42. package/dist/act.d.ts +0 -24
  43. package/dist/act.d.ts.map +0 -1
  44. package/dist/act.js +0 -136
  45. package/dist/act.js.map +0 -1
  46. package/dist/adapters/InMemoryStore.d.ts.map +0 -1
  47. package/dist/adapters/InMemoryStore.js +0 -125
  48. package/dist/adapters/InMemoryStore.js.map +0 -1
  49. package/dist/config.d.ts.map +0 -1
  50. package/dist/config.js +0 -41
  51. package/dist/config.js.map +0 -1
  52. package/dist/event-sourcing.d.ts +0 -5
  53. package/dist/event-sourcing.d.ts.map +0 -1
  54. package/dist/event-sourcing.js +0 -101
  55. package/dist/event-sourcing.js.map +0 -1
  56. package/dist/index.d.ts +0 -10
  57. package/dist/index.d.ts.map +0 -1
  58. package/dist/ports.d.ts.map +0 -1
  59. package/dist/ports.js +0 -56
  60. package/dist/ports.js.map +0 -1
  61. package/dist/state-builder.d.ts.map +0 -1
  62. package/dist/state-builder.js +0 -83
  63. package/dist/state-builder.js.map +0 -1
  64. package/dist/types/action.d.ts.map +0 -1
  65. package/dist/types/action.js +0 -2
  66. package/dist/types/action.js.map +0 -1
  67. package/dist/types/errors.d.ts.map +0 -1
  68. package/dist/types/errors.js +0 -44
  69. package/dist/types/errors.js.map +0 -1
  70. package/dist/types/index.d.ts.map +0 -1
  71. package/dist/types/index.js +0 -17
  72. package/dist/types/index.js.map +0 -1
  73. package/dist/types/ports.d.ts.map +0 -1
  74. package/dist/types/ports.js +0 -2
  75. package/dist/types/ports.js.map +0 -1
  76. package/dist/types/reaction.d.ts.map +0 -1
  77. package/dist/types/reaction.js +0 -2
  78. package/dist/types/reaction.js.map +0 -1
  79. package/dist/types/registry.d.ts.map +0 -1
  80. package/dist/types/registry.js +0 -2
  81. package/dist/types/registry.js.map +0 -1
  82. package/dist/types/schemas.d.ts.map +0 -1
  83. package/dist/types/schemas.js +0 -81
  84. package/dist/types/schemas.js.map +0 -1
  85. package/dist/utils.d.ts.map +0 -1
  86. package/dist/utils.js +0 -73
  87. package/dist/utils.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,28 +1,863 @@
1
- import { config } from "./config";
2
- import { disposeAndExit, logger } from "./ports";
3
- /** @module act */
4
- export * from "./act";
5
- export * from "./act-builder";
6
- export * from "./ports";
7
- export * from "./state-builder";
8
- export * from "./types";
9
- export * from "./utils";
10
- export { config };
11
- // exit on signals
1
+ // src/config.ts
2
+ import * as dotenv from "dotenv";
3
+ import * as fs from "fs";
4
+ import { z as z2 } from "zod/v4";
5
+
6
+ // src/types/errors.ts
7
+ var Errors = {
8
+ ValidationError: "ERR_VALIDATION",
9
+ InvariantError: "ERR_INVARIANT",
10
+ ConcurrencyError: "ERR_CONCURRENCY"
11
+ };
12
+ var ValidationError = class extends Error {
13
+ constructor(target, payload, details) {
14
+ super(`Invalid ${target} payload`);
15
+ this.target = target;
16
+ this.payload = payload;
17
+ this.details = details;
18
+ this.name = Errors.ValidationError;
19
+ }
20
+ };
21
+ var InvariantError = class extends Error {
22
+ details;
23
+ constructor(name, payload, target, description) {
24
+ super(`${name} failed invariant: ${description}`);
25
+ this.name = Errors.InvariantError;
26
+ this.details = { name, payload, target, description };
27
+ }
28
+ };
29
+ var ConcurrencyError = class extends Error {
30
+ constructor(lastVersion, events, expectedVersion) {
31
+ super(
32
+ `Concurrency error committing event "${events.at(0)?.name}". Expected version ${expectedVersion} but found version ${lastVersion}.`
33
+ );
34
+ this.lastVersion = lastVersion;
35
+ this.events = events;
36
+ this.expectedVersion = expectedVersion;
37
+ this.name = Errors.ConcurrencyError;
38
+ }
39
+ };
40
+
41
+ // src/types/schemas.ts
42
+ import { z } from "zod/v4";
43
+ var ZodEmpty = z.record(z.string(), z.never());
44
+ var ActorSchema = z.object({
45
+ id: z.string(),
46
+ name: z.string()
47
+ }).readonly();
48
+ var TargetSchema = z.object({
49
+ stream: z.string(),
50
+ actor: ActorSchema,
51
+ expectedVersion: z.number().optional()
52
+ }).readonly();
53
+ var CausationEventSchema = z.object({
54
+ id: z.number(),
55
+ name: z.string(),
56
+ stream: z.string()
57
+ });
58
+ var EventMetaSchema = z.object({
59
+ correlation: z.string(),
60
+ causation: z.object({
61
+ action: TargetSchema.and(z.object({ name: z.string() })).optional(),
62
+ event: CausationEventSchema.optional()
63
+ })
64
+ }).readonly();
65
+ var CommittedMetaSchema = z.object({
66
+ id: z.number(),
67
+ stream: z.string(),
68
+ version: z.number(),
69
+ created: z.date(),
70
+ meta: EventMetaSchema
71
+ }).readonly();
72
+ function buildSnapshotSchema(s) {
73
+ const events = Object.entries(s.events).map(
74
+ ([name, zod]) => z.object({
75
+ name: z.literal(name),
76
+ data: zod,
77
+ id: z.number(),
78
+ stream: z.string(),
79
+ version: z.number(),
80
+ created: z.date(),
81
+ meta: EventMetaSchema
82
+ })
83
+ );
84
+ return z.object({
85
+ state: s.state.readonly(),
86
+ event: z.union([events[0], events[1], ...events.slice(2)]).optional(),
87
+ patches: z.number(),
88
+ snaps: z.number()
89
+ });
90
+ }
91
+ var QuerySchema = z.object({
92
+ stream: z.string().optional(),
93
+ names: z.string().array().optional(),
94
+ before: z.number().optional(),
95
+ after: z.number().optional(),
96
+ limit: z.number().optional(),
97
+ created_before: z.date().optional(),
98
+ created_after: z.date().optional(),
99
+ backward: z.boolean().optional(),
100
+ correlation: z.string().optional()
101
+ });
102
+
103
+ // src/types/index.ts
104
+ var Environments = [
105
+ "development",
106
+ "test",
107
+ "staging",
108
+ "production"
109
+ ];
110
+ var LogLevels = [
111
+ "fatal",
112
+ "error",
113
+ "warn",
114
+ "info",
115
+ "debug",
116
+ "trace"
117
+ ];
118
+
119
+ // src/utils.ts
120
+ import { prettifyError } from "zod/v4";
121
+ var UNMERGEABLES = [
122
+ RegExp,
123
+ Date,
124
+ Array,
125
+ Map,
126
+ Set,
127
+ WeakMap,
128
+ WeakSet,
129
+ ArrayBuffer,
130
+ SharedArrayBuffer,
131
+ DataView,
132
+ Int8Array,
133
+ Uint8Array,
134
+ Uint8ClampedArray,
135
+ Int16Array,
136
+ Uint16Array,
137
+ Int32Array,
138
+ Uint32Array,
139
+ Float32Array,
140
+ Float64Array
141
+ ];
142
+ var is_mergeable = (value) => !!value && typeof value === "object" && !UNMERGEABLES.some((t) => value instanceof t);
143
+ var patch = (original, patches) => {
144
+ const copy = {};
145
+ Object.keys({ ...original, ...patches }).forEach((key) => {
146
+ const patched_value = patches[key];
147
+ const original_value = original[key];
148
+ const patched = patches && key in patches;
149
+ const deleted = patched && (typeof patched_value === "undefined" || patched_value === null);
150
+ const value = patched && !deleted ? patched_value : original_value;
151
+ !deleted && (copy[key] = is_mergeable(value) ? patch(original_value || {}, patched_value || {}) : value);
152
+ });
153
+ return copy;
154
+ };
155
+ var validate = (target, payload, schema) => {
156
+ try {
157
+ return schema ? schema.parse(payload) : payload;
158
+ } catch (error) {
159
+ if (error instanceof Error && error.name === "ZodError") {
160
+ throw new ValidationError(
161
+ target,
162
+ payload,
163
+ prettifyError(error)
164
+ );
165
+ }
166
+ throw new ValidationError(target, payload, error);
167
+ }
168
+ };
169
+ var extend = (source, schema, target) => {
170
+ const value = validate("config", source, schema);
171
+ return Object.assign(target || {}, value);
172
+ };
173
+ async function sleep(ms) {
174
+ return new Promise((resolve) => setTimeout(resolve, ms ?? config().sleepMs));
175
+ }
176
+
177
+ // src/config.ts
178
+ dotenv.config();
179
+ var PackageSchema = z2.object({
180
+ name: z2.string().min(1),
181
+ version: z2.string().min(1),
182
+ description: z2.string().min(1),
183
+ author: z2.object({ name: z2.string().min(1), email: z2.string().optional() }).or(z2.string().min(1)),
184
+ license: z2.string().min(1),
185
+ dependencies: z2.record(z2.string(), z2.string())
186
+ });
187
+ var getPackage = () => {
188
+ const pkg2 = fs.readFileSync("package.json");
189
+ return JSON.parse(pkg2.toString());
190
+ };
191
+ var BaseSchema = PackageSchema.extend({
192
+ env: z2.enum(Environments),
193
+ logLevel: z2.enum(LogLevels),
194
+ logSingleLine: z2.boolean(),
195
+ sleepMs: z2.number().int().min(0).max(5e3)
196
+ });
197
+ var { NODE_ENV, LOG_LEVEL, LOG_SINGLE_LINE, SLEEP_MS } = process.env;
198
+ var env = NODE_ENV || "development";
199
+ var logLevel = LOG_LEVEL || (NODE_ENV === "test" ? "error" : LOG_LEVEL === "production" ? "info" : "trace");
200
+ var logSingleLine = (LOG_SINGLE_LINE || "true") === "true";
201
+ var sleepMs = parseInt(NODE_ENV === "test" ? "0" : SLEEP_MS ?? "100");
202
+ var pkg = getPackage();
203
+ var config = () => {
204
+ return extend({ ...pkg, env, logLevel, logSingleLine, sleepMs }, BaseSchema);
205
+ };
206
+
207
+ // src/ports.ts
208
+ import { pino } from "pino";
209
+
210
+ // src/adapters/InMemoryStore.ts
211
+ var InMemoryStream = class {
212
+ constructor(stream) {
213
+ this.stream = stream;
214
+ }
215
+ _at = -1;
216
+ _retry = -1;
217
+ _lease;
218
+ _blocked = false;
219
+ lease(lease) {
220
+ if (!this._blocked && lease.at > this._at) {
221
+ this._lease = { ...lease, retry: this._retry + 1 };
222
+ return this._lease;
223
+ }
224
+ }
225
+ ack(lease) {
226
+ if (this._lease && lease.at >= this._at) {
227
+ this._retry = lease.retry;
228
+ this._blocked = lease.block;
229
+ if (!this._blocked && !lease.error) {
230
+ this._at = lease.at;
231
+ this._retry = 0;
232
+ }
233
+ this._lease = void 0;
234
+ }
235
+ }
236
+ };
237
+ var InMemoryStore = class {
238
+ // stored events
239
+ _events = [];
240
+ // stored stream positions and other metadata
241
+ _streams = /* @__PURE__ */ new Map();
242
+ async dispose() {
243
+ await sleep();
244
+ this._events.length = 0;
245
+ }
246
+ async seed() {
247
+ await sleep();
248
+ }
249
+ async drop() {
250
+ await sleep();
251
+ this._events.length = 0;
252
+ }
253
+ async query(callback, query) {
254
+ await sleep();
255
+ const {
256
+ stream,
257
+ names,
258
+ before,
259
+ after = -1,
260
+ limit,
261
+ created_before,
262
+ created_after,
263
+ correlation
264
+ } = query || {};
265
+ let i = after + 1, count = 0;
266
+ while (i < this._events.length) {
267
+ const e = this._events[i++];
268
+ if (stream && e.stream !== stream) continue;
269
+ if (names && !names.includes(e.name)) continue;
270
+ if (correlation && e.meta?.correlation !== correlation) continue;
271
+ if (created_after && e.created <= created_after) continue;
272
+ if (before && e.id >= before) break;
273
+ if (created_before && e.created >= created_before) break;
274
+ callback(e);
275
+ count++;
276
+ if (limit && count >= limit) break;
277
+ }
278
+ return count;
279
+ }
280
+ async commit(stream, msgs, meta, expectedVersion) {
281
+ await sleep();
282
+ const instance = this._events.filter((e) => e.stream === stream);
283
+ if (expectedVersion && instance.length - 1 !== expectedVersion)
284
+ throw new ConcurrencyError(
285
+ instance.length - 1,
286
+ msgs,
287
+ expectedVersion
288
+ );
289
+ let version = instance.length;
290
+ return msgs.map(({ name, data }) => {
291
+ const committed = {
292
+ id: this._events.length,
293
+ stream,
294
+ version,
295
+ created: /* @__PURE__ */ new Date(),
296
+ name,
297
+ data,
298
+ meta
299
+ };
300
+ this._events.push(committed);
301
+ version++;
302
+ return committed;
303
+ });
304
+ }
305
+ /**
306
+ * Fetches new events from stream watermarks
307
+ */
308
+ async fetch(limit) {
309
+ const streams = [...this._streams.values()].filter((s) => !s._blocked).sort((a, b) => a._at - b._at).slice(0, limit);
310
+ const after = streams.length ? streams.reduce(
311
+ (min, s) => Math.min(min, s._at),
312
+ Number.MAX_SAFE_INTEGER
313
+ ) : -1;
314
+ const events = [];
315
+ await this.query((e) => events.push(e), { after, limit });
316
+ return { streams: streams.map(({ stream }) => stream), events };
317
+ }
318
+ async lease(leases) {
319
+ await sleep();
320
+ return leases.map((lease) => {
321
+ const stream = this._streams.get(lease.stream) || // store new correlations
322
+ this._streams.set(lease.stream, new InMemoryStream(lease.stream)).get(lease.stream);
323
+ return stream.lease(lease);
324
+ }).filter((l) => !!l);
325
+ }
326
+ async ack(leases) {
327
+ await sleep();
328
+ leases.forEach((lease) => this._streams.get(lease.stream)?.ack(lease));
329
+ }
330
+ };
331
+
332
+ // src/ports.ts
333
+ var ExitCodes = ["ERROR", "EXIT"];
334
+ var logger = pino({
335
+ transport: config().env !== "production" ? {
336
+ target: "pino-pretty",
337
+ options: {
338
+ ignore: "pid,hostname",
339
+ singleLine: config().logSingleLine,
340
+ colorize: true
341
+ }
342
+ } : void 0,
343
+ level: config().logLevel
344
+ });
345
+ var adapters = /* @__PURE__ */ new Map();
346
+ function port(injector) {
347
+ return function(adapter) {
348
+ if (!adapters.has(injector.name)) {
349
+ const injected = injector(adapter);
350
+ adapters.set(injector.name, injected);
351
+ logger.info(`\u{1F50C} injected ${injector.name}:${injected.constructor.name}`);
352
+ }
353
+ return adapters.get(injector.name);
354
+ };
355
+ }
356
+ var disposers = [];
357
+ async function disposeAndExit(code = "EXIT") {
358
+ if (code === "ERROR" && config().env === "production") return;
359
+ await Promise.all(disposers.map((disposer) => disposer()));
360
+ await Promise.all(
361
+ [...adapters.values()].reverse().map(async (adapter) => {
362
+ await adapter.dispose();
363
+ logger.info(`\u{1F50C} disposed ${adapter.constructor.name}`);
364
+ })
365
+ );
366
+ adapters.clear();
367
+ config().env !== "test" && process.exit(code === "ERROR" ? 1 : 0);
368
+ }
369
+ function dispose(disposer) {
370
+ disposer && disposers.push(disposer);
371
+ return disposeAndExit;
372
+ }
373
+ var SNAP_EVENT = "__snapshot__";
374
+ var store = port(function store2(adapter) {
375
+ return adapter || new InMemoryStore();
376
+ });
377
+
378
+ // src/act.ts
379
+ import { randomUUID as randomUUID2 } from "crypto";
380
+ import EventEmitter from "events";
381
+
382
+ // src/event-sourcing.ts
383
+ import { randomUUID } from "crypto";
384
+ async function snap(snapshot) {
385
+ try {
386
+ const { id, stream, name, meta, version } = snapshot.event;
387
+ const snapped = await store().commit(
388
+ stream,
389
+ [{ name: SNAP_EVENT, data: snapshot.state }],
390
+ {
391
+ correlation: meta.correlation,
392
+ causation: { event: { id, name, stream } }
393
+ },
394
+ version
395
+ // IMPORTANT! - state events are committed right after the snapshot event
396
+ );
397
+ logger.trace(snapped, "\u{1F7E0} snap");
398
+ } catch (error) {
399
+ logger.error(error);
400
+ }
401
+ }
402
+ async function load(me, stream, callback) {
403
+ let state2 = me.init ? me.init() : {};
404
+ let patches = 0;
405
+ let snaps = 0;
406
+ let event;
407
+ await store().query(
408
+ (e) => {
409
+ event = e;
410
+ if (e.name === SNAP_EVENT) {
411
+ state2 = e.data;
412
+ snaps++;
413
+ patches = 0;
414
+ } else if (me.patch[e.name]) {
415
+ state2 = patch(state2, me.patch[e.name](event, state2));
416
+ patches++;
417
+ }
418
+ callback && callback({ event, state: state2, patches, snaps });
419
+ },
420
+ { stream },
421
+ true
422
+ );
423
+ logger.trace({ stream, patches, snaps, state: state2 }, "\u{1F7E2} load");
424
+ return { event, state: state2, patches, snaps };
425
+ }
426
+ async function action(me, action2, target, payload, reactingTo, skipValidation = false) {
427
+ const { stream, expectedVersion, actor } = target;
428
+ if (!stream) throw new Error("Missing target stream");
429
+ payload = skipValidation ? payload : validate(action2, payload, me.actions[action2]);
430
+ logger.trace(
431
+ payload,
432
+ `\u{1F535} ${action2} "${stream}${expectedVersion ? `@${expectedVersion}` : ""}"`
433
+ );
434
+ let snapshot = await load(me, stream);
435
+ if (me.given) {
436
+ const invariants = me.given[action2] || [];
437
+ invariants.forEach(({ valid, description }) => {
438
+ if (!valid(snapshot.state, actor))
439
+ throw new InvariantError(
440
+ action2,
441
+ payload,
442
+ target,
443
+ description
444
+ );
445
+ });
446
+ }
447
+ let { state: state2, patches } = snapshot;
448
+ const result = me.on[action2](payload, state2, target);
449
+ if (!result) return snapshot;
450
+ if (Array.isArray(result) && result.length === 0) {
451
+ return snapshot;
452
+ }
453
+ const tuples = Array.isArray(result[0]) ? result : [result];
454
+ const emitted = tuples.map(([name, data]) => ({
455
+ name,
456
+ data: skipValidation ? data : validate(name, data, me.events[name])
457
+ }));
458
+ const meta = {
459
+ correlation: reactingTo?.meta.correlation || randomUUID(),
460
+ causation: {
461
+ action: {
462
+ name: action2,
463
+ ...target
464
+ // payload: TODO: flag to include action payload in metadata
465
+ // not included by default to avoid large payloads
466
+ },
467
+ event: reactingTo ? {
468
+ id: reactingTo.id,
469
+ name: reactingTo.name,
470
+ stream: reactingTo.stream
471
+ } : void 0
472
+ }
473
+ };
474
+ const committed = await store().commit(
475
+ stream,
476
+ emitted,
477
+ meta,
478
+ // TODO: review reactions not enforcing expected version
479
+ reactingTo ? void 0 : expectedVersion || snapshot.event?.version
480
+ );
481
+ snapshot = committed.map((event) => {
482
+ state2 = patch(state2, me.patch[event.name](event, state2));
483
+ patches++;
484
+ logger.trace({ event, state: state2 }, "\u{1F534} commit");
485
+ return { event, state: state2, patches, snaps: snapshot.snaps };
486
+ }).at(-1);
487
+ me.snap && me.snap(snapshot) && void snap(snapshot);
488
+ return snapshot;
489
+ }
490
+
491
+ // src/act.ts
492
+ var Act = class {
493
+ constructor(registry, drainLimit) {
494
+ this.registry = registry;
495
+ this.drainLimit = drainLimit;
496
+ }
497
+ _emitter = new EventEmitter();
498
+ emit(event, args) {
499
+ return this._emitter.emit(event, args);
500
+ }
501
+ on(event, listener) {
502
+ this._emitter.on(event, listener);
503
+ return this;
504
+ }
505
+ /**
506
+ * Executes an action and emits an event to be committed by the store.
507
+ *
508
+ * @template K The type of action to execute
509
+ * @template T The type of target
510
+ * @template P The type of payloads
511
+ * @param action The action to execute
512
+ * @param target The target of the action
513
+ * @param payload The payload of the action
514
+ * @param reactingTo The event that the action is reacting to
515
+ * @param skipValidation Whether to skip validation
516
+ * @returns The snapshot of the committed Event
517
+ */
518
+ async do(action2, target, payload, reactingTo, skipValidation = false) {
519
+ const snapshot = await action(
520
+ this.registry.actions[action2],
521
+ action2,
522
+ target,
523
+ payload,
524
+ reactingTo,
525
+ skipValidation
526
+ );
527
+ this.emit("committed", snapshot);
528
+ return snapshot;
529
+ }
530
+ /**
531
+ * Loads a snapshot of the state from the store.
532
+ *
533
+ * @template SX The type of state
534
+ * @template EX The type of events
535
+ * @template AX The type of actions
536
+ * @param state The state to load
537
+ * @param stream The stream to load
538
+ * @param callback The callback to call with the snapshot
539
+ * @returns The snapshot of the loaded state
540
+ */
541
+ async load(state2, stream, callback) {
542
+ return await load(state2, stream, callback);
543
+ }
544
+ /**
545
+ * Queries the store for events.
546
+ *
547
+ * @param query The query to execute
548
+ * @param callback The callback to call with the events
549
+ * @returns The query result
550
+ */
551
+ async query(query, callback) {
552
+ let first = void 0, last = void 0;
553
+ const count = await store().query((e) => {
554
+ !first && (first = e);
555
+ last = e;
556
+ callback && callback(e);
557
+ }, query);
558
+ return { first, last, count };
559
+ }
560
+ /**
561
+ * Handles leased reactions.
562
+ *
563
+ * @param lease The lease to handle
564
+ * @param reactions The reactions to handle
565
+ * @returns The lease
566
+ */
567
+ async handle(lease, reactions) {
568
+ const stream = lease.stream;
569
+ lease.retry > 0 && logger.warn(`Retrying ${stream}@${lease.at} (${lease.retry}).`);
570
+ for (const reaction of reactions) {
571
+ const { event, handler, options } = reaction;
572
+ try {
573
+ await handler(event, stream);
574
+ lease.at = event.id;
575
+ } catch (error) {
576
+ lease.error = error;
577
+ if (error instanceof ValidationError)
578
+ logger.error({ stream, error }, error.message);
579
+ else logger.error(error);
580
+ if (lease.retry < options.maxRetries) lease.retry++;
581
+ else if (options.blockOnError) {
582
+ lease.block = true;
583
+ logger.error(`Blocked ${stream} after ${lease.retry} retries.`);
584
+ }
585
+ break;
586
+ }
587
+ }
588
+ return lease;
589
+ }
590
+ drainLocked = false;
591
+ /**
592
+ * Drains events from the store.
593
+ *
594
+ * @returns The number of drained events
595
+ */
596
+ async drain() {
597
+ if (this.drainLocked) return 0;
598
+ this.drainLocked = true;
599
+ const drained = [];
600
+ const { streams, events } = await store().fetch(this.drainLimit);
601
+ if (events.length) {
602
+ logger.trace(
603
+ events.map(({ id, stream, name }) => ({ id, stream, name })).reduce(
604
+ (a, { id, stream, name }) => ({ ...a, [id]: { [stream]: name } }),
605
+ {}
606
+ ),
607
+ "\u26A1\uFE0F fetch"
608
+ );
609
+ const resolved = new Set(streams);
610
+ const correlated = /* @__PURE__ */ new Map();
611
+ for (const event of events)
612
+ for (const reaction of this.registry.events[event.name].reactions.values()) {
613
+ const stream = typeof reaction.resolver === "string" ? reaction.resolver : reaction.resolver(event);
614
+ if (stream) {
615
+ resolved.add(stream);
616
+ (correlated.get(stream) || correlated.set(stream, []).get(stream)).push({ ...reaction, event });
617
+ }
618
+ }
619
+ const last = events.at(-1).id;
620
+ const leases = [...resolved.values()].map((stream) => ({
621
+ by: randomUUID2(),
622
+ stream,
623
+ at: last,
624
+ retry: 0,
625
+ block: false
626
+ }));
627
+ const leased = await store().lease(leases);
628
+ logger.trace(
629
+ leased.map(({ stream, at, retry }) => ({ stream, at, retry })).reduce(
630
+ (a, { stream, at, retry }) => ({ ...a, [stream]: { at, retry } }),
631
+ {}
632
+ ),
633
+ "\u26A1\uFE0F lease"
634
+ );
635
+ const handling = leased.map((lease) => ({
636
+ lease,
637
+ reactions: correlated.get(lease.stream) || []
638
+ })).filter(({ reactions }) => reactions.length);
639
+ if (handling.length) {
640
+ await Promise.allSettled(
641
+ handling.map(({ lease, reactions }) => this.handle(lease, reactions))
642
+ ).then(
643
+ (promise) => {
644
+ promise.forEach((result) => {
645
+ if (result.status === "rejected") logger.error(result.reason);
646
+ else if (!result.value.error) drained.push(result.value);
647
+ });
648
+ },
649
+ (error) => logger.error(error)
650
+ );
651
+ drained.length && this.emit("drained", drained);
652
+ }
653
+ await store().ack(leased);
654
+ logger.trace(
655
+ leased.map(({ stream, at, retry, block, error }) => ({
656
+ stream,
657
+ at,
658
+ retry,
659
+ block,
660
+ error
661
+ })).reduce(
662
+ (a, { stream, at, retry, block, error }) => ({
663
+ ...a,
664
+ [stream]: { at, retry, block, error }
665
+ }),
666
+ {}
667
+ ),
668
+ "\u26A1\uFE0F ack"
669
+ );
670
+ }
671
+ this.drainLocked = false;
672
+ return drained.length;
673
+ }
674
+ };
675
+
676
+ // src/act-builder.ts
677
+ var _this_ = ({ stream }) => stream;
678
+ var _void_ = () => void 0;
679
+ function act(states = /* @__PURE__ */ new Set(), registry = {
680
+ actions: {},
681
+ events: {}
682
+ }) {
683
+ const builder = {
684
+ /**
685
+ * Adds a state to the builder.
686
+ *
687
+ * @template SX The type of state
688
+ * @template EX The type of events
689
+ * @template AX The type of actions
690
+ * @param state The state to add
691
+ * @returns The builder
692
+ */
693
+ with: (state2) => {
694
+ if (!states.has(state2.name)) {
695
+ states.add(state2.name);
696
+ for (const name of Object.keys(state2.actions)) {
697
+ if (registry.actions[name])
698
+ throw new Error(`Duplicate action "${name}"`);
699
+ registry.actions[name] = state2;
700
+ }
701
+ for (const name of Object.keys(state2.events)) {
702
+ if (registry.events[name])
703
+ throw new Error(`Duplicate event "${name}"`);
704
+ registry.events[name] = {
705
+ schema: state2.events[name],
706
+ reactions: /* @__PURE__ */ new Map()
707
+ };
708
+ }
709
+ }
710
+ return act(
711
+ states,
712
+ registry
713
+ );
714
+ },
715
+ /**
716
+ * Adds a reaction to an event.
717
+ *
718
+ * @template K The type of event
719
+ * @param event The event to add a reaction to
720
+ * @returns The builder
721
+ */
722
+ on: (event) => ({
723
+ do: (handler, options) => {
724
+ const reaction = {
725
+ handler,
726
+ resolver: _this_,
727
+ options: {
728
+ blockOnError: options?.blockOnError ?? true,
729
+ maxRetries: options?.maxRetries ?? 3,
730
+ retryDelayMs: options?.retryDelayMs ?? 1e3
731
+ }
732
+ };
733
+ registry.events[event].reactions.set(handler.name, reaction);
734
+ return {
735
+ ...builder,
736
+ to(resolver) {
737
+ registry.events[event].reactions.set(handler.name, {
738
+ ...reaction,
739
+ resolver
740
+ });
741
+ return builder;
742
+ },
743
+ void() {
744
+ registry.events[event].reactions.set(handler.name, {
745
+ ...reaction,
746
+ resolver: _void_
747
+ });
748
+ return builder;
749
+ }
750
+ };
751
+ }
752
+ }),
753
+ build: (drainLimit = 10) => new Act(registry, drainLimit),
754
+ events: registry.events
755
+ };
756
+ return builder;
757
+ }
758
+
759
+ // src/state-builder.ts
760
+ function state(name, state2) {
761
+ return {
762
+ init(init) {
763
+ return {
764
+ emits(events) {
765
+ return {
766
+ patch(patch2) {
767
+ return action_builder({
768
+ events,
769
+ actions: {},
770
+ state: state2,
771
+ name,
772
+ init,
773
+ patch: patch2,
774
+ on: {}
775
+ });
776
+ }
777
+ };
778
+ }
779
+ };
780
+ }
781
+ };
782
+ }
783
+ function action_builder(state2) {
784
+ return {
785
+ on(action2, schema) {
786
+ if (action2 in state2.actions)
787
+ throw new Error(`Duplicate action "${action2}"`);
788
+ const actions = { ...state2.actions, [action2]: schema };
789
+ const on = { ...state2.on };
790
+ const _given = { ...state2.given };
791
+ function given(rules) {
792
+ _given[action2] = rules;
793
+ return { emit };
794
+ }
795
+ function emit(handler) {
796
+ on[action2] = handler;
797
+ return action_builder({
798
+ ...state2,
799
+ actions,
800
+ on,
801
+ given: _given
802
+ });
803
+ }
804
+ return { given, emit };
805
+ },
806
+ snap(snap2) {
807
+ return action_builder({ ...state2, snap: snap2 });
808
+ },
809
+ build() {
810
+ return state2;
811
+ }
812
+ };
813
+ }
814
+
815
+ // src/index.ts
12
816
  process.once("SIGINT", async (arg) => {
13
- logger.info(arg, "SIGINT");
14
- await disposeAndExit("EXIT");
817
+ logger.info(arg, "SIGINT");
818
+ await disposeAndExit("EXIT");
15
819
  });
16
820
  process.once("SIGTERM", async (arg) => {
17
- logger.info(arg, "SIGTERM");
18
- await disposeAndExit("EXIT");
821
+ logger.info(arg, "SIGTERM");
822
+ await disposeAndExit("EXIT");
19
823
  });
20
824
  process.once("uncaughtException", async (arg) => {
21
- logger.error(arg, "Uncaught Exception");
22
- await disposeAndExit("ERROR");
825
+ logger.error(arg, "Uncaught Exception");
826
+ await disposeAndExit("ERROR");
23
827
  });
24
828
  process.once("unhandledRejection", async (arg) => {
25
- logger.error(arg, "Unhandled Rejection");
26
- await disposeAndExit("ERROR");
829
+ logger.error(arg, "Unhandled Rejection");
830
+ await disposeAndExit("ERROR");
27
831
  });
832
+ export {
833
+ Act,
834
+ ActorSchema,
835
+ CausationEventSchema,
836
+ CommittedMetaSchema,
837
+ ConcurrencyError,
838
+ Environments,
839
+ Errors,
840
+ EventMetaSchema,
841
+ ExitCodes,
842
+ InvariantError,
843
+ LogLevels,
844
+ QuerySchema,
845
+ SNAP_EVENT,
846
+ TargetSchema,
847
+ ValidationError,
848
+ ZodEmpty,
849
+ act,
850
+ buildSnapshotSchema,
851
+ config,
852
+ dispose,
853
+ disposeAndExit,
854
+ extend,
855
+ logger,
856
+ patch,
857
+ port,
858
+ sleep,
859
+ state,
860
+ store,
861
+ validate
862
+ };
28
863
  //# sourceMappingURL=index.js.map