@rotorsoft/act 0.24.0 → 0.25.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.
package/dist/index.js CHANGED
@@ -1,5 +1,104 @@
1
- // src/ports.ts
2
- import { pino } from "pino";
1
+ // src/adapters/ConsoleLogger.ts
2
+ var LEVEL_VALUES = {
3
+ fatal: 60,
4
+ error: 50,
5
+ warn: 40,
6
+ info: 30,
7
+ debug: 20,
8
+ trace: 10
9
+ };
10
+ var LEVEL_COLORS = {
11
+ fatal: "\x1B[41m\x1B[37m",
12
+ // white on red bg
13
+ error: "\x1B[31m",
14
+ // red
15
+ warn: "\x1B[33m",
16
+ // yellow
17
+ info: "\x1B[32m",
18
+ // green
19
+ debug: "\x1B[36m",
20
+ // cyan
21
+ trace: "\x1B[90m"
22
+ // gray
23
+ };
24
+ var RESET = "\x1B[0m";
25
+ var noop = () => {
26
+ };
27
+ var ConsoleLogger = class _ConsoleLogger {
28
+ level;
29
+ _pretty;
30
+ fatal;
31
+ error;
32
+ warn;
33
+ info;
34
+ debug;
35
+ trace;
36
+ constructor(options = {}) {
37
+ const {
38
+ level = "info",
39
+ pretty = process.env.NODE_ENV !== "production",
40
+ bindings
41
+ } = options;
42
+ this._pretty = pretty;
43
+ this.level = level;
44
+ const threshold = LEVEL_VALUES[level] ?? 30;
45
+ const write = pretty ? this._prettyWrite.bind(this, bindings) : this._jsonWrite.bind(this, bindings);
46
+ this.fatal = write.bind(this, "fatal", 60);
47
+ this.error = threshold <= 50 ? write.bind(this, "error", 50) : noop;
48
+ this.warn = threshold <= 40 ? write.bind(this, "warn", 40) : noop;
49
+ this.info = threshold <= 30 ? write.bind(this, "info", 30) : noop;
50
+ this.debug = threshold <= 20 ? write.bind(this, "debug", 20) : noop;
51
+ this.trace = threshold <= 10 ? write.bind(this, "trace", 10) : noop;
52
+ }
53
+ async dispose() {
54
+ }
55
+ child(bindings) {
56
+ return new _ConsoleLogger({
57
+ level: this.level,
58
+ pretty: this._pretty,
59
+ bindings
60
+ });
61
+ }
62
+ _jsonWrite(bindings, level, _num, objOrMsg, msg) {
63
+ let obj;
64
+ let message;
65
+ if (typeof objOrMsg === "string") {
66
+ message = objOrMsg;
67
+ obj = {};
68
+ } else if (objOrMsg !== null && typeof objOrMsg === "object") {
69
+ message = msg;
70
+ obj = Object.fromEntries(Object.entries(objOrMsg));
71
+ } else {
72
+ message = msg;
73
+ obj = { value: objOrMsg };
74
+ }
75
+ const entry = Object.assign({ level, time: Date.now() }, bindings, obj);
76
+ if (message) entry.msg = message;
77
+ process.stdout.write(JSON.stringify(entry) + "\n");
78
+ }
79
+ _prettyWrite(bindings, level, _num, objOrMsg, msg) {
80
+ const color = LEVEL_COLORS[level];
81
+ const tag = `${color}${level.toUpperCase().padEnd(5)}${RESET}`;
82
+ const ts = (/* @__PURE__ */ new Date()).toISOString().slice(11, 23);
83
+ let message;
84
+ let data;
85
+ if (typeof objOrMsg === "string") {
86
+ message = objOrMsg;
87
+ } else {
88
+ message = msg ?? "";
89
+ if (objOrMsg !== void 0 && objOrMsg !== null) {
90
+ try {
91
+ data = JSON.stringify(objOrMsg);
92
+ } catch {
93
+ data = "[unserializable]";
94
+ }
95
+ }
96
+ }
97
+ const bindStr = bindings && Object.keys(bindings).length ? ` ${JSON.stringify(bindings)}` : "";
98
+ const parts = [ts, tag, message, data, bindStr].filter(Boolean);
99
+ process.stdout.write(parts.join(" ") + "\n");
100
+ }
101
+ };
3
102
 
