@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.js
CHANGED
|
@@ -1,6 +1,42 @@
|
|
|
1
1
|
// src/ports.ts
|
|
2
2
|
import { pino } from "pino";
|
|
3
3
|
|
|
4
|
+
// src/adapters/InMemoryCache.ts
|
|
5
|
+
var InMemoryCache = class {
|
|
6
|
+
_entries = /* @__PURE__ */ new Map();
|
|
7
|
+
_maxSize;
|
|
8
|
+
constructor(options) {
|
|
9
|
+
this._maxSize = options?.maxSize ?? 1e3;
|
|
10
|
+
}
|
|
11
|
+
async get(stream) {
|
|
12
|
+
const entry = this._entries.get(stream);
|
|
13
|
+
if (!entry) return void 0;
|
|
14
|
+
this._entries.delete(stream);
|
|
15
|
+
this._entries.set(stream, entry);
|
|
16
|
+
return entry;
|
|
17
|
+
}
|
|
18
|
+
async set(stream, entry) {
|
|
19
|
+
this._entries.delete(stream);
|
|
20
|
+
if (this._entries.size >= this._maxSize) {
|
|
21
|
+
const first = this._entries.keys().next().value;
|
|
22
|
+
this._entries.delete(first);
|
|
23
|
+
}
|
|
24
|
+
this._entries.set(stream, entry);
|
|
25
|
+
}
|
|
26
|
+
async invalidate(stream) {
|
|
27
|
+
this._entries.delete(stream);
|
|
28
|
+
}
|
|
29
|
+
async clear() {
|
|
30
|
+
this._entries.clear();
|
|
31
|
+
}
|
|
32
|
+
async dispose() {
|
|
33
|
+
this._entries.clear();
|
|
34
|
+
}
|
|
35
|
+
get size() {
|
|
36
|
+
return this._entries.size;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
4
40
|
// src/types/errors.ts
|
|
5
41
|
var Errors = {
|
|
6
42
|
ValidationError: "ERR_VALIDATION",
|
|
@@ -355,41 +391,54 @@ var InMemoryStore = class {
|
|
|
355
391
|
});
|
|
356
392
|
}
|
|
357
393
|
/**
|
|
358
|
-
*
|
|
359
|
-
*
|
|
360
|
-
* @param
|
|
361
|
-
* @
|
|
394
|
+
* Atomically discovers and leases streams for processing.
|
|
395
|
+
* Fuses poll + lease into a single operation.
|
|
396
|
+
* @param lagging - Max streams from lagging frontier.
|
|
397
|
+
* @param leading - Max streams from leading frontier.
|
|
398
|
+
* @param by - Lease holder identifier.
|
|
399
|
+
* @param millis - Lease duration in milliseconds.
|
|
400
|
+
* @returns Granted leases.
|
|
362
401
|
*/
|
|
363
|
-
async
|
|
402
|
+
async claim(lagging, leading, by, millis) {
|
|
364
403
|
await sleep();
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
404
|
+
const available = [...this._streams.values()].filter((s) => s.is_avaliable);
|
|
405
|
+
const lag = available.sort((a, b) => a.at - b.at).slice(0, lagging).map((s) => ({
|
|
406
|
+
stream: s.stream,
|
|
407
|
+
source: s.source,
|
|
408
|
+
at: s.at,
|
|
369
409
|
lagging: true
|
|
370
410
|
}));
|
|
371
|
-
const
|
|
372
|
-
stream,
|
|
373
|
-
source,
|
|
374
|
-
at,
|
|
411
|
+
const lead = available.sort((a, b) => b.at - a.at).slice(0, leading).map((s) => ({
|
|
412
|
+
stream: s.stream,
|
|
413
|
+
source: s.source,
|
|
414
|
+
at: s.at,
|
|
375
415
|
lagging: false
|
|
376
416
|
}));
|
|
377
|
-
|
|
417
|
+
const seen = /* @__PURE__ */ new Set();
|
|
418
|
+
const combined = [...lag, ...lead].filter((p) => {
|
|
419
|
+
if (seen.has(p.stream)) return false;
|
|
420
|
+
seen.add(p.stream);
|
|
421
|
+
return true;
|
|
422
|
+
});
|
|
423
|
+
return combined.map(
|
|
424
|
+
(p) => this._streams.get(p.stream)?.lease({ ...p, by, retry: 0 }, millis)
|
|
425
|
+
).filter((l) => !!l);
|
|
378
426
|
}
|
|
379
427
|
/**
|
|
380
|
-
*
|
|
381
|
-
* @param
|
|
382
|
-
* @
|
|
383
|
-
* @returns Granted leases.
|
|
428
|
+
* Registers streams for event processing.
|
|
429
|
+
* @param streams - Streams to register with optional source.
|
|
430
|
+
* @returns Number of newly registered streams.
|
|
384
431
|
*/
|
|
385
|
-
async
|
|
432
|
+
async subscribe(streams) {
|
|
386
433
|
await sleep();
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
434
|
+
let count = 0;
|
|
435
|
+
for (const { stream, source } of streams) {
|
|
436
|
+
if (!this._streams.has(stream)) {
|
|
437
|
+
this._streams.set(stream, new InMemoryStream(stream, source));
|
|
438
|
+
count++;
|
|
390
439
|
}
|
|
391
|
-
|
|
392
|
-
|
|
440
|
+
}
|
|
441
|
+
return count;
|
|
393
442
|
}
|
|
394
443
|
/**
|
|
395
444
|
* Acknowledge completion of processing for leased streams.
|
|
@@ -455,6 +504,9 @@ var SNAP_EVENT = "__snapshot__";
|
|
|
455
504
|
var store = port(function store2(adapter) {
|
|
456
505
|
return adapter || new InMemoryStore();
|
|
457
506
|
});
|
|
507
|
+
var cache = port(function cache2(adapter) {
|
|
508
|
+
return adapter || new InMemoryCache();
|
|
509
|
+
});
|
|
458
510
|
function build_tracer(logLevel2) {
|
|
459
511
|
if (logLevel2 === "trace") {
|
|
460
512
|
return {
|
|
@@ -470,8 +522,8 @@ function build_tracer(logLevel2) {
|
|
|
470
522
|
);
|
|
471
523
|
logger.trace(data, "\u26A1\uFE0F fetch");
|
|
472
524
|
},
|
|
473
|
-
correlated: (
|
|
474
|
-
const data =
|
|
525
|
+
correlated: (streams) => {
|
|
526
|
+
const data = streams.map(({ stream }) => stream).join(" ");
|
|
475
527
|
logger.trace(`\u26A1\uFE0F correlate ${data}`);
|
|
476
528
|
},
|
|
477
529
|
leased: (leases) => {
|
|
@@ -556,11 +608,12 @@ async function snap(snapshot) {
|
|
|
556
608
|
}
|
|
557
609
|
}
|
|
558
610
|
async function load(me, stream, callback) {
|
|
559
|
-
|
|
560
|
-
let
|
|
561
|
-
let
|
|
611
|
+
const cached = await cache().get(stream);
|
|
612
|
+
let state2 = cached?.state ?? (me.init ? me.init() : {});
|
|
613
|
+
let patches = cached?.patches ?? 0;
|
|
614
|
+
let snaps = cached?.snaps ?? 0;
|
|
562
615
|
let event;
|
|
563
|
-
await store().query(
|
|
616
|
+
const count = await store().query(
|
|
564
617
|
(e) => {
|
|
565
618
|
event = e;
|
|
566
619
|
if (e.name === SNAP_EVENT) {
|
|
@@ -573,9 +626,12 @@ async function load(me, stream, callback) {
|
|
|
573
626
|
}
|
|
574
627
|
callback && callback({ event, state: state2, patches, snaps });
|
|
575
628
|
},
|
|
576
|
-
{ stream, with_snaps:
|
|
629
|
+
{ stream, with_snaps: !cached, after: cached?.event_id }
|
|
630
|
+
);
|
|
631
|
+
logger.trace(
|
|
632
|
+
state2,
|
|
633
|
+
`\u{1F7E2} load ${stream}${cached && count === 0 ? " (cached)" : ""}`
|
|
577
634
|
);
|
|
578
|
-
logger.trace(state2, `\u{1F7E2} load ${stream}`);
|
|
579
635
|
return { event, state: state2, patches, snaps };
|
|
580
636
|
}
|
|
581
637
|
async function action(me, action2, target, payload, reactingTo, skipValidation = false) {
|
|
@@ -631,13 +687,21 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
631
687
|
emitted.map((e) => e.data),
|
|
632
688
|
`\u{1F534} commit ${stream}.${emitted.map((e) => e.name).join(", ")}`
|
|
633
689
|
);
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
690
|
+
let committed;
|
|
691
|
+
try {
|
|
692
|
+
committed = await store().commit(
|
|
693
|
+
stream,
|
|
694
|
+
emitted,
|
|
695
|
+
meta,
|
|
696
|
+
// TODO: review reactions not enforcing expected version
|
|
697
|
+
reactingTo ? void 0 : expected
|
|
698
|
+
);
|
|
699
|
+
} catch (error) {
|
|
700
|
+
if (error.name === "ERR_CONCURRENCY") {
|
|
701
|
+
void cache().invalidate(stream);
|
|
702
|
+
}
|
|
703
|
+
throw error;
|
|
704
|
+
}
|
|
641
705
|
let { state: state2, patches } = snapshot;
|
|
642
706
|
const snapshots = committed.map((event) => {
|
|
643
707
|
const p = me.patch[event.name](event, state2);
|
|
@@ -646,7 +710,15 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
646
710
|
return { event, state: state2, patches, snaps: snapshot.snaps, patch: p };
|
|
647
711
|
});
|
|
648
712
|
const last = snapshots.at(-1);
|
|
649
|
-
me.snap && me.snap(last)
|
|
713
|
+
const snapped = me.snap && me.snap(last);
|
|
714
|
+
void cache().set(stream, {
|
|
715
|
+
state: last.state,
|
|
716
|
+
version: last.event.version,
|
|
717
|
+
event_id: last.event.id,
|
|
718
|
+
patches: snapped ? 0 : last.patches,
|
|
719
|
+
snaps: snapped ? last.snaps + 1 : last.snaps
|
|
720
|
+
});
|
|
721
|
+
if (snapped) void snap(last);
|
|
650
722
|
return snapshots;
|
|
651
723
|
}
|
|
652
724
|
|
|
@@ -970,9 +1042,16 @@ var Act = class {
|
|
|
970
1042
|
this._drain_locked = true;
|
|
971
1043
|
const lagging = Math.ceil(streamLimit * this._drain_lag2lead_ratio);
|
|
972
1044
|
const leading = streamLimit - lagging;
|
|
973
|
-
const
|
|
1045
|
+
const leased = await store().claim(
|
|
1046
|
+
lagging,
|
|
1047
|
+
leading,
|
|
1048
|
+
randomUUID2(),
|
|
1049
|
+
leaseMillis
|
|
1050
|
+
);
|
|
1051
|
+
if (!leased.length)
|
|
1052
|
+
return { fetched: [], leased: [], acked: [], blocked: [] };
|
|
974
1053
|
const fetched = await Promise.all(
|
|
975
|
-
|
|
1054
|
+
leased.map(async ({ stream, source, at, lagging: lagging2 }) => {
|
|
976
1055
|
const events = await this.query_array({
|
|
977
1056
|
stream: source,
|
|
978
1057
|
after: at,
|
|
@@ -981,71 +1060,60 @@ var Act = class {
|
|
|
981
1060
|
return { stream, source, at, lagging: lagging2, events };
|
|
982
1061
|
})
|
|
983
1062
|
);
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
const
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
}).map((reaction) => ({ ...reaction, event }));
|
|
999
|
-
});
|
|
1000
|
-
leases.set(stream, {
|
|
1001
|
-
lease: {
|
|
1002
|
-
stream,
|
|
1003
|
-
by: randomUUID2(),
|
|
1004
|
-
at: events.at(-1)?.id || fetch_window_at,
|
|
1005
|
-
// ff when no matching events
|
|
1006
|
-
retry: 0,
|
|
1007
|
-
lagging: lagging2
|
|
1008
|
-
},
|
|
1009
|
-
payloads
|
|
1010
|
-
});
|
|
1063
|
+
tracer.fetched(fetched);
|
|
1064
|
+
const payloadsMap = /* @__PURE__ */ new Map();
|
|
1065
|
+
const fetch_window_at = fetched.reduce(
|
|
1066
|
+
(max, { at, events }) => Math.max(max, events.at(-1)?.id || at),
|
|
1067
|
+
0
|
|
1068
|
+
);
|
|
1069
|
+
fetched.forEach(({ stream, events }) => {
|
|
1070
|
+
const payloads = events.flatMap((event) => {
|
|
1071
|
+
const register = this.registry.events[event.name];
|
|
1072
|
+
if (!register) return [];
|
|
1073
|
+
return [...register.reactions.values()].filter((reaction) => {
|
|
1074
|
+
const resolved = typeof reaction.resolver === "function" ? reaction.resolver(event) : reaction.resolver;
|
|
1075
|
+
return resolved && resolved.target === stream;
|
|
1076
|
+
}).map((reaction) => ({ ...reaction, event }));
|
|
1011
1077
|
});
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1078
|
+
payloadsMap.set(stream, payloads);
|
|
1079
|
+
});
|
|
1080
|
+
tracer.leased(leased);
|
|
1081
|
+
const handled = await Promise.all(
|
|
1082
|
+
leased.map((lease) => {
|
|
1083
|
+
const streamFetch = fetched.find((f) => f.stream === lease.stream);
|
|
1084
|
+
const at = streamFetch?.events.at(-1)?.id || fetch_window_at;
|
|
1085
|
+
return this.handle(
|
|
1086
|
+
{ ...lease, at },
|
|
1087
|
+
payloadsMap.get(lease.stream) || []
|
|
1088
|
+
);
|
|
1089
|
+
})
|
|
1090
|
+
);
|
|
1091
|
+
const [lagging_handled, leading_handled] = handled.reduce(
|
|
1092
|
+
([lagging_handled2, leading_handled2], { lease, handled: handled2 }) => [
|
|
1093
|
+
lagging_handled2 + (lease.lagging ? handled2 : 0),
|
|
1094
|
+
leading_handled2 + (lease.lagging ? 0 : handled2)
|
|
1095
|
+
],
|
|
1096
|
+
[0, 0]
|
|
1097
|
+
);
|
|
1098
|
+
const lagging_avg = lagging > 0 ? lagging_handled / lagging : 0;
|
|
1099
|
+
const leading_avg = leading > 0 ? leading_handled / leading : 0;
|
|
1100
|
+
const total = lagging_avg + leading_avg;
|
|
1101
|
+
this._drain_lag2lead_ratio = total > 0 ? Math.max(0.2, Math.min(0.8, lagging_avg / total)) : 0.5;
|
|
1102
|
+
const acked = await store().ack(
|
|
1103
|
+
handled.filter(({ error }) => !error).map(({ at, lease }) => ({ ...lease, at }))
|
|
1104
|
+
);
|
|
1105
|
+
if (acked.length) {
|
|
1106
|
+
tracer.acked(acked);
|
|
1107
|
+
this.emit("acked", acked);
|
|
1108
|
+
}
|
|
1109
|
+
const blocked = await store().block(
|
|
1110
|
+
handled.filter(({ block }) => block).map(({ lease, error }) => ({ ...lease, error }))
|
|
1111
|
+
);
|
|
1112
|
+
if (blocked.length) {
|
|
1113
|
+
tracer.blocked(blocked);
|
|
1114
|
+
this.emit("blocked", blocked);
|
|
1048
1115
|
}
|
|
1116
|
+
return { fetched, leased, acked, blocked };
|
|
1049
1117
|
} catch (error) {
|
|
1050
1118
|
logger.error(error);
|
|
1051
1119
|
} finally {
|
|
@@ -1108,26 +1176,31 @@ var Act = class {
|
|
|
1108
1176
|
if (register) {
|
|
1109
1177
|
for (const reaction of register.reactions.values()) {
|
|
1110
1178
|
const resolved = typeof reaction.resolver === "function" ? reaction.resolver(event) : reaction.resolver;
|
|
1111
|
-
|
|
1179
|
+
if (resolved) {
|
|
1180
|
+
const entry = correlated.get(resolved.target) || {
|
|
1181
|
+
source: resolved.source,
|
|
1182
|
+
payloads: []
|
|
1183
|
+
};
|
|
1184
|
+
entry.payloads.push({
|
|
1185
|
+
...reaction,
|
|
1186
|
+
source: resolved.source,
|
|
1187
|
+
event
|
|
1188
|
+
});
|
|
1189
|
+
correlated.set(resolved.target, entry);
|
|
1190
|
+
}
|
|
1112
1191
|
}
|
|
1113
1192
|
}
|
|
1114
1193
|
}, query);
|
|
1115
1194
|
if (correlated.size) {
|
|
1116
|
-
const
|
|
1195
|
+
const streams = [...correlated.entries()].map(([stream, { source }]) => ({
|
|
1117
1196
|
stream,
|
|
1118
|
-
|
|
1119
|
-
source: payloads.find((p) => p.source)?.source || void 0,
|
|
1120
|
-
by: randomUUID2(),
|
|
1121
|
-
at: 0,
|
|
1122
|
-
retry: 0,
|
|
1123
|
-
lagging: true,
|
|
1124
|
-
payloads
|
|
1197
|
+
source
|
|
1125
1198
|
}));
|
|
1126
|
-
const
|
|
1127
|
-
|
|
1128
|
-
return {
|
|
1199
|
+
const subscribed = await store().subscribe(streams);
|
|
1200
|
+
subscribed && tracer.correlated(streams);
|
|
1201
|
+
return { subscribed, last_id };
|
|
1129
1202
|
}
|
|
1130
|
-
return {
|
|
1203
|
+
return { subscribed: 0, last_id };
|
|
1131
1204
|
}
|
|
1132
1205
|
/**
|
|
1133
1206
|
* Starts automatic periodic correlation worker for discovering new streams.
|
|
@@ -1191,7 +1264,7 @@ var Act = class {
|
|
|
1191
1264
|
this._correlation_timer = setInterval(
|
|
1192
1265
|
() => this.correlate({ ...query, after, limit }).then((result) => {
|
|
1193
1266
|
after = result.last_id;
|
|
1194
|
-
if (callback && result.
|
|
1267
|
+
if (callback && result.subscribed) callback(result.subscribed);
|
|
1195
1268
|
}).catch(console.error),
|
|
1196
1269
|
frequency
|
|
1197
1270
|
);
|
|
@@ -1275,8 +1348,8 @@ var Act = class {
|
|
|
1275
1348
|
(async () => {
|
|
1276
1349
|
let lastDrain;
|
|
1277
1350
|
for (let i = 0; i < maxPasses; i++) {
|
|
1278
|
-
const {
|
|
1279
|
-
if (
|
|
1351
|
+
const { subscribed } = await this.correlate(correlateQuery);
|
|
1352
|
+
if (subscribed === 0 && i > 0) break;
|
|
1280
1353
|
lastDrain = await this.drain(drainOptions);
|
|
1281
1354
|
if (!lastDrain.acked.length && !lastDrain.blocked.length) break;
|
|
1282
1355
|
}
|
|
@@ -1705,6 +1778,8 @@ export {
|
|
|
1705
1778
|
Errors,
|
|
1706
1779
|
EventMetaSchema,
|
|
1707
1780
|
ExitCodes,
|
|
1781
|
+
InMemoryCache,
|
|
1782
|
+
InMemoryStore,
|
|
1708
1783
|
InvariantError,
|
|
1709
1784
|
LogLevels,
|
|
1710
1785
|
PackageSchema,
|
|
@@ -1715,6 +1790,7 @@ export {
|
|
|
1715
1790
|
ZodEmpty,
|
|
1716
1791
|
act,
|
|
1717
1792
|
build_tracer,
|
|
1793
|
+
cache,
|
|
1718
1794
|
config,
|
|
1719
1795
|
dispose,
|
|
1720
1796
|
disposeAndExit,
|