@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.
Files changed (32) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/@types/act.d.ts +34 -15
  3. package/dist/@types/act.d.ts.map +1 -1
  4. package/dist/@types/adapters/in-memory-store.d.ts.map +1 -1
  5. package/dist/@types/internal/autoclose-config.d.ts +44 -11
  6. package/dist/@types/internal/autoclose-config.d.ts.map +1 -1
  7. package/dist/@types/internal/autoclose-cycle.d.ts +11 -6
  8. package/dist/@types/internal/autoclose-cycle.d.ts.map +1 -1
  9. package/dist/@types/internal/close-cycle.d.ts +14 -0
  10. package/dist/@types/internal/close-cycle.d.ts.map +1 -1
  11. package/dist/@types/internal/index.d.ts +1 -1
  12. package/dist/@types/internal/index.d.ts.map +1 -1
  13. package/dist/@types/types/ports.d.ts +49 -0
  14. package/dist/@types/types/ports.d.ts.map +1 -1
  15. package/dist/{chunk-QKVY5QAQ.js → chunk-DBQT3ALY.js} +21 -2
  16. package/dist/{chunk-QKVY5QAQ.js.map → chunk-DBQT3ALY.js.map} +1 -1
  17. package/dist/{chunk-5WRI5ZAA.js → chunk-YLJ4XMA6.js} +6 -2
  18. package/dist/{dist-INXH4GUE.js → dist-EOG547W7.js} +2 -2
  19. package/dist/index.cjs +139 -63
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.js +120 -63
  22. package/dist/index.js.map +1 -1
  23. package/dist/{magic-string.es-ZGO77SPS.js → magic-string.es-5F2IVYTG.js} +2 -2
  24. package/dist/test/index.cjs +27 -4
  25. package/dist/test/index.cjs.map +1 -1
  26. package/dist/test/index.js +4 -4
  27. package/dist/test/index.js.map +1 -1
  28. package/dist/types/index.js +1 -1
  29. package/package.json +2 -2
  30. /package/dist/{chunk-5WRI5ZAA.js.map → chunk-YLJ4XMA6.js.map} +0 -0
  31. /package/dist/{dist-INXH4GUE.js.map → dist-EOG547W7.js.map} +0 -0
  32. /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-QKVY5QAQ.js";
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-5WRI5ZAA.js";
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 DEFAULT_AUTOCLOSE_CYCLE_MS = 6e4;
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
- autocloseCycleMs: z.number().min(1e4).max(36e5).default(DEFAULT_AUTOCLOSE_CYCLE_MS),
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
- autocloseCycleMs: options?.autocloseCycleMs,
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
- await store().query_streams((position) => {
566
- const source_re = position.source ? RegExp(position.source) : void 0;
567
- for (const [stream, info] of stream_info) {
568
- if ((!source_re || source_re.test(stream)) && position.at < info.max_id) {
569
- pending_set.add(stream);
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 stats = await s.query_stats(
694
- {},
695
- { count: true, exclude: [TOMBSTONE_EVENT] }
696
- );
697
- const flush_batch = async (candidates2) => {
698
- if (candidates2.length === 0) return;
699
- const result = await run_close_cycle(candidates2, {
700
- reactive_events_size: deps.reactive_events_size,
701
- event_to_state: deps.event_to_state,
702
- load: deps.load,
703
- tombstone: deps.tombstone,
704
- logger: deps.logger,
705
- correlation: deps.correlation
706
- });
707
- for (const [stream, entry] of result.truncated) {
708
- truncated.set(stream, entry);
709
- }
710
- for (const stream of result.skipped) skipped.push(stream);
711
- await yield_between_batches(deps.config.closeYieldMs);
712
- };
713
- let candidates = [];
714
- for (const [stream, { head, count }] of stats) {
715
- inspected += 1;
716
- const owner = deps.event_to_state.get(head.name);
717
- if (!owner) continue;
718
- const predicate = deps.autoclose_policy(owner.name);
719
- if (!predicate) continue;
720
- let close = false;
721
- try {
722
- close = predicate(stream, head, count ?? 0);
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 (!close) continue;
732
- const archiver = deps.autoclose_archiver(owner.name);
733
- const archive = archiver ? () => archiver(stream, head) : void 0;
734
- candidates.push({ stream, archive });
735
- if (candidates.length >= deps.config.closeBatchSize) {
736
- await flush_batch(candidates);
737
- candidates = [];
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.autocloseCycleMs);
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
- DEFAULT_AUTOCLOSE_CYCLE_MS,
4133
+ DEFAULT_AUTOCLOSE_CYCLE_MINUTES,
4077
4134
  DEFAULT_CLOSE_BATCH_SIZE,
4078
4135
  DEFAULT_CLOSE_YIELD_MS,
4079
4136
  DEFAULT_LANE,