@rotorsoft/act 1.14.0 → 1.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/@types/act.d.ts +34 -15
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/adapters/in-memory-store.d.ts.map +1 -1
- package/dist/@types/internal/autoclose-config.d.ts +44 -11
- package/dist/@types/internal/autoclose-config.d.ts.map +1 -1
- package/dist/@types/internal/autoclose-cycle.d.ts +11 -6
- package/dist/@types/internal/autoclose-cycle.d.ts.map +1 -1
- package/dist/@types/internal/close-cycle.d.ts +14 -0
- package/dist/@types/internal/close-cycle.d.ts.map +1 -1
- package/dist/@types/internal/index.d.ts +1 -1
- package/dist/@types/internal/index.d.ts.map +1 -1
- package/dist/@types/types/ports.d.ts +49 -0
- package/dist/@types/types/ports.d.ts.map +1 -1
- package/dist/{chunk-QKVY5QAQ.js → chunk-DBQT3ALY.js} +21 -2
- package/dist/{chunk-QKVY5QAQ.js.map → chunk-DBQT3ALY.js.map} +1 -1
- package/dist/{chunk-5WRI5ZAA.js → chunk-YLJ4XMA6.js} +6 -2
- package/dist/{dist-INXH4GUE.js → dist-EOG547W7.js} +2 -2
- package/dist/index.cjs +139 -63
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +120 -63
- package/dist/index.js.map +1 -1
- package/dist/{magic-string.es-ZGO77SPS.js → magic-string.es-5F2IVYTG.js} +2 -2
- package/dist/test/index.cjs +27 -4
- 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.js +1 -1
- package/package.json +2 -2
- /package/dist/{chunk-5WRI5ZAA.js.map → chunk-YLJ4XMA6.js.map} +0 -0
- /package/dist/{dist-INXH4GUE.js.map → dist-EOG547W7.js.map} +0 -0
- /package/dist/{magic-string.es-ZGO77SPS.js.map → magic-string.es-5F2IVYTG.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
sleep,
|
|
20
20
|
store,
|
|
21
21
|
validate
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-DBQT3ALY.js";
|
|
23
23
|
import {
|
|
24
24
|
ActorSchema,
|
|
25
25
|
CausationEventSchema,
|
|
@@ -45,7 +45,7 @@ import {
|
|
|
45
45
|
pii_strip,
|
|
46
46
|
sensitive
|
|
47
47
|
} from "./chunk-4TJN4G7L.js";
|
|
48
|
-
import "./chunk-
|
|
48
|
+
import "./chunk-YLJ4XMA6.js";
|
|
49
49
|
|
|
50
50
|
// src/signals.ts
|
|
51
51
|
process.once("SIGINT", async (arg) => {
|
|
@@ -489,25 +489,59 @@ var PASS_FACTORIES = {
|
|
|
489
489
|
|
|
490
490
|
// src/internal/autoclose-config.ts
|
|
491
491
|
import { z } from "zod";
|
|
492
|
-
var
|
|
492
|
+
var DEFAULT_AUTOCLOSE_CYCLE_MINUTES = 720;
|
|
493
493
|
var DEFAULT_CLOSE_BATCH_SIZE = 64;
|
|
494
494
|
var DEFAULT_CLOSE_YIELD_MS = 0;
|
|
495
|
+
var DEFAULT_AUTOCLOSE_WINDOW_TZ = "UTC";
|
|
496
|
+
function is_valid_time_zone(tz) {
|
|
497
|
+
try {
|
|
498
|
+
new Intl.DateTimeFormat("en-US", { timeZone: tz });
|
|
499
|
+
return true;
|
|
500
|
+
} catch {
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
var AutocloseWindowSchema = z.object({
|
|
505
|
+
start: z.number().int().min(0).max(23, { message: "autocloseWindow.start must be an hour in [0, 23]" }),
|
|
506
|
+
end: z.number().int().min(0).max(23, { message: "autocloseWindow.end must be an hour in [0, 23]" }),
|
|
507
|
+
timeZone: z.string().refine(is_valid_time_zone, {
|
|
508
|
+
message: "autocloseWindow.timeZone must be a valid IANA time zone"
|
|
509
|
+
}).default(DEFAULT_AUTOCLOSE_WINDOW_TZ)
|
|
510
|
+
}).refine((w) => w.start !== w.end, {
|
|
511
|
+
message: "autocloseWindow.start and end must differ \u2014 an empty window disables autoclose"
|
|
512
|
+
});
|
|
495
513
|
var AutocloseOptionsSchema = z.object({
|
|
496
|
-
|
|
514
|
+
autocloseCycleMinutes: z.number().int().min(1).max(1440).default(DEFAULT_AUTOCLOSE_CYCLE_MINUTES),
|
|
497
515
|
closeBatchSize: z.number().int().min(1).max(1024).default(DEFAULT_CLOSE_BATCH_SIZE),
|
|
498
516
|
closeYieldMs: z.number().min(0).max(1e3).default(DEFAULT_CLOSE_YIELD_MS),
|
|
499
|
-
closeOnError: z.boolean().default(false)
|
|
517
|
+
closeOnError: z.boolean().default(false),
|
|
518
|
+
autocloseWindow: AutocloseWindowSchema.optional()
|
|
500
519
|
});
|
|
501
520
|
function resolveAutocloseConfig(options) {
|
|
502
521
|
return AutocloseOptionsSchema.parse({
|
|
503
|
-
|
|
522
|
+
autocloseCycleMinutes: options?.autocloseCycleMinutes,
|
|
504
523
|
closeBatchSize: options?.closeBatchSize,
|
|
505
524
|
closeYieldMs: options?.closeYieldMs,
|
|
506
|
-
closeOnError: options?.closeOnError
|
|
525
|
+
closeOnError: options?.closeOnError,
|
|
526
|
+
autocloseWindow: options?.autocloseWindow
|
|
507
527
|
});
|
|
508
528
|
}
|
|
529
|
+
function hour_in_zone(now, timeZone) {
|
|
530
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
531
|
+
timeZone,
|
|
532
|
+
hour: "2-digit",
|
|
533
|
+
hourCycle: "h23"
|
|
534
|
+
}).formatToParts(now);
|
|
535
|
+
return Number(parts.find((p) => p.type === "hour")?.value);
|
|
536
|
+
}
|
|
537
|
+
function in_autoclose_window(window, now) {
|
|
538
|
+
if (!window) return true;
|
|
539
|
+
const hour = hour_in_zone(now, window.timeZone);
|
|
540
|
+
return window.start < window.end ? hour >= window.start && hour < window.end : hour >= window.start || hour < window.end;
|
|
541
|
+
}
|
|
509
542
|
|
|
510
543
|
// src/internal/close-cycle.ts
|
|
544
|
+
var SAFETY_PROBE_PAGE_SIZE = 1e3;
|
|
511
545
|
async function run_close_cycle(targets, deps) {
|
|
512
546
|
const target_map = new Map(targets.map((t) => [t.stream, t]));
|
|
513
547
|
const streams = [...target_map.keys()];
|
|
@@ -516,7 +550,8 @@ async function run_close_cycle(targets, deps) {
|
|
|
516
550
|
const safe = await partition_by_safety(
|
|
517
551
|
stream_info,
|
|
518
552
|
deps.reactive_events_size,
|
|
519
|
-
skipped
|
|
553
|
+
skipped,
|
|
554
|
+
deps.probe_page_size ?? SAFETY_PROBE_PAGE_SIZE
|
|
520
555
|
);
|
|
521
556
|
if (!safe.length) return { truncated: /* @__PURE__ */ new Map(), skipped };
|
|
522
557
|
const { guarded, guard_events } = await guard_with_tombstones(
|
|
@@ -559,17 +594,37 @@ async function scan_stream_heads(streams) {
|
|
|
559
594
|
}
|
|
560
595
|
return out;
|
|
561
596
|
}
|
|
562
|
-
async function partition_by_safety(stream_info, reactive_events_size, skipped) {
|
|
597
|
+
async function partition_by_safety(stream_info, reactive_events_size, skipped, page_size) {
|
|
563
598
|
if (reactive_events_size === 0) return [...stream_info.keys()];
|
|
564
599
|
const pending_set = /* @__PURE__ */ new Set();
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
600
|
+
const source_regex = /* @__PURE__ */ new Map();
|
|
601
|
+
const get_regex = (source) => {
|
|
602
|
+
let re = source_regex.get(source);
|
|
603
|
+
if (!re) {
|
|
604
|
+
re = new RegExp(source);
|
|
605
|
+
source_regex.set(source, re);
|
|
571
606
|
}
|
|
572
|
-
|
|
607
|
+
return re;
|
|
608
|
+
};
|
|
609
|
+
const targets = [...stream_info.keys()];
|
|
610
|
+
let after;
|
|
611
|
+
for (; ; ) {
|
|
612
|
+
let last;
|
|
613
|
+
const { count } = await store().query_streams(
|
|
614
|
+
(position) => {
|
|
615
|
+
last = position.stream;
|
|
616
|
+
const source_re = position.source ? get_regex(position.source) : void 0;
|
|
617
|
+
for (const [stream, info] of stream_info) {
|
|
618
|
+
if ((!source_re || source_re.test(stream)) && position.at < info.max_id) {
|
|
619
|
+
pending_set.add(stream);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
},
|
|
623
|
+
{ after, limit: page_size, source_matches: targets }
|
|
624
|
+
);
|
|
625
|
+
if (count < page_size) break;
|
|
626
|
+
after = last;
|
|
627
|
+
}
|
|
573
628
|
const safe = [];
|
|
574
629
|
for (const [stream] of stream_info) {
|
|
575
630
|
if (pending_set.has(stream)) skipped.push(stream);
|
|
@@ -690,54 +745,54 @@ async function run_autoclose_cycle(deps) {
|
|
|
690
745
|
let predicate_errors = 0;
|
|
691
746
|
const truncated = /* @__PURE__ */ new Map();
|
|
692
747
|
const skipped = [];
|
|
693
|
-
const
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
evaluated += 1;
|
|
724
|
-
} catch (err) {
|
|
725
|
-
predicate_errors += 1;
|
|
726
|
-
deps.logger.error(
|
|
727
|
-
`Autoclose predicate for state "${owner.name}" threw on stream "${stream}": ${err instanceof Error ? err.message : String(err)}`
|
|
728
|
-
);
|
|
729
|
-
close = deps.config.closeOnError;
|
|
748
|
+
const page = deps.config.closeBatchSize;
|
|
749
|
+
let after;
|
|
750
|
+
for (; ; ) {
|
|
751
|
+
const stats = await s.query_stats(
|
|
752
|
+
{},
|
|
753
|
+
{ count: true, exclude: [TOMBSTONE_EVENT], after, limit: page }
|
|
754
|
+
);
|
|
755
|
+
if (stats.size === 0) break;
|
|
756
|
+
inspected += stats.size;
|
|
757
|
+
const candidates = [];
|
|
758
|
+
for (const [stream, { head, count }] of stats) {
|
|
759
|
+
const owner = deps.event_to_state.get(head.name);
|
|
760
|
+
if (!owner) continue;
|
|
761
|
+
const predicate = deps.autoclose_policy(owner.name);
|
|
762
|
+
if (!predicate) continue;
|
|
763
|
+
let close = false;
|
|
764
|
+
try {
|
|
765
|
+
close = predicate(stream, head, count ?? 0);
|
|
766
|
+
evaluated += 1;
|
|
767
|
+
} catch (err) {
|
|
768
|
+
predicate_errors += 1;
|
|
769
|
+
deps.logger.error(
|
|
770
|
+
`Autoclose predicate for state "${owner.name}" threw on stream "${stream}": ${err instanceof Error ? err.message : String(err)}`
|
|
771
|
+
);
|
|
772
|
+
close = deps.config.closeOnError;
|
|
773
|
+
}
|
|
774
|
+
if (!close) continue;
|
|
775
|
+
const archiver = deps.autoclose_archiver(owner.name);
|
|
776
|
+
const archive = archiver ? () => archiver(stream, head) : void 0;
|
|
777
|
+
candidates.push({ stream, archive });
|
|
730
778
|
}
|
|
731
|
-
if (
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
779
|
+
if (candidates.length > 0) {
|
|
780
|
+
const result = await run_close_cycle(candidates, {
|
|
781
|
+
reactive_events_size: deps.reactive_events_size,
|
|
782
|
+
event_to_state: deps.event_to_state,
|
|
783
|
+
load: deps.load,
|
|
784
|
+
tombstone: deps.tombstone,
|
|
785
|
+
logger: deps.logger,
|
|
786
|
+
correlation: deps.correlation
|
|
787
|
+
});
|
|
788
|
+
for (const [stream, entry] of result.truncated)
|
|
789
|
+
truncated.set(stream, entry);
|
|
790
|
+
for (const stream of result.skipped) skipped.push(stream);
|
|
791
|
+
await yield_between_batches(deps.config.closeYieldMs);
|
|
738
792
|
}
|
|
793
|
+
if (stats.size < page) break;
|
|
794
|
+
after = [...stats.keys()].at(-1);
|
|
739
795
|
}
|
|
740
|
-
await flush_batch(candidates);
|
|
741
796
|
return {
|
|
742
797
|
inspected,
|
|
743
798
|
evaluated,
|
|
@@ -767,7 +822,7 @@ var AutocloseController = class {
|
|
|
767
822
|
this._timer = setInterval(() => {
|
|
768
823
|
this.run_once().catch(() => {
|
|
769
824
|
});
|
|
770
|
-
}, this.deps.config.
|
|
825
|
+
}, this.deps.config.autocloseCycleMinutes * 6e4);
|
|
771
826
|
this._timer.unref();
|
|
772
827
|
return true;
|
|
773
828
|
}
|
|
@@ -799,6 +854,8 @@ var AutocloseController = class {
|
|
|
799
854
|
async run_once() {
|
|
800
855
|
if (this._running) return null;
|
|
801
856
|
if (this.deps.breaker.state(Date.now()) === "open") return null;
|
|
857
|
+
if (!in_autoclose_window(this.deps.config.autocloseWindow, /* @__PURE__ */ new Date()))
|
|
858
|
+
return null;
|
|
802
859
|
this._running = true;
|
|
803
860
|
try {
|
|
804
861
|
const correlation = close_correlation(
|
|
@@ -4073,7 +4130,7 @@ export {
|
|
|
4073
4130
|
ConcurrencyError,
|
|
4074
4131
|
ConsoleLogger,
|
|
4075
4132
|
CsvFile,
|
|
4076
|
-
|
|
4133
|
+
DEFAULT_AUTOCLOSE_CYCLE_MINUTES,
|
|
4077
4134
|
DEFAULT_CLOSE_BATCH_SIZE,
|
|
4078
4135
|
DEFAULT_CLOSE_YIELD_MS,
|
|
4079
4136
|
DEFAULT_LANE,
|