@rotorsoft/act 0.5.0 → 0.5.2

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 (40) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/@types/act-builder.d.ts +66 -2
  3. package/dist/@types/act-builder.d.ts.map +1 -1
  4. package/dist/@types/act.d.ts +77 -21
  5. package/dist/@types/act.d.ts.map +1 -1
  6. package/dist/@types/adapters/InMemoryStore.d.ts +49 -2
  7. package/dist/@types/adapters/InMemoryStore.d.ts.map +1 -1
  8. package/dist/@types/config.d.ts +34 -12
  9. package/dist/@types/config.d.ts.map +1 -1
  10. package/dist/@types/event-sourcing.d.ts +30 -9
  11. package/dist/@types/event-sourcing.d.ts.map +1 -1
  12. package/dist/@types/index.d.ts +3 -2
  13. package/dist/@types/index.d.ts.map +1 -1
  14. package/dist/@types/ports.d.ts +51 -4
  15. package/dist/@types/ports.d.ts.map +1 -1
  16. package/dist/@types/signals.d.ts +2 -0
  17. package/dist/@types/signals.d.ts.map +1 -0
  18. package/dist/@types/state-builder.d.ts +54 -3
  19. package/dist/@types/state-builder.d.ts.map +1 -1
  20. package/dist/@types/types/action.d.ts +105 -0
  21. package/dist/@types/types/action.d.ts.map +1 -1
  22. package/dist/@types/types/errors.d.ts +33 -4
  23. package/dist/@types/types/errors.d.ts.map +1 -1
  24. package/dist/@types/types/index.d.ts +28 -0
  25. package/dist/@types/types/index.d.ts.map +1 -1
  26. package/dist/@types/types/ports.d.ts +53 -0
  27. package/dist/@types/types/ports.d.ts.map +1 -1
  28. package/dist/@types/types/reaction.d.ts +51 -0
  29. package/dist/@types/types/reaction.d.ts.map +1 -1
  30. package/dist/@types/types/registry.d.ts +27 -0
  31. package/dist/@types/types/registry.d.ts.map +1 -1
  32. package/dist/@types/types/schemas.d.ts +48 -12
  33. package/dist/@types/types/schemas.d.ts.map +1 -1
  34. package/dist/@types/utils.d.ts +46 -5
  35. package/dist/@types/utils.d.ts.map +1 -1
  36. package/dist/index.cjs +155 -79
  37. package/dist/index.cjs.map +1 -1
  38. package/dist/index.js +153 -78
  39. package/dist/index.js.map +1 -1
  40. package/package.json +1 -2
