@rotorsoft/act 1.10.0 → 1.10.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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/@types/act.d.ts +22 -19
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/adapters/console-logger.d.ts +2 -2
- package/dist/@types/adapters/console-logger.d.ts.map +1 -1
- package/dist/@types/adapters/in-memory-store.d.ts +6 -6
- package/dist/@types/adapters/in-memory-store.d.ts.map +1 -1
- package/dist/@types/builders/act-builder.d.ts.map +1 -1
- package/dist/@types/builders/slice-builder.d.ts +1 -1
- package/dist/@types/builders/state-builder.d.ts +2 -2
- package/dist/@types/builders/state-builder.d.ts.map +1 -1
- package/dist/@types/internal/audit.d.ts +4 -4
- package/dist/@types/internal/audit.d.ts.map +1 -1
- package/dist/@types/internal/backoff.d.ts +1 -1
- package/dist/@types/internal/backoff.d.ts.map +1 -1
- package/dist/@types/internal/build-classify.d.ts +11 -11
- package/dist/@types/internal/build-classify.d.ts.map +1 -1
- package/dist/@types/internal/close-cycle.d.ts +3 -3
- package/dist/@types/internal/close-cycle.d.ts.map +1 -1
- package/dist/@types/internal/correlate-cycle.d.ts +4 -4
- package/dist/@types/internal/correlate-cycle.d.ts.map +1 -1
- package/dist/@types/internal/correlator.d.ts +2 -2
- package/dist/@types/internal/correlator.d.ts.map +1 -1
- package/dist/@types/internal/drain-cycle.d.ts +20 -20
- package/dist/@types/internal/drain-cycle.d.ts.map +1 -1
- package/dist/@types/internal/drain-ratio.d.ts +1 -1
- package/dist/@types/internal/drain-ratio.d.ts.map +1 -1
- package/dist/@types/internal/event-sourcing.d.ts +9 -9
- package/dist/@types/internal/event-sourcing.d.ts.map +1 -1
- package/dist/@types/internal/event-versions.d.ts +2 -2
- package/dist/@types/internal/event-versions.d.ts.map +1 -1
- package/dist/@types/internal/index.d.ts +8 -8
- package/dist/@types/internal/index.d.ts.map +1 -1
- package/dist/@types/internal/merge.d.ts +3 -3
- package/dist/@types/internal/merge.d.ts.map +1 -1
- package/dist/@types/internal/reactions.d.ts +6 -6
- package/dist/@types/internal/reactions.d.ts.map +1 -1
- package/dist/@types/internal/sensitive.d.ts +8 -25
- package/dist/@types/internal/sensitive.d.ts.map +1 -1
- package/dist/@types/internal/settle.d.ts +3 -3
- package/dist/@types/internal/settle.d.ts.map +1 -1
- package/dist/@types/internal/tracing.d.ts +5 -5
- package/dist/@types/internal/tracing.d.ts.map +1 -1
- package/dist/@types/types/action.d.ts +78 -16
- package/dist/@types/types/action.d.ts.map +1 -1
- package/dist/@types/types/registry.d.ts +12 -0
- package/dist/@types/types/registry.d.ts.map +1 -1
- package/dist/{chunk-3ZTFNAY7.js → chunk-5PWLGL5E.js} +134 -133
- package/dist/chunk-5PWLGL5E.js.map +1 -0
- package/dist/{chunk-XSBT63QX.js → chunk-NGVWJXEF.js} +9 -24
- package/dist/{chunk-XSBT63QX.js.map → chunk-NGVWJXEF.js.map} +1 -1
- package/dist/index.cjs +638 -658
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +500 -508
- package/dist/index.js.map +1 -1
- package/dist/test/index.cjs +134 -133
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.js +4 -4
- package/dist/test/index.js.map +1 -1
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.js +1 -1
- package/package.json +4 -4
- package/dist/chunk-3ZTFNAY7.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -125,7 +125,7 @@ var ConsoleLogger = class _ConsoleLogger {
|
|
|
125
125
|
this._pretty = pretty;
|
|
126
126
|
this.level = level;
|
|
127
127
|
const threshold = LEVEL_VALUES[level] ?? 30;
|
|
128
|
-
const write = pretty ? this.
|
|
128
|
+
const write = pretty ? this._pretty_write.bind(this, bindings) : this._json_write.bind(this, bindings);
|
|
129
129
|
this.fatal = write.bind(this, "fatal", 60);
|
|
130
130
|
this.error = threshold <= 50 ? write.bind(this, "error", 50) : noop;
|
|
131
131
|
this.warn = threshold <= 40 ? write.bind(this, "warn", 40) : noop;
|
|
@@ -144,24 +144,24 @@ var ConsoleLogger = class _ConsoleLogger {
|
|
|
144
144
|
bindings
|
|
145
145
|
});
|
|
146
146
|
}
|
|
147
|
-
|
|
147
|
+
_json_write(bindings, level, _num, obj_or_msg, msg) {
|
|
148
148
|
let obj;
|
|
149
149
|
let message;
|
|
150
|
-
if (typeof
|
|
151
|
-
message =
|
|
150
|
+
if (typeof obj_or_msg === "string") {
|
|
151
|
+
message = obj_or_msg;
|
|
152
152
|
obj = {};
|
|
153
|
-
} else if (
|
|
154
|
-
message = msg ??
|
|
153
|
+
} else if (obj_or_msg instanceof Error) {
|
|
154
|
+
message = msg ?? obj_or_msg.message;
|
|
155
155
|
obj = {
|
|
156
|
-
error: { message:
|
|
157
|
-
stack:
|
|
156
|
+
error: { message: obj_or_msg.message, name: obj_or_msg.name },
|
|
157
|
+
stack: obj_or_msg.stack
|
|
158
158
|
};
|
|
159
|
-
} else if (
|
|
159
|
+
} else if (obj_or_msg !== null && typeof obj_or_msg === "object") {
|
|
160
160
|
message = msg;
|
|
161
|
-
obj = { ...
|
|
161
|
+
obj = { ...obj_or_msg };
|
|
162
162
|
} else {
|
|
163
163
|
message = msg;
|
|
164
|
-
obj = { value:
|
|
164
|
+
obj = { value: obj_or_msg };
|
|
165
165
|
}
|
|
166
166
|
const entry = Object.assign({ level, time: Date.now() }, bindings, obj);
|
|
167
167
|
if (message) entry.msg = message;
|
|
@@ -178,29 +178,29 @@ var ConsoleLogger = class _ConsoleLogger {
|
|
|
178
178
|
}
|
|
179
179
|
process.stdout.write(line + "\n");
|
|
180
180
|
}
|
|
181
|
-
|
|
181
|
+
_pretty_write(bindings, level, _num, obj_or_msg, msg) {
|
|
182
182
|
const color = LEVEL_COLORS[level];
|
|
183
183
|
const tag = `${color}${level.toUpperCase().padEnd(5)}${RESET}`;
|
|
184
184
|
const ts = (/* @__PURE__ */ new Date()).toISOString().slice(11, 23);
|
|
185
185
|
let message;
|
|
186
186
|
let data;
|
|
187
|
-
if (typeof
|
|
188
|
-
message =
|
|
189
|
-
} else if (
|
|
190
|
-
message = msg ??
|
|
191
|
-
data =
|
|
187
|
+
if (typeof obj_or_msg === "string") {
|
|
188
|
+
message = obj_or_msg;
|
|
189
|
+
} else if (obj_or_msg instanceof Error) {
|
|
190
|
+
message = msg ?? obj_or_msg.message;
|
|
191
|
+
data = obj_or_msg.stack;
|
|
192
192
|
} else {
|
|
193
193
|
message = msg ?? "";
|
|
194
|
-
if (
|
|
194
|
+
if (obj_or_msg !== void 0 && obj_or_msg !== null) {
|
|
195
195
|
try {
|
|
196
|
-
data = JSON.stringify(
|
|
196
|
+
data = JSON.stringify(obj_or_msg);
|
|
197
197
|
} catch {
|
|
198
198
|
data = "[unserializable]";
|
|
199
199
|
}
|
|
200
200
|
}
|
|
201
201
|
}
|
|
202
|
-
const
|
|
203
|
-
const parts = [ts, tag, message, data,
|
|
202
|
+
const bind_str = bindings && Object.keys(bindings).length ? ` ${JSON.stringify(bindings)}` : "";
|
|
203
|
+
const parts = [ts, tag, message, data, bind_str].filter(Boolean);
|
|
204
204
|
process.stdout.write(parts.join(" ") + "\n");
|
|
205
205
|
}
|
|
206
206
|
};
|
|
@@ -416,32 +416,18 @@ function pii_fields(schema) {
|
|
|
416
416
|
return fields;
|
|
417
417
|
}
|
|
418
418
|
function pii_split(emitted, fields) {
|
|
419
|
-
const
|
|
420
|
-
const clean = {};
|
|
419
|
+
const data = { ...emitted.data };
|
|
421
420
|
const pii = {};
|
|
422
|
-
for (const
|
|
423
|
-
if (
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
}
|
|
428
|
-
function pii_merge(event, fields) {
|
|
429
|
-
const data = event.data;
|
|
430
|
-
const pii = event.pii;
|
|
431
|
-
if (pii != null) {
|
|
432
|
-
return {
|
|
433
|
-
...event,
|
|
434
|
-
data: { ...data, ...pii }
|
|
435
|
-
};
|
|
421
|
+
for (const f of fields) {
|
|
422
|
+
if (f in data) {
|
|
423
|
+
pii[f] = data[f];
|
|
424
|
+
delete data[f];
|
|
425
|
+
}
|
|
436
426
|
}
|
|
437
|
-
|
|
438
|
-
for (const f of fields) shredded[f] = SHREDDED;
|
|
439
|
-
return {
|
|
440
|
-
...event,
|
|
441
|
-
data: shredded
|
|
442
|
-
};
|
|
427
|
+
return { name: emitted.name, data, pii };
|
|
443
428
|
}
|
|
444
429
|
function pii_gate(event, fields, predicate, actor) {
|
|
430
|
+
if (fields.length === 0) return event;
|
|
445
431
|
const data = event.data;
|
|
446
432
|
if (event.pii == null) {
|
|
447
433
|
const shredded = { ...data };
|
|
@@ -559,16 +545,16 @@ var FALLBACK_PACKAGE = {
|
|
|
559
545
|
version: "0.0.0-fallback",
|
|
560
546
|
description: "Synthetic fallback \u2014 package.json could not be loaded"
|
|
561
547
|
};
|
|
562
|
-
var
|
|
548
|
+
var get_package = () => {
|
|
563
549
|
try {
|
|
564
550
|
const raw = fs.readFileSync("package.json");
|
|
565
551
|
return JSON.parse(raw.toString());
|
|
566
552
|
} catch (err) {
|
|
567
|
-
|
|
553
|
+
pkg_load_error = err;
|
|
568
554
|
return FALLBACK_PACKAGE;
|
|
569
555
|
}
|
|
570
556
|
};
|
|
571
|
-
var
|
|
557
|
+
var pkg_load_error;
|
|
572
558
|
var BaseSchema = PackageSchema.extend({
|
|
573
559
|
env: import_zod3.z.enum(Environments),
|
|
574
560
|
logLevel: import_zod3.z.enum(LogLevels),
|
|
@@ -580,7 +566,7 @@ var env = NODE_ENV || "development";
|
|
|
580
566
|
var logLevel = LOG_LEVEL || (NODE_ENV === "test" ? "fatal" : NODE_ENV === "production" ? "info" : "trace");
|
|
581
567
|
var logSingleLine = (LOG_SINGLE_LINE || "true") === "true";
|
|
582
568
|
var sleepMs = parseInt(NODE_ENV === "test" ? "0" : SLEEP_MS ?? "100", 10);
|
|
583
|
-
var pkg =
|
|
569
|
+
var pkg = get_package();
|
|
584
570
|
var _validated;
|
|
585
571
|
var config = () => {
|
|
586
572
|
if (!_validated) {
|
|
@@ -588,12 +574,12 @@ var config = () => {
|
|
|
588
574
|
{ ...pkg, env, logLevel, logSingleLine, sleepMs },
|
|
589
575
|
BaseSchema
|
|
590
576
|
);
|
|
591
|
-
if (
|
|
592
|
-
const msg =
|
|
577
|
+
if (pkg_load_error) {
|
|
578
|
+
const msg = pkg_load_error instanceof Error ? pkg_load_error.message : typeof pkg_load_error === "string" ? pkg_load_error : "unknown error";
|
|
593
579
|
log().warn(
|
|
594
580
|
`[act] Could not read package.json (${msg}); using synthetic name="${FALLBACK_PACKAGE.name}" version="${FALLBACK_PACKAGE.version}".`
|
|
595
581
|
);
|
|
596
|
-
|
|
582
|
+
pkg_load_error = void 0;
|
|
597
583
|
}
|
|
598
584
|
}
|
|
599
585
|
return _validated;
|
|
@@ -650,14 +636,14 @@ var InMemoryStream = class {
|
|
|
650
636
|
* Bump the priority via {@link subscribe}: keeps the maximum across
|
|
651
637
|
* reactions so the highest-priority registrant wins.
|
|
652
638
|
*/
|
|
653
|
-
|
|
639
|
+
bump_priority(priority) {
|
|
654
640
|
if (priority > this._priority) this._priority = priority;
|
|
655
641
|
}
|
|
656
642
|
/**
|
|
657
643
|
* Set the priority outright via {@link prioritize}: operator
|
|
658
644
|
* runtime override that ignores the build-time `max()` invariant.
|
|
659
645
|
*/
|
|
660
|
-
|
|
646
|
+
set_priority(priority) {
|
|
661
647
|
this._priority = priority;
|
|
662
648
|
}
|
|
663
649
|
get is_available() {
|
|
@@ -779,28 +765,28 @@ var InMemoryStore = class {
|
|
|
779
765
|
// stored stream positions and other metadata
|
|
780
766
|
_streams = /* @__PURE__ */ new Map();
|
|
781
767
|
// last committed version per stream — O(1) replacement for filter-on-commit
|
|
782
|
-
|
|
768
|
+
_stream_versions = /* @__PURE__ */ new Map();
|
|
783
769
|
// max non-snapshot event id per stream — drives the source-pattern probe in claim()
|
|
784
770
|
// without scanning the full event log.
|
|
785
|
-
|
|
771
|
+
_max_event_id_by_stream = /* @__PURE__ */ new Map();
|
|
786
772
|
// global max non-snapshot event id — fast pre-check for source-less streams in claim()
|
|
787
|
-
|
|
773
|
+
_max_non_snap_event_id = -1;
|
|
788
774
|
// stream → (event_id → cloned sensitive payload). Two-level so `forget_pii`
|
|
789
775
|
// is O(1) — drop the inner Map for the stream and the wipe is done — mirroring
|
|
790
776
|
// the `DELETE WHERE stream = ?` scope that durable adapters get from their
|
|
791
777
|
// stream index. Entries exist only for events committed with a non-null
|
|
792
778
|
// `pii` field; absence means "no PII" (returned as `null` on load).
|
|
793
779
|
_pii = /* @__PURE__ */ new Map();
|
|
794
|
-
|
|
780
|
+
_reset_indexes() {
|
|
795
781
|
this._events.length = 0;
|
|
796
|
-
this.
|
|
797
|
-
this.
|
|
798
|
-
this.
|
|
782
|
+
this._stream_versions.clear();
|
|
783
|
+
this._max_event_id_by_stream.clear();
|
|
784
|
+
this._max_non_snap_event_id = -1;
|
|
799
785
|
this._pii.clear();
|
|
800
786
|
}
|
|
801
787
|
// Attach the isolated PII payload (or null) to an event before handing it to
|
|
802
788
|
// a caller. Allocation-free for events without PII — by far the common case.
|
|
803
|
-
|
|
789
|
+
_with_pii(e) {
|
|
804
790
|
const pii = this._pii.get(e.stream)?.get(e.id);
|
|
805
791
|
return pii ? { ...e, pii } : e;
|
|
806
792
|
}
|
|
@@ -810,7 +796,7 @@ var InMemoryStore = class {
|
|
|
810
796
|
*/
|
|
811
797
|
async dispose() {
|
|
812
798
|
await sleep();
|
|
813
|
-
this.
|
|
799
|
+
this._reset_indexes();
|
|
814
800
|
}
|
|
815
801
|
/**
|
|
816
802
|
* Seed the store with initial data (no-op for in-memory).
|
|
@@ -825,7 +811,7 @@ var InMemoryStore = class {
|
|
|
825
811
|
*/
|
|
826
812
|
async drop() {
|
|
827
813
|
await sleep();
|
|
828
|
-
this.
|
|
814
|
+
this._reset_indexes();
|
|
829
815
|
this._streams = /* @__PURE__ */ new Map();
|
|
830
816
|
}
|
|
831
817
|
in_query(query, e) {
|
|
@@ -859,7 +845,7 @@ var InMemoryStore = class {
|
|
|
859
845
|
if (query.after && e.id <= query.after) break;
|
|
860
846
|
if (query.created_after && e.created <= query.created_after) break;
|
|
861
847
|
await Promise.resolve(
|
|
862
|
-
callback(this.
|
|
848
|
+
callback(this._with_pii(e))
|
|
863
849
|
);
|
|
864
850
|
count++;
|
|
865
851
|
if (query?.limit && count >= query.limit) break;
|
|
@@ -873,7 +859,7 @@ var InMemoryStore = class {
|
|
|
873
859
|
if (query?.before && e.id >= query.before) break;
|
|
874
860
|
if (query?.created_before && e.created >= query.created_before) break;
|
|
875
861
|
await Promise.resolve(
|
|
876
|
-
callback(this.
|
|
862
|
+
callback(this._with_pii(e))
|
|
877
863
|
);
|
|
878
864
|
count++;
|
|
879
865
|
if (query?.limit && count >= query.limit) break;
|
|
@@ -892,17 +878,17 @@ var InMemoryStore = class {
|
|
|
892
878
|
*/
|
|
893
879
|
async commit(stream, msgs, meta, expectedVersion) {
|
|
894
880
|
await sleep();
|
|
895
|
-
const
|
|
896
|
-
if (typeof expectedVersion === "number" &&
|
|
881
|
+
const current_version = this._stream_versions.get(stream) ?? -1;
|
|
882
|
+
if (typeof expectedVersion === "number" && current_version !== expectedVersion) {
|
|
897
883
|
throw new ConcurrencyError(
|
|
898
884
|
stream,
|
|
899
|
-
|
|
885
|
+
current_version,
|
|
900
886
|
msgs,
|
|
901
887
|
expectedVersion
|
|
902
888
|
);
|
|
903
889
|
}
|
|
904
|
-
let version =
|
|
905
|
-
let
|
|
890
|
+
let version = current_version + 1;
|
|
891
|
+
let last_non_snap_id = -1;
|
|
906
892
|
const committed = msgs.map(({ name, data, pii }) => {
|
|
907
893
|
const c = {
|
|
908
894
|
id: this._events.length,
|
|
@@ -915,21 +901,21 @@ var InMemoryStore = class {
|
|
|
915
901
|
};
|
|
916
902
|
this._events.push(c);
|
|
917
903
|
if (pii != null) {
|
|
918
|
-
let
|
|
919
|
-
if (!
|
|
920
|
-
|
|
921
|
-
this._pii.set(stream,
|
|
904
|
+
let per_stream = this._pii.get(stream);
|
|
905
|
+
if (!per_stream) {
|
|
906
|
+
per_stream = /* @__PURE__ */ new Map();
|
|
907
|
+
this._pii.set(stream, per_stream);
|
|
922
908
|
}
|
|
923
|
-
|
|
909
|
+
per_stream.set(c.id, structuredClone(pii));
|
|
924
910
|
}
|
|
925
|
-
if (name !== SNAP_EVENT)
|
|
911
|
+
if (name !== SNAP_EVENT) last_non_snap_id = c.id;
|
|
926
912
|
version++;
|
|
927
|
-
return this.
|
|
913
|
+
return this._with_pii(c);
|
|
928
914
|
});
|
|
929
|
-
this.
|
|
930
|
-
if (
|
|
931
|
-
this.
|
|
932
|
-
this.
|
|
915
|
+
this._stream_versions.set(stream, version - 1);
|
|
916
|
+
if (last_non_snap_id >= 0) {
|
|
917
|
+
this._max_event_id_by_stream.set(stream, last_non_snap_id);
|
|
918
|
+
this._max_non_snap_event_id = last_non_snap_id;
|
|
933
919
|
}
|
|
934
920
|
return committed;
|
|
935
921
|
}
|
|
@@ -944,26 +930,26 @@ var InMemoryStore = class {
|
|
|
944
930
|
*/
|
|
945
931
|
async claim(lagging, leading, by, millis, lane) {
|
|
946
932
|
await sleep();
|
|
947
|
-
const
|
|
948
|
-
const
|
|
949
|
-
let re =
|
|
933
|
+
const source_regex = /* @__PURE__ */ new Map();
|
|
934
|
+
const get_regex = (source) => {
|
|
935
|
+
let re = source_regex.get(source);
|
|
950
936
|
if (!re) {
|
|
951
937
|
re = new RegExp(source);
|
|
952
|
-
|
|
938
|
+
source_regex.set(source, re);
|
|
953
939
|
}
|
|
954
940
|
return re;
|
|
955
941
|
};
|
|
956
|
-
const
|
|
942
|
+
const has_work = (s) => {
|
|
957
943
|
if (s.at < 0) return true;
|
|
958
|
-
if (!s.source) return s.at < this.
|
|
959
|
-
const re =
|
|
960
|
-
for (const [
|
|
961
|
-
if (maxId > s.at && re.test(
|
|
944
|
+
if (!s.source) return s.at < this._max_non_snap_event_id;
|
|
945
|
+
const re = get_regex(s.source);
|
|
946
|
+
for (const [stream_name, maxId] of this._max_event_id_by_stream) {
|
|
947
|
+
if (maxId > s.at && re.test(stream_name)) return true;
|
|
962
948
|
}
|
|
963
949
|
return false;
|
|
964
950
|
};
|
|
965
951
|
const available = [...this._streams.values()].filter(
|
|
966
|
-
(s) => s.is_available &&
|
|
952
|
+
(s) => s.is_available && has_work(s) && (lane === void 0 || s.lane === lane)
|
|
967
953
|
);
|
|
968
954
|
const lag = available.sort((a, b) => b.priority - a.priority || a.at - b.at).slice(0, lagging).map((s) => ({
|
|
969
955
|
stream: s.stream,
|
|
@@ -1007,7 +993,7 @@ var InMemoryStore = class {
|
|
|
1007
993
|
} of streams) {
|
|
1008
994
|
const existing = this._streams.get(stream);
|
|
1009
995
|
if (existing) {
|
|
1010
|
-
existing.
|
|
996
|
+
existing.bump_priority(priority);
|
|
1011
997
|
existing.lane = lane;
|
|
1012
998
|
} else {
|
|
1013
999
|
this._streams.set(
|
|
@@ -1045,17 +1031,17 @@ var InMemoryStore = class {
|
|
|
1045
1031
|
* cached in the closure so callers can apply it across the streams
|
|
1046
1032
|
* map without re-compiling per iteration.
|
|
1047
1033
|
*/
|
|
1048
|
-
|
|
1049
|
-
const
|
|
1050
|
-
const
|
|
1034
|
+
_filter_predicate(filter) {
|
|
1035
|
+
const stream_re = filter.stream && !filter.stream_exact ? new RegExp(filter.stream) : void 0;
|
|
1036
|
+
const source_re = filter.source && !filter.source_exact ? new RegExp(filter.source) : void 0;
|
|
1051
1037
|
return (s) => {
|
|
1052
1038
|
if (filter.stream !== void 0) {
|
|
1053
|
-
if (filter.stream_exact ? s.stream !== filter.stream : !
|
|
1039
|
+
if (filter.stream_exact ? s.stream !== filter.stream : !stream_re.test(s.stream))
|
|
1054
1040
|
return false;
|
|
1055
1041
|
}
|
|
1056
1042
|
if (filter.source !== void 0) {
|
|
1057
1043
|
if (s.source === void 0) return false;
|
|
1058
|
-
if (filter.source_exact ? s.source !== filter.source : !
|
|
1044
|
+
if (filter.source_exact ? s.source !== filter.source : !source_re.test(s.source))
|
|
1059
1045
|
return false;
|
|
1060
1046
|
}
|
|
1061
1047
|
if (filter.blocked !== void 0 && s.blocked !== filter.blocked)
|
|
@@ -1084,7 +1070,7 @@ var InMemoryStore = class {
|
|
|
1084
1070
|
}
|
|
1085
1071
|
}
|
|
1086
1072
|
} else {
|
|
1087
|
-
const matches = this.
|
|
1073
|
+
const matches = this._filter_predicate(input);
|
|
1088
1074
|
for (const s of this._streams.values()) {
|
|
1089
1075
|
if (matches(s)) {
|
|
1090
1076
|
s.reset();
|
|
@@ -1129,7 +1115,7 @@ var InMemoryStore = class {
|
|
|
1129
1115
|
if (s?.unblock()) count++;
|
|
1130
1116
|
}
|
|
1131
1117
|
} else {
|
|
1132
|
-
const matches = this.
|
|
1118
|
+
const matches = this._filter_predicate({ ...input, blocked: true });
|
|
1133
1119
|
for (const s of this._streams.values()) {
|
|
1134
1120
|
if (matches(s) && s.unblock()) count++;
|
|
1135
1121
|
}
|
|
@@ -1147,12 +1133,12 @@ var InMemoryStore = class {
|
|
|
1147
1133
|
*/
|
|
1148
1134
|
async prioritize(filter, priority) {
|
|
1149
1135
|
await sleep();
|
|
1150
|
-
const matches = this.
|
|
1136
|
+
const matches = this._filter_predicate(filter);
|
|
1151
1137
|
let count = 0;
|
|
1152
1138
|
for (const s of this._streams.values()) {
|
|
1153
1139
|
if (!matches(s)) continue;
|
|
1154
1140
|
if (s.priority !== priority) {
|
|
1155
|
-
s.
|
|
1141
|
+
s.set_priority(priority);
|
|
1156
1142
|
count++;
|
|
1157
1143
|
}
|
|
1158
1144
|
}
|
|
@@ -1168,8 +1154,8 @@ var InMemoryStore = class {
|
|
|
1168
1154
|
const limit = query?.limit ?? 100;
|
|
1169
1155
|
const after = query?.after;
|
|
1170
1156
|
const blocked = query?.blocked;
|
|
1171
|
-
const
|
|
1172
|
-
const
|
|
1157
|
+
const stream_re = query?.stream && !query.stream_exact ? new RegExp(query.stream) : void 0;
|
|
1158
|
+
const source_re = query?.source && !query.source_exact ? new RegExp(query.source) : void 0;
|
|
1173
1159
|
const sorted = [...this._streams.values()].sort(
|
|
1174
1160
|
(a, b) => a.stream.localeCompare(b.stream)
|
|
1175
1161
|
);
|
|
@@ -1177,12 +1163,12 @@ var InMemoryStore = class {
|
|
|
1177
1163
|
for (const s of sorted) {
|
|
1178
1164
|
if (after !== void 0 && s.stream <= after) continue;
|
|
1179
1165
|
if (query?.stream !== void 0) {
|
|
1180
|
-
if (query.stream_exact ? s.stream !== query.stream : !
|
|
1166
|
+
if (query.stream_exact ? s.stream !== query.stream : !stream_re.test(s.stream))
|
|
1181
1167
|
continue;
|
|
1182
1168
|
}
|
|
1183
1169
|
if (query?.source !== void 0) {
|
|
1184
1170
|
if (s.source === void 0) continue;
|
|
1185
|
-
if (query.source_exact ? s.source !== query.source : !
|
|
1171
|
+
if (query.source_exact ? s.source !== query.source : !source_re.test(s.source))
|
|
1186
1172
|
continue;
|
|
1187
1173
|
}
|
|
1188
1174
|
if (blocked !== void 0 && s.blocked !== blocked) continue;
|
|
@@ -1223,44 +1209,44 @@ var InMemoryStore = class {
|
|
|
1223
1209
|
async query_stats(input, options) {
|
|
1224
1210
|
await sleep();
|
|
1225
1211
|
const exclude = new Set(options?.exclude ?? []);
|
|
1226
|
-
const
|
|
1227
|
-
const
|
|
1228
|
-
const
|
|
1212
|
+
const want_tail = options?.tail ?? false;
|
|
1213
|
+
const want_count = options?.count ?? false;
|
|
1214
|
+
const want_names = options?.names ?? false;
|
|
1229
1215
|
const before = options?.before;
|
|
1230
|
-
const
|
|
1216
|
+
const array_targets = Array.isArray(input) ? new Set(input) : null;
|
|
1231
1217
|
const filter = Array.isArray(input) ? null : input;
|
|
1232
|
-
const
|
|
1233
|
-
const
|
|
1234
|
-
const
|
|
1235
|
-
const cached =
|
|
1218
|
+
const stream_re = filter?.stream && !filter.stream_exact ? new RegExp(filter.stream) : void 0;
|
|
1219
|
+
const scope_cache = /* @__PURE__ */ new Map();
|
|
1220
|
+
const in_scope = (stream) => {
|
|
1221
|
+
const cached = scope_cache.get(stream);
|
|
1236
1222
|
if (cached !== void 0) return cached;
|
|
1237
1223
|
let ok = true;
|
|
1238
|
-
if (
|
|
1239
|
-
ok =
|
|
1224
|
+
if (array_targets) {
|
|
1225
|
+
ok = array_targets.has(stream);
|
|
1240
1226
|
} else if (filter?.stream !== void 0) {
|
|
1241
1227
|
ok = filter.stream_exact ? stream === filter.stream : (
|
|
1242
|
-
// biome-ignore lint/style/noNonNullAssertion:
|
|
1243
|
-
|
|
1228
|
+
// biome-ignore lint/style/noNonNullAssertion: stream_re set when stream is regex
|
|
1229
|
+
stream_re.test(stream)
|
|
1244
1230
|
);
|
|
1245
1231
|
}
|
|
1246
|
-
|
|
1232
|
+
scope_cache.set(stream, ok);
|
|
1247
1233
|
return ok;
|
|
1248
1234
|
};
|
|
1249
1235
|
const acc = /* @__PURE__ */ new Map();
|
|
1250
1236
|
for (const e of this._events) {
|
|
1251
1237
|
if (before !== void 0 && e.id >= before) continue;
|
|
1252
|
-
if (!
|
|
1238
|
+
if (!in_scope(e.stream)) continue;
|
|
1253
1239
|
if (exclude.has(e.name)) continue;
|
|
1254
1240
|
let a = acc.get(e.stream);
|
|
1255
1241
|
if (!a) {
|
|
1256
1242
|
a = { head: e, count: 0 };
|
|
1257
|
-
if (
|
|
1258
|
-
if (
|
|
1243
|
+
if (want_tail) a.tail = e;
|
|
1244
|
+
if (want_names) a.names = {};
|
|
1259
1245
|
acc.set(e.stream, a);
|
|
1260
1246
|
}
|
|
1261
1247
|
a.head = e;
|
|
1262
1248
|
a.count++;
|
|
1263
|
-
if (
|
|
1249
|
+
if (want_names) {
|
|
1264
1250
|
const n = String(e.name);
|
|
1265
1251
|
a.names[n] = (a.names[n] ?? 0) + 1;
|
|
1266
1252
|
}
|
|
@@ -1268,9 +1254,9 @@ var InMemoryStore = class {
|
|
|
1268
1254
|
const out = /* @__PURE__ */ new Map();
|
|
1269
1255
|
for (const [stream, a] of acc) {
|
|
1270
1256
|
const stats = { head: a.head };
|
|
1271
|
-
if (
|
|
1272
|
-
if (
|
|
1273
|
-
if (
|
|
1257
|
+
if (want_tail) stats.tail = a.tail;
|
|
1258
|
+
if (want_count) stats.count = a.count;
|
|
1259
|
+
if (want_names) stats.names = a.names;
|
|
1274
1260
|
out.set(stream, stats);
|
|
1275
1261
|
}
|
|
1276
1262
|
return out;
|
|
@@ -1282,18 +1268,18 @@ var InMemoryStore = class {
|
|
|
1282
1268
|
*/
|
|
1283
1269
|
async truncate(targets) {
|
|
1284
1270
|
await sleep();
|
|
1285
|
-
const
|
|
1286
|
-
const
|
|
1271
|
+
const deleted_counts = /* @__PURE__ */ new Map();
|
|
1272
|
+
const stream_set = new Set(targets.map((t) => t.stream));
|
|
1287
1273
|
for (const e of this._events) {
|
|
1288
|
-
if (
|
|
1289
|
-
|
|
1274
|
+
if (stream_set.has(e.stream)) {
|
|
1275
|
+
deleted_counts.set(e.stream, (deleted_counts.get(e.stream) ?? 0) + 1);
|
|
1290
1276
|
}
|
|
1291
1277
|
}
|
|
1292
|
-
this._events = this._events.filter((e) => !
|
|
1293
|
-
for (const stream of
|
|
1278
|
+
this._events = this._events.filter((e) => !stream_set.has(e.stream));
|
|
1279
|
+
for (const stream of stream_set) {
|
|
1294
1280
|
this._streams.delete(stream);
|
|
1295
|
-
this.
|
|
1296
|
-
this.
|
|
1281
|
+
this._stream_versions.delete(stream);
|
|
1282
|
+
this._max_event_id_by_stream.delete(stream);
|
|
1297
1283
|
}
|
|
1298
1284
|
const result = /* @__PURE__ */ new Map();
|
|
1299
1285
|
for (const { stream, snapshot, meta } of targets) {
|
|
@@ -1307,18 +1293,19 @@ var InMemoryStore = class {
|
|
|
1307
1293
|
meta: meta ?? { correlation: "", causation: {} }
|
|
1308
1294
|
};
|
|
1309
1295
|
this._events.push(event);
|
|
1310
|
-
this.
|
|
1296
|
+
this._stream_versions.set(stream, 0);
|
|
1311
1297
|
if (event.name !== SNAP_EVENT) {
|
|
1312
|
-
this.
|
|
1298
|
+
this._max_event_id_by_stream.set(stream, event.id);
|
|
1313
1299
|
}
|
|
1314
1300
|
result.set(stream, {
|
|
1315
|
-
deleted:
|
|
1301
|
+
deleted: deleted_counts.get(stream) ?? 0,
|
|
1316
1302
|
committed: event
|
|
1317
1303
|
});
|
|
1318
1304
|
}
|
|
1319
1305
|
let max = -1;
|
|
1320
|
-
for (const id of this.
|
|
1321
|
-
|
|
1306
|
+
for (const id of this._max_event_id_by_stream.values())
|
|
1307
|
+
if (id > max) max = id;
|
|
1308
|
+
this._max_non_snap_event_id = max;
|
|
1322
1309
|
return result;
|
|
1323
1310
|
}
|
|
1324
1311
|
/**
|
|
@@ -1335,34 +1322,34 @@ var InMemoryStore = class {
|
|
|
1335
1322
|
*/
|
|
1336
1323
|
async restore(driver) {
|
|
1337
1324
|
await sleep();
|
|
1338
|
-
const
|
|
1339
|
-
const
|
|
1340
|
-
const
|
|
1341
|
-
const
|
|
1342
|
-
const
|
|
1325
|
+
const prev_events = this._events;
|
|
1326
|
+
const prev_streams = this._streams;
|
|
1327
|
+
const prev_stream_versions = this._stream_versions;
|
|
1328
|
+
const prev_max_event_id_by_stream = this._max_event_id_by_stream;
|
|
1329
|
+
const prev_max_non_snap_event_id = this._max_non_snap_event_id;
|
|
1343
1330
|
this._events = [];
|
|
1344
1331
|
this._streams = /* @__PURE__ */ new Map();
|
|
1345
|
-
this.
|
|
1346
|
-
this.
|
|
1347
|
-
this.
|
|
1332
|
+
this._stream_versions = /* @__PURE__ */ new Map();
|
|
1333
|
+
this._max_event_id_by_stream = /* @__PURE__ */ new Map();
|
|
1334
|
+
this._max_non_snap_event_id = -1;
|
|
1348
1335
|
try {
|
|
1349
1336
|
await driver(async (event) => {
|
|
1350
1337
|
const id = this._events.length;
|
|
1351
1338
|
const committed = { ...event, id };
|
|
1352
1339
|
this._events.push(committed);
|
|
1353
|
-
this.
|
|
1340
|
+
this._stream_versions.set(event.stream, event.version);
|
|
1354
1341
|
if (event.name !== SNAP_EVENT) {
|
|
1355
|
-
this.
|
|
1356
|
-
this.
|
|
1342
|
+
this._max_event_id_by_stream.set(event.stream, id);
|
|
1343
|
+
this._max_non_snap_event_id = id;
|
|
1357
1344
|
}
|
|
1358
1345
|
return id;
|
|
1359
1346
|
});
|
|
1360
1347
|
} catch (err) {
|
|
1361
|
-
this._events =
|
|
1362
|
-
this._streams =
|
|
1363
|
-
this.
|
|
1364
|
-
this.
|
|
1365
|
-
this.
|
|
1348
|
+
this._events = prev_events;
|
|
1349
|
+
this._streams = prev_streams;
|
|
1350
|
+
this._stream_versions = prev_stream_versions;
|
|
1351
|
+
this._max_event_id_by_stream = prev_max_event_id_by_stream;
|
|
1352
|
+
this._max_non_snap_event_id = prev_max_non_snap_event_id;
|
|
1366
1353
|
throw err;
|
|
1367
1354
|
}
|
|
1368
1355
|
}
|
|
@@ -1458,7 +1445,7 @@ function parse(name) {
|
|
|
1458
1445
|
}
|
|
1459
1446
|
return { base: name, version: 1 };
|
|
1460
1447
|
}
|
|
1461
|
-
function
|
|
1448
|
+
function deprecated_event_names(names) {
|
|
1462
1449
|
const groups = /* @__PURE__ */ new Map();
|
|
1463
1450
|
for (const name of names) {
|
|
1464
1451
|
const { base, version } = parse(name);
|
|
@@ -1474,10 +1461,10 @@ function deprecatedEventNames(names) {
|
|
|
1474
1461
|
}
|
|
1475
1462
|
return deprecated;
|
|
1476
1463
|
}
|
|
1477
|
-
function
|
|
1478
|
-
const target = parse(
|
|
1464
|
+
function current_version_of(deprecated_name, all_names) {
|
|
1465
|
+
const target = parse(deprecated_name);
|
|
1479
1466
|
let highest;
|
|
1480
|
-
for (const name of
|
|
1467
|
+
for (const name of all_names) {
|
|
1481
1468
|
const { base, version } = parse(name);
|
|
1482
1469
|
if (base !== target.base) continue;
|
|
1483
1470
|
if (!highest || version > highest.version) highest = { version, name };
|
|
@@ -1507,27 +1494,27 @@ var ALL_CATEGORIES = [
|
|
|
1507
1494
|
];
|
|
1508
1495
|
async function* audit(deps, categories, options = {}) {
|
|
1509
1496
|
const requested = new Set(categories ?? [...ALL_CATEGORIES]);
|
|
1510
|
-
const
|
|
1511
|
-
const passes =
|
|
1497
|
+
const ordered_categories = ALL_CATEGORIES.filter((c) => requested.has(c));
|
|
1498
|
+
const passes = ordered_categories.map(
|
|
1512
1499
|
(c) => PASS_FACTORIES[c](deps, options)
|
|
1513
1500
|
);
|
|
1514
|
-
const
|
|
1515
|
-
const
|
|
1516
|
-
const
|
|
1517
|
-
if (
|
|
1501
|
+
const need_stats = passes.some((p) => p.on_stat !== void 0);
|
|
1502
|
+
const need_streams = passes.some((p) => p.on_stream !== void 0);
|
|
1503
|
+
const need_events = passes.some((p) => p.on_event !== void 0);
|
|
1504
|
+
if (need_stats) {
|
|
1518
1505
|
const stats = await deps.store().query_stats({}, { count: true, names: true });
|
|
1519
1506
|
for (const [stream, s] of stats) {
|
|
1520
|
-
for (const p of passes) p.
|
|
1507
|
+
for (const p of passes) p.on_stat?.(stream, s);
|
|
1521
1508
|
}
|
|
1522
1509
|
}
|
|
1523
|
-
if (
|
|
1510
|
+
if (need_streams) {
|
|
1524
1511
|
await deps.store().query_streams((pos) => {
|
|
1525
|
-
for (const p of passes) p.
|
|
1512
|
+
for (const p of passes) p.on_stream?.(pos);
|
|
1526
1513
|
});
|
|
1527
1514
|
}
|
|
1528
|
-
if (
|
|
1515
|
+
if (need_events) {
|
|
1529
1516
|
await deps.store().query((event) => {
|
|
1530
|
-
for (const p of passes) p.
|
|
1517
|
+
for (const p of passes) p.on_event?.(event);
|
|
1531
1518
|
}, options.query);
|
|
1532
1519
|
}
|
|
1533
1520
|
for (const p of passes) await p.finalize?.(deps);
|
|
@@ -1535,11 +1522,11 @@ async function* audit(deps, categories, options = {}) {
|
|
|
1535
1522
|
for (const f of p.drain()) yield f;
|
|
1536
1523
|
}
|
|
1537
1524
|
}
|
|
1538
|
-
var
|
|
1525
|
+
var make_schema_pass = (deps) => {
|
|
1539
1526
|
const findings = [];
|
|
1540
1527
|
return {
|
|
1541
1528
|
category: "schema",
|
|
1542
|
-
|
|
1529
|
+
on_event(event) {
|
|
1543
1530
|
const name = String(event.name);
|
|
1544
1531
|
const state2 = deps.event_to_state.get(name);
|
|
1545
1532
|
if (!state2) {
|
|
@@ -1569,19 +1556,19 @@ var makeSchemaPass = (deps) => {
|
|
|
1569
1556
|
drain: () => findings
|
|
1570
1557
|
};
|
|
1571
1558
|
};
|
|
1572
|
-
var
|
|
1559
|
+
var make_deprecated_load_pass = (deps, options) => {
|
|
1573
1560
|
const share_min = options.thresholds?.deprecated_min ?? DEFAULTS.deprecated_min;
|
|
1574
1561
|
const totals = /* @__PURE__ */ new Map();
|
|
1575
|
-
const
|
|
1562
|
+
const per_stream = /* @__PURE__ */ new Map();
|
|
1576
1563
|
return {
|
|
1577
1564
|
category: "deprecated-load",
|
|
1578
|
-
|
|
1565
|
+
on_stat(stream, { names }) {
|
|
1579
1566
|
for (const [name, count] of Object.entries(names)) {
|
|
1580
1567
|
totals.set(name, (totals.get(name) ?? 0) + count);
|
|
1581
|
-
let m =
|
|
1568
|
+
let m = per_stream.get(name);
|
|
1582
1569
|
if (!m) {
|
|
1583
1570
|
m = /* @__PURE__ */ new Map();
|
|
1584
|
-
|
|
1571
|
+
per_stream.set(name, m);
|
|
1585
1572
|
}
|
|
1586
1573
|
m.set(stream, count);
|
|
1587
1574
|
}
|
|
@@ -1590,33 +1577,33 @@ var makeDeprecatedLoadPass = (deps, options) => {
|
|
|
1590
1577
|
const findings = [];
|
|
1591
1578
|
const grand = [...totals.values()].reduce((s, n) => s + n, 0);
|
|
1592
1579
|
if (grand === 0) return findings;
|
|
1593
|
-
const deprecated =
|
|
1580
|
+
const deprecated = deprecated_event_names(deps.known_events);
|
|
1594
1581
|
const sorted = [...deprecated].map((name) => ({ name, count: totals.get(name) ?? 0 })).sort((a, b) => b.count - a.count);
|
|
1595
1582
|
for (const { name, count } of sorted) {
|
|
1596
1583
|
if (count === 0) continue;
|
|
1597
1584
|
if (count / grand < share_min) continue;
|
|
1598
|
-
const
|
|
1599
|
-
const
|
|
1585
|
+
const current_version = current_version_of(name, deps.known_events);
|
|
1586
|
+
const top_streams = [...per_stream.get(name).entries()].map(([stream, c]) => ({ stream, count: c })).sort((a, b) => b.count - a.count).slice(0, 10);
|
|
1600
1587
|
findings.push({
|
|
1601
1588
|
category: "deprecated-load",
|
|
1602
1589
|
name,
|
|
1603
|
-
current_version
|
|
1590
|
+
current_version,
|
|
1604
1591
|
total: count,
|
|
1605
|
-
top_streams
|
|
1592
|
+
top_streams
|
|
1606
1593
|
});
|
|
1607
1594
|
}
|
|
1608
1595
|
return findings;
|
|
1609
1596
|
}
|
|
1610
1597
|
};
|
|
1611
1598
|
};
|
|
1612
|
-
var
|
|
1599
|
+
var make_close_candidate_pass = (deps, options) => {
|
|
1613
1600
|
const idle_days = options.thresholds?.idle_days ?? DEFAULTS.idle_days;
|
|
1614
1601
|
const terminal_events = new Set(options.thresholds?.terminal_events ?? []);
|
|
1615
1602
|
const idle_cutoff = Date.now() - idle_days * 24 * 60 * 60 * 1e3;
|
|
1616
1603
|
const findings = [];
|
|
1617
1604
|
return {
|
|
1618
1605
|
category: "close-candidate",
|
|
1619
|
-
|
|
1606
|
+
on_stat(stream, { head }) {
|
|
1620
1607
|
const head_name = String(head.name);
|
|
1621
1608
|
if (head_name.startsWith("__")) return;
|
|
1622
1609
|
const head_time = head.created.getTime();
|
|
@@ -1629,22 +1616,22 @@ var makeCloseCandidatePass = (deps, options) => {
|
|
|
1629
1616
|
last_event_at: head.created.toISOString(),
|
|
1630
1617
|
reason: is_terminal ? "terminal" : "idle",
|
|
1631
1618
|
idle_days: is_idle ? Math.floor((Date.now() - head_time) / (24 * 60 * 60 * 1e3)) : void 0,
|
|
1632
|
-
restart_supported:
|
|
1619
|
+
restart_supported: restart_is_supported(deps, head_name)
|
|
1633
1620
|
});
|
|
1634
1621
|
},
|
|
1635
1622
|
drain: () => findings
|
|
1636
1623
|
};
|
|
1637
1624
|
};
|
|
1638
|
-
var
|
|
1625
|
+
var make_restart_candidate_pass = (deps, options) => {
|
|
1639
1626
|
const threshold = options.thresholds?.restart_min ?? DEFAULTS.restart_min;
|
|
1640
1627
|
const findings = [];
|
|
1641
1628
|
return {
|
|
1642
1629
|
category: "restart-candidate",
|
|
1643
|
-
|
|
1630
|
+
on_stat(stream, { head, count, names }) {
|
|
1644
1631
|
if (count < threshold) return;
|
|
1645
1632
|
const head_name = String(head.name);
|
|
1646
1633
|
if (head_name.startsWith("__")) return;
|
|
1647
|
-
if (!
|
|
1634
|
+
if (!restart_is_supported(deps, head_name)) return;
|
|
1648
1635
|
findings.push({
|
|
1649
1636
|
category: "restart-candidate",
|
|
1650
1637
|
stream,
|
|
@@ -1658,14 +1645,14 @@ var makeRestartCandidatePass = (deps, options) => {
|
|
|
1658
1645
|
drain: () => findings
|
|
1659
1646
|
};
|
|
1660
1647
|
};
|
|
1661
|
-
var
|
|
1648
|
+
var make_reaction_health_pass = (_deps, options) => {
|
|
1662
1649
|
const near_block = options.thresholds?.near_block ?? DEFAULTS.near_block;
|
|
1663
1650
|
const stuck_minutes = options.thresholds?.stuck_minutes ?? DEFAULTS.stuck_minutes;
|
|
1664
1651
|
const stuck_cutoff = Date.now() - stuck_minutes * 60 * 1e3;
|
|
1665
1652
|
const findings = [];
|
|
1666
1653
|
return {
|
|
1667
1654
|
category: "reaction-health",
|
|
1668
|
-
|
|
1655
|
+
on_stream(p) {
|
|
1669
1656
|
if (p.blocked) {
|
|
1670
1657
|
findings.push({
|
|
1671
1658
|
category: "reaction-health",
|
|
@@ -1702,14 +1689,14 @@ var makeReactionHealthPass = (_deps, options) => {
|
|
|
1702
1689
|
drain: () => findings
|
|
1703
1690
|
};
|
|
1704
1691
|
};
|
|
1705
|
-
var
|
|
1692
|
+
var make_snapshot_drift_pass = (deps, options) => {
|
|
1706
1693
|
const drift_min = options.thresholds?.drift_min ?? DEFAULTS.drift_min;
|
|
1707
1694
|
const candidates = [];
|
|
1708
1695
|
const findings = [];
|
|
1709
1696
|
return {
|
|
1710
1697
|
category: "snapshot-drift",
|
|
1711
|
-
|
|
1712
|
-
if (!
|
|
1698
|
+
on_stat(stream, { head, count, names }) {
|
|
1699
|
+
if (!restart_is_supported(deps, String(head.name))) return;
|
|
1713
1700
|
if (count < drift_min) return;
|
|
1714
1701
|
candidates.push({
|
|
1715
1702
|
stream,
|
|
@@ -1758,12 +1745,12 @@ var makeSnapshotDriftPass = (deps, options) => {
|
|
|
1758
1745
|
drain: () => findings
|
|
1759
1746
|
};
|
|
1760
1747
|
};
|
|
1761
|
-
var
|
|
1748
|
+
var make_routing_health_pass = (deps) => {
|
|
1762
1749
|
const findings = [];
|
|
1763
|
-
const
|
|
1750
|
+
const seen_event_names = /* @__PURE__ */ new Set();
|
|
1764
1751
|
return {
|
|
1765
1752
|
category: "routing-health",
|
|
1766
|
-
|
|
1753
|
+
on_stream(p) {
|
|
1767
1754
|
if (!p.lane) return;
|
|
1768
1755
|
if (deps.declared_lanes.has(p.lane)) return;
|
|
1769
1756
|
findings.push({
|
|
@@ -1773,13 +1760,13 @@ var makeRoutingHealthPass = (deps) => {
|
|
|
1773
1760
|
lane: p.lane
|
|
1774
1761
|
});
|
|
1775
1762
|
},
|
|
1776
|
-
|
|
1763
|
+
on_stat(_stream, { names }) {
|
|
1777
1764
|
for (const name of Object.keys(names)) {
|
|
1778
|
-
|
|
1765
|
+
seen_event_names.add(name);
|
|
1779
1766
|
}
|
|
1780
1767
|
},
|
|
1781
1768
|
finalize() {
|
|
1782
|
-
for (const name of
|
|
1769
|
+
for (const name of seen_event_names) {
|
|
1783
1770
|
if (name.startsWith("__")) continue;
|
|
1784
1771
|
if (deps.routed_events.has(name)) continue;
|
|
1785
1772
|
findings.push({
|
|
@@ -1793,23 +1780,23 @@ var makeRoutingHealthPass = (deps) => {
|
|
|
1793
1780
|
drain: () => findings
|
|
1794
1781
|
};
|
|
1795
1782
|
};
|
|
1796
|
-
var
|
|
1797
|
-
const
|
|
1783
|
+
var make_correlation_gaps_pass = () => {
|
|
1784
|
+
const seen_ids = /* @__PURE__ */ new Set();
|
|
1798
1785
|
const checks = [];
|
|
1799
1786
|
return {
|
|
1800
1787
|
category: "correlation-gaps",
|
|
1801
|
-
|
|
1802
|
-
|
|
1788
|
+
on_event(e) {
|
|
1789
|
+
seen_ids.add(e.id);
|
|
1803
1790
|
const causation = e.meta?.causation;
|
|
1804
|
-
const
|
|
1805
|
-
if (
|
|
1806
|
-
checks.push({ stream: e.stream, id: e.id,
|
|
1791
|
+
const parent_id = causation?.event?.id;
|
|
1792
|
+
if (parent_id !== void 0) {
|
|
1793
|
+
checks.push({ stream: e.stream, id: e.id, parent_id });
|
|
1807
1794
|
}
|
|
1808
1795
|
},
|
|
1809
1796
|
drain() {
|
|
1810
1797
|
const findings = [];
|
|
1811
|
-
for (const { stream, id,
|
|
1812
|
-
if (!
|
|
1798
|
+
for (const { stream, id, parent_id } of checks) {
|
|
1799
|
+
if (!seen_ids.has(parent_id)) {
|
|
1813
1800
|
findings.push({
|
|
1814
1801
|
category: "correlation-gaps",
|
|
1815
1802
|
stream,
|
|
@@ -1822,12 +1809,12 @@ var makeCorrelationGapsPass = () => {
|
|
|
1822
1809
|
}
|
|
1823
1810
|
};
|
|
1824
1811
|
};
|
|
1825
|
-
var
|
|
1812
|
+
var make_clock_anomalies_pass = () => {
|
|
1826
1813
|
const findings = [];
|
|
1827
|
-
const
|
|
1814
|
+
const last_per_stream = /* @__PURE__ */ new Map();
|
|
1828
1815
|
return {
|
|
1829
1816
|
category: "clock-anomalies",
|
|
1830
|
-
|
|
1817
|
+
on_event(e) {
|
|
1831
1818
|
const created = e.created.getTime();
|
|
1832
1819
|
if (created > Date.now()) {
|
|
1833
1820
|
findings.push({
|
|
@@ -1837,7 +1824,7 @@ var makeClockAnomaliesPass = () => {
|
|
|
1837
1824
|
reason: "future-created"
|
|
1838
1825
|
});
|
|
1839
1826
|
}
|
|
1840
|
-
const prev =
|
|
1827
|
+
const prev = last_per_stream.get(e.stream);
|
|
1841
1828
|
if (prev !== void 0 && created < prev) {
|
|
1842
1829
|
findings.push({
|
|
1843
1830
|
category: "clock-anomalies",
|
|
@@ -1846,48 +1833,48 @@ var makeClockAnomaliesPass = () => {
|
|
|
1846
1833
|
reason: "out-of-order"
|
|
1847
1834
|
});
|
|
1848
1835
|
}
|
|
1849
|
-
|
|
1836
|
+
last_per_stream.set(e.stream, created);
|
|
1850
1837
|
},
|
|
1851
1838
|
drain: () => findings
|
|
1852
1839
|
};
|
|
1853
1840
|
};
|
|
1854
|
-
function
|
|
1855
|
-
const state2 = deps.event_to_state.get(
|
|
1841
|
+
function restart_is_supported(deps, head_event_name) {
|
|
1842
|
+
const state2 = deps.event_to_state.get(head_event_name);
|
|
1856
1843
|
return state2?.snap !== void 0;
|
|
1857
1844
|
}
|
|
1858
1845
|
var PASS_FACTORIES = {
|
|
1859
|
-
schema:
|
|
1860
|
-
"deprecated-load":
|
|
1861
|
-
"close-candidate":
|
|
1862
|
-
"restart-candidate":
|
|
1863
|
-
"reaction-health":
|
|
1864
|
-
"snapshot-drift":
|
|
1865
|
-
"routing-health":
|
|
1866
|
-
"correlation-gaps":
|
|
1867
|
-
"clock-anomalies":
|
|
1846
|
+
schema: make_schema_pass,
|
|
1847
|
+
"deprecated-load": make_deprecated_load_pass,
|
|
1848
|
+
"close-candidate": make_close_candidate_pass,
|
|
1849
|
+
"restart-candidate": make_restart_candidate_pass,
|
|
1850
|
+
"reaction-health": make_reaction_health_pass,
|
|
1851
|
+
"snapshot-drift": make_snapshot_drift_pass,
|
|
1852
|
+
"routing-health": make_routing_health_pass,
|
|
1853
|
+
"correlation-gaps": make_correlation_gaps_pass,
|
|
1854
|
+
"clock-anomalies": make_clock_anomalies_pass
|
|
1868
1855
|
};
|
|
1869
1856
|
|
|
1870
1857
|
// src/internal/build-classify.ts
|
|
1871
1858
|
var ALL_LANES = /* @__PURE__ */ Symbol("act-1103/all-lanes");
|
|
1872
|
-
function
|
|
1859
|
+
function classify_registry(registry, states) {
|
|
1873
1860
|
const statics = /* @__PURE__ */ new Map();
|
|
1874
|
-
const
|
|
1875
|
-
const
|
|
1876
|
-
let
|
|
1861
|
+
const reactive_events = /* @__PURE__ */ new Set();
|
|
1862
|
+
const event_to_lanes = /* @__PURE__ */ new Map();
|
|
1863
|
+
let has_dynamic_resolvers = false;
|
|
1877
1864
|
for (const [name, register] of Object.entries(registry.events)) {
|
|
1878
|
-
if (register.reactions.size > 0)
|
|
1865
|
+
if (register.reactions.size > 0) reactive_events.add(name);
|
|
1879
1866
|
for (const reaction of register.reactions.values()) {
|
|
1880
1867
|
if (typeof reaction.resolver === "function") {
|
|
1881
|
-
|
|
1882
|
-
|
|
1868
|
+
has_dynamic_resolvers = true;
|
|
1869
|
+
event_to_lanes.set(name, ALL_LANES);
|
|
1883
1870
|
} else {
|
|
1884
1871
|
const { target, source, priority = 0, lane } = reaction.resolver;
|
|
1885
1872
|
const lane_name = lane ?? "default";
|
|
1886
|
-
const existing_lanes =
|
|
1873
|
+
const existing_lanes = event_to_lanes.get(name);
|
|
1887
1874
|
if (existing_lanes !== ALL_LANES) {
|
|
1888
1875
|
const set = existing_lanes ?? /* @__PURE__ */ new Set();
|
|
1889
1876
|
set.add(lane_name);
|
|
1890
|
-
|
|
1877
|
+
event_to_lanes.set(name, set);
|
|
1891
1878
|
}
|
|
1892
1879
|
const key = `${target}|${source ?? ""}`;
|
|
1893
1880
|
const existing = statics.get(key);
|
|
@@ -1905,59 +1892,59 @@ function classifyRegistry(registry, states) {
|
|
|
1905
1892
|
}
|
|
1906
1893
|
}
|
|
1907
1894
|
}
|
|
1908
|
-
const
|
|
1895
|
+
const event_to_state = /* @__PURE__ */ new Map();
|
|
1909
1896
|
for (const merged of states.values()) {
|
|
1910
|
-
for (const
|
|
1911
|
-
|
|
1897
|
+
for (const event_name of Object.keys(merged.events)) {
|
|
1898
|
+
event_to_state.set(event_name, merged);
|
|
1912
1899
|
}
|
|
1913
1900
|
}
|
|
1914
1901
|
return {
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1902
|
+
static_targets: [...statics.values()],
|
|
1903
|
+
has_dynamic_resolvers,
|
|
1904
|
+
reactive_events,
|
|
1905
|
+
event_to_state,
|
|
1906
|
+
event_to_lanes
|
|
1920
1907
|
};
|
|
1921
1908
|
}
|
|
1922
1909
|
|
|
1923
1910
|
// src/internal/close-cycle.ts
|
|
1924
|
-
async function
|
|
1925
|
-
const
|
|
1926
|
-
const streams = [...
|
|
1911
|
+
async function run_close_cycle(targets, deps) {
|
|
1912
|
+
const target_map = new Map(targets.map((t) => [t.stream, t]));
|
|
1913
|
+
const streams = [...target_map.keys()];
|
|
1927
1914
|
const skipped = [];
|
|
1928
|
-
const
|
|
1929
|
-
const safe = await
|
|
1930
|
-
|
|
1931
|
-
deps.
|
|
1915
|
+
const stream_info = await scan_stream_heads(streams);
|
|
1916
|
+
const safe = await partition_by_safety(
|
|
1917
|
+
stream_info,
|
|
1918
|
+
deps.reactive_events_size,
|
|
1932
1919
|
skipped
|
|
1933
1920
|
);
|
|
1934
1921
|
if (!safe.length) return { truncated: /* @__PURE__ */ new Map(), skipped };
|
|
1935
|
-
const { guarded,
|
|
1922
|
+
const { guarded, guard_events } = await guard_with_tombstones(
|
|
1936
1923
|
safe,
|
|
1937
|
-
|
|
1924
|
+
stream_info,
|
|
1938
1925
|
deps.correlation,
|
|
1939
1926
|
deps.tombstone,
|
|
1940
1927
|
skipped
|
|
1941
1928
|
);
|
|
1942
1929
|
if (!guarded.length) return { truncated: /* @__PURE__ */ new Map(), skipped };
|
|
1943
|
-
const
|
|
1930
|
+
const seed_states = await load_restart_seeds(
|
|
1944
1931
|
guarded,
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
deps.
|
|
1932
|
+
target_map,
|
|
1933
|
+
stream_info,
|
|
1934
|
+
deps.event_to_state,
|
|
1948
1935
|
deps.load,
|
|
1949
1936
|
deps.logger
|
|
1950
1937
|
);
|
|
1951
|
-
await
|
|
1952
|
-
const truncated = await
|
|
1938
|
+
await run_archive_callbacks(guarded, target_map);
|
|
1939
|
+
const truncated = await truncate_and_warm_cache(
|
|
1953
1940
|
guarded,
|
|
1954
|
-
|
|
1955
|
-
|
|
1941
|
+
seed_states,
|
|
1942
|
+
guard_events,
|
|
1956
1943
|
deps.correlation
|
|
1957
1944
|
);
|
|
1958
1945
|
return { truncated, skipped };
|
|
1959
1946
|
}
|
|
1960
|
-
async function
|
|
1947
|
+
async function scan_stream_heads(streams) {
|
|
1961
1948
|
const stats = await store2().query_stats(streams, {
|
|
1962
1949
|
exclude: [SNAP_EVENT]
|
|
1963
1950
|
});
|
|
@@ -1965,76 +1952,76 @@ async function scanStreamHeads(streams) {
|
|
|
1965
1952
|
for (const [stream, { head }] of stats) {
|
|
1966
1953
|
if (head.name === TOMBSTONE_EVENT) continue;
|
|
1967
1954
|
out.set(stream, {
|
|
1968
|
-
|
|
1955
|
+
max_id: head.id,
|
|
1969
1956
|
version: head.version,
|
|
1970
|
-
|
|
1957
|
+
last_event_name: head.name
|
|
1971
1958
|
});
|
|
1972
1959
|
}
|
|
1973
1960
|
return out;
|
|
1974
1961
|
}
|
|
1975
|
-
async function
|
|
1976
|
-
if (
|
|
1977
|
-
const
|
|
1962
|
+
async function partition_by_safety(stream_info, reactive_events_size, skipped) {
|
|
1963
|
+
if (reactive_events_size === 0) return [...stream_info.keys()];
|
|
1964
|
+
const pending_set = /* @__PURE__ */ new Set();
|
|
1978
1965
|
await store2().query_streams((position) => {
|
|
1979
|
-
const
|
|
1980
|
-
for (const [stream, info] of
|
|
1981
|
-
if ((!
|
|
1982
|
-
|
|
1966
|
+
const source_re = position.source ? RegExp(position.source) : void 0;
|
|
1967
|
+
for (const [stream, info] of stream_info) {
|
|
1968
|
+
if ((!source_re || source_re.test(stream)) && position.at < info.max_id) {
|
|
1969
|
+
pending_set.add(stream);
|
|
1983
1970
|
}
|
|
1984
1971
|
}
|
|
1985
1972
|
});
|
|
1986
1973
|
const safe = [];
|
|
1987
|
-
for (const [stream] of
|
|
1988
|
-
if (
|
|
1974
|
+
for (const [stream] of stream_info) {
|
|
1975
|
+
if (pending_set.has(stream)) skipped.push(stream);
|
|
1989
1976
|
else safe.push(stream);
|
|
1990
1977
|
}
|
|
1991
1978
|
return safe;
|
|
1992
1979
|
}
|
|
1993
|
-
async function
|
|
1980
|
+
async function guard_with_tombstones(safe, stream_info, correlation, tombstone2, skipped) {
|
|
1994
1981
|
const guarded = [];
|
|
1995
|
-
const
|
|
1982
|
+
const guard_events = /* @__PURE__ */ new Map();
|
|
1996
1983
|
await Promise.all(
|
|
1997
1984
|
safe.map(async (stream) => {
|
|
1998
|
-
const info =
|
|
1985
|
+
const info = stream_info.get(stream);
|
|
1999
1986
|
const committed = await tombstone2(stream, info.version, correlation);
|
|
2000
1987
|
if (committed) {
|
|
2001
1988
|
guarded.push(stream);
|
|
2002
|
-
|
|
1989
|
+
guard_events.set(stream, { id: committed.id, stream });
|
|
2003
1990
|
} else {
|
|
2004
1991
|
skipped.push(stream);
|
|
2005
1992
|
}
|
|
2006
1993
|
})
|
|
2007
1994
|
);
|
|
2008
|
-
return { guarded,
|
|
1995
|
+
return { guarded, guard_events };
|
|
2009
1996
|
}
|
|
2010
|
-
async function
|
|
2011
|
-
const
|
|
1997
|
+
async function load_restart_seeds(guarded, target_map, stream_info, event_to_state, load2, logger) {
|
|
1998
|
+
const seed_states = /* @__PURE__ */ new Map();
|
|
2012
1999
|
await Promise.all(
|
|
2013
|
-
guarded.filter((s) =>
|
|
2014
|
-
const
|
|
2015
|
-
const
|
|
2016
|
-
if (!
|
|
2000
|
+
guarded.filter((s) => target_map.get(s)?.restart).map(async (stream) => {
|
|
2001
|
+
const last_event_name = stream_info.get(stream).last_event_name;
|
|
2002
|
+
const owner_state = event_to_state.get(last_event_name);
|
|
2003
|
+
if (!owner_state) {
|
|
2017
2004
|
logger.error(
|
|
2018
|
-
`Cannot seed restart for "${stream}": no registered state owns event "${
|
|
2005
|
+
`Cannot seed restart for "${stream}": no registered state owns event "${last_event_name}". Stream will be tombstoned instead.`
|
|
2019
2006
|
);
|
|
2020
2007
|
return;
|
|
2021
2008
|
}
|
|
2022
|
-
const snap2 = await load2(
|
|
2023
|
-
|
|
2009
|
+
const snap2 = await load2(owner_state, { stream });
|
|
2010
|
+
seed_states.set(stream, snap2.state);
|
|
2024
2011
|
})
|
|
2025
2012
|
);
|
|
2026
|
-
return
|
|
2013
|
+
return seed_states;
|
|
2027
2014
|
}
|
|
2028
|
-
async function
|
|
2015
|
+
async function run_archive_callbacks(guarded, target_map) {
|
|
2029
2016
|
for (const stream of guarded) {
|
|
2030
|
-
const
|
|
2031
|
-
if (
|
|
2017
|
+
const archive_fn = target_map.get(stream)?.archive;
|
|
2018
|
+
if (archive_fn) await archive_fn();
|
|
2032
2019
|
}
|
|
2033
2020
|
}
|
|
2034
|
-
async function
|
|
2035
|
-
const
|
|
2036
|
-
const snapshot =
|
|
2037
|
-
const guard =
|
|
2021
|
+
async function truncate_and_warm_cache(guarded, seed_states, guard_events, correlation) {
|
|
2022
|
+
const trunc_targets = guarded.map((stream) => {
|
|
2023
|
+
const snapshot = seed_states.get(stream);
|
|
2024
|
+
const guard = guard_events.get(stream);
|
|
2038
2025
|
return {
|
|
2039
2026
|
stream,
|
|
2040
2027
|
snapshot,
|
|
@@ -2046,11 +2033,11 @@ async function truncateAndWarmCache(guarded, seedStates, guardEvents, correlatio
|
|
|
2046
2033
|
}
|
|
2047
2034
|
};
|
|
2048
2035
|
});
|
|
2049
|
-
const truncated = await store2().truncate(
|
|
2036
|
+
const truncated = await store2().truncate(trunc_targets);
|
|
2050
2037
|
await Promise.all(
|
|
2051
2038
|
guarded.map(async (stream) => {
|
|
2052
2039
|
const entry = truncated.get(stream);
|
|
2053
|
-
const state2 =
|
|
2040
|
+
const state2 = seed_states.get(stream);
|
|
2054
2041
|
if (state2 && entry) {
|
|
2055
2042
|
await cache2().set(stream, {
|
|
2056
2043
|
state: state2,
|
|
@@ -2078,13 +2065,13 @@ var CorrelateCycle = class {
|
|
|
2078
2065
|
_has_dynamic_resolvers;
|
|
2079
2066
|
_cd;
|
|
2080
2067
|
_on_init;
|
|
2081
|
-
constructor(registry,
|
|
2068
|
+
constructor(registry, static_targets, has_dynamic_resolvers, cd, maxSubscribedStreams, on_init) {
|
|
2082
2069
|
this._subscribed = new LruSet(maxSubscribedStreams);
|
|
2083
2070
|
this._registry = registry;
|
|
2084
|
-
this._static_targets =
|
|
2085
|
-
this._has_dynamic_resolvers =
|
|
2071
|
+
this._static_targets = static_targets;
|
|
2072
|
+
this._has_dynamic_resolvers = has_dynamic_resolvers;
|
|
2086
2073
|
this._cd = cd;
|
|
2087
|
-
this._on_init =
|
|
2074
|
+
this._on_init = on_init;
|
|
2088
2075
|
}
|
|
2089
2076
|
/** Last correlated event id. */
|
|
2090
2077
|
get checkpoint() {
|
|
@@ -2095,7 +2082,7 @@ var CorrelateCycle = class {
|
|
|
2095
2082
|
* - Reads max(at) from store as cold-start checkpoint
|
|
2096
2083
|
* - Subscribes static resolver targets (idempotent upsert)
|
|
2097
2084
|
* - Populates the subscribed-streams LRU
|
|
2098
|
-
* - Fires `
|
|
2085
|
+
* - Fires `on_init` once (Act uses this to flag a cold-start drain)
|
|
2099
2086
|
*/
|
|
2100
2087
|
async init() {
|
|
2101
2088
|
if (this._initialized) return;
|
|
@@ -2128,15 +2115,15 @@ var CorrelateCycle = class {
|
|
|
2128
2115
|
if (typeof reaction.resolver !== "function") continue;
|
|
2129
2116
|
const resolved = reaction.resolver(event);
|
|
2130
2117
|
if (resolved && !this._subscribed.has(resolved.target)) {
|
|
2131
|
-
const
|
|
2118
|
+
const incoming_priority = resolved.priority ?? 0;
|
|
2132
2119
|
const entry = correlated.get(resolved.target) || {
|
|
2133
2120
|
source: resolved.source,
|
|
2134
|
-
priority:
|
|
2121
|
+
priority: incoming_priority,
|
|
2135
2122
|
lane: resolved.lane,
|
|
2136
2123
|
payloads: []
|
|
2137
2124
|
};
|
|
2138
|
-
if (
|
|
2139
|
-
entry.priority =
|
|
2125
|
+
if (incoming_priority > entry.priority)
|
|
2126
|
+
entry.priority = incoming_priority;
|
|
2140
2127
|
entry.payloads.push({
|
|
2141
2128
|
...reaction,
|
|
2142
2129
|
source: resolved.source,
|
|
@@ -2175,7 +2162,7 @@ var CorrelateCycle = class {
|
|
|
2175
2162
|
* running. Errors from `correlate()` are routed through `log()` so they
|
|
2176
2163
|
* land in the configured logger (the timer keeps running on failure).
|
|
2177
2164
|
*/
|
|
2178
|
-
|
|
2165
|
+
start_polling(query = {}, frequency = 1e4, callback) {
|
|
2179
2166
|
if (this._timer) return false;
|
|
2180
2167
|
const limit = query.limit || 100;
|
|
2181
2168
|
this._timer = setInterval(
|
|
@@ -2187,7 +2174,7 @@ var CorrelateCycle = class {
|
|
|
2187
2174
|
return true;
|
|
2188
2175
|
}
|
|
2189
2176
|
/** Stop the periodic correlation worker. Idempotent. */
|
|
2190
|
-
|
|
2177
|
+
stop_polling() {
|
|
2191
2178
|
if (this._timer) {
|
|
2192
2179
|
clearInterval(this._timer);
|
|
2193
2180
|
this._timer = void 0;
|
|
@@ -2203,14 +2190,14 @@ var SEG_SPACE = BASE ** SEG_WIDTH;
|
|
|
2203
2190
|
function seg(n) {
|
|
2204
2191
|
return n.toString(BASE).padStart(SEG_WIDTH, "0");
|
|
2205
2192
|
}
|
|
2206
|
-
var
|
|
2193
|
+
var default_correlator = ({ state: state2, action: action2 }) => {
|
|
2207
2194
|
const s = state2.slice(0, SEG_WIDTH).toLowerCase();
|
|
2208
2195
|
const a = action2.slice(0, SEG_WIDTH).toLowerCase();
|
|
2209
2196
|
const ts = seg(Date.now() % SEG_SPACE);
|
|
2210
2197
|
const rnd = seg((0, import_node_crypto.randomInt)(SEG_SPACE));
|
|
2211
2198
|
return `${s}-${a}-${ts}${rnd}`;
|
|
2212
2199
|
};
|
|
2213
|
-
function
|
|
2200
|
+
function close_correlation(correlator, actor) {
|
|
2214
2201
|
return correlator({
|
|
2215
2202
|
state: "$close",
|
|
2216
2203
|
action: "close",
|
|
@@ -2226,7 +2213,7 @@ var import_node_crypto2 = require("crypto");
|
|
|
2226
2213
|
var RATIO_MIN = 0.2;
|
|
2227
2214
|
var RATIO_MAX = 0.8;
|
|
2228
2215
|
var RATIO_DEFAULT = 0.5;
|
|
2229
|
-
function
|
|
2216
|
+
function compute_lag_lead_ratio(handled, lagging, leading) {
|
|
2230
2217
|
let lagging_handled = 0;
|
|
2231
2218
|
let leading_handled = 0;
|
|
2232
2219
|
for (const { lease, handled: count } of handled) {
|
|
@@ -2263,7 +2250,7 @@ var subscribe = (streams) => store2().subscribe(streams);
|
|
|
2263
2250
|
var import_act_patch = require("@rotorsoft/act-patch");
|
|
2264
2251
|
|
|
2265
2252
|
// src/internal/backoff.ts
|
|
2266
|
-
function
|
|
2253
|
+
function compute_backoff_delay(retry, opts) {
|
|
2267
2254
|
if (!opts || opts.baseMs <= 0) return 0;
|
|
2268
2255
|
const r = Math.max(0, retry);
|
|
2269
2256
|
let delay;
|
|
@@ -2383,7 +2370,6 @@ async function scan(source, opts = {}, callback) {
|
|
|
2383
2370
|
migrated = {
|
|
2384
2371
|
...event,
|
|
2385
2372
|
name: migration.to,
|
|
2386
|
-
// biome-ignore lint/suspicious/noExplicitAny: migration target shape is caller-defined
|
|
2387
2373
|
data: new_data
|
|
2388
2374
|
};
|
|
2389
2375
|
migrated_count++;
|
|
@@ -2432,39 +2418,37 @@ async function scan(source, opts = {}, callback) {
|
|
|
2432
2418
|
}
|
|
2433
2419
|
};
|
|
2434
2420
|
}
|
|
2435
|
-
async function load(me,
|
|
2436
|
-
const
|
|
2437
|
-
const
|
|
2421
|
+
async function load(me, target, callback) {
|
|
2422
|
+
const { stream, actor, asOf } = target;
|
|
2423
|
+
const time_travel = !!asOf && Object.values(asOf).some((v) => v !== void 0);
|
|
2424
|
+
const cached = time_travel ? void 0 : await cache2().get(stream);
|
|
2438
2425
|
const cache_hit = !!cached;
|
|
2439
2426
|
let state2 = cached?.state ?? (me.init ? me.init() : {});
|
|
2440
2427
|
let patches = cached?.patches ?? 0;
|
|
2441
2428
|
let snaps = cached?.snaps ?? 0;
|
|
2442
|
-
let version = cached?.version ?? -1;
|
|
2443
2429
|
let replayed = 0;
|
|
2444
2430
|
let event;
|
|
2445
2431
|
await store2().query(
|
|
2446
|
-
(
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
if (e.name === SNAP_EVENT) {
|
|
2451
|
-
state2 = e.data;
|
|
2432
|
+
(raw) => {
|
|
2433
|
+
event = me.view(raw, actor);
|
|
2434
|
+
if (event.name === SNAP_EVENT) {
|
|
2435
|
+
state2 = event.data;
|
|
2452
2436
|
snaps++;
|
|
2453
2437
|
patches = 0;
|
|
2454
2438
|
replayed++;
|
|
2455
|
-
} else if (me.patch[
|
|
2456
|
-
state2 = (0, import_act_patch.patch)(state2, me.patch[
|
|
2439
|
+
} else if (me.patch[event.name]) {
|
|
2440
|
+
state2 = (0, import_act_patch.patch)(state2, me.patch[event.name](event, state2));
|
|
2457
2441
|
patches++;
|
|
2458
2442
|
replayed++;
|
|
2459
|
-
} else if (
|
|
2443
|
+
} else if (event.name !== TOMBSTONE_EVENT) {
|
|
2460
2444
|
log().warn(
|
|
2461
|
-
`Skipping unknown event "${String(
|
|
2445
|
+
`Skipping unknown event "${String(event.name)}" on stream "${stream}" (id=${event.id}) \u2014 no reducer in state "${me.name}"`
|
|
2462
2446
|
);
|
|
2463
2447
|
}
|
|
2464
2448
|
callback?.({
|
|
2465
2449
|
event,
|
|
2466
2450
|
state: state2,
|
|
2467
|
-
version,
|
|
2451
|
+
version: event.version,
|
|
2468
2452
|
patches,
|
|
2469
2453
|
snaps,
|
|
2470
2454
|
cache_hit,
|
|
@@ -2477,39 +2461,43 @@ async function load(me, stream, callback, asOf, actor) {
|
|
|
2477
2461
|
...cached ? { after: cached.event_id } : { with_snaps: true, ...asOf }
|
|
2478
2462
|
}
|
|
2479
2463
|
);
|
|
2480
|
-
if (replayed > 0 && !
|
|
2464
|
+
if (replayed > 0 && !time_travel && event && !me.pii_aware) {
|
|
2481
2465
|
await cache2().set(stream, {
|
|
2482
2466
|
state: state2,
|
|
2483
|
-
version,
|
|
2467
|
+
version: event.version,
|
|
2484
2468
|
event_id: event.id,
|
|
2485
2469
|
patches,
|
|
2486
2470
|
snaps
|
|
2487
2471
|
});
|
|
2488
2472
|
}
|
|
2489
|
-
return {
|
|
2473
|
+
return {
|
|
2474
|
+
event,
|
|
2475
|
+
state: state2,
|
|
2476
|
+
version: event?.version ?? cached?.version ?? -1,
|
|
2477
|
+
patches,
|
|
2478
|
+
snaps,
|
|
2479
|
+
cache_hit,
|
|
2480
|
+
replayed
|
|
2481
|
+
};
|
|
2490
2482
|
}
|
|
2491
|
-
async function action(me, action2, target, payload,
|
|
2483
|
+
async function action(me, action2, target, payload, options) {
|
|
2492
2484
|
const { stream, expectedVersion, actor } = target;
|
|
2493
2485
|
if (!stream) throw new Error("Missing target stream");
|
|
2494
|
-
const
|
|
2486
|
+
const reactingTo = options?.reactingTo;
|
|
2487
|
+
const correlator = options?.correlator ?? default_correlator;
|
|
2488
|
+
const validated = validate(action2, payload, me.actions[action2]);
|
|
2495
2489
|
const opts = me.options?.[action2];
|
|
2496
|
-
const
|
|
2490
|
+
const max_retries = opts?.maxRetries ?? 0;
|
|
2497
2491
|
for (let attempt = 0; ; attempt++) {
|
|
2498
2492
|
try {
|
|
2499
|
-
const snapshot = await load(
|
|
2500
|
-
me,
|
|
2501
|
-
stream,
|
|
2502
|
-
void 0,
|
|
2503
|
-
void 0,
|
|
2504
|
-
target.actor
|
|
2505
|
-
);
|
|
2493
|
+
const snapshot = await load(me, { stream, actor: target.actor });
|
|
2506
2494
|
if (snapshot.event?.name === TOMBSTONE_EVENT)
|
|
2507
2495
|
throw new StreamClosedError(stream);
|
|
2508
2496
|
const expected = expectedVersion ?? snapshot.event?.version;
|
|
2509
2497
|
if (me.given) {
|
|
2510
2498
|
const invariants = me.given[action2] || [];
|
|
2511
|
-
invariants.forEach(({ valid, description }) => {
|
|
2512
|
-
if (!
|
|
2499
|
+
invariants.forEach(({ valid: valid2, description }) => {
|
|
2500
|
+
if (!valid2(snapshot.state, actor))
|
|
2513
2501
|
throw new InvariantError(
|
|
2514
2502
|
action2,
|
|
2515
2503
|
validated,
|
|
@@ -2525,24 +2513,11 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
2525
2513
|
return [snapshot];
|
|
2526
2514
|
}
|
|
2527
2515
|
const tuples = Array.isArray(result[0]) ? result : [result];
|
|
2528
|
-
const
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
const evt = name;
|
|
2534
|
-
if (deprecated.has(evt) && !warned.has(evt)) {
|
|
2535
|
-
warned.add(evt);
|
|
2536
|
-
log().warn(
|
|
2537
|
-
`Action "${String(action2)}" emitted deprecated event "${evt}". A newer version exists in the registry \u2014 update the action's .emit() to target the current version. (warned once per process)`
|
|
2538
|
-
);
|
|
2539
|
-
}
|
|
2540
|
-
}
|
|
2541
|
-
}
|
|
2542
|
-
const emitted = tuples.map(([name, data]) => {
|
|
2543
|
-
const validated2 = skipValidation ? data : validate(name, data, me.events[name]);
|
|
2544
|
-
return me.message({ name, data: validated2 });
|
|
2545
|
-
});
|
|
2516
|
+
const valid = tuples.map(([name, data]) => ({
|
|
2517
|
+
name,
|
|
2518
|
+
data: validate(name, data, me.events[name])
|
|
2519
|
+
}));
|
|
2520
|
+
const emitted = valid.map((e) => me.message(e));
|
|
2546
2521
|
const meta = {
|
|
2547
2522
|
correlation: reactingTo?.meta.correlation || correlator({
|
|
2548
2523
|
action: action2,
|
|
@@ -2583,12 +2558,13 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
2583
2558
|
throw error;
|
|
2584
2559
|
}
|
|
2585
2560
|
let { state: state2, patches } = snapshot;
|
|
2586
|
-
const snapshots = committed.map((
|
|
2561
|
+
const snapshots = committed.map((row, i) => {
|
|
2562
|
+
const event = { ...row, data: valid[i].data };
|
|
2587
2563
|
const p = me.patch[event.name](event, state2);
|
|
2588
2564
|
state2 = (0, import_act_patch.patch)(state2, p);
|
|
2589
2565
|
patches++;
|
|
2590
2566
|
return {
|
|
2591
|
-
event
|
|
2567
|
+
event,
|
|
2592
2568
|
state: state2,
|
|
2593
2569
|
version: event.version,
|
|
2594
2570
|
patches,
|
|
@@ -2600,21 +2576,23 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
2600
2576
|
});
|
|
2601
2577
|
const last = snapshots.at(-1);
|
|
2602
2578
|
const snapped = me.snap?.(last);
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2579
|
+
if (!me.pii_aware) {
|
|
2580
|
+
cache2().set(stream, {
|
|
2581
|
+
state: last.state,
|
|
2582
|
+
version: last.event.version,
|
|
2583
|
+
event_id: last.event.id,
|
|
2584
|
+
patches: snapped ? 0 : last.patches,
|
|
2585
|
+
snaps: snapped ? last.snaps + 1 : last.snaps
|
|
2586
|
+
}).catch((err) => log().error(err));
|
|
2587
|
+
}
|
|
2610
2588
|
if (snapped) void snap(last);
|
|
2611
2589
|
return snapshots;
|
|
2612
2590
|
} catch (error) {
|
|
2613
2591
|
if (!(error instanceof ConcurrencyError)) throw error;
|
|
2614
|
-
if (attempt >=
|
|
2592
|
+
if (attempt >= max_retries) throw error;
|
|
2615
2593
|
if (opts?.backoff) {
|
|
2616
|
-
const
|
|
2617
|
-
if (
|
|
2594
|
+
const delay_ms = compute_backoff_delay(attempt, opts.backoff);
|
|
2595
|
+
if (delay_ms > 0) await sleep(delay_ms);
|
|
2618
2596
|
}
|
|
2619
2597
|
}
|
|
2620
2598
|
}
|
|
@@ -2638,12 +2616,12 @@ var C_STREAM = "\x1B[38;5;226m";
|
|
|
2638
2616
|
var dim = (text) => PRETTY ? `${C_DIM}${text}${C_RESET}` : text;
|
|
2639
2617
|
var hue = (color, text) => PRETTY ? `${color}${text}${C_RESET}` : text;
|
|
2640
2618
|
var drain_caption = (caption, lane) => {
|
|
2641
|
-
const
|
|
2619
|
+
const show_lane = lane && lane !== "default";
|
|
2642
2620
|
if (PRETTY) {
|
|
2643
2621
|
const tag = `${C_DRAIN}>> ${caption}${C_RESET}`;
|
|
2644
|
-
return
|
|
2622
|
+
return show_lane ? `${tag} ${C_LANE}${lane}${C_RESET}` : tag;
|
|
2645
2623
|
}
|
|
2646
|
-
return
|
|
2624
|
+
return show_lane ? `>> ${caption} ${lane}` : `>> ${caption}`;
|
|
2647
2625
|
};
|
|
2648
2626
|
var cache_marker = (hit) => {
|
|
2649
2627
|
const word = hit ? "hit" : "miss";
|
|
@@ -2672,16 +2650,8 @@ var traced = (inner, exit, entry) => (async (...args) => {
|
|
|
2672
2650
|
exit?.(result, ...args);
|
|
2673
2651
|
return result;
|
|
2674
2652
|
});
|
|
2675
|
-
function
|
|
2676
|
-
const bound_action = (me,
|
|
2677
|
-
me,
|
|
2678
|
-
actionName,
|
|
2679
|
-
target,
|
|
2680
|
-
payload,
|
|
2681
|
-
reactingTo,
|
|
2682
|
-
skipValidation,
|
|
2683
|
-
correlator
|
|
2684
|
-
);
|
|
2653
|
+
function build_es(logger, correlator = default_correlator) {
|
|
2654
|
+
const bound_action = (me, action_name, target, payload, options) => action(me, action_name, target, payload, { correlator, ...options });
|
|
2685
2655
|
if (logger.level !== "trace") {
|
|
2686
2656
|
return {
|
|
2687
2657
|
snap,
|
|
@@ -2700,7 +2670,7 @@ function buildEs(logger, correlator = defaultCorrelator) {
|
|
|
2700
2670
|
)
|
|
2701
2671
|
);
|
|
2702
2672
|
}),
|
|
2703
|
-
load: traced(load, (result, _me,
|
|
2673
|
+
load: traced(load, (result, _me, target) => {
|
|
2704
2674
|
const stats = stats_marker(
|
|
2705
2675
|
result.version,
|
|
2706
2676
|
result.replayed,
|
|
@@ -2711,7 +2681,7 @@ function buildEs(logger, correlator = defaultCorrelator) {
|
|
|
2711
2681
|
es_caption(
|
|
2712
2682
|
"load",
|
|
2713
2683
|
C_GREEN,
|
|
2714
|
-
`${stream}${as_of_marker(asOf)} ${cache_marker(result.cache_hit)} ${stats}`
|
|
2684
|
+
`${target.stream}${as_of_marker(target.asOf)} ${cache_marker(result.cache_hit)} ${stats}`
|
|
2715
2685
|
)
|
|
2716
2686
|
);
|
|
2717
2687
|
}),
|
|
@@ -2745,7 +2715,7 @@ function buildEs(logger, correlator = defaultCorrelator) {
|
|
|
2745
2715
|
})
|
|
2746
2716
|
};
|
|
2747
2717
|
}
|
|
2748
|
-
function
|
|
2718
|
+
function build_drain(logger) {
|
|
2749
2719
|
return {
|
|
2750
2720
|
claim,
|
|
2751
2721
|
fetch,
|
|
@@ -2754,46 +2724,48 @@ function buildDrain(logger) {
|
|
|
2754
2724
|
subscribe: logger.level !== "trace" ? subscribe : traced(subscribe, (result, streams) => {
|
|
2755
2725
|
if (!result.subscribed) return;
|
|
2756
2726
|
const lanes = new Set(streams.map((s) => s.lane ?? "default"));
|
|
2757
|
-
const
|
|
2727
|
+
const uniform_lane = lanes.size === 1 ? streams[0]?.lane : void 0;
|
|
2758
2728
|
const data = streams.map(
|
|
2759
|
-
({ stream, lane }) =>
|
|
2729
|
+
({ stream, lane }) => uniform_lane || !lane || lane === "default" ? hue(C_STREAM, stream) : `${hue(C_STREAM, stream)}${dim(`[${lane}]`)}`
|
|
2760
2730
|
).join(" ");
|
|
2761
|
-
logger.trace(
|
|
2731
|
+
logger.trace(
|
|
2732
|
+
`${drain_caption("correlated", uniform_lane)} ${data}`
|
|
2733
|
+
);
|
|
2762
2734
|
})
|
|
2763
2735
|
};
|
|
2764
2736
|
}
|
|
2765
|
-
function
|
|
2737
|
+
function trace_cycle(logger, leased, fetched, handled, acked, blocked) {
|
|
2766
2738
|
if (logger.level !== "trace" || !leased.length) return;
|
|
2767
2739
|
const lane = leased[0]?.lane;
|
|
2768
|
-
const
|
|
2769
|
-
const
|
|
2770
|
-
const
|
|
2771
|
-
const
|
|
2740
|
+
const fetch_by_stream = new Map(fetched.map((f) => [f.stream, f]));
|
|
2741
|
+
const acked_by_stream = new Map(acked.map((a) => [a.stream, a.at]));
|
|
2742
|
+
const blocked_by_stream = new Map(blocked.map((b) => [b.stream, b.error]));
|
|
2743
|
+
const failed_by_stream = new Map(
|
|
2772
2744
|
handled.filter((h) => h.error).map((h) => [h.lease.stream, h])
|
|
2773
2745
|
);
|
|
2774
2746
|
const detail = leased.map(({ stream, at, retry }) => {
|
|
2775
|
-
const f =
|
|
2747
|
+
const f = fetch_by_stream.get(stream);
|
|
2776
2748
|
const key = f?.source ? `${hue(C_STREAM, stream)}${dim(`<-${f.source}`)}` : hue(C_STREAM, stream);
|
|
2777
2749
|
const events = f && f.events.length ? ` ${dim(
|
|
2778
2750
|
`[${f.events.map(({ id, name }) => `#${id} ${String(name)}`).join(", ")}]`
|
|
2779
2751
|
)}` : "";
|
|
2780
|
-
const
|
|
2781
|
-
const
|
|
2782
|
-
const failure =
|
|
2783
|
-
let
|
|
2752
|
+
const acked_at = acked_by_stream.get(stream);
|
|
2753
|
+
const ack_part = acked_at !== void 0 ? hue(C_HIT, `\u2713 @${acked_at}`) : "";
|
|
2754
|
+
const failure = failed_by_stream.get(stream);
|
|
2755
|
+
let fail_part = "";
|
|
2784
2756
|
if (failure) {
|
|
2785
|
-
const
|
|
2786
|
-
const
|
|
2787
|
-
if (
|
|
2788
|
-
|
|
2757
|
+
const failed_at = failure.failed_at ?? at;
|
|
2758
|
+
const blocked_error = blocked_by_stream.get(stream);
|
|
2759
|
+
if (blocked_error !== void 0) {
|
|
2760
|
+
fail_part = `${hue(C_ERR, `\u2717 @${failed_at}/${retry}`)} ${dim(`(${blocked_error})`)}`;
|
|
2789
2761
|
} else {
|
|
2790
|
-
|
|
2762
|
+
fail_part = `${hue(C_MISS, `\u26A0 @${failed_at}/${retry}`)} ${dim(`(${failure.error})`)}`;
|
|
2791
2763
|
}
|
|
2792
2764
|
}
|
|
2793
2765
|
let tail;
|
|
2794
|
-
if (
|
|
2795
|
-
else if (
|
|
2796
|
-
else if (
|
|
2766
|
+
if (ack_part && fail_part) tail = ` ${ack_part} ${fail_part}`;
|
|
2767
|
+
else if (ack_part) tail = ` ${ack_part}`;
|
|
2768
|
+
else if (fail_part) tail = ` ${fail_part}`;
|
|
2797
2769
|
else tail = ` ${dim(`\u2298 @${at}/${retry}`)}`;
|
|
2798
2770
|
return `${key}${events}${tail}`;
|
|
2799
2771
|
}).join(", ");
|
|
@@ -2801,7 +2773,7 @@ function traceCycle(logger, leased, fetched, handled, acked, blocked) {
|
|
|
2801
2773
|
}
|
|
2802
2774
|
|
|
2803
2775
|
// src/internal/drain-cycle.ts
|
|
2804
|
-
async function
|
|
2776
|
+
async function run_drain_cycle(ops, registry, batch_handlers, handle, handle_batch, lagging, leading, eventLimit, leaseMillis, is_deferred, lane) {
|
|
2805
2777
|
const leased = await ops.claim(
|
|
2806
2778
|
lagging,
|
|
2807
2779
|
leading,
|
|
@@ -2810,7 +2782,7 @@ async function runDrainCycle(ops, registry, batchHandlers, handle, handleBatch,
|
|
|
2810
2782
|
lane
|
|
2811
2783
|
);
|
|
2812
2784
|
if (!leased.length) return void 0;
|
|
2813
|
-
const active =
|
|
2785
|
+
const active = is_deferred ? leased.filter((l) => !is_deferred(l.stream)) : leased;
|
|
2814
2786
|
if (!active.length) {
|
|
2815
2787
|
return {
|
|
2816
2788
|
leased,
|
|
@@ -2821,7 +2793,7 @@ async function runDrainCycle(ops, registry, batchHandlers, handle, handleBatch,
|
|
|
2821
2793
|
};
|
|
2822
2794
|
}
|
|
2823
2795
|
const fetched = await ops.fetch(active, eventLimit);
|
|
2824
|
-
const
|
|
2796
|
+
const fetch_map = /* @__PURE__ */ new Map();
|
|
2825
2797
|
const fetch_window_at = fetched.reduce(
|
|
2826
2798
|
(max, { at, events }) => Math.max(max, events.at(-1)?.id || at),
|
|
2827
2799
|
0
|
|
@@ -2836,16 +2808,16 @@ async function runDrainCycle(ops, registry, batchHandlers, handle, handleBatch,
|
|
|
2836
2808
|
return resolved && resolved.target === stream;
|
|
2837
2809
|
}).map((reaction) => ({ ...reaction, event }));
|
|
2838
2810
|
});
|
|
2839
|
-
|
|
2811
|
+
fetch_map.set(stream, { fetch: f, payloads });
|
|
2840
2812
|
}
|
|
2841
2813
|
const handled = await Promise.all(
|
|
2842
2814
|
active.map((lease) => {
|
|
2843
|
-
const entry =
|
|
2815
|
+
const entry = fetch_map.get(lease.stream);
|
|
2844
2816
|
const at = entry.fetch.events.at(-1)?.id || fetch_window_at;
|
|
2845
2817
|
const { payloads } = entry;
|
|
2846
|
-
const batchHandler =
|
|
2818
|
+
const batchHandler = batch_handlers.get(lease.stream);
|
|
2847
2819
|
if (batchHandler && payloads.length > 0) {
|
|
2848
|
-
return
|
|
2820
|
+
return handle_batch({ ...lease, at }, payloads, batchHandler);
|
|
2849
2821
|
}
|
|
2850
2822
|
return handle({ ...lease, at }, payloads);
|
|
2851
2823
|
})
|
|
@@ -2869,14 +2841,14 @@ var DrainController = class {
|
|
|
2869
2841
|
_locked = false;
|
|
2870
2842
|
_ratio = 0.5;
|
|
2871
2843
|
/**
|
|
2872
|
-
* Per-stream backoff: `stream →
|
|
2873
|
-
* `_finalize` via `HandleResult.
|
|
2844
|
+
* Per-stream backoff: `stream → next_attempt_at` (ms since epoch). Set by
|
|
2845
|
+
* `_finalize` via `HandleResult.next_attempt_at`; cleared on successful
|
|
2874
2846
|
* ack or terminal block. Lives in process memory — per-worker pacing
|
|
2875
2847
|
* by design (see {@link BackoffOptions} for the multi-worker trade-off).
|
|
2876
2848
|
*/
|
|
2877
2849
|
_backoff = /* @__PURE__ */ new Map();
|
|
2878
|
-
/** Timer re-arming drain at the earliest pending `
|
|
2879
|
-
|
|
2850
|
+
/** Timer re-arming drain at the earliest pending `next_attempt_at`. */
|
|
2851
|
+
_backoff_timer;
|
|
2880
2852
|
/** Worker timer (ACT-1103). Set when `start()` is active, undefined otherwise. */
|
|
2881
2853
|
_worker;
|
|
2882
2854
|
_stopped = false;
|
|
@@ -2897,7 +2869,7 @@ var DrainController = class {
|
|
|
2897
2869
|
return this._armed;
|
|
2898
2870
|
}
|
|
2899
2871
|
/** Returns true when `stream` is currently within a backoff window. */
|
|
2900
|
-
|
|
2872
|
+
is_deferred = (stream) => {
|
|
2901
2873
|
const next = this._backoff.get(stream);
|
|
2902
2874
|
return next !== void 0 && next > Date.now();
|
|
2903
2875
|
};
|
|
@@ -2907,20 +2879,20 @@ var DrainController = class {
|
|
|
2907
2879
|
* Idempotent — collapses many simultaneously deferred streams into a
|
|
2908
2880
|
* single timer.
|
|
2909
2881
|
*/
|
|
2910
|
-
|
|
2911
|
-
if (this.
|
|
2882
|
+
schedule_backoff_wake() {
|
|
2883
|
+
if (this._backoff_timer) clearTimeout(this._backoff_timer);
|
|
2912
2884
|
let earliest = Number.POSITIVE_INFINITY;
|
|
2913
2885
|
for (const t of this._backoff.values()) if (t < earliest) earliest = t;
|
|
2914
2886
|
const delay = Math.max(0, earliest - Date.now());
|
|
2915
|
-
this.
|
|
2916
|
-
this.
|
|
2887
|
+
this._backoff_timer = setTimeout(() => {
|
|
2888
|
+
this._backoff_timer = void 0;
|
|
2917
2889
|
const now = Date.now();
|
|
2918
2890
|
for (const [stream, at] of this._backoff) {
|
|
2919
2891
|
if (at <= now) this._backoff.delete(stream);
|
|
2920
2892
|
}
|
|
2921
2893
|
this._armed = true;
|
|
2922
2894
|
}, delay);
|
|
2923
|
-
this.
|
|
2895
|
+
this._backoff_timer.unref();
|
|
2924
2896
|
}
|
|
2925
2897
|
/** Lane this controller drains (undefined = legacy single-lane span). */
|
|
2926
2898
|
get lane() {
|
|
@@ -2966,17 +2938,17 @@ var DrainController = class {
|
|
|
2966
2938
|
this._locked = true;
|
|
2967
2939
|
const lagging = Math.ceil(streamLimit * this._ratio);
|
|
2968
2940
|
const leading = streamLimit - lagging;
|
|
2969
|
-
const cycle = await
|
|
2941
|
+
const cycle = await run_drain_cycle(
|
|
2970
2942
|
this._deps.ops,
|
|
2971
2943
|
this._deps.registry,
|
|
2972
|
-
this._deps.
|
|
2944
|
+
this._deps.batch_handlers,
|
|
2973
2945
|
this._deps.handle,
|
|
2974
|
-
this._deps.
|
|
2946
|
+
this._deps.handle_batch,
|
|
2975
2947
|
lagging,
|
|
2976
2948
|
leading,
|
|
2977
2949
|
eventLimit,
|
|
2978
2950
|
leaseMillis,
|
|
2979
|
-
this._backoff.size > 0 ? this.
|
|
2951
|
+
this._backoff.size > 0 ? this.is_deferred : void 0,
|
|
2980
2952
|
this._deps.lane
|
|
2981
2953
|
);
|
|
2982
2954
|
if (!cycle) {
|
|
@@ -2984,20 +2956,20 @@ var DrainController = class {
|
|
|
2984
2956
|
return EMPTY_DRAIN;
|
|
2985
2957
|
}
|
|
2986
2958
|
const { leased, fetched, handled, acked, blocked } = cycle;
|
|
2987
|
-
|
|
2988
|
-
this._ratio =
|
|
2959
|
+
trace_cycle(this._deps.logger, leased, fetched, handled, acked, blocked);
|
|
2960
|
+
this._ratio = compute_lag_lead_ratio(handled, lagging, leading);
|
|
2989
2961
|
for (const lease of acked) this._backoff.delete(lease.stream);
|
|
2990
2962
|
for (const lease of blocked) this._backoff.delete(lease.stream);
|
|
2991
2963
|
for (const h of handled) {
|
|
2992
|
-
if (h.
|
|
2993
|
-
this._backoff.set(h.lease.stream, h.
|
|
2964
|
+
if (h.next_attempt_at !== void 0 && !h.block) {
|
|
2965
|
+
this._backoff.set(h.lease.stream, h.next_attempt_at);
|
|
2994
2966
|
}
|
|
2995
2967
|
}
|
|
2996
|
-
if (this._backoff.size > 0) this.
|
|
2997
|
-
if (acked.length) this._deps.
|
|
2998
|
-
if (blocked.length) this._deps.
|
|
2999
|
-
const
|
|
3000
|
-
if (!acked.length && !blocked.length && !
|
|
2968
|
+
if (this._backoff.size > 0) this.schedule_backoff_wake();
|
|
2969
|
+
if (acked.length) this._deps.on_acked(acked);
|
|
2970
|
+
if (blocked.length) this._deps.on_blocked(blocked);
|
|
2971
|
+
const has_errors = handled.some(({ error }) => error);
|
|
2972
|
+
if (!acked.length && !blocked.length && !has_errors) this._armed = false;
|
|
3001
2973
|
return { fetched, leased, acked, blocked };
|
|
3002
2974
|
} catch (error) {
|
|
3003
2975
|
this._deps.logger.error(error);
|
|
@@ -3010,44 +2982,44 @@ var DrainController = class {
|
|
|
3010
2982
|
|
|
3011
2983
|
// src/internal/merge.ts
|
|
3012
2984
|
var import_zod5 = require("zod");
|
|
3013
|
-
function
|
|
2985
|
+
function base_type_name(zodType) {
|
|
3014
2986
|
let t = zodType;
|
|
3015
2987
|
while (typeof t.unwrap === "function") {
|
|
3016
2988
|
t = t.unwrap();
|
|
3017
2989
|
}
|
|
3018
2990
|
return t.constructor.name;
|
|
3019
2991
|
}
|
|
3020
|
-
function
|
|
2992
|
+
function merge_schemas(existing, incoming, state_name) {
|
|
3021
2993
|
if (existing instanceof import_zod5.ZodObject && incoming instanceof import_zod5.ZodObject) {
|
|
3022
|
-
const
|
|
3023
|
-
const
|
|
3024
|
-
for (const key of Object.keys(
|
|
3025
|
-
if (key in
|
|
3026
|
-
const
|
|
3027
|
-
const
|
|
3028
|
-
if (
|
|
2994
|
+
const existing_shape = existing.shape;
|
|
2995
|
+
const incoming_shape = incoming.shape;
|
|
2996
|
+
for (const key of Object.keys(incoming_shape)) {
|
|
2997
|
+
if (key in existing_shape) {
|
|
2998
|
+
const existing_base = base_type_name(existing_shape[key]);
|
|
2999
|
+
const incoming_base = base_type_name(incoming_shape[key]);
|
|
3000
|
+
if (existing_base !== incoming_base) {
|
|
3029
3001
|
throw new Error(
|
|
3030
|
-
`Schema conflict in "${
|
|
3002
|
+
`Schema conflict in "${state_name}": key "${key}" has type "${existing_base}" but incoming partial declares "${incoming_base}"`
|
|
3031
3003
|
);
|
|
3032
3004
|
}
|
|
3033
3005
|
}
|
|
3034
3006
|
}
|
|
3035
|
-
return existing.extend(
|
|
3007
|
+
return existing.extend(incoming_shape);
|
|
3036
3008
|
}
|
|
3037
3009
|
return existing;
|
|
3038
3010
|
}
|
|
3039
|
-
function
|
|
3011
|
+
function merge_inits(existing, incoming) {
|
|
3040
3012
|
return () => ({ ...existing(), ...incoming() });
|
|
3041
3013
|
}
|
|
3042
|
-
function
|
|
3014
|
+
function register_state(state2, states, actions, events) {
|
|
3043
3015
|
const existing = states.get(state2.name);
|
|
3044
3016
|
if (existing) {
|
|
3045
|
-
|
|
3017
|
+
merge_into_existing(state2, existing, states, actions, events);
|
|
3046
3018
|
} else {
|
|
3047
|
-
|
|
3019
|
+
register_new_state(state2, states, actions, events);
|
|
3048
3020
|
}
|
|
3049
3021
|
}
|
|
3050
|
-
function
|
|
3022
|
+
function register_new_state(state2, states, actions, events) {
|
|
3051
3023
|
states.set(state2.name, state2);
|
|
3052
3024
|
for (const name of Object.keys(state2.actions)) {
|
|
3053
3025
|
if (actions[name]) throw new Error(`Duplicate action "${name}"`);
|
|
@@ -3058,7 +3030,7 @@ function registerNewState(state2, states, actions, events) {
|
|
|
3058
3030
|
events[name] = { schema: state2.events[name], reactions: /* @__PURE__ */ new Map() };
|
|
3059
3031
|
}
|
|
3060
3032
|
}
|
|
3061
|
-
function
|
|
3033
|
+
function merge_into_existing(state2, existing, states, actions, events) {
|
|
3062
3034
|
for (const name of Object.keys(state2.actions)) {
|
|
3063
3035
|
if (existing.actions[name] === state2.actions[name]) continue;
|
|
3064
3036
|
if (actions[name]) throw new Error(`Duplicate action "${name}"`);
|
|
@@ -3072,14 +3044,14 @@ function mergeIntoExisting(state2, existing, states, actions, events) {
|
|
|
3072
3044
|
}
|
|
3073
3045
|
if (events[name]) throw new Error(`Duplicate event "${name}"`);
|
|
3074
3046
|
}
|
|
3075
|
-
const
|
|
3047
|
+
const merged_patch = merge_patches(existing.patch, state2.patch, state2.name);
|
|
3076
3048
|
const merged = {
|
|
3077
3049
|
...existing,
|
|
3078
|
-
state:
|
|
3079
|
-
init:
|
|
3050
|
+
state: merge_schemas(existing.state, state2.state, state2.name),
|
|
3051
|
+
init: merge_inits(existing.init, state2.init),
|
|
3080
3052
|
events: { ...existing.events, ...state2.events },
|
|
3081
3053
|
actions: { ...existing.actions, ...state2.actions },
|
|
3082
|
-
patch:
|
|
3054
|
+
patch: merged_patch,
|
|
3083
3055
|
on: { ...existing.on, ...state2.on },
|
|
3084
3056
|
given: { ...existing.given, ...state2.given },
|
|
3085
3057
|
snap: state2.snap && existing.snap && state2.snap !== existing.snap ? (() => {
|
|
@@ -3097,48 +3069,48 @@ function mergeIntoExisting(state2, existing, states, actions, events) {
|
|
|
3097
3069
|
events[name] = { schema: state2.events[name], reactions: /* @__PURE__ */ new Map() };
|
|
3098
3070
|
}
|
|
3099
3071
|
}
|
|
3100
|
-
function
|
|
3072
|
+
function merge_patches(existing, incoming, state_name) {
|
|
3101
3073
|
const merged = { ...existing };
|
|
3102
3074
|
for (const name of Object.keys(incoming)) {
|
|
3103
|
-
const
|
|
3104
|
-
const
|
|
3105
|
-
if (!
|
|
3106
|
-
merged[name] =
|
|
3075
|
+
const existing_p = existing[name];
|
|
3076
|
+
const incoming_p = incoming[name];
|
|
3077
|
+
if (!existing_p) {
|
|
3078
|
+
merged[name] = incoming_p;
|
|
3107
3079
|
continue;
|
|
3108
3080
|
}
|
|
3109
|
-
const
|
|
3110
|
-
const
|
|
3111
|
-
if (!
|
|
3081
|
+
const existing_is_default = existing_p._passthrough;
|
|
3082
|
+
const incoming_is_default = incoming_p._passthrough;
|
|
3083
|
+
if (!existing_is_default && !incoming_is_default && existing_p !== incoming_p) {
|
|
3112
3084
|
throw new Error(
|
|
3113
|
-
`Duplicate custom patch for event "${name}" in state "${
|
|
3085
|
+
`Duplicate custom patch for event "${name}" in state "${state_name}"`
|
|
3114
3086
|
);
|
|
3115
3087
|
}
|
|
3116
|
-
if (
|
|
3117
|
-
merged[name] =
|
|
3088
|
+
if (existing_is_default && !incoming_is_default) {
|
|
3089
|
+
merged[name] = incoming_p;
|
|
3118
3090
|
}
|
|
3119
3091
|
}
|
|
3120
3092
|
return merged;
|
|
3121
3093
|
}
|
|
3122
|
-
function
|
|
3123
|
-
for (const [
|
|
3124
|
-
const
|
|
3125
|
-
if (!
|
|
3126
|
-
for (const [name, reaction] of
|
|
3127
|
-
|
|
3094
|
+
function merge_event_register(target, source) {
|
|
3095
|
+
for (const [event_name, source_reg] of Object.entries(source)) {
|
|
3096
|
+
const target_reg = target[event_name];
|
|
3097
|
+
if (!target_reg) continue;
|
|
3098
|
+
for (const [name, reaction] of source_reg.reactions) {
|
|
3099
|
+
target_reg.reactions.set(name, reaction);
|
|
3128
3100
|
}
|
|
3129
3101
|
}
|
|
3130
3102
|
}
|
|
3131
|
-
function
|
|
3132
|
-
for (const
|
|
3133
|
-
const
|
|
3134
|
-
const existing = events[
|
|
3103
|
+
function merge_projection(proj, events) {
|
|
3104
|
+
for (const event_name of Object.keys(proj.events)) {
|
|
3105
|
+
const proj_register = proj.events[event_name];
|
|
3106
|
+
const existing = events[event_name];
|
|
3135
3107
|
if (!existing) {
|
|
3136
|
-
events[
|
|
3137
|
-
schema:
|
|
3138
|
-
reactions: new Map(
|
|
3108
|
+
events[event_name] = {
|
|
3109
|
+
schema: proj_register.schema,
|
|
3110
|
+
reactions: new Map(proj_register.reactions)
|
|
3139
3111
|
};
|
|
3140
3112
|
} else {
|
|
3141
|
-
for (const [name, reaction] of
|
|
3113
|
+
for (const [name, reaction] of proj_register.reactions) {
|
|
3142
3114
|
let key = name;
|
|
3143
3115
|
while (existing.reactions.has(key)) key = `${key}_p`;
|
|
3144
3116
|
existing.reactions.set(key, reaction);
|
|
@@ -3155,24 +3127,24 @@ var _this_ = ({ stream }) => ({
|
|
|
3155
3127
|
function finalize(lease, handled, at, error, options, logger, failed_at) {
|
|
3156
3128
|
if (!error) return { lease, handled, acked_at: at };
|
|
3157
3129
|
logger.error(error);
|
|
3158
|
-
const
|
|
3159
|
-
const block2 = options.blockOnError && (
|
|
3130
|
+
const non_retryable = error instanceof NonRetryableError;
|
|
3131
|
+
const block2 = options.blockOnError && (non_retryable || lease.retry >= options.maxRetries);
|
|
3160
3132
|
if (block2)
|
|
3161
3133
|
logger.error(
|
|
3162
|
-
|
|
3134
|
+
non_retryable ? `Blocking ${lease.stream} on non-retryable error.` : `Blocking ${lease.stream} after ${lease.retry} retries.`
|
|
3163
3135
|
);
|
|
3164
|
-
const
|
|
3136
|
+
const next_attempt_at = !block2 && options.backoff ? Date.now() + compute_backoff_delay(lease.retry, options.backoff) : void 0;
|
|
3165
3137
|
return {
|
|
3166
3138
|
lease,
|
|
3167
3139
|
handled,
|
|
3168
3140
|
acked_at: at,
|
|
3169
3141
|
error: error.message,
|
|
3170
3142
|
block: block2,
|
|
3171
|
-
|
|
3143
|
+
next_attempt_at,
|
|
3172
3144
|
failed_at
|
|
3173
3145
|
};
|
|
3174
3146
|
}
|
|
3175
|
-
function
|
|
3147
|
+
function build_handle(deps) {
|
|
3176
3148
|
const {
|
|
3177
3149
|
logger,
|
|
3178
3150
|
bound_do,
|
|
@@ -3188,7 +3160,7 @@ function buildHandle(deps) {
|
|
|
3188
3160
|
let handled = 0;
|
|
3189
3161
|
if (lease.retry > 0)
|
|
3190
3162
|
logger.warn(`Retrying ${stream}@${at} (${lease.retry}).`);
|
|
3191
|
-
const
|
|
3163
|
+
const scoped_app = {
|
|
3192
3164
|
do: bound_do,
|
|
3193
3165
|
load: bound_load,
|
|
3194
3166
|
query: bound_query,
|
|
@@ -3197,15 +3169,12 @@ function buildHandle(deps) {
|
|
|
3197
3169
|
};
|
|
3198
3170
|
for (const payload of payloads) {
|
|
3199
3171
|
const { event, handler } = payload;
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
reactingTo ?? event,
|
|
3205
|
-
skipValidation
|
|
3206
|
-
);
|
|
3172
|
+
scoped_app.do = (action2, target, action_payload, options) => bound_do(action2, target, action_payload, {
|
|
3173
|
+
...options,
|
|
3174
|
+
reactingTo: options?.reactingTo ?? event
|
|
3175
|
+
});
|
|
3207
3176
|
try {
|
|
3208
|
-
await handler(event, stream,
|
|
3177
|
+
await handler(event, stream, scoped_app);
|
|
3209
3178
|
at = event.id;
|
|
3210
3179
|
handled++;
|
|
3211
3180
|
} catch (error) {
|
|
@@ -3223,7 +3192,7 @@ function buildHandle(deps) {
|
|
|
3223
3192
|
return finalize(lease, handled, at, void 0, payloads[0].options, logger);
|
|
3224
3193
|
};
|
|
3225
3194
|
}
|
|
3226
|
-
function
|
|
3195
|
+
function build_handle_batch(logger) {
|
|
3227
3196
|
return async (lease, payloads, batchHandler) => {
|
|
3228
3197
|
const stream = lease.stream;
|
|
3229
3198
|
const events = payloads.map(
|
|
@@ -3255,23 +3224,23 @@ var SettleLoop = class {
|
|
|
3255
3224
|
_deps;
|
|
3256
3225
|
/** Debounce window applied when the caller doesn't override via `SettleOptions.debounceMs`. */
|
|
3257
3226
|
_default_debounce_ms;
|
|
3258
|
-
constructor(deps,
|
|
3227
|
+
constructor(deps, default_debounce_ms) {
|
|
3259
3228
|
this._deps = deps;
|
|
3260
|
-
this._default_debounce_ms =
|
|
3229
|
+
this._default_debounce_ms = default_debounce_ms;
|
|
3261
3230
|
}
|
|
3262
3231
|
/**
|
|
3263
3232
|
* Schedule a settle pass. Multiple calls inside the debounce window
|
|
3264
3233
|
* coalesce into one cycle. The cycle runs correlate→drain in a loop
|
|
3265
3234
|
* until no progress is made (no new subscriptions, no acks, no blocks)
|
|
3266
3235
|
* or `maxPasses` is reached, then emits the `"settled"` lifecycle event
|
|
3267
|
-
* via {@link SettleDeps.
|
|
3236
|
+
* via {@link SettleDeps.on_settled}.
|
|
3268
3237
|
*/
|
|
3269
3238
|
schedule(options = {}) {
|
|
3270
3239
|
const {
|
|
3271
3240
|
debounceMs = this._default_debounce_ms,
|
|
3272
|
-
correlate:
|
|
3241
|
+
correlate: correlate_query = { after: -1, limit: 100 },
|
|
3273
3242
|
maxPasses = Infinity,
|
|
3274
|
-
...
|
|
3243
|
+
...drain_options
|
|
3275
3244
|
} = options;
|
|
3276
3245
|
if (this._timer) clearTimeout(this._timer);
|
|
3277
3246
|
this._timer = setTimeout(() => {
|
|
@@ -3280,17 +3249,17 @@ var SettleLoop = class {
|
|
|
3280
3249
|
this._running = true;
|
|
3281
3250
|
(async () => {
|
|
3282
3251
|
await this._deps.init();
|
|
3283
|
-
let
|
|
3252
|
+
let last_drain;
|
|
3284
3253
|
for (let i = 0; i < maxPasses; i++) {
|
|
3285
3254
|
const { subscribed } = await this._deps.correlate({
|
|
3286
|
-
...
|
|
3255
|
+
...correlate_query,
|
|
3287
3256
|
after: this._deps.checkpoint()
|
|
3288
3257
|
});
|
|
3289
|
-
|
|
3290
|
-
const made_progress = subscribed > 0 ||
|
|
3258
|
+
last_drain = await this._deps.drain(drain_options);
|
|
3259
|
+
const made_progress = subscribed > 0 || last_drain.acked.length > 0 || last_drain.blocked.length > 0;
|
|
3291
3260
|
if (!made_progress) break;
|
|
3292
3261
|
}
|
|
3293
|
-
if (
|
|
3262
|
+
if (last_drain) this._deps.on_settled(last_drain);
|
|
3294
3263
|
})().catch((err) => this._deps.logger.error(err)).finally(() => {
|
|
3295
3264
|
this._running = false;
|
|
3296
3265
|
});
|
|
@@ -3377,7 +3346,7 @@ var Act = class {
|
|
|
3377
3346
|
_event_to_state;
|
|
3378
3347
|
/**
|
|
3379
3348
|
* Event-name → lane fan-in for selective arming (ACT-1103). Built by
|
|
3380
|
-
* `
|
|
3349
|
+
* `classify_registry` once per build. `"all"` means at least one of
|
|
3381
3350
|
* the event's reactions is a dynamic resolver (lane opaque until
|
|
3382
3351
|
* runtime); a `Set<string>` lists the static lanes only that event's
|
|
3383
3352
|
* reactions target.
|
|
@@ -3400,9 +3369,9 @@ var Act = class {
|
|
|
3400
3369
|
_scoped;
|
|
3401
3370
|
/**
|
|
3402
3371
|
* Correlation-id generator for originating actions. Bound at
|
|
3403
|
-
* construction from `options.correlator ??
|
|
3372
|
+
* construction from `options.correlator ?? default_correlator`. The
|
|
3404
3373
|
* `do()` path passes this into the `_es.action` closure; close-cycle
|
|
3405
|
-
* uses it via {@link
|
|
3374
|
+
* uses it via {@link close_correlation}.
|
|
3406
3375
|
*/
|
|
3407
3376
|
_correlator;
|
|
3408
3377
|
/** Pre-bound IAct methods reused across drain cycles. Only `do` varies per
|
|
@@ -3412,7 +3381,7 @@ var Act = class {
|
|
|
3412
3381
|
_bound_query = this.query.bind(this);
|
|
3413
3382
|
_bound_query_array = this.query_array.bind(this);
|
|
3414
3383
|
_bound_forget = this.forget.bind(this);
|
|
3415
|
-
/** Reaction dispatchers built once and handed to
|
|
3384
|
+
/** Reaction dispatchers built once and handed to run_drain_cycle each cycle. */
|
|
3416
3385
|
_handle;
|
|
3417
3386
|
_handle_batch;
|
|
3418
3387
|
/** Declared drain lanes (ACT-1103). */
|
|
@@ -3429,16 +3398,16 @@ var Act = class {
|
|
|
3429
3398
|
*
|
|
3430
3399
|
* @param registry Schemas for every event and action across registered states
|
|
3431
3400
|
* @param states Merged map of state name → state definition
|
|
3432
|
-
* @param
|
|
3401
|
+
* @param batch_handlers Static-target projection batch handlers (target → handler)
|
|
3433
3402
|
* @param options Tuning knobs — see {@link ActOptions}
|
|
3434
3403
|
* @param lanes Declared drain lanes (ACT-1103). The builder collects
|
|
3435
3404
|
* these from `.withLane(...)` calls. Slice 1 records them on the
|
|
3436
3405
|
* instance; later slices fan out one `DrainController` per lane.
|
|
3437
3406
|
*/
|
|
3438
|
-
constructor(registry, states = /* @__PURE__ */ new Map(),
|
|
3407
|
+
constructor(registry, states = /* @__PURE__ */ new Map(), batch_handlers = /* @__PURE__ */ new Map(), options = {}, lanes = []) {
|
|
3439
3408
|
this.registry = registry;
|
|
3440
3409
|
this._states = states;
|
|
3441
|
-
this._batch_handlers =
|
|
3410
|
+
this._batch_handlers = batch_handlers;
|
|
3442
3411
|
this._lanes = lanes;
|
|
3443
3412
|
if (options.onlyLanes && options.onlyLanes.length > 0) {
|
|
3444
3413
|
const declared = /* @__PURE__ */ new Set([
|
|
@@ -3452,10 +3421,10 @@ var Act = class {
|
|
|
3452
3421
|
);
|
|
3453
3422
|
}
|
|
3454
3423
|
this._scoped = options.scoped ? (fn) => scoped.run(options.scoped, fn) : (fn) => fn();
|
|
3455
|
-
this._correlator = options.correlator ??
|
|
3456
|
-
this._es =
|
|
3457
|
-
this._cd =
|
|
3458
|
-
this._handle =
|
|
3424
|
+
this._correlator = options.correlator ?? default_correlator;
|
|
3425
|
+
this._es = build_es(this._logger, this._correlator);
|
|
3426
|
+
this._cd = build_drain(this._logger);
|
|
3427
|
+
this._handle = build_handle({
|
|
3459
3428
|
logger: this._logger,
|
|
3460
3429
|
bound_do: this._bound_do,
|
|
3461
3430
|
bound_load: this._bound_load,
|
|
@@ -3463,39 +3432,39 @@ var Act = class {
|
|
|
3463
3432
|
bound_query_array: this._bound_query_array,
|
|
3464
3433
|
bound_forget: this._bound_forget
|
|
3465
3434
|
});
|
|
3466
|
-
this._handle_batch =
|
|
3435
|
+
this._handle_batch = build_handle_batch(this._logger);
|
|
3467
3436
|
const {
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
} =
|
|
3474
|
-
this._reactive_events =
|
|
3437
|
+
static_targets,
|
|
3438
|
+
has_dynamic_resolvers,
|
|
3439
|
+
reactive_events,
|
|
3440
|
+
event_to_state,
|
|
3441
|
+
event_to_lanes
|
|
3442
|
+
} = classify_registry(this.registry, this._states);
|
|
3443
|
+
this._reactive_events = reactive_events;
|
|
3475
3444
|
this._listen = options.listen !== false;
|
|
3476
3445
|
this._drain = options.drain !== false;
|
|
3477
|
-
this._event_to_state =
|
|
3478
|
-
this._event_to_lanes =
|
|
3479
|
-
const
|
|
3480
|
-
const
|
|
3481
|
-
const
|
|
3482
|
-
const
|
|
3446
|
+
this._event_to_state = event_to_state;
|
|
3447
|
+
this._event_to_lanes = event_to_lanes;
|
|
3448
|
+
const all_lanes = ["default", ...lanes.map((l) => l.name)];
|
|
3449
|
+
const only_set = options.onlyLanes && options.onlyLanes.length > 0 ? new Set(options.onlyLanes) : void 0;
|
|
3450
|
+
const active_lanes = only_set ? all_lanes.filter((n) => only_set.has(n)) : all_lanes;
|
|
3451
|
+
const single_default_lane = active_lanes.length === 1 && active_lanes[0] === "default";
|
|
3483
3452
|
this._drain_controllers = /* @__PURE__ */ new Map();
|
|
3484
|
-
for (const name of
|
|
3453
|
+
for (const name of active_lanes) {
|
|
3485
3454
|
const cfg = lanes.find((l) => l.name === name);
|
|
3486
3455
|
const controller = new DrainController({
|
|
3487
3456
|
logger: this._logger,
|
|
3488
3457
|
ops: this._cd,
|
|
3489
3458
|
registry: this.registry,
|
|
3490
|
-
|
|
3459
|
+
batch_handlers: this._batch_handlers,
|
|
3491
3460
|
handle: this._handle,
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3461
|
+
handle_batch: this._handle_batch,
|
|
3462
|
+
on_acked: (acked) => this.emit("acked", acked),
|
|
3463
|
+
on_blocked: (blocked) => this.emit("blocked", blocked),
|
|
3495
3464
|
// Pass lane only when a true per-lane controller is active.
|
|
3496
3465
|
// The all-lanes (single default) case keeps lane=undefined so
|
|
3497
3466
|
// adapter SQL collapses to the pre-1103 shape.
|
|
3498
|
-
lane:
|
|
3467
|
+
lane: single_default_lane ? void 0 : name,
|
|
3499
3468
|
defaults: cfg && {
|
|
3500
3469
|
streamLimit: cfg.streamLimit,
|
|
3501
3470
|
leaseMillis: cfg.leaseMillis
|
|
@@ -3508,22 +3477,22 @@ var Act = class {
|
|
|
3508
3477
|
this._audit_deps = {
|
|
3509
3478
|
store: store2,
|
|
3510
3479
|
logger: this._logger,
|
|
3511
|
-
event_to_state
|
|
3480
|
+
event_to_state,
|
|
3512
3481
|
states: this._states,
|
|
3513
|
-
known_events: new Set(
|
|
3482
|
+
known_events: new Set(event_to_state.keys()),
|
|
3514
3483
|
declared_lanes: new Set(this._drain_controllers.keys()),
|
|
3515
|
-
routed_events: new Set(
|
|
3484
|
+
routed_events: new Set(event_to_lanes.keys())
|
|
3516
3485
|
};
|
|
3517
3486
|
this._correlate = new CorrelateCycle(
|
|
3518
3487
|
this.registry,
|
|
3519
|
-
|
|
3520
|
-
|
|
3488
|
+
static_targets,
|
|
3489
|
+
has_dynamic_resolvers,
|
|
3521
3490
|
this._cd,
|
|
3522
3491
|
options.maxSubscribedStreams ?? DEFAULT_MAX_SUBSCRIBED_STREAMS,
|
|
3523
3492
|
// Cold start: assume drain is needed (historical events may need processing).
|
|
3524
3493
|
// #803: writer-only instances skip the cold-start arm.
|
|
3525
3494
|
() => {
|
|
3526
|
-
if (this._drain && this._reactive_events.size > 0) this.
|
|
3495
|
+
if (this._drain && this._reactive_events.size > 0) this._arm_all();
|
|
3527
3496
|
}
|
|
3528
3497
|
);
|
|
3529
3498
|
this._settle = new SettleLoop(
|
|
@@ -3533,11 +3502,11 @@ var Act = class {
|
|
|
3533
3502
|
checkpoint: () => this._correlate.checkpoint,
|
|
3534
3503
|
correlate: (q) => this.correlate(q),
|
|
3535
3504
|
drain: (o) => this.drain(o),
|
|
3536
|
-
|
|
3505
|
+
on_settled: (drain) => this.emit("settled", drain)
|
|
3537
3506
|
},
|
|
3538
3507
|
options.settleDebounceMs ?? DEFAULT_SETTLE_DEBOUNCE_MS
|
|
3539
3508
|
);
|
|
3540
|
-
this._notify_disposer = this.
|
|
3509
|
+
this._notify_disposer = this._wire_notify(options.scoped?.store ?? store2());
|
|
3541
3510
|
dispose(() => this.shutdown());
|
|
3542
3511
|
}
|
|
3543
3512
|
/** True after the first `shutdown()` call. Guards idempotency. */
|
|
@@ -3571,7 +3540,7 @@ var Act = class {
|
|
|
3571
3540
|
* subscription was made). Errors during subscription are logged but
|
|
3572
3541
|
* never thrown — `notify` is a hint, not a contract.
|
|
3573
3542
|
*/
|
|
3574
|
-
async
|
|
3543
|
+
async _wire_notify(s) {
|
|
3575
3544
|
if (this._reactive_events.size === 0) return void 0;
|
|
3576
3545
|
if (!s.notify) return void 0;
|
|
3577
3546
|
if (!this._listen) return void 0;
|
|
@@ -3580,7 +3549,7 @@ var Act = class {
|
|
|
3580
3549
|
try {
|
|
3581
3550
|
this.emit("notified", notification);
|
|
3582
3551
|
if (this._drain) {
|
|
3583
|
-
const armed = this.
|
|
3552
|
+
const armed = this._arm_for_event_names(
|
|
3584
3553
|
notification.events.map((e) => e.name)
|
|
3585
3554
|
);
|
|
3586
3555
|
if (armed) this._settle.schedule({ debounceMs: 0 });
|
|
@@ -3609,8 +3578,9 @@ var Act = class {
|
|
|
3609
3578
|
* @param action - The name of the action to execute
|
|
3610
3579
|
* @param target - Target specification with stream ID and actor context
|
|
3611
3580
|
* @param payload - Action payload matching the action's schema
|
|
3612
|
-
* @param
|
|
3613
|
-
*
|
|
3581
|
+
* @param options - Per-call dispatch options ({@link DoOptions}) —
|
|
3582
|
+
* `reactingTo` to thread correlation, `correlator` to override the
|
|
3583
|
+
* framework or orchestrator-level correlator for this call only.
|
|
3614
3584
|
* @returns Array of snapshots for all affected states (usually one)
|
|
3615
3585
|
*
|
|
3616
3586
|
* @throws {ValidationError} If payload doesn't match action schema
|
|
@@ -3665,7 +3635,7 @@ var Act = class {
|
|
|
3665
3635
|
* { amount: event.data.items.length }
|
|
3666
3636
|
* );
|
|
3667
3637
|
* // To use a different correlation, pass reactingTo explicitly:
|
|
3668
|
-
* // await app.do("reduceStock", target, payload, customEvent);
|
|
3638
|
+
* // await app.do("reduceStock", target, payload, { reactingTo: customEvent });
|
|
3669
3639
|
* })
|
|
3670
3640
|
* .to("inventory-1")
|
|
3671
3641
|
* .build();
|
|
@@ -3675,25 +3645,24 @@ var Act = class {
|
|
|
3675
3645
|
* @see {@link Snapshot} for return value structure
|
|
3676
3646
|
* @see {@link ValidationError}, {@link InvariantError}, {@link ConcurrencyError}
|
|
3677
3647
|
*/
|
|
3678
|
-
async do(action2, target, payload,
|
|
3648
|
+
async do(action2, target, payload, options) {
|
|
3679
3649
|
return this._scoped(async () => {
|
|
3680
3650
|
const snapshots = await this._es.action(
|
|
3681
3651
|
this.registry.actions[action2],
|
|
3682
3652
|
action2,
|
|
3683
3653
|
target,
|
|
3684
3654
|
payload,
|
|
3685
|
-
|
|
3686
|
-
skipValidation
|
|
3655
|
+
options
|
|
3687
3656
|
);
|
|
3688
3657
|
if (this._reactive_events.size > 0)
|
|
3689
|
-
this.
|
|
3658
|
+
this._arm_for_event_names(
|
|
3690
3659
|
snapshots.map((s) => s.event.name)
|
|
3691
3660
|
);
|
|
3692
3661
|
this.emit("committed", snapshots);
|
|
3693
3662
|
return snapshots;
|
|
3694
3663
|
});
|
|
3695
3664
|
}
|
|
3696
|
-
async load(stateOrName,
|
|
3665
|
+
async load(stateOrName, streamOrTarget, callback, asOf) {
|
|
3697
3666
|
return this._scoped(async () => {
|
|
3698
3667
|
let merged;
|
|
3699
3668
|
if (typeof stateOrName === "string") {
|
|
@@ -3703,7 +3672,12 @@ var Act = class {
|
|
|
3703
3672
|
} else {
|
|
3704
3673
|
merged = this._states.get(stateOrName.name) || stateOrName;
|
|
3705
3674
|
}
|
|
3706
|
-
|
|
3675
|
+
const target = typeof streamOrTarget === "string" ? {
|
|
3676
|
+
stream: streamOrTarget,
|
|
3677
|
+
actor: void 0,
|
|
3678
|
+
asOf
|
|
3679
|
+
} : streamOrTarget;
|
|
3680
|
+
return await this._es.load(merged, target, callback);
|
|
3707
3681
|
});
|
|
3708
3682
|
}
|
|
3709
3683
|
/**
|
|
@@ -3865,28 +3839,28 @@ var Act = class {
|
|
|
3865
3839
|
async drain(options = {}) {
|
|
3866
3840
|
if (!this._drain)
|
|
3867
3841
|
return { fetched: [], leased: [], acked: [], blocked: [] };
|
|
3868
|
-
return this._scoped(() => this.
|
|
3842
|
+
return this._scoped(() => this._drain_all(options));
|
|
3869
3843
|
}
|
|
3870
3844
|
/** Arm every active lane controller (ACT-1103). */
|
|
3871
|
-
|
|
3845
|
+
_arm_all() {
|
|
3872
3846
|
for (const c of this._drain_controllers.values()) c.arm();
|
|
3873
3847
|
}
|
|
3874
3848
|
/**
|
|
3875
3849
|
* Arm only the lane controllers whose reactions match the supplied
|
|
3876
3850
|
* event names (ACT-1103 selective arming). Events with any dynamic
|
|
3877
|
-
* resolver fall back to `
|
|
3851
|
+
* resolver fall back to `_arm_all()` via the `"all"` sentinel — the
|
|
3878
3852
|
* resolver's lane isn't known until correlate runs the function.
|
|
3879
3853
|
* Events with no reactions are skipped; `_event_to_lanes` doesn't
|
|
3880
3854
|
* carry them. Returns true when any controller was armed (used by
|
|
3881
3855
|
* the notify handler to decide whether to schedule a settle).
|
|
3882
3856
|
*/
|
|
3883
|
-
|
|
3857
|
+
_arm_for_event_names(names) {
|
|
3884
3858
|
const to_arm = /* @__PURE__ */ new Set();
|
|
3885
3859
|
for (const name of names) {
|
|
3886
3860
|
const set = this._event_to_lanes.get(name);
|
|
3887
3861
|
if (set === void 0) continue;
|
|
3888
3862
|
if (set === ALL_LANES) {
|
|
3889
|
-
this.
|
|
3863
|
+
this._arm_all();
|
|
3890
3864
|
return true;
|
|
3891
3865
|
}
|
|
3892
3866
|
for (const lane of set) to_arm.add(lane);
|
|
@@ -3903,7 +3877,7 @@ var Act = class {
|
|
|
3903
3877
|
* `SKIP LOCKED` keeps cross-controller races safe. Lifecycle events
|
|
3904
3878
|
* (`acked`, `blocked`) may interleave by lane — listeners filter via
|
|
3905
3879
|
* `lease.lane`. */
|
|
3906
|
-
async
|
|
3880
|
+
async _drain_all(options) {
|
|
3907
3881
|
const results = await Promise.all(
|
|
3908
3882
|
[...this._drain_controllers.values()].map((c) => c.drain(options))
|
|
3909
3883
|
);
|
|
@@ -4023,7 +3997,7 @@ var Act = class {
|
|
|
4023
3997
|
* @see {@link stop_correlations} to stop the worker
|
|
4024
3998
|
*/
|
|
4025
3999
|
start_correlations(query = {}, frequency = 1e4, callback) {
|
|
4026
|
-
return this._correlate.
|
|
4000
|
+
return this._correlate.start_polling(query, frequency, callback);
|
|
4027
4001
|
}
|
|
4028
4002
|
/**
|
|
4029
4003
|
* Stops the automatic correlation worker.
|
|
@@ -4043,7 +4017,7 @@ var Act = class {
|
|
|
4043
4017
|
* @see {@link start_correlations}
|
|
4044
4018
|
*/
|
|
4045
4019
|
stop_correlations() {
|
|
4046
|
-
this._correlate.
|
|
4020
|
+
this._correlate.stop_polling();
|
|
4047
4021
|
}
|
|
4048
4022
|
/**
|
|
4049
4023
|
* Cancels any pending or active settle cycle.
|
|
@@ -4089,7 +4063,7 @@ var Act = class {
|
|
|
4089
4063
|
async reset(input) {
|
|
4090
4064
|
return this._scoped(async () => {
|
|
4091
4065
|
const count = await store2().reset(input);
|
|
4092
|
-
if (count > 0 && this._reactive_events.size > 0) this.
|
|
4066
|
+
if (count > 0 && this._reactive_events.size > 0) this._arm_all();
|
|
4093
4067
|
return count;
|
|
4094
4068
|
});
|
|
4095
4069
|
}
|
|
@@ -4123,7 +4097,7 @@ var Act = class {
|
|
|
4123
4097
|
async unblock(input) {
|
|
4124
4098
|
return this._scoped(async () => {
|
|
4125
4099
|
const count = await store2().unblock(input);
|
|
4126
|
-
if (count > 0 && this._reactive_events.size > 0) this.
|
|
4100
|
+
if (count > 0 && this._reactive_events.size > 0) this._arm_all();
|
|
4127
4101
|
return count;
|
|
4128
4102
|
});
|
|
4129
4103
|
}
|
|
@@ -4349,14 +4323,14 @@ var Act = class {
|
|
|
4349
4323
|
if (!targets.length) return { truncated: /* @__PURE__ */ new Map(), skipped: [] };
|
|
4350
4324
|
return this._scoped(async () => {
|
|
4351
4325
|
await this.correlate({ limit: 1e3 });
|
|
4352
|
-
const
|
|
4353
|
-
const result = await
|
|
4354
|
-
|
|
4355
|
-
|
|
4326
|
+
const close_actor = { id: "$close", name: "close" };
|
|
4327
|
+
const result = await run_close_cycle(targets, {
|
|
4328
|
+
reactive_events_size: this._reactive_events.size,
|
|
4329
|
+
event_to_state: this._event_to_state,
|
|
4356
4330
|
load: this._es.load,
|
|
4357
4331
|
tombstone: this._es.tombstone,
|
|
4358
4332
|
logger: this._logger,
|
|
4359
|
-
correlation:
|
|
4333
|
+
correlation: close_correlation(this._correlator, close_actor)
|
|
4360
4334
|
});
|
|
4361
4335
|
this.emit("closed", result);
|
|
4362
4336
|
return result;
|
|
@@ -4399,17 +4373,17 @@ var Act = class {
|
|
|
4399
4373
|
};
|
|
4400
4374
|
|
|
4401
4375
|
// src/builders/act-builder.ts
|
|
4402
|
-
function
|
|
4376
|
+
function register_batch_handler(proj, batch_handlers) {
|
|
4403
4377
|
if (!proj.batchHandler || !proj.target) return;
|
|
4404
|
-
const existing =
|
|
4378
|
+
const existing = batch_handlers.get(proj.target);
|
|
4405
4379
|
if (existing && existing !== proj.batchHandler) {
|
|
4406
4380
|
throw new Error(`Duplicate batch handler for target "${proj.target}"`);
|
|
4407
4381
|
}
|
|
4408
|
-
|
|
4382
|
+
batch_handlers.set(proj.target, proj.batchHandler);
|
|
4409
4383
|
}
|
|
4410
|
-
function
|
|
4384
|
+
function validate_lane_references(registry, lanes) {
|
|
4411
4385
|
const declared = /* @__PURE__ */ new Set([DEFAULT_LANE, ...lanes.map((l) => l.name)]);
|
|
4412
|
-
for (const [
|
|
4386
|
+
for (const [event_name, def] of Object.entries(registry.events)) {
|
|
4413
4387
|
const entry = def;
|
|
4414
4388
|
for (const [handlerName, reaction] of entry.reactions) {
|
|
4415
4389
|
const resolver = reaction.resolver;
|
|
@@ -4417,7 +4391,7 @@ function validateLaneReferences(registry, lanes) {
|
|
|
4417
4391
|
const lane = resolver.lane;
|
|
4418
4392
|
if (lane && !declared.has(lane)) {
|
|
4419
4393
|
throw new Error(
|
|
4420
|
-
`Reaction "${handlerName}" on "${
|
|
4394
|
+
`Reaction "${handlerName}" on "${event_name}" targets undeclared lane "${lane}". Declared lanes: ${[...declared].map((l) => `"${l}"`).join(", ")}. Add \`.withLane({ name: "${lane}", ... })\` to act() or correct the .to() declaration.`
|
|
4421
4395
|
);
|
|
4422
4396
|
}
|
|
4423
4397
|
}
|
|
@@ -4427,78 +4401,81 @@ function act() {
|
|
|
4427
4401
|
const states = /* @__PURE__ */ new Map();
|
|
4428
4402
|
const _sf = /* @__PURE__ */ new Map();
|
|
4429
4403
|
const _dp = /* @__PURE__ */ new Map();
|
|
4404
|
+
const _de = /* @__PURE__ */ new Map();
|
|
4405
|
+
const EMPTY_DEPRECATED = /* @__PURE__ */ new Set();
|
|
4430
4406
|
const registry = {
|
|
4431
4407
|
actions: {},
|
|
4432
4408
|
events: {},
|
|
4433
|
-
sensitive_fields: (
|
|
4434
|
-
disclosure_predicate: (
|
|
4409
|
+
sensitive_fields: (event_name) => _sf.get(event_name) ?? [],
|
|
4410
|
+
disclosure_predicate: (state_name) => _dp.get(state_name) ?? null,
|
|
4411
|
+
deprecated_events: (state_name) => _de.get(state_name) ?? EMPTY_DEPRECATED
|
|
4435
4412
|
};
|
|
4436
|
-
const
|
|
4437
|
-
const
|
|
4413
|
+
const pending_projections = [];
|
|
4414
|
+
const batch_handlers = /* @__PURE__ */ new Map();
|
|
4438
4415
|
const lanes = [];
|
|
4439
4416
|
let _built = false;
|
|
4440
|
-
const
|
|
4441
|
-
const
|
|
4417
|
+
const finalize_deprecations = () => {
|
|
4418
|
+
const deprecation_summary = [];
|
|
4442
4419
|
for (const state2 of states.values()) {
|
|
4443
|
-
const
|
|
4444
|
-
const deprecated =
|
|
4420
|
+
const event_names = Object.keys(state2.events);
|
|
4421
|
+
const deprecated = deprecated_event_names(event_names);
|
|
4445
4422
|
if (deprecated.size === 0) continue;
|
|
4446
|
-
state2.
|
|
4423
|
+
_de.set(state2.name, deprecated);
|
|
4447
4424
|
for (const name of deprecated) {
|
|
4448
|
-
const current =
|
|
4449
|
-
|
|
4450
|
-
|
|
4425
|
+
const current = current_version_of(name, event_names);
|
|
4426
|
+
deprecation_summary.push({
|
|
4427
|
+
state_name: state2.name,
|
|
4451
4428
|
deprecated: name,
|
|
4452
4429
|
current
|
|
4453
4430
|
});
|
|
4454
4431
|
}
|
|
4455
|
-
for (const [
|
|
4456
|
-
const
|
|
4457
|
-
if (
|
|
4458
|
-
const current =
|
|
4432
|
+
for (const [action_name, handler] of Object.entries(state2.on)) {
|
|
4433
|
+
const static_target = handler?._static_emit;
|
|
4434
|
+
if (static_target && deprecated.has(static_target)) {
|
|
4435
|
+
const current = current_version_of(static_target, event_names);
|
|
4459
4436
|
throw new Error(
|
|
4460
|
-
`Action "${
|
|
4437
|
+
`Action "${action_name}" in state "${state2.name}" emits deprecated event "${static_target}". A newer version exists: "${current}". Update the .emit() call to target the current version. The reducer (.patch) for "${static_target}" stays as-is \u2014 historical events still need it.`
|
|
4461
4438
|
);
|
|
4462
4439
|
}
|
|
4463
4440
|
}
|
|
4464
4441
|
}
|
|
4465
|
-
if (
|
|
4466
|
-
const list =
|
|
4467
|
-
(d) => `"${d.deprecated}" (current: "${d.current}", state: "${d.
|
|
4442
|
+
if (deprecation_summary.length > 0) {
|
|
4443
|
+
const list = deprecation_summary.map(
|
|
4444
|
+
(d) => `"${d.deprecated}" (current: "${d.current}", state: "${d.state_name}")`
|
|
4468
4445
|
).join(", ");
|
|
4469
4446
|
log().info(
|
|
4470
|
-
`Act registered ${
|
|
4447
|
+
`Act registered ${deprecation_summary.length} deprecated event(s): ${list}. These are legacy versions kept for the read path. Consider truncating closed streams via app.close() when feasible to reduce historical event load. See docs/docs/architecture/event-schema-evolution.md.`
|
|
4471
4448
|
);
|
|
4472
4449
|
}
|
|
4473
4450
|
};
|
|
4474
4451
|
const builder = {
|
|
4475
4452
|
withState: (state2) => {
|
|
4476
|
-
|
|
4453
|
+
register_state(state2, states, registry.actions, registry.events);
|
|
4477
4454
|
return builder;
|
|
4478
4455
|
},
|
|
4479
4456
|
withSlice: (input) => {
|
|
4480
4457
|
for (const s of input.states.values()) {
|
|
4481
|
-
|
|
4458
|
+
register_state(s, states, registry.actions, registry.events);
|
|
4482
4459
|
}
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
for (const
|
|
4486
|
-
const existing = lanes.find((l) => l.name ===
|
|
4460
|
+
merge_event_register(registry.events, input.events);
|
|
4461
|
+
pending_projections.push(...input.projections);
|
|
4462
|
+
for (const slice_lane of input.lanes) {
|
|
4463
|
+
const existing = lanes.find((l) => l.name === slice_lane.name);
|
|
4487
4464
|
if (!existing) {
|
|
4488
|
-
lanes.push(
|
|
4465
|
+
lanes.push(slice_lane);
|
|
4489
4466
|
continue;
|
|
4490
4467
|
}
|
|
4491
|
-
if (existing.leaseMillis !==
|
|
4468
|
+
if (existing.leaseMillis !== slice_lane.leaseMillis || existing.streamLimit !== slice_lane.streamLimit || existing.cycleMs !== slice_lane.cycleMs) {
|
|
4492
4469
|
throw new Error(
|
|
4493
|
-
`Lane "${
|
|
4470
|
+
`Lane "${slice_lane.name}" was already declared with a different config`
|
|
4494
4471
|
);
|
|
4495
4472
|
}
|
|
4496
4473
|
}
|
|
4497
4474
|
return builder;
|
|
4498
4475
|
},
|
|
4499
4476
|
withProjection: (proj) => {
|
|
4500
|
-
|
|
4501
|
-
|
|
4477
|
+
merge_projection(proj, registry.events);
|
|
4478
|
+
register_batch_handler(proj, batch_handlers);
|
|
4502
4479
|
return builder;
|
|
4503
4480
|
},
|
|
4504
4481
|
withActor: () => builder,
|
|
@@ -4536,18 +4513,18 @@ function act() {
|
|
|
4536
4513
|
}),
|
|
4537
4514
|
build: (options) => {
|
|
4538
4515
|
if (!_built) {
|
|
4539
|
-
for (const proj of
|
|
4540
|
-
|
|
4541
|
-
|
|
4516
|
+
for (const proj of pending_projections) {
|
|
4517
|
+
merge_projection(proj, registry.events);
|
|
4518
|
+
register_batch_handler(proj, batch_handlers);
|
|
4542
4519
|
}
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
for (const [
|
|
4520
|
+
finalize_deprecations();
|
|
4521
|
+
validate_lane_references(registry, lanes);
|
|
4522
|
+
for (const [event_name, reg] of Object.entries(
|
|
4546
4523
|
registry.events
|
|
4547
4524
|
)) {
|
|
4548
4525
|
const fields = pii_fields(reg.schema);
|
|
4549
4526
|
if (fields.length === 0) continue;
|
|
4550
|
-
_sf.set(
|
|
4527
|
+
_sf.set(event_name, fields);
|
|
4551
4528
|
for (const [name, reaction] of reg.reactions) {
|
|
4552
4529
|
const inner = reaction.handler;
|
|
4553
4530
|
const wrapped = (event, stream, app) => inner(pii_strip(event, fields), stream, app);
|
|
@@ -4559,9 +4536,9 @@ function act() {
|
|
|
4559
4536
|
for (const state2 of states.values()) {
|
|
4560
4537
|
if (state2.disclose) _dp.set(state2.name, state2.disclose);
|
|
4561
4538
|
const fields_by_event = /* @__PURE__ */ new Map();
|
|
4562
|
-
for (const
|
|
4563
|
-
const fields = _sf.get(
|
|
4564
|
-
if (fields) fields_by_event.set(
|
|
4539
|
+
for (const event_name of Object.keys(state2.events)) {
|
|
4540
|
+
const fields = _sf.get(event_name);
|
|
4541
|
+
if (fields) fields_by_event.set(event_name, fields);
|
|
4565
4542
|
}
|
|
4566
4543
|
if (fields_by_event.size === 0) continue;
|
|
4567
4544
|
if (state2.snap) {
|
|
@@ -4571,20 +4548,19 @@ function act() {
|
|
|
4571
4548
|
);
|
|
4572
4549
|
}
|
|
4573
4550
|
const disclose = state2.disclose ?? null;
|
|
4574
|
-
state2.
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4551
|
+
state2.pii_aware = true;
|
|
4552
|
+
state2.view = (event, actor) => pii_gate(
|
|
4553
|
+
event,
|
|
4554
|
+
fields_by_event.get(event.name) ?? [],
|
|
4555
|
+
disclose,
|
|
4556
|
+
actor
|
|
4557
|
+
);
|
|
4578
4558
|
state2.message = (validated) => {
|
|
4579
4559
|
const fields = fields_by_event.get(validated.name);
|
|
4580
4560
|
return fields ? pii_split(validated, fields) : validated;
|
|
4581
4561
|
};
|
|
4582
|
-
for (const [eventName, fields] of fields_by_event) {
|
|
4583
|
-
const original = state2.patch[eventName];
|
|
4584
|
-
state2.patch[eventName] = (event, s) => original(pii_merge(event, fields), s);
|
|
4585
|
-
}
|
|
4586
4562
|
}
|
|
4587
|
-
for (const [target, original] of
|
|
4563
|
+
for (const [target, original] of batch_handlers) {
|
|
4588
4564
|
const wrapped = async (events, stream) => {
|
|
4589
4565
|
const stripped = events.map((e) => {
|
|
4590
4566
|
const f = _sf.get(e.name);
|
|
@@ -4592,14 +4568,14 @@ function act() {
|
|
|
4592
4568
|
});
|
|
4593
4569
|
return original(stripped, stream);
|
|
4594
4570
|
};
|
|
4595
|
-
|
|
4571
|
+
batch_handlers.set(target, wrapped);
|
|
4596
4572
|
}
|
|
4597
4573
|
_built = true;
|
|
4598
4574
|
}
|
|
4599
4575
|
return new Act(
|
|
4600
4576
|
registry,
|
|
4601
4577
|
states,
|
|
4602
|
-
|
|
4578
|
+
batch_handlers,
|
|
4603
4579
|
options,
|
|
4604
4580
|
lanes
|
|
4605
4581
|
);
|
|
@@ -4612,7 +4588,7 @@ function act() {
|
|
|
4612
4588
|
// src/builders/projection-builder.ts
|
|
4613
4589
|
function _projection(target) {
|
|
4614
4590
|
const events = {};
|
|
4615
|
-
const
|
|
4591
|
+
const default_resolver = typeof target === "string" ? { target } : void 0;
|
|
4616
4592
|
const base = {
|
|
4617
4593
|
on: (entry) => {
|
|
4618
4594
|
const keys = Object.keys(entry);
|
|
@@ -4629,7 +4605,7 @@ function _projection(target) {
|
|
|
4629
4605
|
do: (handler) => {
|
|
4630
4606
|
const reaction = {
|
|
4631
4607
|
handler,
|
|
4632
|
-
resolver:
|
|
4608
|
+
resolver: default_resolver ?? _this_,
|
|
4633
4609
|
options: {
|
|
4634
4610
|
blockOnError: true,
|
|
4635
4611
|
maxRetries: 3
|
|
@@ -4685,7 +4661,7 @@ function slice() {
|
|
|
4685
4661
|
const lanes = [];
|
|
4686
4662
|
const builder = {
|
|
4687
4663
|
withState: (state2) => {
|
|
4688
|
-
|
|
4664
|
+
register_state(state2, states, actions, events);
|
|
4689
4665
|
return builder;
|
|
4690
4666
|
},
|
|
4691
4667
|
withProjection: (proj) => {
|
|
@@ -4741,12 +4717,12 @@ function state(entry) {
|
|
|
4741
4717
|
const keys = Object.keys(entry);
|
|
4742
4718
|
if (keys.length !== 1) throw new Error("state() requires exactly one key");
|
|
4743
4719
|
const name = keys[0];
|
|
4744
|
-
const
|
|
4720
|
+
const state_schema = entry[name];
|
|
4745
4721
|
return {
|
|
4746
4722
|
init(init) {
|
|
4747
4723
|
return {
|
|
4748
4724
|
emits(events) {
|
|
4749
|
-
const
|
|
4725
|
+
const default_patch = Object.fromEntries(
|
|
4750
4726
|
Object.keys(events).map((k) => {
|
|
4751
4727
|
const fn = Object.assign(({ data }) => data, {
|
|
4752
4728
|
_passthrough: true
|
|
@@ -4757,14 +4733,15 @@ function state(entry) {
|
|
|
4757
4733
|
const internal = {
|
|
4758
4734
|
events,
|
|
4759
4735
|
actions: {},
|
|
4760
|
-
state:
|
|
4736
|
+
state: state_schema,
|
|
4761
4737
|
name,
|
|
4762
4738
|
init,
|
|
4763
|
-
patch:
|
|
4739
|
+
patch: default_patch,
|
|
4764
4740
|
on: {},
|
|
4765
|
-
// Step delegates initialized as identity
|
|
4766
|
-
//
|
|
4767
|
-
// the gate / split.
|
|
4741
|
+
// Step delegates initialized as identity, `pii_aware` false.
|
|
4742
|
+
// `act().build()` flips both on states with `sensitive(...)`
|
|
4743
|
+
// events to bake in the gate / split and gate the cache write.
|
|
4744
|
+
pii_aware: false,
|
|
4768
4745
|
view: (event) => event,
|
|
4769
4746
|
message: (validated) => validated
|
|
4770
4747
|
};
|
|
@@ -4802,11 +4779,14 @@ function action_builder(state2) {
|
|
|
4802
4779
|
}
|
|
4803
4780
|
function emit(handler) {
|
|
4804
4781
|
if (typeof handler === "string") {
|
|
4805
|
-
const
|
|
4806
|
-
const
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4782
|
+
const event_name = handler;
|
|
4783
|
+
const emit_fn = Object.assign(
|
|
4784
|
+
(payload) => [event_name, payload],
|
|
4785
|
+
{
|
|
4786
|
+
_static_emit: event_name
|
|
4787
|
+
}
|
|
4788
|
+
);
|
|
4789
|
+
internal.on[action2] = emit_fn;
|
|
4810
4790
|
} else {
|
|
4811
4791
|
internal.on[action2] = handler;
|
|
4812
4792
|
}
|
|
@@ -4859,7 +4839,7 @@ var CsvFile = class {
|
|
|
4859
4839
|
let header = null;
|
|
4860
4840
|
for await (const line of lines) {
|
|
4861
4841
|
if (!line.trim()) continue;
|
|
4862
|
-
const fields =
|
|
4842
|
+
const fields = parse_csv_line(line);
|
|
4863
4843
|
if (!header) {
|
|
4864
4844
|
header = fields;
|
|
4865
4845
|
const expected = CSV_COLUMNS.join(",");
|
|
@@ -4896,21 +4876,21 @@ var CsvFile = class {
|
|
|
4896
4876
|
flags: "w",
|
|
4897
4877
|
encoding: "utf8"
|
|
4898
4878
|
});
|
|
4899
|
-
let
|
|
4879
|
+
let next_id = 1;
|
|
4900
4880
|
try {
|
|
4901
|
-
await
|
|
4881
|
+
await write_line(writer, CSV_COLUMNS.join(","));
|
|
4902
4882
|
await driver(async (event) => {
|
|
4903
|
-
const id =
|
|
4883
|
+
const id = next_id++;
|
|
4904
4884
|
const row = [
|
|
4905
4885
|
String(id),
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4886
|
+
csv_escape(event.name),
|
|
4887
|
+
csv_escape(JSON.stringify(event.data)),
|
|
4888
|
+
csv_escape(event.stream),
|
|
4909
4889
|
String(event.version),
|
|
4910
4890
|
event.created.toISOString(),
|
|
4911
|
-
|
|
4891
|
+
csv_escape(JSON.stringify(event.meta))
|
|
4912
4892
|
].join(",");
|
|
4913
|
-
await
|
|
4893
|
+
await write_line(writer, row);
|
|
4914
4894
|
return id;
|
|
4915
4895
|
});
|
|
4916
4896
|
} finally {
|
|
@@ -4943,7 +4923,7 @@ async function* linesFromBlob(blob) {
|
|
|
4943
4923
|
await Promise.resolve();
|
|
4944
4924
|
}
|
|
4945
4925
|
}
|
|
4946
|
-
function
|
|
4926
|
+
function parse_csv_line(line) {
|
|
4947
4927
|
const fields = [];
|
|
4948
4928
|
let i = 0;
|
|
4949
4929
|
while (i < line.length) {
|
|
@@ -4976,11 +4956,11 @@ function parseCsvLine(line) {
|
|
|
4976
4956
|
}
|
|
4977
4957
|
return fields;
|
|
4978
4958
|
}
|
|
4979
|
-
function
|
|
4959
|
+
function csv_escape(value) {
|
|
4980
4960
|
if (/[",\n\r]/.test(value)) return `"${value.replace(/"/g, '""')}"`;
|
|
4981
4961
|
return value;
|
|
4982
4962
|
}
|
|
4983
|
-
function
|
|
4963
|
+
function write_line(writer, line) {
|
|
4984
4964
|
return new Promise((resolve, reject) => {
|
|
4985
4965
|
writer.write(`${line}
|
|
4986
4966
|
`, (err) => {
|