@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.js CHANGED
@@ -1,6 +1,5 @@
1
- // src/config.ts
2
- import * as fs from "fs";
3
- import { z as z2 } from "zod/v4";
1
+ // src/ports.ts
2
+ import { pino } from "pino";
4
3
 
5
4
  // src/types/errors.ts
6
5
  var Errors = {
@@ -37,6 +36,13 @@ var ConcurrencyError = class extends Error {
37
36
  }
38
37
  };
39
38
 
39
+ // src/utils.ts
40
+ import { prettifyError } from "zod/v4";
41
+
42
+ // src/config.ts
43
+ import * as fs from "fs";
44
+ import { z as z2 } from "zod/v4";
45
+
40
46
  // src/types/schemas.ts
41
47
  import { z } from "zod/v4";
42
48
  var ZodEmpty = z.record(z.string(), z.never());
@@ -115,8 +121,36 @@ var LogLevels = [
115
121
  "trace"
116
122
  ];
117
123
 
124
+ // src/config.ts
125
+ var PackageSchema = z2.object({
126
+ name: z2.string().min(1),
127
+ version: z2.string().min(1),
128
+ description: z2.string().min(1).optional(),
129
+ author: z2.object({ name: z2.string().min(1), email: z2.string().optional() }).optional().or(z2.string().min(1)).optional(),
130
+ license: z2.string().min(1).optional(),
131
+ dependencies: z2.record(z2.string(), z2.string()).optional()
132
+ });
133
+ var getPackage = () => {
134
+ const pkg2 = fs.readFileSync("package.json");
135
+ return JSON.parse(pkg2.toString());
136
+ };
137
+ var BaseSchema = PackageSchema.extend({
138
+ env: z2.enum(Environments),
139
+ logLevel: z2.enum(LogLevels),
140
+ logSingleLine: z2.boolean(),
141
+ sleepMs: z2.number().int().min(0).max(5e3)
142
+ });
143
+ var { NODE_ENV, LOG_LEVEL, LOG_SINGLE_LINE, SLEEP_MS } = process.env;
144
+ var env = NODE_ENV || "development";
145
+ var logLevel = LOG_LEVEL || (NODE_ENV === "test" ? "error" : LOG_LEVEL === "production" ? "info" : "trace");
146
+ var logSingleLine = (LOG_SINGLE_LINE || "true") === "true";
147
+ var sleepMs = parseInt(NODE_ENV === "test" ? "0" : SLEEP_MS ?? "100");
148
+ var pkg = getPackage();
149
+ var config = () => {
150
+ return extend({ ...pkg, env, logLevel, logSingleLine, sleepMs }, BaseSchema);
151
+ };
152
+
118
153
  // src/utils.ts
