@rotorsoft/act 0.20.0 → 0.22.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 +16 -57
- package/dist/.tsbuildinfo +1 -1
- package/dist/@types/act.d.ts +16 -2
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/adapters/InMemoryStore.d.ts +18 -15
- package/dist/@types/adapters/InMemoryStore.d.ts.map +1 -1
- package/dist/@types/ports.d.ts +4 -1
- package/dist/@types/ports.d.ts.map +1 -1
- package/dist/@types/types/ports.d.ts +53 -47
- 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 +193 -125
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +193 -125
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -391,41 +391,58 @@ var InMemoryStore = class {
|
|
|
391
391
|
});
|
|
392
392
|
}
|
|
393
393
|
/**
|
|
394
|
-
*
|
|
395
|
-
*
|
|
396
|
-
* @param
|
|
397
|
-
* @
|
|
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.
|
|
398
401
|
*/
|
|
399
|
-
async
|
|
402
|
+
async claim(lagging, leading, by, millis) {
|
|
400
403
|
await sleep();
|
|
401
|
-
const
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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,
|
|
405
409
|
lagging: true
|
|
406
410
|
}));
|
|
407
|
-
const
|
|
408
|
-
stream,
|
|
409
|
-
source,
|
|
410
|
-
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,
|
|
411
415
|
lagging: false
|
|
412
416
|
}));
|
|
413
|
-
|
|
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);
|
|
414
426
|
}
|
|
415
427
|
/**
|
|
416
|
-
*
|
|
417
|
-
* @param
|
|
418
|
-
* @
|
|
419
|
-
* @returns Granted leases.
|
|
428
|
+
* Registers streams for event processing.
|
|
429
|
+
* @param streams - Streams to register with optional source.
|
|
430
|
+
* @returns subscribed count and current max watermark.
|
|
420
431
|
*/
|
|
421
|
-
async
|
|
432
|
+
async subscribe(streams) {
|
|
422
433
|
await sleep();
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
434
|
+
let subscribed = 0;
|
|
435
|
+
for (const { stream, source } of streams) {
|
|
436
|
+
if (!this._streams.has(stream)) {
|
|
437
|
+
this._streams.set(stream, new InMemoryStream(stream, source));
|
|
438
|
+
subscribed++;
|
|
426
439
|
}
|
|
427
|
-
|
|
428
|
-
|
|
440
|
+
}
|
|
441
|
+
let watermark = -1;
|
|
442
|
+
for (const s of this._streams.values()) {
|
|
443
|
+
if (s.at > watermark) watermark = s.at;
|
|
444
|
+
}
|
|
445
|
+
return { subscribed, watermark };
|
|
429
446
|
}
|
|
430
447
|
/**
|
|
431
448
|
* Acknowledge completion of processing for leased streams.
|
|
@@ -509,8 +526,8 @@ function build_tracer(logLevel2) {
|
|
|
509
526
|
);
|
|
510
527
|
logger.trace(data, "\u26A1\uFE0F fetch");
|
|
511
528
|
},
|
|
512
|
-
correlated: (
|
|
513
|
-
const data =
|
|
529
|
+
correlated: (streams) => {
|
|
530
|
+
const data = streams.map(({ stream }) => stream).join(" ");
|
|
514
531
|
logger.trace(`\u26A1\uFE0F correlate ${data}`);
|
|
515
532
|
},
|
|
516
533
|
leased: (leases) => {
|
|
@@ -712,15 +729,21 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
712
729
|
// src/act.ts
|
|
713
730
|
var tracer = build_tracer(config().logLevel);
|
|
714
731
|
var Act = class {
|
|
715
|
-
/**
|
|
716
|
-
* Create a new Act orchestrator.
|
|
717
|
-
*
|
|
718
|
-
* @param registry The registry of state, event, and action schemas
|
|
719
|
-
* @param states Map of state names to their (potentially merged) state definitions
|
|
720
|
-
*/
|
|
721
732
|
constructor(registry, _states = /* @__PURE__ */ new Map()) {
|
|
722
733
|
this.registry = registry;
|
|
723
734
|
this._states = _states;
|
|
735
|
+
const statics = [];
|
|
736
|
+
for (const register of Object.values(this.registry.events)) {
|
|
737
|
+
for (const reaction of register.reactions.values()) {
|
|
738
|
+
if (typeof reaction.resolver === "function") {
|
|
739
|
+
this._has_dynamic_resolvers = true;
|
|
740
|
+
} else if (reaction.resolver) {
|
|
741
|
+
const r = reaction.resolver;
|
|
742
|
+
statics.push({ stream: r.target, source: r.source });
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
this._static_targets = statics;
|
|
724
747
|
dispose(() => {
|
|
725
748
|
this._emitter.removeAllListeners();
|
|
726
749
|
this.stop_correlations();
|
|
@@ -734,6 +757,10 @@ var Act = class {
|
|
|
734
757
|
_correlation_timer = void 0;
|
|
735
758
|
_settle_timer = void 0;
|
|
736
759
|
_settling = false;
|
|
760
|
+
_correlation_checkpoint = -1;
|
|
761
|
+
_subscribed_statics = /* @__PURE__ */ new Set();
|
|
762
|
+
_has_dynamic_resolvers = false;
|
|
763
|
+
_correlation_initialized = false;
|
|
737
764
|
emit(event, args) {
|
|
738
765
|
return this._emitter.emit(event, args);
|
|
739
766
|
}
|
|
@@ -745,6 +772,14 @@ var Act = class {
|
|
|
745
772
|
this._emitter.off(event, listener);
|
|
746
773
|
return this;
|
|
747
774
|
}
|
|
775
|
+
/**
|
|
776
|
+
* Create a new Act orchestrator.
|
|
777
|
+
*
|
|
778
|
+
* @param registry The registry of state, event, and action schemas
|
|
779
|
+
* @param states Map of state names to their (potentially merged) state definitions
|
|
780
|
+
*/
|
|
781
|
+
/** Static resolver targets collected at build time */
|
|
782
|
+
_static_targets;
|
|
748
783
|
/**
|
|
749
784
|
* Executes an action on a state instance, committing resulting events.
|
|
750
785
|
*
|
|
@@ -1029,9 +1064,16 @@ var Act = class {
|
|
|
1029
1064
|
this._drain_locked = true;
|
|
1030
1065
|
const lagging = Math.ceil(streamLimit * this._drain_lag2lead_ratio);
|
|
1031
1066
|
const leading = streamLimit - lagging;
|
|
1032
|
-
const
|
|
1067
|
+
const leased = await store().claim(
|
|
1068
|
+
lagging,
|
|
1069
|
+
leading,
|
|
1070
|
+
randomUUID2(),
|
|
1071
|
+
leaseMillis
|
|
1072
|
+
);
|
|
1073
|
+
if (!leased.length)
|
|
1074
|
+
return { fetched: [], leased: [], acked: [], blocked: [] };
|
|
1033
1075
|
const fetched = await Promise.all(
|
|
1034
|
-
|
|
1076
|
+
leased.map(async ({ stream, source, at, lagging: lagging2 }) => {
|
|
1035
1077
|
const events = await this.query_array({
|
|
1036
1078
|
stream: source,
|
|
1037
1079
|
after: at,
|
|
@@ -1040,71 +1082,60 @@ var Act = class {
|
|
|
1040
1082
|
return { stream, source, at, lagging: lagging2, events };
|
|
1041
1083
|
})
|
|
1042
1084
|
);
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
const
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
}).map((reaction) => ({ ...reaction, event }));
|
|
1058
|
-
});
|
|
1059
|
-
leases.set(stream, {
|
|
1060
|
-
lease: {
|
|
1061
|
-
stream,
|
|
1062
|
-
by: randomUUID2(),
|
|
1063
|
-
at: events.at(-1)?.id || fetch_window_at,
|
|
1064
|
-
// ff when no matching events
|
|
1065
|
-
retry: 0,
|
|
1066
|
-
lagging: lagging2
|
|
1067
|
-
},
|
|
1068
|
-
payloads
|
|
1069
|
-
});
|
|
1085
|
+
tracer.fetched(fetched);
|
|
1086
|
+
const payloadsMap = /* @__PURE__ */ new Map();
|
|
1087
|
+
const fetch_window_at = fetched.reduce(
|
|
1088
|
+
(max, { at, events }) => Math.max(max, events.at(-1)?.id || at),
|
|
1089
|
+
0
|
|
1090
|
+
);
|
|
1091
|
+
fetched.forEach(({ stream, events }) => {
|
|
1092
|
+
const payloads = events.flatMap((event) => {
|
|
1093
|
+
const register = this.registry.events[event.name];
|
|
1094
|
+
if (!register) return [];
|
|
1095
|
+
return [...register.reactions.values()].filter((reaction) => {
|
|
1096
|
+
const resolved = typeof reaction.resolver === "function" ? reaction.resolver(event) : reaction.resolver;
|
|
1097
|
+
return resolved && resolved.target === stream;
|
|
1098
|
+
}).map((reaction) => ({ ...reaction, event }));
|
|
1070
1099
|
});
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
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
|
-
if (blocked.length) {
|
|
1103
|
-
tracer.blocked(blocked);
|
|
1104
|
-
this.emit("blocked", blocked);
|
|
1105
|
-
}
|
|
1106
|
-
return { fetched, leased, acked, blocked };
|
|
1100
|
+
payloadsMap.set(stream, payloads);
|
|
1101
|
+
});
|
|
1102
|
+
tracer.leased(leased);
|
|
1103
|
+
const handled = await Promise.all(
|
|
1104
|
+
leased.map((lease) => {
|
|
1105
|
+
const streamFetch = fetched.find((f) => f.stream === lease.stream);
|
|
1106
|
+
const at = streamFetch?.events.at(-1)?.id || fetch_window_at;
|
|
1107
|
+
return this.handle(
|
|
1108
|
+
{ ...lease, at },
|
|
1109
|
+
payloadsMap.get(lease.stream) || []
|
|
1110
|
+
);
|
|
1111
|
+
})
|
|
1112
|
+
);
|
|
1113
|
+
const [lagging_handled, leading_handled] = handled.reduce(
|
|
1114
|
+
([lagging_handled2, leading_handled2], { lease, handled: handled2 }) => [
|
|
1115
|
+
lagging_handled2 + (lease.lagging ? handled2 : 0),
|
|
1116
|
+
leading_handled2 + (lease.lagging ? 0 : handled2)
|
|
1117
|
+
],
|
|
1118
|
+
[0, 0]
|
|
1119
|
+
);
|
|
1120
|
+
const lagging_avg = lagging > 0 ? lagging_handled / lagging : 0;
|
|
1121
|
+
const leading_avg = leading > 0 ? leading_handled / leading : 0;
|
|
1122
|
+
const total = lagging_avg + leading_avg;
|
|
1123
|
+
this._drain_lag2lead_ratio = total > 0 ? Math.max(0.2, Math.min(0.8, lagging_avg / total)) : 0.5;
|
|
1124
|
+
const acked = await store().ack(
|
|
1125
|
+
handled.filter(({ error }) => !error).map(({ at, lease }) => ({ ...lease, at }))
|
|
1126
|
+
);
|
|
1127
|
+
if (acked.length) {
|
|
1128
|
+
tracer.acked(acked);
|
|
1129
|
+
this.emit("acked", acked);
|
|
1107
1130
|
}
|
|
1131
|
+
const blocked = await store().block(
|
|
1132
|
+
handled.filter(({ block }) => block).map(({ lease, error }) => ({ ...lease, error }))
|
|
1133
|
+
);
|
|
1134
|
+
if (blocked.length) {
|
|
1135
|
+
tracer.blocked(blocked);
|
|
1136
|
+
this.emit("blocked", blocked);
|
|
1137
|
+
}
|
|
1138
|
+
return { fetched, leased, acked, blocked };
|
|
1108
1139
|
} catch (error) {
|
|
1109
1140
|
logger.error(error);
|
|
1110
1141
|
} finally {
|
|
@@ -1158,35 +1189,70 @@ var Act = class {
|
|
|
1158
1189
|
* @see {@link start_correlations} for automatic periodic correlation
|
|
1159
1190
|
* @see {@link stop_correlations} to stop automatic correlation
|
|
1160
1191
|
*/
|
|
1192
|
+
/**
|
|
1193
|
+
* Initialize correlation state on first call.
|
|
1194
|
+
* - Reads max(at) from store as cold-start checkpoint
|
|
1195
|
+
* - Subscribes static resolver targets (idempotent upsert)
|
|
1196
|
+
* - Populates the subscribed statics set
|
|
1197
|
+
* @internal
|
|
1198
|
+
*/
|
|
1199
|
+
async _init_correlation() {
|
|
1200
|
+
if (this._correlation_initialized) return;
|
|
1201
|
+
this._correlation_initialized = true;
|
|
1202
|
+
const { watermark } = await store().subscribe(this._static_targets);
|
|
1203
|
+
this._correlation_checkpoint = watermark;
|
|
1204
|
+
for (const { stream } of this._static_targets) {
|
|
1205
|
+
this._subscribed_statics.add(stream);
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1161
1208
|
async correlate(query = { after: -1, limit: 10 }) {
|
|
1209
|
+
await this._init_correlation();
|
|
1210
|
+
if (!this._has_dynamic_resolvers)
|
|
1211
|
+
return { subscribed: 0, last_id: this._correlation_checkpoint };
|
|
1212
|
+
const after = Math.max(this._correlation_checkpoint, query.after || -1);
|
|
1162
1213
|
const correlated = /* @__PURE__ */ new Map();
|
|
1163
|
-
let last_id =
|
|
1164
|
-
await store().query(
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
const
|
|
1170
|
-
|
|
1214
|
+
let last_id = after;
|
|
1215
|
+
await store().query(
|
|
1216
|
+
(event) => {
|
|
1217
|
+
last_id = event.id;
|
|
1218
|
+
const register = this.registry.events[event.name];
|
|
1219
|
+
if (register) {
|
|
1220
|
+
for (const reaction of register.reactions.values()) {
|
|
1221
|
+
if (typeof reaction.resolver !== "function") continue;
|
|
1222
|
+
const resolved = reaction.resolver(event);
|
|
1223
|
+
if (resolved && !this._subscribed_statics.has(resolved.target)) {
|
|
1224
|
+
const entry = correlated.get(resolved.target) || {
|
|
1225
|
+
source: resolved.source,
|
|
1226
|
+
payloads: []
|
|
1227
|
+
};
|
|
1228
|
+
entry.payloads.push({
|
|
1229
|
+
...reaction,
|
|
1230
|
+
source: resolved.source,
|
|
1231
|
+
event
|
|
1232
|
+
});
|
|
1233
|
+
correlated.set(resolved.target, entry);
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1171
1236
|
}
|
|
1172
|
-
}
|
|
1173
|
-
|
|
1237
|
+
},
|
|
1238
|
+
{ ...query, after }
|
|
1239
|
+
);
|
|
1240
|
+
this._correlation_checkpoint = last_id;
|
|
1174
1241
|
if (correlated.size) {
|
|
1175
|
-
const
|
|
1242
|
+
const streams = [...correlated.entries()].map(([stream, { source }]) => ({
|
|
1176
1243
|
stream,
|
|
1177
|
-
|
|
1178
|
-
source: payloads.find((p) => p.source)?.source || void 0,
|
|
1179
|
-
by: randomUUID2(),
|
|
1180
|
-
at: 0,
|
|
1181
|
-
retry: 0,
|
|
1182
|
-
lagging: true,
|
|
1183
|
-
payloads
|
|
1244
|
+
source
|
|
1184
1245
|
}));
|
|
1185
|
-
const
|
|
1186
|
-
|
|
1187
|
-
|
|
1246
|
+
const { subscribed } = await store().subscribe(streams);
|
|
1247
|
+
if (subscribed) {
|
|
1248
|
+
tracer.correlated(streams);
|
|
1249
|
+
for (const { stream } of streams) {
|
|
1250
|
+
this._subscribed_statics.add(stream);
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
return { subscribed, last_id };
|
|
1188
1254
|
}
|
|
1189
|
-
return {
|
|
1255
|
+
return { subscribed: 0, last_id };
|
|
1190
1256
|
}
|
|
1191
1257
|
/**
|
|
1192
1258
|
* Starts automatic periodic correlation worker for discovering new streams.
|
|
@@ -1246,11 +1312,9 @@ var Act = class {
|
|
|
1246
1312
|
start_correlations(query = {}, frequency = 1e4, callback) {
|
|
1247
1313
|
if (this._correlation_timer) return false;
|
|
1248
1314
|
const limit = query.limit || 100;
|
|
1249
|
-
let after = query.after || -1;
|
|
1250
1315
|
this._correlation_timer = setInterval(
|
|
1251
|
-
() => this.correlate({ ...query, after, limit }).then((result) => {
|
|
1252
|
-
|
|
1253
|
-
if (callback && result.leased.length) callback(result.leased);
|
|
1316
|
+
() => this.correlate({ ...query, after: this._correlation_checkpoint, limit }).then((result) => {
|
|
1317
|
+
if (callback && result.subscribed) callback(result.subscribed);
|
|
1254
1318
|
}).catch(console.error),
|
|
1255
1319
|
frequency
|
|
1256
1320
|
);
|
|
@@ -1332,10 +1396,14 @@ var Act = class {
|
|
|
1332
1396
|
if (this._settling) return;
|
|
1333
1397
|
this._settling = true;
|
|
1334
1398
|
(async () => {
|
|
1399
|
+
await this._init_correlation();
|
|
1335
1400
|
let lastDrain;
|
|
1336
1401
|
for (let i = 0; i < maxPasses; i++) {
|
|
1337
|
-
const {
|
|
1338
|
-
|
|
1402
|
+
const { subscribed } = await this.correlate({
|
|
1403
|
+
...correlateQuery,
|
|
1404
|
+
after: this._correlation_checkpoint
|
|
1405
|
+
});
|
|
1406
|
+
if (subscribed === 0 && i > 0) break;
|
|
1339
1407
|
lastDrain = await this.drain(drainOptions);
|
|
1340
1408
|
if (!lastDrain.acked.length && !lastDrain.blocked.length) break;
|
|
1341
1409
|
}
|