@rotorsoft/act 0.19.1 → 0.21.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/README.md +44 -13
- package/dist/.tsbuildinfo +1 -1
- package/dist/@types/act.d.ts +2 -2
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/adapters/InMemoryCache.d.ts +29 -0
- package/dist/@types/adapters/InMemoryCache.d.ts.map +1 -0
- package/dist/@types/adapters/InMemoryStore.d.ts +15 -15
- package/dist/@types/adapters/InMemoryStore.d.ts.map +1 -1
- package/dist/@types/adapters/index.d.ts +3 -0
- package/dist/@types/adapters/index.d.ts.map +1 -0
- package/dist/@types/event-sourcing.d.ts +4 -0
- package/dist/@types/event-sourcing.d.ts.map +1 -1
- package/dist/@types/index.d.ts +1 -0
- package/dist/@types/index.d.ts.map +1 -1
- package/dist/@types/ports.d.ts +18 -2
- package/dist/@types/ports.d.ts.map +1 -1
- package/dist/@types/types/ports.d.ts +75 -48
- package/dist/@types/types/ports.d.ts.map +1 -1
- package/dist/@types/types/reaction.d.ts +0 -13
- package/dist/@types/types/reaction.d.ts.map +1 -1
- package/dist/index.cjs +200 -121
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +197 -121
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -39,6 +39,8 @@ __export(index_exports, {
|
|
|
39
39
|
Errors: () => Errors,
|
|
40
40
|
EventMetaSchema: () => EventMetaSchema,
|
|
41
41
|
ExitCodes: () => ExitCodes,
|
|
42
|
+
InMemoryCache: () => InMemoryCache,
|
|
43
|
+
InMemoryStore: () => InMemoryStore,
|
|
42
44
|
InvariantError: () => InvariantError,
|
|
43
45
|
LogLevels: () => LogLevels,
|
|
44
46
|
PackageSchema: () => PackageSchema,
|
|
@@ -49,6 +51,7 @@ __export(index_exports, {
|
|
|
49
51
|
ZodEmpty: () => ZodEmpty,
|
|
50
52
|
act: () => act,
|
|
51
53
|
build_tracer: () => build_tracer,
|
|
54
|
+
cache: () => cache,
|
|
52
55
|
config: () => config,
|
|
53
56
|
dispose: () => dispose,
|
|
54
57
|
disposeAndExit: () => disposeAndExit,
|
|
@@ -67,6 +70,42 @@ module.exports = __toCommonJS(index_exports);
|
|
|
67
70
|
// src/ports.ts
|
|
68
71
|
var import_pino = require("pino");
|
|
69
72
|
|
|
73
|
+
// src/adapters/InMemoryCache.ts
|
|
74
|
+
var InMemoryCache = class {
|
|
75
|
+
_entries = /* @__PURE__ */ new Map();
|
|
76
|
+
_maxSize;
|
|
77
|
+
constructor(options) {
|
|
78
|
+
this._maxSize = options?.maxSize ?? 1e3;
|
|
79
|
+
}
|
|
80
|
+
async get(stream) {
|
|
81
|
+
const entry = this._entries.get(stream);
|
|
82
|
+
if (!entry) return void 0;
|
|
83
|
+
this._entries.delete(stream);
|
|
84
|
+
this._entries.set(stream, entry);
|
|
85
|
+
return entry;
|
|
86
|
+
}
|
|
87
|
+
async set(stream, entry) {
|
|
88
|
+
this._entries.delete(stream);
|
|
89
|
+
if (this._entries.size >= this._maxSize) {
|
|
90
|
+
const first = this._entries.keys().next().value;
|
|
91
|
+
this._entries.delete(first);
|
|
92
|
+
}
|
|
93
|
+
this._entries.set(stream, entry);
|
|
94
|
+
}
|
|
95
|
+
async invalidate(stream) {
|
|
96
|
+
this._entries.delete(stream);
|
|
97
|
+
}
|
|
98
|
+
async clear() {
|
|
99
|
+
this._entries.clear();
|
|
100
|
+
}
|
|
101
|
+
async dispose() {
|
|
102
|
+
this._entries.clear();
|
|
103
|
+
}
|
|
104
|
+
get size() {
|
|
105
|
+
return this._entries.size;
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
70
109
|
// src/types/errors.ts
|
|
71
110
|
var Errors = {
|
|
72
111
|
ValidationError: "ERR_VALIDATION",
|
|
@@ -421,41 +460,54 @@ var InMemoryStore = class {
|
|
|
421
460
|
});
|
|
422
461
|
}
|
|
423
462
|
/**
|
|
424
|
-
*
|
|
425
|
-
*
|
|
426
|
-
* @param
|
|
427
|
-
* @
|
|
463
|
+
* Atomically discovers and leases streams for processing.
|
|
464
|
+
* Fuses poll + lease into a single operation.
|
|
465
|
+
* @param lagging - Max streams from lagging frontier.
|
|
466
|
+
* @param leading - Max streams from leading frontier.
|
|
467
|
+
* @param by - Lease holder identifier.
|
|
468
|
+
* @param millis - Lease duration in milliseconds.
|
|
469
|
+
* @returns Granted leases.
|
|
428
470
|
*/
|
|
429
|
-
async
|
|
471
|
+
async claim(lagging, leading, by, millis) {
|
|
430
472
|
await sleep();
|
|
431
|
-
const
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
473
|
+
const available = [...this._streams.values()].filter((s) => s.is_avaliable);
|
|
474
|
+
const lag = available.sort((a, b) => a.at - b.at).slice(0, lagging).map((s) => ({
|
|
475
|
+
stream: s.stream,
|
|
476
|
+
source: s.source,
|
|
477
|
+
at: s.at,
|
|
435
478
|
lagging: true
|
|
436
479
|
}));
|
|
437
|
-
const
|
|
438
|
-
stream,
|
|
439
|
-
source,
|
|
440
|
-
at,
|
|
480
|
+
const lead = available.sort((a, b) => b.at - a.at).slice(0, leading).map((s) => ({
|
|
481
|
+
stream: s.stream,
|
|
482
|
+
source: s.source,
|
|
483
|
+
at: s.at,
|
|
441
484
|
lagging: false
|
|
442
485
|
}));
|
|
443
|
-
|
|
486
|
+
const seen = /* @__PURE__ */ new Set();
|
|
487
|
+
const combined = [...lag, ...lead].filter((p) => {
|
|
488
|
+
if (seen.has(p.stream)) return false;
|
|
489
|
+
seen.add(p.stream);
|
|
490
|
+
return true;
|
|
491
|
+
});
|
|
492
|
+
return combined.map(
|
|
493
|
+
(p) => this._streams.get(p.stream)?.lease({ ...p, by, retry: 0 }, millis)
|
|
494
|
+
).filter((l) => !!l);
|
|
444
495
|
}
|
|
445
496
|
/**
|
|
446
|
-
*
|
|
447
|
-
* @param
|
|
448
|
-
* @
|
|
449
|
-
* @returns Granted leases.
|
|
497
|
+
* Registers streams for event processing.
|
|
498
|
+
* @param streams - Streams to register with optional source.
|
|
499
|
+
* @returns Number of newly registered streams.
|
|
450
500
|
*/
|
|
451
|
-
async
|
|
501
|
+
async subscribe(streams) {
|
|
452
502
|
await sleep();
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
503
|
+
let count = 0;
|
|
504
|
+
for (const { stream, source } of streams) {
|
|
505
|
+
if (!this._streams.has(stream)) {
|
|
506
|
+
this._streams.set(stream, new InMemoryStream(stream, source));
|
|
507
|
+
count++;
|
|
456
508
|
}
|
|
457
|
-
|
|
458
|
-
|
|
509
|
+
}
|
|
510
|
+
return count;
|
|
459
511
|
}
|
|
460
512
|
/**
|
|
461
513
|
* Acknowledge completion of processing for leased streams.
|
|
@@ -521,6 +573,9 @@ var SNAP_EVENT = "__snapshot__";
|
|
|
521
573
|
var store = port(function store2(adapter) {
|
|
522
574
|
return adapter || new InMemoryStore();
|
|
523
575
|
});
|
|
576
|
+
var cache = port(function cache2(adapter) {
|
|
577
|
+
return adapter || new InMemoryCache();
|
|
578
|
+
});
|
|
524
579
|
function build_tracer(logLevel2) {
|
|
525
580
|
if (logLevel2 === "trace") {
|
|
526
581
|
return {
|
|
@@ -536,8 +591,8 @@ function build_tracer(logLevel2) {
|
|
|
536
591
|
);
|
|
537
592
|
logger.trace(data, "\u26A1\uFE0F fetch");
|
|
538
593
|
},
|
|
539
|
-
correlated: (
|
|
540
|
-
const data =
|
|
594
|
+
correlated: (streams) => {
|
|
595
|
+
const data = streams.map(({ stream }) => stream).join(" ");
|
|
541
596
|
logger.trace(`\u26A1\uFE0F correlate ${data}`);
|
|
542
597
|
},
|
|
543
598
|
leased: (leases) => {
|
|
@@ -622,11 +677,12 @@ async function snap(snapshot) {
|
|
|
622
677
|
}
|
|
623
678
|
}
|
|
624
679
|
async function load(me, stream, callback) {
|
|
625
|
-
|
|
626
|
-
let
|
|
627
|
-
let
|
|
680
|
+
const cached = await cache().get(stream);
|
|
681
|
+
let state2 = cached?.state ?? (me.init ? me.init() : {});
|
|
682
|
+
let patches = cached?.patches ?? 0;
|
|
683
|
+
let snaps = cached?.snaps ?? 0;
|
|
628
684
|
let event;
|
|
629
|
-
await store().query(
|
|
685
|
+
const count = await store().query(
|
|
630
686
|
(e) => {
|
|
631
687
|
event = e;
|
|
632
688
|
if (e.name === SNAP_EVENT) {
|
|
@@ -639,9 +695,12 @@ async function load(me, stream, callback) {
|
|
|
639
695
|
}
|
|
640
696
|
callback && callback({ event, state: state2, patches, snaps });
|
|
641
697
|
},
|
|
642
|
-
{ stream, with_snaps:
|
|
698
|
+
{ stream, with_snaps: !cached, after: cached?.event_id }
|
|
699
|
+
);
|
|
700
|
+
logger.trace(
|
|
701
|
+
state2,
|
|
702
|
+
`\u{1F7E2} load ${stream}${cached && count === 0 ? " (cached)" : ""}`
|
|
643
703
|
);
|
|
644
|
-
logger.trace(state2, `\u{1F7E2} load ${stream}`);
|
|
645
704
|
return { event, state: state2, patches, snaps };
|
|
646
705
|
}
|
|
647
706
|
async function action(me, action2, target, payload, reactingTo, skipValidation = false) {
|
|
@@ -697,13 +756,21 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
697
756
|
emitted.map((e) => e.data),
|
|
698
757
|
`\u{1F534} commit ${stream}.${emitted.map((e) => e.name).join(", ")}`
|
|
699
758
|
);
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
759
|
+
let committed;
|
|
760
|
+
try {
|
|
761
|
+
committed = await store().commit(
|
|
762
|
+
stream,
|
|
763
|
+
emitted,
|
|
764
|
+
meta,
|
|
765
|
+
// TODO: review reactions not enforcing expected version
|
|
766
|
+
reactingTo ? void 0 : expected
|
|
767
|
+
);
|
|
768
|
+
} catch (error) {
|
|
769
|
+
if (error.name === "ERR_CONCURRENCY") {
|
|
770
|
+
void cache().invalidate(stream);
|
|
771
|
+
}
|
|
772
|
+
throw error;
|
|
773
|
+
}
|
|
707
774
|
let { state: state2, patches } = snapshot;
|
|
708
775
|
const snapshots = committed.map((event) => {
|
|
709
776
|
const p = me.patch[event.name](event, state2);
|
|
@@ -712,7 +779,15 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
712
779
|
return { event, state: state2, patches, snaps: snapshot.snaps, patch: p };
|
|
713
780
|
});
|
|
714
781
|
const last = snapshots.at(-1);
|
|
715
|
-
me.snap && me.snap(last)
|
|
782
|
+
const snapped = me.snap && me.snap(last);
|
|
783
|
+
void cache().set(stream, {
|
|
784
|
+
state: last.state,
|
|
785
|
+
version: last.event.version,
|
|
786
|
+
event_id: last.event.id,
|
|
787
|
+
patches: snapped ? 0 : last.patches,
|
|
788
|
+
snaps: snapped ? last.snaps + 1 : last.snaps
|
|
789
|
+
});
|
|
790
|
+
if (snapped) void snap(last);
|
|
716
791
|
return snapshots;
|
|
717
792
|
}
|
|
718
793
|
|
|
@@ -1036,9 +1111,16 @@ var Act = class {
|
|
|
1036
1111
|
this._drain_locked = true;
|
|
1037
1112
|
const lagging = Math.ceil(streamLimit * this._drain_lag2lead_ratio);
|
|
1038
1113
|
const leading = streamLimit - lagging;
|
|
1039
|
-
const
|
|
1114
|
+
const leased = await store().claim(
|
|
1115
|
+
lagging,
|
|
1116
|
+
leading,
|
|
1117
|
+
(0, import_crypto2.randomUUID)(),
|
|
1118
|
+
leaseMillis
|
|
1119
|
+
);
|
|
1120
|
+
if (!leased.length)
|
|
1121
|
+
return { fetched: [], leased: [], acked: [], blocked: [] };
|
|
1040
1122
|
const fetched = await Promise.all(
|
|
1041
|
-
|
|
1123
|
+
leased.map(async ({ stream, source, at, lagging: lagging2 }) => {
|
|
1042
1124
|
const events = await this.query_array({
|
|
1043
1125
|
stream: source,
|
|
1044
1126
|
after: at,
|
|
@@ -1047,71 +1129,60 @@ var Act = class {
|
|
|
1047
1129
|
return { stream, source, at, lagging: lagging2, events };
|
|
1048
1130
|
})
|
|
1049
1131
|
);
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
const
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
}).map((reaction) => ({ ...reaction, event }));
|
|
1065
|
-
});
|
|
1066
|
-
leases.set(stream, {
|
|
1067
|
-
lease: {
|
|
1068
|
-
stream,
|
|
1069
|
-
by: (0, import_crypto2.randomUUID)(),
|
|
1070
|
-
at: events.at(-1)?.id || fetch_window_at,
|
|
1071
|
-
// ff when no matching events
|
|
1072
|
-
retry: 0,
|
|
1073
|
-
lagging: lagging2
|
|
1074
|
-
},
|
|
1075
|
-
payloads
|
|
1076
|
-
});
|
|
1132
|
+
tracer.fetched(fetched);
|
|
1133
|
+
const payloadsMap = /* @__PURE__ */ new Map();
|
|
1134
|
+
const fetch_window_at = fetched.reduce(
|
|
1135
|
+
(max, { at, events }) => Math.max(max, events.at(-1)?.id || at),
|
|
1136
|
+
0
|
|
1137
|
+
);
|
|
1138
|
+
fetched.forEach(({ stream, events }) => {
|
|
1139
|
+
const payloads = events.flatMap((event) => {
|
|
1140
|
+
const register = this.registry.events[event.name];
|
|
1141
|
+
if (!register) return [];
|
|
1142
|
+
return [...register.reactions.values()].filter((reaction) => {
|
|
1143
|
+
const resolved = typeof reaction.resolver === "function" ? reaction.resolver(event) : reaction.resolver;
|
|
1144
|
+
return resolved && resolved.target === stream;
|
|
1145
|
+
}).map((reaction) => ({ ...reaction, event }));
|
|
1077
1146
|
});
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1147
|
+
payloadsMap.set(stream, payloads);
|
|
1148
|
+
});
|
|
1149
|
+
tracer.leased(leased);
|
|
1150
|
+
const handled = await Promise.all(
|
|
1151
|
+
leased.map((lease) => {
|
|
1152
|
+
const streamFetch = fetched.find((f) => f.stream === lease.stream);
|
|
1153
|
+
const at = streamFetch?.events.at(-1)?.id || fetch_window_at;
|
|
1154
|
+
return this.handle(
|
|
1155
|
+
{ ...lease, at },
|
|
1156
|
+
payloadsMap.get(lease.stream) || []
|
|
1157
|
+
);
|
|
1158
|
+
})
|
|
1159
|
+
);
|
|
1160
|
+
const [lagging_handled, leading_handled] = handled.reduce(
|
|
1161
|
+
([lagging_handled2, leading_handled2], { lease, handled: handled2 }) => [
|
|
1162
|
+
lagging_handled2 + (lease.lagging ? handled2 : 0),
|
|
1163
|
+
leading_handled2 + (lease.lagging ? 0 : handled2)
|
|
1164
|
+
],
|
|
1165
|
+
[0, 0]
|
|
1166
|
+
);
|
|
1167
|
+
const lagging_avg = lagging > 0 ? lagging_handled / lagging : 0;
|
|
1168
|
+
const leading_avg = leading > 0 ? leading_handled / leading : 0;
|
|
1169
|
+
const total = lagging_avg + leading_avg;
|
|
1170
|
+
this._drain_lag2lead_ratio = total > 0 ? Math.max(0.2, Math.min(0.8, lagging_avg / total)) : 0.5;
|
|
1171
|
+
const acked = await store().ack(
|
|
1172
|
+
handled.filter(({ error }) => !error).map(({ at, lease }) => ({ ...lease, at }))
|
|
1173
|
+
);
|
|
1174
|
+
if (acked.length) {
|
|
1175
|
+
tracer.acked(acked);
|
|
1176
|
+
this.emit("acked", acked);
|
|
1177
|
+
}
|
|
1178
|
+
const blocked = await store().block(
|
|
1179
|
+
handled.filter(({ block }) => block).map(({ lease, error }) => ({ ...lease, error }))
|
|
1180
|
+
);
|
|
1181
|
+
if (blocked.length) {
|
|
1182
|
+
tracer.blocked(blocked);
|
|
1183
|
+
this.emit("blocked", blocked);
|
|
1114
1184
|
}
|
|
1185
|
+
return { fetched, leased, acked, blocked };
|
|
1115
1186
|
} catch (error) {
|
|
1116
1187
|
logger.error(error);
|
|
1117
1188
|
} finally {
|
|
@@ -1174,26 +1245,31 @@ var Act = class {
|
|
|
1174
1245
|
if (register) {
|
|
1175
1246
|
for (const reaction of register.reactions.values()) {
|
|
1176
1247
|
const resolved = typeof reaction.resolver === "function" ? reaction.resolver(event) : reaction.resolver;
|
|
1177
|
-
|
|
1248
|
+
if (resolved) {
|
|
1249
|
+
const entry = correlated.get(resolved.target) || {
|
|
1250
|
+
source: resolved.source,
|
|
1251
|
+
payloads: []
|
|
1252
|
+
};
|
|
1253
|
+
entry.payloads.push({
|
|
1254
|
+
...reaction,
|
|
1255
|
+
source: resolved.source,
|
|
1256
|
+
event
|
|
1257
|
+
});
|
|
1258
|
+
correlated.set(resolved.target, entry);
|
|
1259
|
+
}
|
|
1178
1260
|
}
|
|
1179
1261
|
}
|
|
1180
1262
|
}, query);
|
|
1181
1263
|
if (correlated.size) {
|
|
1182
|
-
const
|
|
1264
|
+
const streams = [...correlated.entries()].map(([stream, { source }]) => ({
|
|
1183
1265
|
stream,
|
|
1184
|
-
|
|
1185
|
-
source: payloads.find((p) => p.source)?.source || void 0,
|
|
1186
|
-
by: (0, import_crypto2.randomUUID)(),
|
|
1187
|
-
at: 0,
|
|
1188
|
-
retry: 0,
|
|
1189
|
-
lagging: true,
|
|
1190
|
-
payloads
|
|
1266
|
+
source
|
|
1191
1267
|
}));
|
|
1192
|
-
const
|
|
1193
|
-
|
|
1194
|
-
return {
|
|
1268
|
+
const subscribed = await store().subscribe(streams);
|
|
1269
|
+
subscribed && tracer.correlated(streams);
|
|
1270
|
+
return { subscribed, last_id };
|
|
1195
1271
|
}
|
|
1196
|
-
return {
|
|
1272
|
+
return { subscribed: 0, last_id };
|
|
1197
1273
|
}
|
|
1198
1274
|
/**
|
|
1199
1275
|
* Starts automatic periodic correlation worker for discovering new streams.
|
|
@@ -1257,7 +1333,7 @@ var Act = class {
|
|
|
1257
1333
|
this._correlation_timer = setInterval(
|
|
1258
1334
|
() => this.correlate({ ...query, after, limit }).then((result) => {
|
|
1259
1335
|
after = result.last_id;
|
|
1260
|
-
if (callback && result.
|
|
1336
|
+
if (callback && result.subscribed) callback(result.subscribed);
|
|
1261
1337
|
}).catch(console.error),
|
|
1262
1338
|
frequency
|
|
1263
1339
|
);
|
|
@@ -1341,8 +1417,8 @@ var Act = class {
|
|
|
1341
1417
|
(async () => {
|
|
1342
1418
|
let lastDrain;
|
|
1343
1419
|
for (let i = 0; i < maxPasses; i++) {
|
|
1344
|
-
const {
|
|
1345
|
-
if (
|
|
1420
|
+
const { subscribed } = await this.correlate(correlateQuery);
|
|
1421
|
+
if (subscribed === 0 && i > 0) break;
|
|
1346
1422
|
lastDrain = await this.drain(drainOptions);
|
|
1347
1423
|
if (!lastDrain.acked.length && !lastDrain.blocked.length) break;
|
|
1348
1424
|
}
|
|
@@ -1772,6 +1848,8 @@ function action_builder(state2) {
|
|
|
1772
1848
|
Errors,
|
|
1773
1849
|
EventMetaSchema,
|
|
1774
1850
|
ExitCodes,
|
|
1851
|
+
InMemoryCache,
|
|
1852
|
+
InMemoryStore,
|
|
1775
1853
|
InvariantError,
|
|
1776
1854
|
LogLevels,
|
|
1777
1855
|
PackageSchema,
|
|
@@ -1782,6 +1860,7 @@ function action_builder(state2) {
|
|
|
1782
1860
|
ZodEmpty,
|
|
1783
1861
|
act,
|
|
1784
1862
|
build_tracer,
|
|
1863
|
+
cache,
|
|
1785
1864
|
config,
|
|
1786
1865
|
dispose,
|
|
1787
1866
|
disposeAndExit,
|