@rotorsoft/act 1.14.1 → 1.16.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/action.d.ts +7 -1
- package/dist/@types/types/action.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-46OCUAVF.js} +30 -2
- package/dist/chunk-46OCUAVF.js.map +1 -0
- package/dist/index.cjs +140 -64
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +111 -63
- package/dist/index.js.map +1 -1
- package/dist/test/index.cjs +29 -1
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-QKVY5QAQ.js.map +0 -1
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-46OCUAVF.js";
|
|
23
23
|
import {
|
|
24
24
|
ActorSchema,
|
|
25
25
|
CausationEventSchema,
|
|
@@ -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,7 +594,7 @@ 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
600
|
const source_regex = /* @__PURE__ */ new Map();
|
|
@@ -571,14 +606,25 @@ async function partition_by_safety(stream_info, reactive_events_size, skipped) {
|
|
|
571
606
|
}
|
|
572
607
|
return re;
|
|
573
608
|
};
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
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
|
+
}
|
|
582
628
|
const safe = [];
|
|
583
629
|
for (const [stream] of stream_info) {
|
|
584
630
|
if (pending_set.has(stream)) skipped.push(stream);
|
|
@@ -699,54 +745,54 @@ async function run_autoclose_cycle(deps) {
|
|
|
699
745
|
let predicate_errors = 0;
|
|
700
746
|
const truncated = /* @__PURE__ */ new Map();
|
|
701
747
|
const skipped = [];
|
|
702
|
-
const
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
evaluated += 1;
|
|
733
|
-
} catch (err) {
|
|
734
|
-
predicate_errors += 1;
|
|
735
|
-
deps.logger.error(
|
|
736
|
-
`Autoclose predicate for state "${owner.name}" threw on stream "${stream}": ${err instanceof Error ? err.message : String(err)}`
|
|
737
|
-
);
|
|
738
|
-
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 });
|
|
739
778
|
}
|
|
740
|
-
if (
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
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);
|
|
747
792
|
}
|
|
793
|
+
if (stats.size < page) break;
|
|
794
|
+
after = [...stats.keys()].at(-1);
|
|
748
795
|
}
|
|
749
|
-
await flush_batch(candidates);
|
|
750
796
|
return {
|
|
751
797
|
inspected,
|
|
752
798
|
evaluated,
|
|
@@ -776,7 +822,7 @@ var AutocloseController = class {
|
|
|
776
822
|
this._timer = setInterval(() => {
|
|
777
823
|
this.run_once().catch(() => {
|
|
778
824
|
});
|
|
779
|
-
}, this.deps.config.
|
|
825
|
+
}, this.deps.config.autocloseCycleMinutes * 6e4);
|
|
780
826
|
this._timer.unref();
|
|
781
827
|
return true;
|
|
782
828
|
}
|
|
@@ -808,6 +854,8 @@ var AutocloseController = class {
|
|
|
808
854
|
async run_once() {
|
|
809
855
|
if (this._running) return null;
|
|
810
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;
|
|
811
859
|
this._running = true;
|
|
812
860
|
try {
|
|
813
861
|
const correlation = close_correlation(
|
|
@@ -4082,7 +4130,7 @@ export {
|
|
|
4082
4130
|
ConcurrencyError,
|
|
4083
4131
|
ConsoleLogger,
|
|
4084
4132
|
CsvFile,
|
|
4085
|
-
|
|
4133
|
+
DEFAULT_AUTOCLOSE_CYCLE_MINUTES,
|
|
4086
4134
|
DEFAULT_CLOSE_BATCH_SIZE,
|
|
4087
4135
|
DEFAULT_CLOSE_YIELD_MS,
|
|
4088
4136
|
DEFAULT_LANE,
|