@rotorsoft/act 1.32.24 → 1.32.26

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.
@@ -472,13 +472,9 @@ var InMemoryStream = class {
472
472
  _leased_until = void 0;
473
473
  _priority = 0;
474
474
  _lane = DEFAULT_LANE;
475
- // Persisted next-visit time. When set and still in the future, the
476
- // stream is held out of `claim` entirely — so a deferred reaction is not
477
- // re-claimed (and `retry` is never bumped) until its due-time passes. Unlike
478
- // in-process backoff, this is durable store state shared across workers.
475
+ // While in the future, the stream is held out of `claim`.
479
476
  _deferred_at = void 0;
480
- // Work mark: highest event id observed to resolve to this target.
481
- // `undefined` means the row was never correlated, so `claim` skips it.
477
+ // Work mark; `undefined` = never correlated, so `claim` skips it.
482
478
  _correlated_at = void 0;
483
479
  constructor(stream, source, priority = 0, lane = DEFAULT_LANE) {
484
480
  this.stream = stream;
@@ -492,44 +488,28 @@ var InMemoryStream = class {
492
488
  get lane() {
493
489
  return this._lane;
494
490
  }
495
- /** Replace on every subscribe — current builder config wins on restart. */
491
+ /** Replaced on every subscribe, so the current config wins on restart. */
496
492
  set lane(value) {
497
493
  this._lane = value;
498
494
  }
499
- /**
500
- * Bump the priority via {@link subscribe}: keeps the maximum across
501
- * reactions so the highest-priority registrant wins.
502
- */
503
495
  bump_priority(priority) {
504
496
  if (priority > this._priority) this._priority = priority;
505
497
  }
506
498
  get correlated_at() {
507
499
  return this._correlated_at;
508
500
  }
509
- /**
510
- * Raise the work mark via {@link subscribe}: keeps the maximum, for every
511
- * value including zero and negatives, so a mark never regresses.
512
- */
501
+ /** A mark never regresses, including at zero and negatives. */
513
502
  mark(correlated_at) {
514
503
  if (this._correlated_at === void 0 || correlated_at > this._correlated_at)
515
504
  this._correlated_at = correlated_at;
516
505
  }
517
- /**
518
- * Set the priority outright via {@link prioritize}: operator
519
- * runtime override that ignores the build-time `max()` invariant.
520
- */
506
+ /** Operator override; ignores the subscribe-time `max()`. */
521
507
  set_priority(priority) {
522
508
  this._priority = priority;
523
509
  }
524
510
  get is_available() {
525
- return !this._blocked && (!this._leased_until || this._leased_until <= /* @__PURE__ */ new Date()) && // A stream deferred to a future time is not claimable until due.
526
- (!this._deferred_at || this._deferred_at <= Date.now());
527
- }
528
- /**
529
- * Hold this stream out of `claim` until `deferred_at` (ms since epoch).
530
- * Set by a deliberate `defer` outcome — not a failure, so retry/blocked
531
- * state is untouched. Cleared by ack/block/reset/unblock.
532
- */
511
+ return !this._blocked && (!this._leased_until || this._leased_until <= /* @__PURE__ */ new Date()) && (!this._deferred_at || this._deferred_at <= Date.now());
512
+ }
533
513
  defer(deferred_at) {
534
514
  this._deferred_at = deferred_at;
535
515
  this._retry = -1;
@@ -552,16 +532,9 @@ var InMemoryStream = class {
552
532
  get leased_until() {
553
533
  return this._leased_until;
554
534
  }
555
- /** Persisted next-visit time, or undefined when no active defer. */
556
535
  get deferred_at() {
557
536
  return this._deferred_at;
558
537
  }
559
- /**
560
- * Attempt to lease this stream for processing.
561
- * @param lease - The lease request.
562
- * @param millis - Lease duration in milliseconds.
563
- * @returns The granted lease or undefined if blocked.
564
- */
565
538
  lease(lease, millis) {
566
539
  this._leased_by = lease.by;
567
540
  this._leased_until = new Date(Date.now() + millis);
@@ -576,12 +549,7 @@ var InMemoryStream = class {
576
549
  lane: this._lane
577
550
  };
578
551
  }
579
- /**
580
- * Finalize this stream's lease: ack (advance the watermark) or, when the
581
- * lease carries a `due` marker, defer (persist the schedule, hold the
582
- * watermark) — see {@link Store.ack}.
583
- * @param lease - The lease request.
584
- */
552
+ /** Ack, or with `due`, also persist the schedule. See {@link Store.ack}. */
585
553
  ack(lease) {
586
554
  if (this._leased_by === lease.by) {
587
555
  this._leased_by = void 0;
@@ -606,11 +574,6 @@ var InMemoryStream = class {
606
574
  };
607
575
  }
608
576
  }
609
- /**
610
- * Block a stream for processing after failing to process and reaching max retries with blocking enabled.
611
- * @param lease - The lease request.
612
- * @param error Blocked error message.
613
- */
614
577
  block(lease, error) {
615
578
  if (this._leased_by === lease.by && !this._blocked) {
616
579
  this._blocked = true;
@@ -628,11 +591,7 @@ var InMemoryStream = class {
628
591
  };
629
592
  }
630
593
  }
631
- /**
632
- * Reset this stream's watermark and state for replay. The retry counter
633
- * resets to -1 to match the constructor + ack() invariant ("released
634
- * stream"); the next claim() bumps it to 0 (first attempt).
635
- */
594
+ /** Retry resets to -1 (released); the next claim makes it 0. */
636
595
  reset() {
637
596
  this._at = -1;
638
597
  this._retry = -1;
@@ -642,11 +601,7 @@ var InMemoryStream = class {
642
601
  this._leased_until = void 0;
643
602
  this._deferred_at = void 0;
644
603
  }
645
- /**
646
- * Clear the blocked flag and lease bookkeeping without touching the
647
- * watermark. Returns true if the stream was actually blocked (and is
648
- * now flipped); false otherwise.
649
- */
604
+ /** True only when the stream was blocked. */
650
605
  unblock() {
651
606
  if (!this._blocked) return false;
652
607
  this._blocked = false;
@@ -659,33 +614,17 @@ var InMemoryStream = class {
659
614
  }
660
615
  };
661
616
  var InMemoryStore = class {
662
- // stored events
663
617
  _events = [];
664
- // next event id — monotonic, never reused. Deletions (truncate, windowed
665
- // prune) punch holes in the id sequence, so ids are NOT array indexes and
666
- // NOT `_events.length`; they only stay sorted ascending in `_events`.
618
+ // Never reused. Deletions punch holes, so ids are not array indexes.
667
619
  _next_id = 0;
668
- // stored stream positions and other metadata
669
620
  _streams = /* @__PURE__ */ new Map();
670
621
  /** Correlate checkpoint: how far the log has been READ. */
671
622
  _correlated_at = -1;
672
- /**
673
- * Per-correlator checkpoint and lease, keyed by correlator.
674
- *
675
- * Keyed rather than singular because correlators that look for different
676
- * things read the log for different reasons: sharing one position lets a
677
- * partial-behaviour worker inherit another's, and sharing one lease lets
678
- * one starve the other. Callers that supply no correlator use
679
- * `_correlated_at` instead, the shared checkpoint.
680
- */
623
+ /** Per-correlator checkpoint and lease; keyless callers use `_correlated_at`. */
681
624
  _correlators = /* @__PURE__ */ new Map();
682
- // last committed version per stream — O(1) replacement for filter-on-commit
683
625
  _stream_versions = /* @__PURE__ */ new Map();
684
- // stream → (event_id → cloned sensitive payload). Two-level so `forget_pii`
685
- // is O(1) — drop the inner Map for the stream and the wipe is done — mirroring
686
- // the `DELETE WHERE stream = ?` scope that durable adapters get from their
687
- // stream index. Entries exist only for events committed with a non-null
688
- // `pii` field; absence means "no PII" (returned as `null` on load).
626
+ // stream → (event_id → cloned pii). Two levels so `forget_pii` drops one
627
+ // inner Map. Absence means no PII.
689
628
  _pii = /* @__PURE__ */ new Map();
690
629
  _reset_indexes() {
691
630
  this._events.length = 0;
@@ -703,10 +642,7 @@ var InMemoryStore = class {
703
642
  }
704
643
  per_stream.set(id, structuredClone(pii));
705
644
  }
706
- // First index whose event id is greater than `after`. `_events` stays
707
- // sorted ascending by id (append-only commits; deletions preserve
708
- // order), so id-bounded scans binary-search their start instead of
709
- // assuming id === index — an invariant that truncation breaks.
645
+ // First index whose id is greater than `after` (ids are sorted, not dense).
710
646
  _first_index_after(after) {
711
647
  let lo = 0;
712
648
  let hi = this._events.length;
@@ -717,32 +653,19 @@ var InMemoryStore = class {
717
653
  }
718
654
  return lo;
719
655
  }
720
- // Attach the isolated PII payload (or null) to an event before handing it to
721
- // a caller. Allocation-free for events without PII — by far the common case.
656
+ // Allocation-free for events without PII, the common case.
722
657
  _with_pii(e, wanted = true) {
723
658
  if (!wanted) return e;
724
659
  const pii = this._pii.get(e.stream)?.get(e.id);
725
660
  return pii ? { ...e, pii } : e;
726
661
  }
727
- /**
728
- * Dispose of the store and clear all events.
729
- * @returns Promise that resolves when disposal is complete.
730
- */
731
662
  async dispose() {
732
663
  await sleep();
733
664
  this._reset_indexes();
734
665
  }
735
- /**
736
- * Seed the store with initial data (no-op for in-memory).
737
- * @returns Promise that resolves when seeding is complete.
738
- */
739
666
  async seed() {
740
667
  await sleep();
741
668
  }
742
- /**
743
- * Drop all data from the store.
744
- * @returns Promise that resolves when the store is cleared.
745
- */
746
669
  async drop() {
747
670
  await sleep();
748
671
  this._reset_indexes();
@@ -762,12 +685,7 @@ var InMemoryStore = class {
762
685
  _stream_matches(pattern, query, stream) {
763
686
  return query.stream_exact ? stream === pattern : RegExp(pattern).test(stream);
764
687
  }
765
- /**
766
- * Index of the latest snapshot of the queried stream, or -1. `with_snaps`
767
- * on an exact single stream resumes there, so earlier events aren't read.
768
- * The orchestrator asks only for unbounded current-state loads, so the
769
- * floor applies whenever asked; an explicit `after` wins over it.
770
- */
688
+ /** Index of the stream's latest snapshot, or -1; an explicit `after` wins. */
771
689
  _snap_floor(query) {
772
690
  if (!query?.with_snaps || !query.stream_exact || query.stream === void 0 || query.after !== void 0)
773
691
  return -1;
@@ -777,12 +695,6 @@ var InMemoryStore = class {
777
695
  }
778
696
  return -1;
779
697
  }
780
- /**
781
- * Query events in the store, optionally filtered by query options.
782
- * @param callback - Function to call for each event.
783
- * @param query - Optional query options.
784
- * @returns The number of events processed.
785
- */
786
698
  async query(callback, query) {
787
699
  await sleep();
788
700
  const floor = this._snap_floor(query);
@@ -801,15 +713,6 @@ var InMemoryStore = class {
801
713
  }
802
714
  return count;
803
715
  }
804
- /**
805
- * Commit one or more events to a stream.
806
- * @param stream - The stream name.
807
- * @param msgs - The events/messages to commit.
808
- * @param meta - Event metadata.
809
- * @param expectedVersion - Optional optimistic concurrency check.
810
- * @returns The committed events with metadata.
811
- * @throws ConcurrencyError if expectedVersion does not match.
812
- */
813
716
  async commit(stream, msgs, meta, expectedVersion) {
814
717
  await sleep();
815
718
  const current_version = this._stream_versions.get(stream) ?? -1;
@@ -840,15 +743,6 @@ var InMemoryStore = class {
840
743
  this._stream_versions.set(stream, version - 1);
841
744
  return committed;
842
745
  }
843
- /**
844
- * Atomically discovers and leases streams for processing.
845
- * Fuses poll + lease into a single operation.
846
- * @param lagging - Max streams from lagging frontier.
847
- * @param leading - Max streams from leading frontier.
848
- * @param by - Lease holder identifier.
849
- * @param millis - Lease duration in milliseconds.
850
- * @returns Granted leases.
851
- */
852
746
  async claim(lagging, leading, by, millis, lane) {
853
747
  await sleep();
854
748
  const has_work = (s) => s.correlated_at !== void 0 && s.at < s.correlated_at;
@@ -884,15 +778,6 @@ var InMemoryStore = class {
884
778
  (p) => this._streams.get(p.stream)?.lease({ ...p, by, retry: 0 }, millis)
885
779
  ).filter((l) => !!l);
886
780
  }
887
- /**
888
- * Registers streams for event processing. When the same stream is
889
- * resubscribed with a different priority, the **maximum** wins — so
890
- * the highest-priority registered reaction sets the scheduling lane.
891
- * Use {@link prioritize} for operator runtime overrides.
892
- *
893
- * @param streams - Streams to register with optional source + priority.
894
- * @returns subscribed count and current max watermark.
895
- */
896
781
  async subscribe(streams, correlated_at, correlator) {
897
782
  await sleep();
898
783
  let correlating;
@@ -907,9 +792,7 @@ var InMemoryStore = class {
907
792
  this._correlators.set(correlator.key, {
908
793
  at,
909
794
  by: correlator.by,
910
- // A non-positive `millis` releases outright rather than setting an
911
- // expiry a hair in the future: a successor asking inside the same
912
- // millisecond would otherwise still be refused.
795
+ // Release outright, or a successor in the same ms is still refused.
913
796
  until: correlator.millis > 0 ? now + correlator.millis : 0
914
797
  });
915
798
  } else {
@@ -951,53 +834,26 @@ var InMemoryStore = class {
951
834
  ...correlating === void 0 ? {} : { correlating }
952
835
  };
953
836
  }
954
- /**
955
- * Acknowledge completion of processing for leased streams.
956
- * @param leases - Leases to acknowledge, including last processed watermark and lease holder.
957
- */
958
837
  async ack(leases) {
959
838
  await sleep();
960
839
  return leases.map((l) => this._streams.get(l.stream)?.ack(l)).filter((l) => !!l);
961
840
  }
962
- /**
963
- * Block a stream for processing after failing to process and reaching max retries with blocking enabled.
964
- * @param leases - Leases to block, including lease holder and last error message.
965
- * @returns Blocked leases.
966
- */
967
841
  async block(leases) {
968
842
  await sleep();
969
843
  return leases.map((l) => this._streams.get(l.stream)?.block(l, l.error)).filter((l) => !!l);
970
844
  }
971
- /**
972
- * Hold the matched streams out of {@link claim} until `deferred_at`
973
- * (ms since epoch) — see {@link Store.defer}. Accepts an explicit list
974
- * of names or a {@link StreamFilter}, mirroring {@link reset}/{@link unblock}.
975
- * Persisted store state (unlike in-process backoff), so the skip is honored
976
- * by every competing worker. Unknown names are silently skipped.
977
- *
978
- * @returns Count of streams whose `deferred_at` was set.
979
- */
980
845
  async defer(input, deferred_at) {
981
846
  await sleep();
982
847
  const selected = this._select(input);
983
848
  for (const s of selected) s.defer(deferred_at);
984
849
  return selected.length;
985
850
  }
986
- /**
987
- * The streams named by `input`: an explicit list (de-duplicated, so a
988
- * repeated name counts once, matching the SQL adapters' set-based
989
- * `stream = ANY(...)`; unknown names are skipped) or a {@link StreamFilter}.
990
- */
851
+ /** De-duplicated like the SQL `stream = ANY(...)`; unknown names skipped. */
991
852
  _select(input) {
992
853
  if (Array.isArray(input))
993
854
  return [...new Set(input)].map((name) => this._streams.get(name)).filter((s) => s !== void 0);
994
855
  return [...this._streams.values()].filter(this._filter_predicate(input));
995
856
  }
996
- /**
997
- * Build a predicate from a {@link StreamFilter}. Compiled regexes are
998
- * cached in the closure so callers can apply it across the streams
999
- * map without re-compiling per iteration.
1000
- */
1001
857
  _filter_predicate(filter) {
1002
858
  const stream_re = filter.stream !== void 0 && !filter.stream_exact ? new RegExp(filter.stream) : void 0;
1003
859
  const source_re = filter.source !== void 0 && !filter.source_exact ? new RegExp(filter.source) : void 0;
@@ -1017,46 +873,18 @@ var InMemoryStore = class {
1017
873
  return true;
1018
874
  };
1019
875
  }
1020
- /**
1021
- * Reset watermarks to -1, clearing retry, blocked, error, and lease
1022
- * state so the matched streams can be replayed from the beginning.
1023
- * Accepts either an explicit list of names or a {@link StreamFilter}.
1024
- *
1025
- * @param input - Stream names or a filter selecting the streams to reset.
1026
- * @returns Count of streams that were actually reset.
1027
- */
1028
876
  async reset(input) {
1029
877
  await sleep();
1030
878
  const selected = this._select(input);
1031
879
  for (const s of selected) s.reset();
1032
880
  return selected.length;
1033
881
  }
1034
- /**
1035
- * Wipe the sensitive-data payload for every event on the stream — see
1036
- * {@link Store.forget_pii}. O(1) drop of the stream's inner Map; the size of
1037
- * that Map is the count of events that had PII. Idempotent: a second call
1038
- * finds no inner Map and returns `0`.
1039
- *
1040
- * @param stream - Target stream.
1041
- * @returns Count of events whose isolated PII payload was deleted.
1042
- */
1043
882
  async forget_pii(stream) {
1044
883
  await sleep();
1045
884
  const count = this._pii.get(stream)?.size ?? 0;
1046
885
  this._pii.delete(stream);
1047
886
  return count;
1048
887
  }
1049
- /**
1050
- * Clear the blocked flag (and retry / error / lease) on the matched
1051
- * streams without touching the watermark. Streams that aren't blocked
1052
- * at call time are silently skipped. Accepts either an explicit list
1053
- * of names or a {@link StreamFilter}. The filter form always restricts
1054
- * to blocked streams — passing `blocked: false` matches nothing.
1055
- * See {@link Store.unblock}.
1056
- *
1057
- * @param input - Stream names or a filter selecting the streams to unblock.
1058
- * @returns Count of streams that were actually flipped (were blocked).
1059
- */
1060
888
  async unblock(input) {
1061
889
  await sleep();
1062
890
  const selected = this._select(
@@ -1064,26 +892,12 @@ var InMemoryStore = class {
1064
892
  );
1065
893
  return selected.filter((s) => s.unblock()).length;
1066
894
  }
1067
- /**
1068
- * Bulk-update priority of streams matching `filter`. Mirrors
1069
- * {@link query_streams}'s filter semantics — see {@link Store.prioritize}.
1070
- * Unlike {@link subscribe} (which keeps `max()` of registered
1071
- * priorities), this sets the priority outright — operator override
1072
- * for the build-time scheduling policy.
1073
- *
1074
- * @returns Count of streams whose priority changed.
1075
- */
1076
895
  async prioritize(filter, priority) {
1077
896
  await sleep();
1078
897
  const changed = this._select(filter).filter((s) => s.priority !== priority);
1079
898
  for (const s of changed) s.set_priority(priority);
1080
899
  return changed.length;
1081
900
  }
1082
- /**
1083
- * Streams registered subscription positions to the callback, ordered by
1084
- * stream name. Returns the highest event id in the store and the count
1085
- * of positions emitted.
1086
- */
1087
901
  async query_streams(callback, query) {
1088
902
  await sleep();
1089
903
  const limit = query?.limit ?? 100;
@@ -1126,19 +940,6 @@ var InMemoryStore = class {
1126
940
  }
1127
941
  return { maxEventId: this._events.at(-1)?.id ?? -1, count };
1128
942
  }
1129
- /**
1130
- * Per-stream aggregated stats — see {@link Store.query_stats}.
1131
- *
1132
- * Single forward scan over the in-memory event list, accumulating per
1133
- * stream. The "cheap heads" cost tier from durable adapters doesn't
1134
- * apply here (InMemory has no indexes); correctness is the goal, perf
1135
- * is a non-issue.
1136
- *
1137
- * Scope rules:
1138
- * - Array `input` — explicit stream names, regardless of subscription.
1139
- * - Filter `input` — `stream`/`stream_exact` match against event-bearing
1140
- * stream names. Empty filter `{}` matches every event-bearing stream.
1141
- */
1142
943
  async query_stats(input, options) {
1143
944
  await sleep();
1144
945
  const exclude = new Set(options?.exclude ?? []);
@@ -1200,15 +1001,6 @@ var InMemoryStore = class {
1200
1001
  }
1201
1002
  return out;
1202
1003
  }
1203
- /**
1204
- * Atomically truncates streams and seeds each with a snapshot or tombstone.
1205
- * Windowed targets (`before` set) prune the prefix below the closest safe
1206
- * `__snapshot__` instead — no seed, subscriptions untouched, no-op when no
1207
- * snapshot qualifies.
1208
- * @param targets - Streams to truncate with optional snapshot state and meta,
1209
- * or a `before`/`max_id` boundary for a windowed prefix delete.
1210
- * @returns Map keyed by stream name, each entry with `deleted` count and `committed` event.
1211
- */
1212
1004
  async truncate(targets) {
1213
1005
  await sleep();
1214
1006
  const result = /* @__PURE__ */ new Map();
@@ -1259,18 +1051,7 @@ var InMemoryStore = class {
1259
1051
  }
1260
1052
  return result;
1261
1053
  }
1262
- /**
1263
- * Atomically wipe-and-rebuild the store under an in-process snapshot.
1264
- *
1265
- * Captures every index state up front, clears it, then hands the
1266
- * orchestrator a per-event insert `callback` via the driver. Any
1267
- * throw inside the driver restores the snapshot, leaving the store
1268
- * byte-for-byte unchanged from the operator's perspective.
1269
- *
1270
- * `id`s are reassigned `0..N-1` as events arrive (dense — the
1271
- * monotonic id counter restarts at 0 for the rebuild). `created` is
1272
- * preserved verbatim from the source.
1273
- */
1054
+ /** A throw inside the driver restores the captured state. Ids restart at 0. */
1274
1055
  async restore(driver) {
1275
1056
  await sleep();
1276
1057
  const prev_events = this._events;