package/dist/index.cjs CHANGED
@@ -41,6 +41,7 @@ __export(index_exports, {
41
41
  ExitCodes: () => ExitCodes,
42
42
  InvariantError: () => InvariantError,
43
43
  LogLevels: () => LogLevels,
44
+ PackageSchema: () => PackageSchema,
44
45
  QuerySchema: () => QuerySchema,
45
46
  SNAP_EVENT: () => SNAP_EVENT,
46
47
  TargetSchema: () => TargetSchema,
@@ -62,9 +63,8 @@ __export(index_exports, {
62
63
  });
63
64
  module.exports = __toCommonJS(index_exports);
64
65
 
65
- // src/config.ts
66
- var fs = __toESM(require("fs"), 1);
67
- var import_v43 = require("zod/v4");
66
+ // src/ports.ts
67
+ var import_pino = require("pino");
68
68
 
69
69
  // src/types/errors.ts
70
70
  var Errors = {
@@ -101,6 +101,13 @@ var ConcurrencyError = class extends Error {
101
101
  }
102
102
  };
103
103
 
104
+ // src/utils.ts
105
+ var import_v43 = require("zod/v4");
106
+
107
+ // src/config.ts
108
+ var fs = __toESM(require("fs"), 1);
109
+ var import_v42 = require("zod/v4");
110
+
104
111
  // src/types/schemas.ts
105
112
  var import_v4 = require("zod/v4");
106
113
  var ZodEmpty = import_v4.z.record(import_v4.z.string(), import_v4.z.never());
@@ -179,8 +186,36 @@ var LogLevels = [
179
186
  "trace"
180
187
  ];
181
188
 
189
+ // src/config.ts
190
+ var PackageSchema = import_v42.z.object({
191
+ name: import_v42.z.string().min(1),
192
+ version: import_v42.z.string().min(1),
193
+ description: import_v42.z.string().min(1).optional(),
194
+ author: import_v42.z.object({ name: import_v42.z.string().min(1), email: import_v42.z.string().optional() }).optional().or(import_v42.z.string().min(1)).optional(),
195
+ license: import_v42.z.string().min(1).optional(),
196
+ dependencies: import_v42.z.record(import_v42.z.string(), import_v42.z.string()).optional()
197
+ });
198
+ var getPackage = () => {
199
+ const pkg2 = fs.readFileSync("package.json");
200
+ return JSON.parse(pkg2.toString());
201
+ };
202
+ var BaseSchema = PackageSchema.extend({
203
+ env: import_v42.z.enum(Environments),
204
+ logLevel: import_v42.z.enum(LogLevels),
205
+ logSingleLine: import_v42.z.boolean(),
206
+ sleepMs: import_v42.z.number().int().min(0).max(5e3)
207
+ });
208
+ var { NODE_ENV, LOG_LEVEL, LOG_SINGLE_LINE, SLEEP_MS } = process.env;
209
+ var env = NODE_ENV || "development";
210
+ var logLevel = LOG_LEVEL || (NODE_ENV === "test" ? "error" : LOG_LEVEL === "production" ? "info" : "trace");
211
+ var logSingleLine = (LOG_SINGLE_LINE || "true") === "true";
212
+ var sleepMs = parseInt(NODE_ENV === "test" ? "0" : SLEEP_MS ?? "100");
213
+ var pkg = getPackage();
214
+ var config = () => {
215
+ return extend({ ...pkg, env, logLevel, logSingleLine, sleepMs }, BaseSchema);
216
+ };
217
+
182
218
  // src/utils.ts
183
- var import_v42 = require("zod/v4");
184
219
  var UNMERGEABLES = [
185
220
  RegExp,
186
221
  Date,
@@ -223,7 +258,7 @@ var validate = (target, payload, schema) => {
223
258
  throw new ValidationError(
224
259
  target,
225
260
  payload,
226
- (0, import_v42.prettifyError)(error)
261
+ (0, import_v43.prettifyError)(error)
227
262
  );
228
263
  }
229
264
  throw new ValidationError(target, payload, error);
@@ -237,38 +272,6 @@ async function sleep(ms) {
237
272
  return new Promise((resolve) => setTimeout(resolve, ms ?? config().sleepMs));
238
273
  }
239
274
 
240
- // src/config.ts
241
- var PackageSchema = import_v43.z.object({
242
- name: import_v43.z.string().min(1),
243
- version: import_v43.z.string().min(1),
244
- description: import_v43.z.string().min(1),
245
- author: import_v43.z.object({ name: import_v43.z.string().min(1), email: import_v43.z.string().optional() }).or(import_v43.z.string().min(1)),
246
- license: import_v43.z.string().min(1),
247
- dependencies: import_v43.z.record(import_v43.z.string(), import_v43.z.string())
248
- });
249
- var getPackage = () => {
250
- const pkg2 = fs.readFileSync("package.json");
251
- return JSON.parse(pkg2.toString());
252
- };
253
- var BaseSchema = PackageSchema.extend({
254
- env: import_v43.z.enum(Environments),
255
- logLevel: import_v43.z.enum(LogLevels),
256
- logSingleLine: import_v43.z.boolean(),
257
- sleepMs: import_v43.z.number().int().min(0).max(5e3)
258
- });
259
- var { NODE_ENV, LOG_LEVEL, LOG_SINGLE_LINE, SLEEP_MS } = process.env;
260
- var env = NODE_ENV || "development";
261
- var logLevel = LOG_LEVEL || (NODE_ENV === "test" ? "error" : LOG_LEVEL === "production" ? "info" : "trace");
262
- var logSingleLine = (LOG_SINGLE_LINE || "true") === "true";
263
- var sleepMs = parseInt(NODE_ENV === "test" ? "0" : SLEEP_MS ?? "100");
264
- var pkg = getPackage();
265
- var config = () => {
266
- return extend({ ...pkg, env, logLevel, logSingleLine, sleepMs }, BaseSchema);
267
- };
268
-
269
- // src/ports.ts
270
- var import_pino = require("pino");
271
-
272
275
  // src/adapters/InMemoryStore.ts
273
276
  var InMemoryStream = class {
274
277
  constructor(stream) {
@@ -278,12 +281,21 @@ var InMemoryStream = class {
278
281
  _retry = -1;
279
282
  _lease;
280
283
  _blocked = false;
284
+ /**
285
+ * Attempt to lease this stream for processing.
286
+ * @param lease - Lease request.
287
+ * @returns The granted lease or undefined if blocked.
288
+ */
281
289
  lease(lease) {
282
290
  if (!this._blocked && lease.at > this._at) {
283
291
  this._lease = { ...lease, retry: this._retry + 1 };
284
292
  return this._lease;
285
293
  }
286
294
  }
295
+ /**
296
+ * Acknowledge completion of processing for this stream.
297
+ * @param lease - Lease to acknowledge.
298
+ */
287
299
  ack(lease) {
288
300
  if (this._lease && lease.at >= this._at) {
289
301
  this._retry = lease.retry;
@@ -301,17 +313,35 @@ var InMemoryStore = class {
301
313
  _events = [];
302
314
  // stored stream positions and other metadata
303
315
  _streams = /* @__PURE__ */ new Map();
316
+ /**
317
+ * Dispose of the store and clear all events.
318
+ * @returns Promise that resolves when disposal is complete.
319
+ */
304
320
  async dispose() {
305
321
  await sleep();
306
322
  this._events.length = 0;
307
323
  }
324
+ /**
325
+ * Seed the store with initial data (no-op for in-memory).
326
+ * @returns Promise that resolves when seeding is complete.
327
+ */
308
328
  async seed() {
309
329
  await sleep();
310
330
  }
331
+ /**
332
+ * Drop all data from the store.
333
+ * @returns Promise that resolves when the store is cleared.
334
+ */
311
335
  async drop() {
312
336
  await sleep();
313
337
  this._events.length = 0;
314
338
  }
339
+ /**
340
+ * Query events in the store, optionally filtered by query options.
341
+ * @param callback - Function to call for each event.
342
+ * @param query - Optional query options.
343
+ * @returns The number of events processed.
344
+ */
315
345
  async query(callback, query) {
316
346
  await sleep();
317
347
  const {
@@ -339,10 +369,19 @@ var InMemoryStore = class {
339
369
  }
340
370
  return count;
341
371
  }
372
+ /**
373
+ * Commit one or more events to a stream.
374
+ * @param stream - The stream name.
375
+ * @param msgs - The events/messages to commit.
376
+ * @param meta - Event metadata.
377
+ * @param expectedVersion - Optional optimistic concurrency check.
378
+ * @returns The committed events with metadata.
379
+ * @throws ConcurrencyError if expectedVersion does not match.
380
+ */
342
381
  async commit(stream, msgs, meta, expectedVersion) {
343
382
  await sleep();
344
383
  const instance = this._events.filter((e) => e.stream === stream);
345
- if (expectedVersion && instance.length - 1 !== expectedVersion)
384
+ if (typeof expectedVersion === "number" && instance.length - 1 !== expectedVersion)
346
385
  throw new ConcurrencyError(
347
386
  instance.length - 1,
348
387
  msgs,
@@ -365,7 +404,9 @@ var InMemoryStore = class {
365
404
  });
366
405
  }
367
406
  /**
368
- * Fetches new events from stream watermarks
407
+ * Fetches new events from stream watermarks for processing.
408
+ * @param limit - Maximum number of streams to fetch.
409
+ * @returns Fetched streams and events.
369
410
  */
370
411
  async fetch(limit) {
371
412
  const streams = [...this._streams.values()].filter((s) => !s._blocked).sort((a, b) => a._at - b._at).slice(0, limit);
@@ -374,9 +415,17 @@ var InMemoryStore = class {
374
415
  Number.MAX_SAFE_INTEGER
375
416
  ) : -1;
376
417
  const events = [];
377
- await this.query((e) => events.push(e), { after, limit });
418
+ await this.query((e) => e.name !== SNAP_EVENT && events.push(e), {
419
+ after,
420
+ limit
421
+ });
378
422
  return { streams: streams.map(({ stream }) => stream), events };
379
423
  }
424
+ /**
425
+ * Lease streams for processing (e.g., for distributed consumers).
426
+ * @param leases - Lease requests.
427
+ * @returns Granted leases.
428
+ */
380
429
  async lease(leases) {
381
430
  await sleep();
382
431
  return leases.map((lease) => {
@@ -385,6 +434,10 @@ var InMemoryStore = class {
385
434
  return stream.lease(lease);
386
435
  }).filter((l) => !!l);
387
436
  }
437
+ /**
438
+ * Acknowledge completion of processing for leased streams.
439
+ * @param leases - Leases to acknowledge.
440
+ */
388
441
  async ack(leases) {
389
442
  await sleep();
390
443
  leases.forEach((lease) => this._streams.get(lease.stream)?.ack(lease));
@@ -437,6 +490,24 @@ var store = port(function store2(adapter) {
437
490
  return adapter || new InMemoryStore();
438
491
  });
439
492
 
493
+ // src/signals.ts
494
+ process.once("SIGINT", async (arg) => {
495
+ logger.info(arg, "SIGINT");
496
+ await disposeAndExit("EXIT");
497
+ });
498
+ process.once("SIGTERM", async (arg) => {
499
+ logger.info(arg, "SIGTERM");
500
+ await disposeAndExit("EXIT");
501
+ });
502
+ process.once("uncaughtException", async (arg) => {
503
+ logger.error(arg, "Uncaught Exception");
504
+ await disposeAndExit("ERROR");
505
+ });
506
+ process.once("unhandledRejection", async (arg) => {
507
+ logger.error(arg, "Unhandled Rejection");
508
+ await disposeAndExit("ERROR");
509
+ });
510
+
440
511
  // src/act.ts
441
512
  var import_crypto2 = require("crypto");
442
513
  var import_events = __toESM(require("events"), 1);
@@ -552,6 +623,12 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
552
623
 
553
624
  // src/act.ts
554
625
  var Act = class {
626
+ /**
627
+ * Create a new Act orchestrator.
628
+ *
629
+ * @param registry The registry of state, event, and action schemas
630
+ * @param drainLimit The maximum number of events to drain per cycle
631
+ */
555
632
  constructor(registry, drainLimit) {
556
633
  this.registry = registry;
557
634
  this.drainLimit = drainLimit;
@@ -569,17 +646,18 @@ var Act = class {
569
646
  return this;
570
647
  }
571
648
  /**
572
- * Executes an action and emits an event to be committed by the store.
649
+ * Executes an action (command) against a state machine, emitting and committing the resulting event(s).
573
650
  *
574
651
  * @template K The type of action to execute
575
- * @template T The type of target
576
- * @template P The type of payloads
577
- * @param action The action to execute
578
- * @param target The target of the action
579
- * @param payload The payload of the action
580
- * @param reactingTo The event that the action is reacting to
581
- * @param skipValidation Whether to skip validation
582
- * @returns The snapshot of the committed Event
652
+ * @param action The action name (key of the action schema)
653
+ * @param target The target (stream and actor) for the action
654
+ * @param payload The action payload (validated against the schema)
655
+ * @param reactingTo (Optional) The event this action is reacting to
656
+ * @param skipValidation (Optional) If true, skips schema validation (not recommended)
657
+ * @returns The snapshot of the committed event
658
+ *
659
+ * @example
660
+ * await app.do("increment", { stream: "counter1", actor }, { by: 1 });
583
661
  */
584
662
  async do(action2, target, payload, reactingTo, skipValidation = false) {
585
663
  const snapshot = await action(
@@ -594,25 +672,31 @@ var Act = class {
594
672
  return snapshot;
595
673
  }
596
674
  /**
597
- * Loads a snapshot of the state from the store.
675
+ * Loads the current state snapshot for a given state machine and stream.
598
676
  *
599
677
  * @template SX The type of state
600
678
  * @template EX The type of events
601
679
  * @template AX The type of actions
602
- * @param state The state to load
603
- * @param stream The stream to load
604
- * @param callback The callback to call with the snapshot
680
+ * @param state The state machine definition
681
+ * @param stream The stream (instance) to load
682
+ * @param callback (Optional) Callback to receive the loaded snapshot
605
683
  * @returns The snapshot of the loaded state
684
+ *
685
+ * @example
686
+ * const snapshot = await app.load(Counter, "counter1");
606
687
  */
607
688
  async load(state2, stream, callback) {
608
689
  return await load(state2, stream, callback);
609
690
  }
610
691
  /**
611
- * Queries the store for events.
692
+ * Query the event store for events matching a filter.
693
+ *
694
+ * @param query The query filter (e.g., by stream, event name, or time range)
695
+ * @param callback (Optional) Callback for each event found
696
+ * @returns An object with the first and last event found, and the total count
612
697
  *
613
- * @param query The query to execute
614
- * @param callback The callback to call with the events
615
- * @returns The query result
698
+ * @example
699
+ * const { count } = await app.query({ stream: "counter1" }, (event) => console.log(event));
616
700
  */
617
701
  async query(query, callback) {
618
702
  let first = void 0, last = void 0;
@@ -626,6 +710,7 @@ var Act = class {
626
710
  /**
627
711
  * Handles leased reactions.
628
712
  *
713
+ * @internal
629
714
  * @param lease The lease to handle
630
715
  * @param reactions The reactions to handle
631
716
  * @returns The lease
@@ -655,9 +740,14 @@ var Act = class {
655
740
  }
656
741
  drainLocked = false;
657
742
  /**
658
- * Drains events from the store.
743
+ * Drains and processes events from the store, triggering reactions and updating state.
659
744
  *
660
- * @returns The number of drained events
745
+ * This is typically called in a background loop or after committing new events.
746
+ *
747
+ * @returns The number of events drained and processed
748
+ *
749
+ * @example
750
+ * await app.drain();
661
751
  */
662
752
  async drain() {
663
753
  if (this.drainLocked) return 0;
@@ -674,14 +764,17 @@ var Act = class {
674
764
  );
675
765
  const resolved = new Set(streams);
676
766
  const correlated = /* @__PURE__ */ new Map();
677
- for (const event of events)
678
- for (const reaction of this.registry.events[event.name].reactions.values()) {
767
+ for (const event of events) {
768
+ const register = this.registry.events[event.name];
769
+ if (!register) continue;
770
+ for (const reaction of register.reactions.values()) {
679
771
  const stream = typeof reaction.resolver === "string" ? reaction.resolver : reaction.resolver(event);
680
772
  if (stream) {
681
773
  resolved.add(stream);
682
774
  (correlated.get(stream) || correlated.set(stream, []).get(stream)).push({ ...reaction, event });
683
775
  }
684
776
  }
777
+ }
685
778
  const last = events.at(-1).id;
686
779
  const leases = [...resolved.values()].map((stream) => ({
687
780
  by: (0, import_crypto2.randomUUID)(),
@@ -877,24 +970,6 @@ function action_builder(state2) {
877
970
  }
878
971
  };
879
972
  }
880
-
881
- // src/index.ts
882
- process.once("SIGINT", async (arg) => {
883
- logger.info(arg, "SIGINT");
884
- await disposeAndExit("EXIT");
885
- });
886
- process.once("SIGTERM", async (arg) => {
887
- logger.info(arg, "SIGTERM");
888
- await disposeAndExit("EXIT");
889
- });
890
- process.once("uncaughtException", async (arg) => {
891
- logger.error(arg, "Uncaught Exception");
892
- await disposeAndExit("ERROR");
893
- });
894
- process.once("unhandledRejection", async (arg) => {
895
- logger.error(arg, "Unhandled Rejection");
896
- await disposeAndExit("ERROR");
897
- });
898
973
  // Annotate the CommonJS export names for ESM import in node:
899
974
  0 && (module.exports = {
900
975
  Act,
@@ -908,6 +983,7 @@ process.once("unhandledRejection", async (arg) => {
908
983
  ExitCodes,
909
984
  InvariantError,
910
985
  LogLevels,
986
+ PackageSchema,
911
987
  QuerySchema,
912
988
  SNAP_EVENT,
913
989
  TargetSchema,