119
- import { prettifyError } from "zod/v4";
120
154
  var UNMERGEABLES = [
121
155
  RegExp,
122
156
  Date,
@@ -173,38 +207,6 @@ async function sleep(ms) {
173
207
  return new Promise((resolve) => setTimeout(resolve, ms ?? config().sleepMs));
174
208
  }
175
209
 
176
- // src/config.ts
177
- var PackageSchema = z2.object({
178
- name: z2.string().min(1),
179
- version: z2.string().min(1),
180
- description: z2.string().min(1),
181
- author: z2.object({ name: z2.string().min(1), email: z2.string().optional() }).or(z2.string().min(1)),
182
- license: z2.string().min(1),
183
- dependencies: z2.record(z2.string(), z2.string())
184
- });
185
- var getPackage = () => {
186
- const pkg2 = fs.readFileSync("package.json");
187
- return JSON.parse(pkg2.toString());
188
- };
189
- var BaseSchema = PackageSchema.extend({
190
- env: z2.enum(Environments),
191
- logLevel: z2.enum(LogLevels),
192
- logSingleLine: z2.boolean(),
193
- sleepMs: z2.number().int().min(0).max(5e3)
194
- });
195
- var { NODE_ENV, LOG_LEVEL, LOG_SINGLE_LINE, SLEEP_MS } = process.env;
196
- var env = NODE_ENV || "development";
197
- var logLevel = LOG_LEVEL || (NODE_ENV === "test" ? "error" : LOG_LEVEL === "production" ? "info" : "trace");
198
- var logSingleLine = (LOG_SINGLE_LINE || "true") === "true";
199
- var sleepMs = parseInt(NODE_ENV === "test" ? "0" : SLEEP_MS ?? "100");
200
- var pkg = getPackage();
201
- var config = () => {
202
- return extend({ ...pkg, env, logLevel, logSingleLine, sleepMs }, BaseSchema);
203
- };
204
-
205
- // src/ports.ts
206
- import { pino } from "pino";
207
-
208
210
  // src/adapters/InMemoryStore.ts
209
211
  var InMemoryStream = class {
210
212
  constructor(stream) {
@@ -214,12 +216,21 @@ var InMemoryStream = class {
214
216
  _retry = -1;
215
217
  _lease;
216
218
  _blocked = false;
219
+ /**
220
+ * Attempt to lease this stream for processing.
221
+ * @param lease - Lease request.
222
+ * @returns The granted lease or undefined if blocked.
223
+ */
217
224
  lease(lease) {
218
225
  if (!this._blocked && lease.at > this._at) {
219
226
  this._lease = { ...lease, retry: this._retry + 1 };
220
227
  return this._lease;
221
228
  }
222
229
  }
230
+ /**
231
+ * Acknowledge completion of processing for this stream.
232
+ * @param lease - Lease to acknowledge.
233
+ */
223
234
  ack(lease) {
224
235
  if (this._lease && lease.at >= this._at) {
225
236
  this._retry = lease.retry;
@@ -237,17 +248,35 @@ var InMemoryStore = class {
237
248
  _events = [];
238
249
  // stored stream positions and other metadata
239
250
  _streams = /* @__PURE__ */ new Map();
251
+ /**
252
+ * Dispose of the store and clear all events.
253
+ * @returns Promise that resolves when disposal is complete.
254
+ */
240
255
  async dispose() {
241
256
  await sleep();
242
257
  this._events.length = 0;
243
258
  }
259
+ /**
260
+ * Seed the store with initial data (no-op for in-memory).
261
+ * @returns Promise that resolves when seeding is complete.
262
+ */
244
263
  async seed() {
245
264
  await sleep();
246
265
  }
266
+ /**
267
+ * Drop all data from the store.
268
+ * @returns Promise that resolves when the store is cleared.
269
+ */
247
270
  async drop() {
248
271
  await sleep();
249
272
  this._events.length = 0;
250
273
  }
274
+ /**
275
+ * Query events in the store, optionally filtered by query options.
276
+ * @param callback - Function to call for each event.
277
+ * @param query - Optional query options.
278
+ * @returns The number of events processed.
279
+ */
251
280
  async query(callback, query) {
252
281
  await sleep();
253
282
  const {
@@ -275,10 +304,19 @@ var InMemoryStore = class {
275
304
  }
276
305
  return count;
277
306
  }
307
+ /**
308
+ * Commit one or more events to a stream.
309
+ * @param stream - The stream name.
310
+ * @param msgs - The events/messages to commit.
311
+ * @param meta - Event metadata.
312
+ * @param expectedVersion - Optional optimistic concurrency check.
313
+ * @returns The committed events with metadata.
314
+ * @throws ConcurrencyError if expectedVersion does not match.
315
+ */
278
316
  async commit(stream, msgs, meta, expectedVersion) {
279
317
  await sleep();
280
318
  const instance = this._events.filter((e) => e.stream === stream);
281
- if (expectedVersion && instance.length - 1 !== expectedVersion)
319
+ if (typeof expectedVersion === "number" && instance.length - 1 !== expectedVersion)
282
320
  throw new ConcurrencyError(
283
321
  instance.length - 1,
284
322
  msgs,
@@ -301,7 +339,9 @@ var InMemoryStore = class {
301
339
  });
302
340
  }
303
341
  /**
304
- * Fetches new events from stream watermarks
342
+ * Fetches new events from stream watermarks for processing.
343
+ * @param limit - Maximum number of streams to fetch.
344
+ * @returns Fetched streams and events.
305
345
  */
306
346
  async fetch(limit) {
307
347
  const streams = [...this._streams.values()].filter((s) => !s._blocked).sort((a, b) => a._at - b._at).slice(0, limit);
@@ -310,9 +350,17 @@ var InMemoryStore = class {
310
350
  Number.MAX_SAFE_INTEGER
311
351
  ) : -1;
312
352
  const events = [];
313
- await this.query((e) => events.push(e), { after, limit });
353
+ await this.query((e) => e.name !== SNAP_EVENT && events.push(e), {
354
+ after,
355
+ limit
356
+ });
314
357
  return { streams: streams.map(({ stream }) => stream), events };
315
358
  }
359
+ /**
360
+ * Lease streams for processing (e.g., for distributed consumers).
361
+ * @param leases - Lease requests.
362
+ * @returns Granted leases.
363
+ */
316
364
  async lease(leases) {
317
365
  await sleep();
318
366
  return leases.map((lease) => {
@@ -321,6 +369,10 @@ var InMemoryStore = class {
321
369
  return stream.lease(lease);
322
370
  }).filter((l) => !!l);
323
371
  }
372
+ /**
373
+ * Acknowledge completion of processing for leased streams.
374
+ * @param leases - Leases to acknowledge.
375
+ */
324
376
  async ack(leases) {
325
377
  await sleep();
326
378
  leases.forEach((lease) => this._streams.get(lease.stream)?.ack(lease));
@@ -373,6 +425,24 @@ var store = port(function store2(adapter) {
373
425
  return adapter || new InMemoryStore();
374
426
  });
375
427
 
428
+ // src/signals.ts
429
+ process.once("SIGINT", async (arg) => {
430
+ logger.info(arg, "SIGINT");
431
+ await disposeAndExit("EXIT");
432
+ });
433
+ process.once("SIGTERM", async (arg) => {
434
+ logger.info(arg, "SIGTERM");
435
+ await disposeAndExit("EXIT");
436
+ });
437
+ process.once("uncaughtException", async (arg) => {
438
+ logger.error(arg, "Uncaught Exception");
439
+ await disposeAndExit("ERROR");
440
+ });
441
+ process.once("unhandledRejection", async (arg) => {
442
+ logger.error(arg, "Unhandled Rejection");
443
+ await disposeAndExit("ERROR");
444
+ });
445
+
376
446
  // src/act.ts
377
447
  import { randomUUID as randomUUID2 } from "crypto";
378
448
  import EventEmitter from "events";
@@ -488,6 +558,12 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
488
558
 
489
559
  // src/act.ts
490
560
  var Act = class {
561
+ /**
562
+ * Create a new Act orchestrator.
563
+ *
564
+ * @param registry The registry of state, event, and action schemas
565
+ * @param drainLimit The maximum number of events to drain per cycle
566
+ */
491
567
  constructor(registry, drainLimit) {
492
568
  this.registry = registry;
493
569
  this.drainLimit = drainLimit;
@@ -505,17 +581,18 @@ var Act = class {
505
581
  return this;
506
582
  }
507
583
  /**
508
- * Executes an action and emits an event to be committed by the store.
584
+ * Executes an action (command) against a state machine, emitting and committing the resulting event(s).
509
585
  *
510
586
  * @template K The type of action to execute
511
- * @template T The type of target
512
- * @template P The type of payloads
513
- * @param action The action to execute
514
- * @param target The target of the action
515
- * @param payload The payload of the action
516
- * @param reactingTo The event that the action is reacting to
517
- * @param skipValidation Whether to skip validation
518
- * @returns The snapshot of the committed Event
587
+ * @param action The action name (key of the action schema)
588
+ * @param target The target (stream and actor) for the action
589
+ * @param payload The action payload (validated against the schema)
590
+ * @param reactingTo (Optional) The event this action is reacting to
591
+ * @param skipValidation (Optional) If true, skips schema validation (not recommended)
592
+ * @returns The snapshot of the committed event
593
+ *
594
+ * @example
595
+ * await app.do("increment", { stream: "counter1", actor }, { by: 1 });
519
596
  */
520
597
  async do(action2, target, payload, reactingTo, skipValidation = false) {
521
598
  const snapshot = await action(
@@ -530,25 +607,31 @@ var Act = class {
530
607
  return snapshot;
531
608
  }
532
609
  /**
533
- * Loads a snapshot of the state from the store.
610
+ * Loads the current state snapshot for a given state machine and stream.
534
611
  *
535
612
  * @template SX The type of state
536
613
  * @template EX The type of events
537
614
  * @template AX The type of actions
538
- * @param state The state to load
539
- * @param stream The stream to load
540
- * @param callback The callback to call with the snapshot
615
+ * @param state The state machine definition
616
+ * @param stream The stream (instance) to load
617
+ * @param callback (Optional) Callback to receive the loaded snapshot
541
618
  * @returns The snapshot of the loaded state
619
+ *
620
+ * @example
621
+ * const snapshot = await app.load(Counter, "counter1");
542
622
  */
543
623
  async load(state2, stream, callback) {
544
624
  return await load(state2, stream, callback);
545
625
  }
546
626
  /**
547
- * Queries the store for events.
627
+ * Query the event store for events matching a filter.
628
+ *
629
+ * @param query The query filter (e.g., by stream, event name, or time range)
630
+ * @param callback (Optional) Callback for each event found
631
+ * @returns An object with the first and last event found, and the total count
548
632
  *
549
- * @param query The query to execute
550
- * @param callback The callback to call with the events
551
- * @returns The query result
633
+ * @example
634
+ * const { count } = await app.query({ stream: "counter1" }, (event) => console.log(event));
552
635
  */
553
636
  async query(query, callback) {
554
637
  let first = void 0, last = void 0;
@@ -562,6 +645,7 @@ var Act = class {
562
645
  /**
563
646
  * Handles leased reactions.
564
647
  *
648
+ * @internal
565
649
  * @param lease The lease to handle
566
650
  * @param reactions The reactions to handle
567
651
  * @returns The lease
@@ -591,9 +675,14 @@ var Act = class {
591
675
  }
592
676
  drainLocked = false;
593
677
  /**
594
- * Drains events from the store.
678
+ * Drains and processes events from the store, triggering reactions and updating state.
595
679
  *
596
- * @returns The number of drained events
680
+ * This is typically called in a background loop or after committing new events.
681
+ *
682
+ * @returns The number of events drained and processed
683
+ *
684
+ * @example
685
+ * await app.drain();
597
686
  */
598
687
  async drain() {
599
688
  if (this.drainLocked) return 0;
@@ -610,14 +699,17 @@ var Act = class {
610
699
  );
611
700
  const resolved = new Set(streams);
612
701
  const correlated = /* @__PURE__ */ new Map();
613
- for (const event of events)
614
- for (const reaction of this.registry.events[event.name].reactions.values()) {
702
+ for (const event of events) {
703
+ const register = this.registry.events[event.name];
704
+ if (!register) continue;
705
+ for (const reaction of register.reactions.values()) {
615
706
  const stream = typeof reaction.resolver === "string" ? reaction.resolver : reaction.resolver(event);
616
707
  if (stream) {
617
708
  resolved.add(stream);
618
709
  (correlated.get(stream) || correlated.set(stream, []).get(stream)).push({ ...reaction, event });
619
710
  }
620
711
  }
712
+ }
621
713
  const last = events.at(-1).id;
622
714
  const leases = [...resolved.values()].map((stream) => ({
623
715
  by: randomUUID2(),
@@ -813,24 +905,6 @@ function action_builder(state2) {
813
905
  }
814
906
  };
815
907
  }
816
-
817
- // src/index.ts
818
- process.once("SIGINT", async (arg) => {
819
- logger.info(arg, "SIGINT");
820
- await disposeAndExit("EXIT");
821
- });
822
- process.once("SIGTERM", async (arg) => {
823
- logger.info(arg, "SIGTERM");
824
- await disposeAndExit("EXIT");
825
- });
826
- process.once("uncaughtException", async (arg) => {
827
- logger.error(arg, "Uncaught Exception");
828
- await disposeAndExit("ERROR");
829
- });
830
- process.once("unhandledRejection", async (arg) => {
831
- logger.error(arg, "Unhandled Rejection");
832
- await disposeAndExit("ERROR");
833
- });
834
908
  export {
835
909
  Act,
836
910
  ActorSchema,
@@ -843,6 +917,7 @@ export {
843
917
  ExitCodes,
844
918
  InvariantError,
845
919
  LogLevels,
920
+ PackageSchema,
846
921
  QuerySchema,
847
922
  SNAP_EVENT,
848
923
  TargetSchema,