@rotorsoft/act 1.22.4 → 1.22.6

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/index.cjs CHANGED
@@ -70,6 +70,7 @@ __export(index_exports, {
70
70
  dispose: () => dispose,
71
71
  disposeAndExit: () => disposeAndExit,
72
72
  extend: () => extend,
73
+ is_literal_source: () => is_literal_source,
73
74
  log: () => log,
74
75
  port: () => port,
75
76
  projection: () => projection,
@@ -619,6 +620,10 @@ var extend = (source, schema, target) => {
619
620
  async function sleep(ms) {
620
621
  return new Promise((resolve) => setTimeout(resolve, ms ?? config().sleepMs));
621
622
  }
623
+ var SOURCE_METACHARACTERS = /[\^$.*+?()[\]{}|\\]/;
624
+ function is_literal_source(source) {
625
+ return !SOURCE_METACHARACTERS.test(source);
626
+ }
622
627
 
623
628
  // src/adapters/in-memory-store.ts
624
629
  var InMemoryStream = class {
@@ -811,8 +816,9 @@ var InMemoryStore = class {
811
816
  _streams = /* @__PURE__ */ new Map();
812
817
  // last committed version per stream — O(1) replacement for filter-on-commit
813
818
  _stream_versions = /* @__PURE__ */ new Map();
814
- // max non-snapshot event id per stream — drives the exact-source probe in claim()
815
- // without scanning the full event log.
819
+ // max non-snapshot event id per stream — drives the has-work probe in
820
+ // claim(): an O(1) lookup for a literal source, and the per-stream max
821
+ // that a pattern source scans, without touching the full event log.
816
822
  _max_event_id_by_stream = /* @__PURE__ */ new Map();
817
823
  // global max non-snapshot event id — fast pre-check for source-less streams in claim()
818
824
  _max_non_snap_event_id = -1;
@@ -1002,8 +1008,15 @@ var InMemoryStore = class {
1002
1008
  const has_work = (s) => {
1003
1009
  if (s.at < 0) return true;
1004
1010
  if (!s.source) return s.at < this._max_non_snap_event_id;
1005
- const max_id = this._max_event_id_by_stream.get(s.source);
1006
- return max_id !== void 0 && max_id > s.at;
1011
+ if (is_literal_source(s.source)) {
1012
+ const max_id = this._max_event_id_by_stream.get(s.source);
1013
+ return max_id !== void 0 && max_id > s.at;
1014
+ }
1015
+ const re = new RegExp(s.source);
1016
+ for (const [stream_name, max_id] of this._max_event_id_by_stream) {
1017
+ if (max_id > s.at && re.test(stream_name)) return true;
1018
+ }
1019
+ return false;
1007
1020
  };
1008
1021
  const available = [...this._streams.values()].filter(
1009
1022
  (s) => s.is_available && has_work(s) && (lane === void 0 || s.lane === lane)
@@ -1594,6 +1607,15 @@ function deprecated_event_names(names) {
1594
1607
  const deprecated = /* @__PURE__ */ new Set();
1595
1608
  for (const list of groups.values()) {
1596
1609
  if (list.length < 2) continue;
1610
+ const by_version = /* @__PURE__ */ new Map();
1611
+ for (const { version, name } of list) {
1612
+ const clash = by_version.get(version);
1613
+ if (clash)
1614
+ throw new Error(
1615
+ `duplicate event version: ${clash} and ${name} both map to version ${version} \u2014 leading zeros in a \`_v<n>\` suffix are not allowed; use a single canonical spelling`
1616
+ );
1617
+ by_version.set(version, name);
1618
+ }
1597
1619
  list.sort((a, b) => b.version - a.version);
1598
1620
  for (let i = 1; i < list.length; i++) deprecated.add(list[i].name);
1599
1621
  }
@@ -2039,18 +2061,33 @@ function hour_in_zone(now, timeZone) {
2039
2061
  }).formatToParts(now);
2040
2062
  return Number(parts.find((p) => p.type === "hour")?.value);
2041
2063
  }
2064
+ function hour_in_range(hour, start, end) {
2065
+ return start < end ? hour >= start && hour < end : hour >= start || hour < end;
2066
+ }
2067
+ function is_dst_gap_open(now, timeZone, start) {
2068
+ const hour = hour_in_zone(now, timeZone);
2069
+ if (hour <= start) return false;
2070
+ const prev = hour_in_zone(new Date(now.getTime() - 36e5), timeZone);
2071
+ return prev === start - 1 && hour === start + 1;
2072
+ }
2042
2073
  function in_autoclose_window(window, now) {
2043
2074
  if (!window) return true;
2044
2075
  const hour = hour_in_zone(now, window.timeZone);
2045
- return window.start < window.end ? hour >= window.start && hour < window.end : hour >= window.start || hour < window.end;
2076
+ return hour_in_range(hour, window.start, window.end) || is_dst_gap_open(now, window.timeZone, window.start);
2046
2077
  }
2047
2078
  function next_window_open(window, now) {
2079
+ const start_ms = now.getTime();
2048
2080
  for (let h = 0; h <= 48; h++) {
2049
- const candidate = new Date(now.getTime() + h * 36e5);
2081
+ const candidate = new Date(start_ms + h * 36e5);
2050
2082
  if (hour_in_zone(candidate, window.timeZone) === window.start)
2051
2083
  return candidate;
2084
+ const boundary = new Date(
2085
+ candidate.getTime() - candidate.getTime() % 36e5
2086
+ );
2087
+ if (is_dst_gap_open(boundary, window.timeZone, window.start))
2088
+ return boundary;
2052
2089
  }
2053
- return new Date(now.getTime() + 864e5);
2090
+ return new Date(start_ms + 864e5);
2054
2091
  }
2055
2092
 
2056
2093
  // src/internal/autoclose-policy.ts
@@ -3144,8 +3181,10 @@ async function fetch(leased, eventLimit) {
3144
3181
  return Promise.all(
3145
3182
  leased.map(async ({ stream, source, at, lagging }) => {
3146
3183
  const events = [];
3184
+ const stream_exact = source !== void 0 && is_literal_source(source) ? true : void 0;
3147
3185
  await store2().query((e) => events.push(e), {
3148
3186
  stream: source,
3187
+ stream_exact,
3149
3188
  after: at,
3150
3189
  limit: eventLimit
3151
3190
  });
@@ -6093,6 +6132,7 @@ function write_line(writer, line) {
6093
6132
  dispose,
6094
6133
  disposeAndExit,
6095
6134
  extend,
6135
+ is_literal_source,
6096
6136
  log,
6097
6137
  port,
6098
6138
  projection,