@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.cjs
CHANGED
|
@@ -37,7 +37,7 @@ __export(index_exports, {
|
|
|
37
37
|
ConcurrencyError: () => ConcurrencyError,
|
|
38
38
|
ConsoleLogger: () => ConsoleLogger,
|
|
39
39
|
CsvFile: () => CsvFile,
|
|
40
|
-
|
|
40
|
+
DEFAULT_AUTOCLOSE_CYCLE_MINUTES: () => DEFAULT_AUTOCLOSE_CYCLE_MINUTES,
|
|
41
41
|
DEFAULT_CLOSE_BATCH_SIZE: () => DEFAULT_CLOSE_BATCH_SIZE,
|
|
42
42
|
DEFAULT_CLOSE_YIELD_MS: () => DEFAULT_CLOSE_YIELD_MS,
|
|
43
43
|
DEFAULT_LANE: () => DEFAULT_LANE,
|
|
@@ -1169,8 +1169,18 @@ var InMemoryStore = class {
|
|
|
1169
1169
|
const limit = query?.limit ?? 100;
|
|
1170
1170
|
const after = query?.after;
|
|
1171
1171
|
const blocked = query?.blocked;
|
|
1172
|
+
const source_matches = query?.source_matches;
|
|
1172
1173
|
const stream_re = query?.stream && !query.stream_exact ? new RegExp(query.stream) : void 0;
|
|
1173
1174
|
const source_re = query?.source && !query.source_exact ? new RegExp(query.source) : void 0;
|
|
1175
|
+
const reverse_cache = /* @__PURE__ */ new Map();
|
|
1176
|
+
const reverse_match = (source) => {
|
|
1177
|
+
let re = reverse_cache.get(source);
|
|
1178
|
+
if (!re) {
|
|
1179
|
+
re = new RegExp(source);
|
|
1180
|
+
reverse_cache.set(source, re);
|
|
1181
|
+
}
|
|
1182
|
+
return source_matches.some((name) => re.test(name));
|
|
1183
|
+
};
|
|
1174
1184
|
const sorted = [...this._streams.values()].sort(
|
|
1175
1185
|
(a, b) => a.stream.localeCompare(b.stream)
|
|
1176
1186
|
);
|
|
@@ -1186,6 +1196,9 @@ var InMemoryStore = class {
|
|
|
1186
1196
|
if (query.source_exact ? s.source !== query.source : !source_re.test(s.source))
|
|
1187
1197
|
continue;
|
|
1188
1198
|
}
|
|
1199
|
+
if (source_matches !== void 0) {
|
|
1200
|
+
if (s.source && !reverse_match(s.source)) continue;
|
|
1201
|
+
}
|
|
1189
1202
|
if (blocked !== void 0 && s.blocked !== blocked) continue;
|
|
1190
1203
|
if (query?.lane !== void 0 && s.lane !== query.lane) continue;
|
|
1191
1204
|
callback({
|
|
@@ -1228,6 +1241,8 @@ var InMemoryStore = class {
|
|
|
1228
1241
|
const want_count = options?.count ?? false;
|
|
1229
1242
|
const want_names = options?.names ?? false;
|
|
1230
1243
|
const before = options?.before;
|
|
1244
|
+
const after = options?.after;
|
|
1245
|
+
const limit = options?.limit;
|
|
1231
1246
|
const array_targets = Array.isArray(input) ? new Set(input) : null;
|
|
1232
1247
|
const filter = Array.isArray(input) ? null : input;
|
|
1233
1248
|
const stream_re = filter?.stream && !filter.stream_exact ? new RegExp(filter.stream) : void 0;
|
|
@@ -1266,13 +1281,17 @@ var InMemoryStore = class {
|
|
|
1266
1281
|
a.names[n] = (a.names[n] ?? 0) + 1;
|
|
1267
1282
|
}
|
|
1268
1283
|
}
|
|
1284
|
+
const ordered = [...acc.keys()].sort((x, y) => x.localeCompare(y));
|
|
1269
1285
|
const out = /* @__PURE__ */ new Map();
|
|
1270
|
-
for (const
|
|
1286
|
+
for (const stream of ordered) {
|
|
1287
|
+
if (after !== void 0 && stream <= after) continue;
|
|
1288
|
+
const a = acc.get(stream);
|
|
1271
1289
|
const stats = { head: a.head };
|
|
1272
1290
|
if (want_tail) stats.tail = a.tail;
|
|
1273
1291
|
if (want_count) stats.count = a.count;
|
|
1274
1292
|
if (want_names) stats.names = a.names;
|
|
1275
1293
|
out.set(stream, stats);
|
|
1294
|
+
if (limit !== void 0 && out.size >= limit) break;
|
|
1276
1295
|
}
|
|
1277
1296
|
return out;
|
|
1278
1297
|
}
|
|
@@ -1871,25 +1890,59 @@ var PASS_FACTORIES = {
|
|
|
1871
1890
|
|
|
1872
1891
|
// src/internal/autoclose-config.ts
|
|
1873
1892
|
var import_zod5 = require("zod");
|
|
1874
|
-
var
|
|
1893
|
+
var DEFAULT_AUTOCLOSE_CYCLE_MINUTES = 720;
|
|
1875
1894
|
var DEFAULT_CLOSE_BATCH_SIZE = 64;
|
|
1876
1895
|
var DEFAULT_CLOSE_YIELD_MS = 0;
|
|
1896
|
+
var DEFAULT_AUTOCLOSE_WINDOW_TZ = "UTC";
|
|
1897
|
+
function is_valid_time_zone(tz) {
|
|
1898
|
+
try {
|
|
1899
|
+
new Intl.DateTimeFormat("en-US", { timeZone: tz });
|
|
1900
|
+
return true;
|
|
1901
|
+
} catch {
|
|
1902
|
+
return false;
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
var AutocloseWindowSchema = import_zod5.z.object({
|
|
1906
|
+
start: import_zod5.z.number().int().min(0).max(23, { message: "autocloseWindow.start must be an hour in [0, 23]" }),
|
|
1907
|
+
end: import_zod5.z.number().int().min(0).max(23, { message: "autocloseWindow.end must be an hour in [0, 23]" }),
|
|
1908
|
+
timeZone: import_zod5.z.string().refine(is_valid_time_zone, {
|
|
1909
|
+
message: "autocloseWindow.timeZone must be a valid IANA time zone"
|
|
1910
|
+
}).default(DEFAULT_AUTOCLOSE_WINDOW_TZ)
|
|
1911
|
+
}).refine((w) => w.start !== w.end, {
|
|
1912
|
+
message: "autocloseWindow.start and end must differ \u2014 an empty window disables autoclose"
|
|
1913
|
+
});
|
|
1877
1914
|
var AutocloseOptionsSchema = import_zod5.z.object({
|
|
1878
|
-
|
|
1915
|
+
autocloseCycleMinutes: import_zod5.z.number().int().min(1).max(1440).default(DEFAULT_AUTOCLOSE_CYCLE_MINUTES),
|
|
1879
1916
|
closeBatchSize: import_zod5.z.number().int().min(1).max(1024).default(DEFAULT_CLOSE_BATCH_SIZE),
|
|
1880
1917
|
closeYieldMs: import_zod5.z.number().min(0).max(1e3).default(DEFAULT_CLOSE_YIELD_MS),
|
|
1881
|
-
closeOnError: import_zod5.z.boolean().default(false)
|
|
1918
|
+
closeOnError: import_zod5.z.boolean().default(false),
|
|
1919
|
+
autocloseWindow: AutocloseWindowSchema.optional()
|
|
1882
1920
|
});
|
|
1883
1921
|
function resolveAutocloseConfig(options) {
|
|
1884
1922
|
return AutocloseOptionsSchema.parse({
|
|
1885
|
-
|
|
1923
|
+
autocloseCycleMinutes: options?.autocloseCycleMinutes,
|
|
1886
1924
|
closeBatchSize: options?.closeBatchSize,
|
|
1887
1925
|
closeYieldMs: options?.closeYieldMs,
|
|
1888
|
-
closeOnError: options?.closeOnError
|
|
1926
|
+
closeOnError: options?.closeOnError,
|
|
1927
|
+
autocloseWindow: options?.autocloseWindow
|
|
1889
1928
|
});
|
|
1890
1929
|
}
|
|
1930
|
+
function hour_in_zone(now, timeZone) {
|
|
1931
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
1932
|
+
timeZone,
|
|
1933
|
+
hour: "2-digit",
|
|
1934
|
+
hourCycle: "h23"
|
|
1935
|
+
}).formatToParts(now);
|
|
1936
|
+
return Number(parts.find((p) => p.type === "hour")?.value);
|
|
1937
|
+
}
|
|
1938
|
+
function in_autoclose_window(window, now) {
|
|
1939
|
+
if (!window) return true;
|
|
1940
|
+
const hour = hour_in_zone(now, window.timeZone);
|
|
1941
|
+
return window.start < window.end ? hour >= window.start && hour < window.end : hour >= window.start || hour < window.end;
|
|
1942
|
+
}
|
|
1891
1943
|
|
|
1892
1944
|
// src/internal/close-cycle.ts
|
|
1945
|
+
var SAFETY_PROBE_PAGE_SIZE = 1e3;
|
|
1893
1946
|
async function run_close_cycle(targets, deps) {
|
|
1894
1947
|
const target_map = new Map(targets.map((t) => [t.stream, t]));
|
|
1895
1948
|
const streams = [...target_map.keys()];
|
|
@@ -1898,7 +1951,8 @@ async function run_close_cycle(targets, deps) {
|
|
|
1898
1951
|
const safe = await partition_by_safety(
|
|
1899
1952
|
stream_info,
|
|
1900
1953
|
deps.reactive_events_size,
|
|
1901
|
-
skipped
|
|
1954
|
+
skipped,
|
|
1955
|
+
deps.probe_page_size ?? SAFETY_PROBE_PAGE_SIZE
|
|
1902
1956
|
);
|
|
1903
1957
|
if (!safe.length) return { truncated: /* @__PURE__ */ new Map(), skipped };
|
|
1904
1958
|
const { guarded, guard_events } = await guard_with_tombstones(
|
|
@@ -1941,17 +1995,37 @@ async function scan_stream_heads(streams) {
|
|
|
1941
1995
|
}
|
|
1942
1996
|
return out;
|
|
1943
1997
|
}
|
|
1944
|
-
async function partition_by_safety(stream_info, reactive_events_size, skipped) {
|
|
1998
|
+
async function partition_by_safety(stream_info, reactive_events_size, skipped, page_size) {
|
|
1945
1999
|
if (reactive_events_size === 0) return [...stream_info.keys()];
|
|
1946
2000
|
const pending_set = /* @__PURE__ */ new Set();
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
2001
|
+
const source_regex = /* @__PURE__ */ new Map();
|
|
2002
|
+
const get_regex = (source) => {
|
|
2003
|
+
let re = source_regex.get(source);
|
|
2004
|
+
if (!re) {
|
|
2005
|
+
re = new RegExp(source);
|
|
2006
|
+
source_regex.set(source, re);
|
|
1953
2007
|
}
|
|
1954
|
-
|
|
2008
|
+
return re;
|
|
2009
|
+
};
|
|
2010
|
+
const targets = [...stream_info.keys()];
|
|
2011
|
+
let after;
|
|
2012
|
+
for (; ; ) {
|
|
2013
|
+
let last;
|
|
2014
|
+
const { count } = await store2().query_streams(
|
|
2015
|
+
(position) => {
|
|
2016
|
+
last = position.stream;
|
|
2017
|
+
const source_re = position.source ? get_regex(position.source) : void 0;
|
|
2018
|
+
for (const [stream, info] of stream_info) {
|
|
2019
|
+
if ((!source_re || source_re.test(stream)) && position.at < info.max_id) {
|
|
2020
|
+
pending_set.add(stream);
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
},
|
|
2024
|
+
{ after, limit: page_size, source_matches: targets }
|
|
2025
|
+
);
|
|
2026
|
+
if (count < page_size) break;
|
|
2027
|
+
after = last;
|
|
2028
|
+
}
|
|
1955
2029
|
const safe = [];
|
|
1956
2030
|
for (const [stream] of stream_info) {
|
|
1957
2031
|
if (pending_set.has(stream)) skipped.push(stream);
|
|
@@ -2072,54 +2146,54 @@ async function run_autoclose_cycle(deps) {
|
|
|
2072
2146
|
let predicate_errors = 0;
|
|
2073
2147
|
const truncated = /* @__PURE__ */ new Map();
|
|
2074
2148
|
const skipped = [];
|
|
2075
|
-
const
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
evaluated += 1;
|
|
2106
|
-
} catch (err) {
|
|
2107
|
-
predicate_errors += 1;
|
|
2108
|
-
deps.logger.error(
|
|
2109
|
-
`Autoclose predicate for state "${owner.name}" threw on stream "${stream}": ${err instanceof Error ? err.message : String(err)}`
|
|
2110
|
-
);
|
|
2111
|
-
close = deps.config.closeOnError;
|
|
2149
|
+
const page = deps.config.closeBatchSize;
|
|
2150
|
+
let after;
|
|
2151
|
+
for (; ; ) {
|
|
2152
|
+
const stats = await s.query_stats(
|
|
2153
|
+
{},
|
|
2154
|
+
{ count: true, exclude: [TOMBSTONE_EVENT], after, limit: page }
|
|
2155
|
+
);
|
|
2156
|
+
if (stats.size === 0) break;
|
|
2157
|
+
inspected += stats.size;
|
|
2158
|
+
const candidates = [];
|
|
2159
|
+
for (const [stream, { head, count }] of stats) {
|
|
2160
|
+
const owner = deps.event_to_state.get(head.name);
|
|
2161
|
+
if (!owner) continue;
|
|
2162
|
+
const predicate = deps.autoclose_policy(owner.name);
|
|
2163
|
+
if (!predicate) continue;
|
|
2164
|
+
let close = false;
|
|
2165
|
+
try {
|
|
2166
|
+
close = predicate(stream, head, count ?? 0);
|
|
2167
|
+
evaluated += 1;
|
|
2168
|
+
} catch (err) {
|
|
2169
|
+
predicate_errors += 1;
|
|
2170
|
+
deps.logger.error(
|
|
2171
|
+
`Autoclose predicate for state "${owner.name}" threw on stream "${stream}": ${err instanceof Error ? err.message : String(err)}`
|
|
2172
|
+
);
|
|
2173
|
+
close = deps.config.closeOnError;
|
|
2174
|
+
}
|
|
2175
|
+
if (!close) continue;
|
|
2176
|
+
const archiver = deps.autoclose_archiver(owner.name);
|
|
2177
|
+
const archive = archiver ? () => archiver(stream, head) : void 0;
|
|
2178
|
+
candidates.push({ stream, archive });
|
|
2112
2179
|
}
|
|
2113
|
-
if (
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2180
|
+
if (candidates.length > 0) {
|
|
2181
|
+
const result = await run_close_cycle(candidates, {
|
|
2182
|
+
reactive_events_size: deps.reactive_events_size,
|
|
2183
|
+
event_to_state: deps.event_to_state,
|
|
2184
|
+
load: deps.load,
|
|
2185
|
+
tombstone: deps.tombstone,
|
|
2186
|
+
logger: deps.logger,
|
|
2187
|
+
correlation: deps.correlation
|
|
2188
|
+
});
|
|
2189
|
+
for (const [stream, entry] of result.truncated)
|
|
2190
|
+
truncated.set(stream, entry);
|
|
2191
|
+
for (const stream of result.skipped) skipped.push(stream);
|
|
2192
|
+
await yield_between_batches(deps.config.closeYieldMs);
|
|
2120
2193
|
}
|
|
2194
|
+
if (stats.size < page) break;
|
|
2195
|
+
after = [...stats.keys()].at(-1);
|
|
2121
2196
|
}
|
|
2122
|
-
await flush_batch(candidates);
|
|
2123
2197
|
return {
|
|
2124
2198
|
inspected,
|
|
2125
2199
|
evaluated,
|
|
@@ -2149,7 +2223,7 @@ var AutocloseController = class {
|
|
|
2149
2223
|
this._timer = setInterval(() => {
|
|
2150
2224
|
this.run_once().catch(() => {
|
|
2151
2225
|
});
|
|
2152
|
-
}, this.deps.config.
|
|
2226
|
+
}, this.deps.config.autocloseCycleMinutes * 6e4);
|
|
2153
2227
|
this._timer.unref();
|
|
2154
2228
|
return true;
|
|
2155
2229
|
}
|
|
@@ -2181,6 +2255,8 @@ var AutocloseController = class {
|
|
|
2181
2255
|
async run_once() {
|
|
2182
2256
|
if (this._running) return null;
|
|
2183
2257
|
if (this.deps.breaker.state(Date.now()) === "open") return null;
|
|
2258
|
+
if (!in_autoclose_window(this.deps.config.autocloseWindow, /* @__PURE__ */ new Date()))
|
|
2259
|
+
return null;
|
|
2184
2260
|
this._running = true;
|
|
2185
2261
|
try {
|
|
2186
2262
|
const correlation = close_correlation(
|
|
@@ -5456,7 +5532,7 @@ function write_line(writer, line) {
|
|
|
5456
5532
|
ConcurrencyError,
|
|
5457
5533
|
ConsoleLogger,
|
|
5458
5534
|
CsvFile,
|
|
5459
|
-
|
|
5535
|
+
DEFAULT_AUTOCLOSE_CYCLE_MINUTES,
|
|
5460
5536
|
DEFAULT_CLOSE_BATCH_SIZE,
|
|
5461
5537
|
DEFAULT_CLOSE_YIELD_MS,
|
|
5462
5538
|
DEFAULT_LANE,
|