4
103
  // src/adapters/InMemoryCache.ts
5
104
  var InMemoryCache = class {
@@ -467,28 +566,30 @@ var InMemoryStore = class {
467
566
 
468
567
  // src/ports.ts
469
568
  var ExitCodes = ["ERROR", "EXIT"];
470
- var logger = pino({
471
- transport: config().env !== "production" ? {
472
- target: "pino-pretty",
473
- options: {
474
- ignore: "pid,hostname",
475
- singleLine: config().logSingleLine,
476
- colorize: true
477
- }
478
- } : void 0,
479
- level: config().logLevel
480
- });
481
569
  var adapters = /* @__PURE__ */ new Map();
482
570
  function port(injector) {
483
571
  return function(adapter) {
484
572
  if (!adapters.has(injector.name)) {
485
573
  const injected = injector(adapter);
486
574
  adapters.set(injector.name, injected);
487
- logger.info(`\u{1F50C} injected ${injector.name}:${injected.constructor.name}`);
575
+ console.log(`[act] + ${injector.name}:${injected.constructor.name}`);
488
576
  }
489
577
  return adapters.get(injector.name);
490
578
  };
491
579
  }
580
+ var log = port(function log2(adapter) {
581
+ const cfg = config();
582
+ return adapter || new ConsoleLogger({
583
+ level: cfg.logLevel,
584
+ pretty: cfg.env !== "production"
585
+ });
586
+ });
587
+ var store = port(function store2(adapter) {
588
+ return adapter || new InMemoryStore();
589
+ });
590
+ var cache = port(function cache2(adapter) {
591
+ return adapter || new InMemoryCache();
592
+ });
492
593
  var disposers = [];
493
594
  async function disposeAndExit(code = "EXIT") {
494
595
  if (code === "ERROR" && config().env === "production") return;
@@ -496,7 +597,7 @@ async function disposeAndExit(code = "EXIT") {
496
597
  await Promise.all(
497
598
  [...adapters.values()].reverse().map(async (adapter) => {
498
599
  await adapter.dispose();
499
- logger.info(`\u{1F50C} disposed ${adapter.constructor.name}`);
600
+ console.log(`[act] - ${adapter.constructor.name}`);
500
601
  })
501
602
  );
502
603
  adapters.clear();
@@ -507,14 +608,9 @@ function dispose(disposer) {
507
608
  return disposeAndExit;
508
609
  }
509
610
  var SNAP_EVENT = "__snapshot__";
510
- var store = port(function store2(adapter) {
511
- return adapter || new InMemoryStore();
512
- });
513
- var cache = port(function cache2(adapter) {
514
- return adapter || new InMemoryCache();
515
- });
516
611
  function build_tracer(logLevel2) {
517
612
  if (logLevel2 === "trace") {
613
+ const logger4 = log();
518
614
  return {
519
615
  fetched: (fetched) => {
520
616
  const data = Object.fromEntries(
@@ -526,23 +622,23 @@ function build_tracer(logLevel2) {
526
622
  return [key, value];
527
623
  })
528
624
  );
529
- logger.trace(data, "\u26A1\uFE0F fetch");
625
+ logger4.trace(data, ">> fetch");
530
626
  },
531
627
  correlated: (streams) => {
532
628
  const data = streams.map(({ stream }) => stream).join(" ");
533
- logger.trace(`\u26A1\uFE0F correlate ${data}`);
629
+ logger4.trace(`>> correlate ${data}`);
534
630
  },
535
631
  leased: (leases) => {
536
632
  const data = Object.fromEntries(
537
633
  leases.map(({ stream, at, retry }) => [stream, { at, retry }])
538
634
  );
539
- logger.trace(data, "\u26A1\uFE0F lease");
635
+ logger4.trace(data, ">> lease");
540
636
  },
541
637
  acked: (leases) => {
542
638
  const data = Object.fromEntries(
543
639
  leases.map(({ stream, at, retry }) => [stream, { at, retry }])
544
640
  );
545
- logger.trace(data, "\u26A1\uFE0F ack");
641
+ logger4.trace(data, ">> ack");
546
642
  },
547
643
  blocked: (leases) => {
548
644
  const data = Object.fromEntries(
@@ -551,7 +647,7 @@ function build_tracer(logLevel2) {
551
647
  { at, retry, error }
552
648
  ])
553
649
  );
554
- logger.trace(data, "\u26A1\uFE0F block");
650
+ logger4.trace(data, ">> block");
555
651
  }
556
652
  };
557
653
  } else {
@@ -571,6 +667,7 @@ function build_tracer(logLevel2) {
571
667
  }
572
668
 
573
669
  // src/signals.ts
670
+ var logger = log();
574
671
  process.once("SIGINT", async (arg) => {
575
672
  logger.info(arg, "SIGINT");
576
673
  await disposeAndExit("EXIT");
@@ -595,6 +692,7 @@ import EventEmitter from "events";
595
692
  // src/event-sourcing.ts
596
693
  import { patch } from "@rotorsoft/act-patch";
597
694
  import { randomUUID } from "crypto";
695
+ var logger2 = log();
598
696
  async function snap(snapshot) {
599
697
  try {
600
698
  const { id, stream, name, meta, version } = snapshot.event;
@@ -608,9 +706,9 @@ async function snap(snapshot) {
608
706
  version
609
707
  // IMPORTANT! - state events are committed right after the snapshot event
610
708
  );
611
- logger.trace(snapped, "\u{1F7E0} snap");
709
+ logger2.trace(snapped, "\u{1F7E0} snap");
612
710
  } catch (error) {
613
- logger.error(error);
711
+ logger2.error(error);
614
712
  }
615
713
  }
616
714
  async function load(me, stream, callback) {
@@ -634,7 +732,7 @@ async function load(me, stream, callback) {
634
732
  },
635
733
  { stream, with_snaps: !cached, after: cached?.event_id }
636
734
  );
637
- logger.trace(
735
+ logger2.trace(
638
736
  state2,
639
737
  `\u{1F7E2} load ${stream}${cached && count === 0 ? " (cached)" : ""}`
640
738
  );
@@ -646,7 +744,7 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
646
744
  payload = skipValidation ? payload : validate(action2, payload, me.actions[action2]);
647
745
  const snapshot = await load(me, stream);
648
746
  const expected = expectedVersion || snapshot.event?.version;
649
- logger.trace(
747
+ logger2.trace(
650
748
  payload,
651
749
  `\u{1F535} ${stream}.${action2}${typeof expected === "number" ? `.${expected}` : ""}`
652
750
  );
@@ -689,7 +787,7 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
689
787
  } : void 0
690
788
  }
691
789
  };
692
- logger.trace(
790
+ logger2.trace(
693
791
  emitted.map((e) => e.data),
694
792
  `\u{1F534} commit ${stream}.${emitted.map((e) => e.name).join(", ")}`
695
793
  );
@@ -729,6 +827,7 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
729
827
  }
730
828
 
731
829
  // src/act.ts
830
+ var logger3 = log();
732
831
  var tracer = build_tracer(config().logLevel);
733
832
  var Act = class {
734
833
  constructor(registry, _states = /* @__PURE__ */ new Map()) {
@@ -1009,7 +1108,7 @@ var Act = class {
1009
1108
  if (payloads.length === 0) return { lease, handled: 0, at: lease.at };
1010
1109
  const stream = lease.stream;
1011
1110
  let at = payloads.at(0).event.id, handled = 0;
1012
- lease.retry > 0 && logger.warn(`Retrying ${stream}@${at} (${lease.retry}).`);
1111
+ lease.retry > 0 && logger3.warn(`Retrying ${stream}@${at} (${lease.retry}).`);
1013
1112
  for (const payload of payloads) {
1014
1113
  const { event, handler, options } = payload;
1015
1114
  try {
@@ -1017,9 +1116,9 @@ var Act = class {
1017
1116
  at = event.id;
1018
1117
  handled++;
1019
1118
  } catch (error) {
1020
- logger.error(error);
1119
+ logger3.error(error);
1021
1120
  const block = lease.retry >= options.maxRetries && options.blockOnError;
1022
- block && logger.error(`Blocking ${stream} after ${lease.retry} retries.`);
1121
+ block && logger3.error(`Blocking ${stream} after ${lease.retry} retries.`);
1023
1122
  return {
1024
1123
  lease,
1025
1124
  handled,
@@ -1163,7 +1262,7 @@ var Act = class {
1163
1262
  this._needs_drain = false;
1164
1263
  return result;
1165
1264
  } catch (error) {
1166
- logger.error(error);
1265
+ logger3.error(error);
1167
1266
  } finally {
1168
1267
  this._drain_locked = false;
1169
1268
  }
@@ -1436,7 +1535,7 @@ var Act = class {
1436
1535
  if (!lastDrain.acked.length && !lastDrain.blocked.length) break;
1437
1536
  }
1438
1537
  if (lastDrain) this.emit("settled", lastDrain);
1439
- })().catch((err) => logger.error(err)).finally(() => {
1538
+ })().catch((err) => logger3.error(err)).finally(() => {
1440
1539
  this._settling = false;
1441
1540
  });
1442
1541
  }, debounceMs);
@@ -1483,15 +1582,35 @@ function registerState(state2, states, actions, events) {
1483
1582
  }
1484
1583
  for (const name of Object.keys(state2.events)) {
1485
1584
  if (existing.events[name] === state2.events[name]) continue;
1585
+ if (existing.events[name]) continue;
1486
1586
  if (events[name]) throw new Error(`Duplicate event "${name}"`);
1487
1587
  }
1588
+ const mergedPatch = { ...existing.patch };
1589
+ for (const name of Object.keys(state2.patch)) {
1590
+ const existingP = existing.patch[name];
1591
+ const incomingP = state2.patch[name];
1592
+ if (!existingP) {
1593
+ mergedPatch[name] = incomingP;
1594
+ } else {
1595
+ const existingIsDefault = existingP._passthrough;
1596
+ const incomingIsDefault = incomingP._passthrough;
1597
+ if (!existingIsDefault && !incomingIsDefault && existingP !== incomingP) {
1598
+ throw new Error(
1599
+ `Duplicate custom patch for event "${name}" in state "${state2.name}"`
1600
+ );
1601
+ }
1602
+ if (existingIsDefault && !incomingIsDefault) {
1603
+ mergedPatch[name] = incomingP;
1604
+ }
1605
+ }
1606
+ }
1488
1607
  const merged = {
1489
1608
  ...existing,
1490
1609
  state: mergeSchemas(existing.state, state2.state, state2.name),
1491
1610
  init: mergeInits(existing.init, state2.init),
1492
1611
  events: { ...existing.events, ...state2.events },
1493
1612
  actions: { ...existing.actions, ...state2.actions },
1494
- patch: { ...existing.patch, ...state2.patch },
1613
+ patch: mergedPatch,
1495
1614
  on: { ...existing.on, ...state2.on },
1496
1615
  given: { ...existing.given, ...state2.given },
1497
1616
  snap: state2.snap || existing.snap
@@ -1772,10 +1891,11 @@ function state(entry) {
1772
1891
  return {
1773
1892
  emits(events) {
1774
1893
  const defaultPatch = Object.fromEntries(
1775
- Object.keys(events).map((k) => [
1776
- k,
1777
- ({ data }) => data
1778
- ])
1894
+ Object.keys(events).map((k) => {
1895
+ const fn = ({ data }) => data;
1896
+ fn._passthrough = true;
1897
+ return [k, fn];
1898
+ })
1779
1899
  );
1780
1900
  const builder = action_builder({
1781
1901
  events,
@@ -1856,6 +1976,7 @@ export {
1856
1976
  CausationEventSchema,
1857
1977
  CommittedMetaSchema,
1858
1978
  ConcurrencyError,
1979
+ ConsoleLogger,
1859
1980
  Environments,
1860
1981
  Errors,
1861
1982
  EventMetaSchema,
@@ -1877,7 +1998,7 @@ export {
1877
1998
  dispose,
1878
1999
  disposeAndExit,
1879
2000
  extend,
1880
- logger,
2001
+ log,
1881
2002
  port,
1882
2003
  projection,
1883
2004
  sleep,