@dudousxd/nestjs-durable-core 0.71.1 → 0.72.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/index.cjs +841 -129
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +452 -2
- package/dist/index.d.ts +452 -2
- package/dist/index.js +830 -129
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -25,7 +25,9 @@ __export(index_exports, {
|
|
|
25
25
|
CONTROL_FLOW_SIGNAL: () => CONTROL_FLOW_SIGNAL,
|
|
26
26
|
CURRENT_PROTOCOL_VERSION: () => CURRENT_PROTOCOL_VERSION,
|
|
27
27
|
CodecStateStore: () => CodecStateStore,
|
|
28
|
+
ConcurrencyLimitError: () => ConcurrencyLimitError,
|
|
28
29
|
ContinueAsNew: () => ContinueAsNew,
|
|
30
|
+
DEFAULT_CONCURRENCY_STATUSES: () => DEFAULT_CONCURRENCY_STATUSES,
|
|
29
31
|
DEFAULT_PARTITION: () => DEFAULT_PARTITION,
|
|
30
32
|
DURABLE_OPTIONS: () => DURABLE_OPTIONS,
|
|
31
33
|
DURABLE_OPTIONS_CANONICAL: () => DURABLE_OPTIONS_CANONICAL,
|
|
@@ -55,6 +57,9 @@ __export(index_exports, {
|
|
|
55
57
|
RunGateway: () => RunGateway,
|
|
56
58
|
STATE_STORE: () => STATE_STORE,
|
|
57
59
|
STATE_STORE_CANONICAL: () => STATE_STORE_CANONICAL,
|
|
60
|
+
ScheduleAlreadyExistsError: () => ScheduleAlreadyExistsError,
|
|
61
|
+
ScheduleClient: () => ScheduleClient,
|
|
62
|
+
ScheduleNotFoundError: () => ScheduleNotFoundError,
|
|
58
63
|
SignalTimeoutError: () => SignalTimeoutError,
|
|
59
64
|
SingletonQueueFullError: () => SingletonQueueFullError,
|
|
60
65
|
TERMINAL_RUN_STATUSES: () => TERMINAL_RUN_STATUSES,
|
|
@@ -68,6 +73,7 @@ __export(index_exports, {
|
|
|
68
73
|
aggregateAnnouncements: () => aggregateAnnouncements,
|
|
69
74
|
announcementsOf: () => announcementsOf,
|
|
70
75
|
applyAttributeQuery: () => applyAttributeQuery,
|
|
76
|
+
assertConcurrency: () => assertConcurrency,
|
|
71
77
|
attributeColumnFor: () => attributeColumnFor,
|
|
72
78
|
attributeOperand: () => attributeOperand,
|
|
73
79
|
attributePredicateOperands: () => attributePredicateOperands,
|
|
@@ -80,7 +86,9 @@ __export(index_exports, {
|
|
|
80
86
|
classifyWaiterToken: () => classifyWaiterToken,
|
|
81
87
|
clearDurableExecutionWrappers: () => clearDurableExecutionWrappers,
|
|
82
88
|
collectMetrics: () => collectMetrics,
|
|
89
|
+
concurrencyTag: () => concurrencyTag,
|
|
83
90
|
controlPlaneDescriptor: () => controlPlaneDescriptor,
|
|
91
|
+
countRuns: () => countRuns,
|
|
84
92
|
coveredTokens: () => coveredTokens,
|
|
85
93
|
createStepLogger: () => createStepLogger,
|
|
86
94
|
currentStep: () => currentStep,
|
|
@@ -102,6 +110,7 @@ __export(index_exports, {
|
|
|
102
110
|
isEngineMintedTag: () => isEngineMintedTag,
|
|
103
111
|
isEventToken: () => isEventToken,
|
|
104
112
|
isLegacyDescriptor: () => isLegacyDescriptor,
|
|
113
|
+
isNonEmptyRunScope: () => isNonEmptyRunScope,
|
|
105
114
|
isWorkflowControlFlowSignal: () => isWorkflowControlFlowSignal,
|
|
106
115
|
log: () => log,
|
|
107
116
|
matchesAttributes: () => matchesAttributes,
|
|
@@ -109,6 +118,7 @@ __export(index_exports, {
|
|
|
109
118
|
mergeRunValueFacetRows: () => mergeRunValueFacetRows,
|
|
110
119
|
missingCapabilities: () => missingCapabilities,
|
|
111
120
|
negotiate: () => negotiate,
|
|
121
|
+
nextCronFireMs: () => nextCronFireMs,
|
|
112
122
|
normalizeAttributeRows: () => normalizeAttributeRows,
|
|
113
123
|
normalizeDescriptor: () => normalizeDescriptor,
|
|
114
124
|
observedAnnouncements: () => observedAnnouncements,
|
|
@@ -118,6 +128,7 @@ __export(index_exports, {
|
|
|
118
128
|
prevCronFireMs: () => prevCronFireMs,
|
|
119
129
|
readSearchAttributes: () => readSearchAttributes,
|
|
120
130
|
requiredCapabilities: () => requiredCapabilities,
|
|
131
|
+
resolveConcurrency: () => resolveConcurrency,
|
|
121
132
|
resolveRouting: () => resolveRouting,
|
|
122
133
|
resolveRunWaiting: () => resolveRunWaiting,
|
|
123
134
|
runDurableExecution: () => runDurableExecution,
|
|
@@ -391,6 +402,23 @@ var FatalError = class extends Error {
|
|
|
391
402
|
this.code = code;
|
|
392
403
|
}
|
|
393
404
|
};
|
|
405
|
+
var ConcurrencyLimitError = class extends Error {
|
|
406
|
+
static {
|
|
407
|
+
__name(this, "ConcurrencyLimitError");
|
|
408
|
+
}
|
|
409
|
+
workflow;
|
|
410
|
+
key;
|
|
411
|
+
limit;
|
|
412
|
+
active;
|
|
413
|
+
constructor(workflow, key, limit, active) {
|
|
414
|
+
super(`concurrency limit reached for key "${key}" (${active}/${limit} in flight); ${workflow} was not started \u2014 retry later`);
|
|
415
|
+
this.name = "ConcurrencyLimitError";
|
|
416
|
+
this.workflow = workflow;
|
|
417
|
+
this.key = key;
|
|
418
|
+
this.limit = limit;
|
|
419
|
+
this.active = active;
|
|
420
|
+
}
|
|
421
|
+
};
|
|
394
422
|
var SingletonQueueFullError = class extends Error {
|
|
395
423
|
static {
|
|
396
424
|
__name(this, "SingletonQueueFullError");
|
|
@@ -904,6 +932,58 @@ function instantCheckpoint(p) {
|
|
|
904
932
|
}
|
|
905
933
|
__name(instantCheckpoint, "instantCheckpoint");
|
|
906
934
|
|
|
935
|
+
// src/concurrency.ts
|
|
936
|
+
var DEFAULT_CONCURRENCY_STATUSES = [
|
|
937
|
+
"pending",
|
|
938
|
+
"running",
|
|
939
|
+
"suspended",
|
|
940
|
+
"cancelling",
|
|
941
|
+
"blocked"
|
|
942
|
+
];
|
|
943
|
+
function concurrencyTag(key) {
|
|
944
|
+
return `concurrency:${key}`;
|
|
945
|
+
}
|
|
946
|
+
__name(concurrencyTag, "concurrencyTag");
|
|
947
|
+
async function resolveConcurrency(override, config, input) {
|
|
948
|
+
if (override) return override;
|
|
949
|
+
if (!config) return void 0;
|
|
950
|
+
const key = config.key(input);
|
|
951
|
+
if (key === void 0) return void 0;
|
|
952
|
+
const limit = typeof config.limit === "function" ? await config.limit(key) : config.limit;
|
|
953
|
+
return {
|
|
954
|
+
key,
|
|
955
|
+
limit,
|
|
956
|
+
countStatuses: config.countStatuses
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
__name(resolveConcurrency, "resolveConcurrency");
|
|
960
|
+
async function countRuns(store, query) {
|
|
961
|
+
if (store.countRuns) return store.countRuns(query);
|
|
962
|
+
if (store.runFacets && !query.status && !query.statuses && query.origin === void 0) {
|
|
963
|
+
const cells = await store.runFacets(query);
|
|
964
|
+
return cells.reduce((sum, c) => sum + c.count, 0);
|
|
965
|
+
}
|
|
966
|
+
if (store.runFacets && query.statuses && !query.status && query.origin === void 0) {
|
|
967
|
+
const { statuses, ...rest } = query;
|
|
968
|
+
const cells = await store.runFacets(rest);
|
|
969
|
+
return cells.filter((c) => statuses.includes(c.status)).reduce((sum, c) => sum + c.count, 0);
|
|
970
|
+
}
|
|
971
|
+
return (await store.listRuns(query)).length;
|
|
972
|
+
}
|
|
973
|
+
__name(countRuns, "countRuns");
|
|
974
|
+
async function assertConcurrency(store, workflow, quota) {
|
|
975
|
+
const active = quota.limit <= 0 ? 0 : await countRuns(store, {
|
|
976
|
+
tag: concurrencyTag(quota.key),
|
|
977
|
+
statuses: [
|
|
978
|
+
...quota.countStatuses ?? DEFAULT_CONCURRENCY_STATUSES
|
|
979
|
+
]
|
|
980
|
+
});
|
|
981
|
+
if (quota.limit <= 0 || active >= quota.limit) {
|
|
982
|
+
throw new ConcurrencyLimitError(workflow, quota.key, quota.limit, active);
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
__name(assertConcurrency, "assertConcurrency");
|
|
986
|
+
|
|
907
987
|
// src/entities.ts
|
|
908
988
|
var ENTITY_WORKFLOW = "__entity";
|
|
909
989
|
var entityRunId = /* @__PURE__ */ __name((name, key) => `entity:${name}:${key}`, "entityRunId");
|
|
@@ -1736,6 +1816,19 @@ function planDispatch(requires, liveDescriptors, cp, token) {
|
|
|
1736
1816
|
}
|
|
1737
1817
|
__name(planDispatch, "planDispatch");
|
|
1738
1818
|
|
|
1819
|
+
// src/interfaces.ts
|
|
1820
|
+
var TERMINAL_RUN_STATUSES = [
|
|
1821
|
+
"completed",
|
|
1822
|
+
"failed",
|
|
1823
|
+
"cancelled",
|
|
1824
|
+
"dead"
|
|
1825
|
+
];
|
|
1826
|
+
function isNonEmptyRunScope(scope) {
|
|
1827
|
+
if (!scope) return false;
|
|
1828
|
+
return scope.workflow !== void 0 || scope.workflows !== void 0 || scope.tag !== void 0 || scope.tags !== void 0 || scope.namespace !== void 0 || scope.namespaces !== void 0 || scope.attributes !== void 0 && scope.attributes.length > 0;
|
|
1829
|
+
}
|
|
1830
|
+
__name(isNonEmptyRunScope, "isNonEmptyRunScope");
|
|
1831
|
+
|
|
1739
1832
|
// src/tenant-group.ts
|
|
1740
1833
|
function tenantGroup(baseGroup, tenant) {
|
|
1741
1834
|
return tenant !== void 0 && tenant !== "" && tenant !== "default" ? `${baseGroup}@${tenant}` : baseGroup;
|
|
@@ -1826,6 +1919,486 @@ function indexWaitersByRun(waiters) {
|
|
|
1826
1919
|
}
|
|
1827
1920
|
__name(indexWaitersByRun, "indexWaitersByRun");
|
|
1828
1921
|
|
|
1922
|
+
// src/scheduler.ts
|
|
1923
|
+
var import_node_module = require("module");
|
|
1924
|
+
|
|
1925
|
+
// src/cron-compat.ts
|
|
1926
|
+
function isIndexable(value) {
|
|
1927
|
+
return typeof value === "object" && value !== null || typeof value === "function";
|
|
1928
|
+
}
|
|
1929
|
+
__name(isIndexable, "isIndexable");
|
|
1930
|
+
function unwrapDefault(module_) {
|
|
1931
|
+
if (!isIndexable(module_)) return module_;
|
|
1932
|
+
if (Reflect.get(module_, "parseExpression") !== void 0) return module_;
|
|
1933
|
+
if (Reflect.get(module_, "CronExpressionParser") !== void 0) return module_;
|
|
1934
|
+
const inner = Reflect.get(module_, "default");
|
|
1935
|
+
return inner === void 0 ? module_ : inner;
|
|
1936
|
+
}
|
|
1937
|
+
__name(unwrapDefault, "unwrapDefault");
|
|
1938
|
+
function v4Entry(module_) {
|
|
1939
|
+
if (!isIndexable(module_)) return void 0;
|
|
1940
|
+
const parse = Reflect.get(module_, "parseExpression");
|
|
1941
|
+
if (typeof parse !== "function") return void 0;
|
|
1942
|
+
return (expr, options) => parse.call(module_, expr, options);
|
|
1943
|
+
}
|
|
1944
|
+
__name(v4Entry, "v4Entry");
|
|
1945
|
+
function v5Entry(module_) {
|
|
1946
|
+
if (!isIndexable(module_)) return void 0;
|
|
1947
|
+
const parser = Reflect.get(module_, "CronExpressionParser");
|
|
1948
|
+
if (!isIndexable(parser)) return void 0;
|
|
1949
|
+
const parse = Reflect.get(parser, "parse");
|
|
1950
|
+
if (typeof parse !== "function") return void 0;
|
|
1951
|
+
return (expr, options) => parse.call(parser, expr, options);
|
|
1952
|
+
}
|
|
1953
|
+
__name(v5Entry, "v5Entry");
|
|
1954
|
+
function resolveCronParse(module_) {
|
|
1955
|
+
const resolved = unwrapDefault(module_);
|
|
1956
|
+
return v4Entry(resolved) ?? v5Entry(resolved);
|
|
1957
|
+
}
|
|
1958
|
+
__name(resolveCronParse, "resolveCronParse");
|
|
1959
|
+
|
|
1960
|
+
// src/scheduler.ts
|
|
1961
|
+
var nodeRequire = (0, import_node_module.createRequire)(__importMetaUrl);
|
|
1962
|
+
var defaultSleep = /* @__PURE__ */ __name((ms) => new Promise((r) => setTimeout(r, ms)), "defaultSleep");
|
|
1963
|
+
function scheduledRunId(key, everyMs, nowMs) {
|
|
1964
|
+
return `sched:${key}:${Math.floor(nowMs / everyMs)}`;
|
|
1965
|
+
}
|
|
1966
|
+
__name(scheduledRunId, "scheduledRunId");
|
|
1967
|
+
var cronParser;
|
|
1968
|
+
function loadCronParser() {
|
|
1969
|
+
if (cronParser !== void 0) return cronParser;
|
|
1970
|
+
try {
|
|
1971
|
+
cronParser = nodeRequire("cron-parser");
|
|
1972
|
+
} catch {
|
|
1973
|
+
throw new Error('cron schedules need the optional peer dependency "cron-parser" \u2014 install it (e.g. `npm i cron-parser`).');
|
|
1974
|
+
}
|
|
1975
|
+
return cronParser;
|
|
1976
|
+
}
|
|
1977
|
+
__name(loadCronParser, "loadCronParser");
|
|
1978
|
+
function prevCronFireMs(expr, nowMs, timezone = "UTC") {
|
|
1979
|
+
const parse = resolveCronParse(loadCronParser());
|
|
1980
|
+
if (!parse) {
|
|
1981
|
+
throw new Error(`the installed "cron-parser" exposes neither v4's parseExpression nor v5's CronExpressionParser.parse \u2014 the supported range is ^4.0.0 || ^5.0.0.`);
|
|
1982
|
+
}
|
|
1983
|
+
return parse(expr, {
|
|
1984
|
+
currentDate: new Date(nowMs + 1),
|
|
1985
|
+
tz: timezone
|
|
1986
|
+
}).prev().toDate().getTime();
|
|
1987
|
+
}
|
|
1988
|
+
__name(prevCronFireMs, "prevCronFireMs");
|
|
1989
|
+
function nextCronFireMs(expr, afterMs, timezone = "UTC") {
|
|
1990
|
+
const parse = resolveCronParse(loadCronParser());
|
|
1991
|
+
if (!parse) {
|
|
1992
|
+
throw new Error(`the installed "cron-parser" exposes neither v4's parseExpression nor v5's CronExpressionParser.parse \u2014 the supported range is ^4.0.0 || ^5.0.0.`);
|
|
1993
|
+
}
|
|
1994
|
+
return parse(expr, {
|
|
1995
|
+
currentDate: new Date(afterMs),
|
|
1996
|
+
tz: timezone
|
|
1997
|
+
}).next().toDate().getTime();
|
|
1998
|
+
}
|
|
1999
|
+
__name(nextCronFireMs, "nextCronFireMs");
|
|
2000
|
+
function scheduleRunIdAt(s, nowMs) {
|
|
2001
|
+
if (s.cron != null) return `sched:${s.key}:${prevCronFireMs(s.cron, nowMs, s.timezone)}`;
|
|
2002
|
+
if (s.everyMs != null) return scheduledRunId(s.key, s.everyMs, nowMs);
|
|
2003
|
+
throw new Error(`schedule "${s.key}" needs either "everyMs" or "cron"`);
|
|
2004
|
+
}
|
|
2005
|
+
__name(scheduleRunIdAt, "scheduleRunIdAt");
|
|
2006
|
+
function scheduleRunIdsAt(s, nowMs) {
|
|
2007
|
+
const current = scheduleRunIdAt(s, nowMs);
|
|
2008
|
+
if (!s.backfill || s.backfill.maxCatchup <= 0) return [
|
|
2009
|
+
current
|
|
2010
|
+
];
|
|
2011
|
+
const ids = [];
|
|
2012
|
+
if (s.everyMs != null) {
|
|
2013
|
+
const bucket = Math.floor(nowMs / s.everyMs);
|
|
2014
|
+
const oldest = Math.max(0, bucket - s.backfill.maxCatchup);
|
|
2015
|
+
for (let b = oldest; b < bucket; b += 1) ids.push(`sched:${s.key}:${b}`);
|
|
2016
|
+
} else if (s.cron != null) {
|
|
2017
|
+
const prior = [];
|
|
2018
|
+
let cursor = prevCronFireMs(s.cron, nowMs, s.timezone);
|
|
2019
|
+
for (let i = 0; i < s.backfill.maxCatchup; i += 1) {
|
|
2020
|
+
const before = prevCronFireMs(s.cron, cursor - 1, s.timezone);
|
|
2021
|
+
prior.push(`sched:${s.key}:${before}`);
|
|
2022
|
+
cursor = before;
|
|
2023
|
+
}
|
|
2024
|
+
ids.push(...prior.reverse());
|
|
2025
|
+
}
|
|
2026
|
+
ids.push(current);
|
|
2027
|
+
return ids;
|
|
2028
|
+
}
|
|
2029
|
+
__name(scheduleRunIdsAt, "scheduleRunIdsAt");
|
|
2030
|
+
async function runSchedules(engine, schedules, nowMs, opts) {
|
|
2031
|
+
const random = opts?.random ?? Math.random;
|
|
2032
|
+
const sleep = opts?.sleep ?? defaultSleep;
|
|
2033
|
+
const ids = [];
|
|
2034
|
+
for (const s of schedules) {
|
|
2035
|
+
if (s.paused) continue;
|
|
2036
|
+
if (s.overlap === "skip" && s.everyMs) {
|
|
2037
|
+
const prevBucket = Math.floor(nowMs / s.everyMs) - 1;
|
|
2038
|
+
if (prevBucket >= 0) {
|
|
2039
|
+
const prev = await engine.getRun(`sched:${s.key}:${prevBucket}`);
|
|
2040
|
+
if (prev && (prev.status === "running" || prev.status === "suspended")) continue;
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
if (s.jitter && s.jitter > 0) await sleep(Math.floor(random() * s.jitter));
|
|
2044
|
+
for (const runId of scheduleRunIdsAt(s, nowMs)) {
|
|
2045
|
+
await engine.start(s.workflow, s.input, runId);
|
|
2046
|
+
ids.push(runId);
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
return ids;
|
|
2050
|
+
}
|
|
2051
|
+
__name(runSchedules, "runSchedules");
|
|
2052
|
+
|
|
2053
|
+
// src/schedules.ts
|
|
2054
|
+
var ScheduleAlreadyExistsError = class extends Error {
|
|
2055
|
+
static {
|
|
2056
|
+
__name(this, "ScheduleAlreadyExistsError");
|
|
2057
|
+
}
|
|
2058
|
+
scheduleId;
|
|
2059
|
+
constructor(scheduleId) {
|
|
2060
|
+
super(`schedule "${scheduleId}" already exists`), this.scheduleId = scheduleId;
|
|
2061
|
+
this.name = "ScheduleAlreadyExistsError";
|
|
2062
|
+
}
|
|
2063
|
+
};
|
|
2064
|
+
var ScheduleNotFoundError = class extends Error {
|
|
2065
|
+
static {
|
|
2066
|
+
__name(this, "ScheduleNotFoundError");
|
|
2067
|
+
}
|
|
2068
|
+
scheduleId;
|
|
2069
|
+
constructor(scheduleId) {
|
|
2070
|
+
super(`schedule "${scheduleId}" not found`), this.scheduleId = scheduleId;
|
|
2071
|
+
this.name = "ScheduleNotFoundError";
|
|
2072
|
+
}
|
|
2073
|
+
};
|
|
2074
|
+
var DEFAULT_NAMESPACE = "default";
|
|
2075
|
+
var ON_TIME_GRACE_MS = 6e4;
|
|
2076
|
+
var TICK_BATCH = 100;
|
|
2077
|
+
var ScheduleClient = class {
|
|
2078
|
+
static {
|
|
2079
|
+
__name(this, "ScheduleClient");
|
|
2080
|
+
}
|
|
2081
|
+
store;
|
|
2082
|
+
host;
|
|
2083
|
+
options;
|
|
2084
|
+
constructor(store, host, options = {}) {
|
|
2085
|
+
this.store = store;
|
|
2086
|
+
this.host = host;
|
|
2087
|
+
this.options = options;
|
|
2088
|
+
}
|
|
2089
|
+
now() {
|
|
2090
|
+
return (this.options.clock ?? Date.now)();
|
|
2091
|
+
}
|
|
2092
|
+
/** Whether the store persists schedules (implements the schedule methods). */
|
|
2093
|
+
get supported() {
|
|
2094
|
+
const s = this.store;
|
|
2095
|
+
return !!(s.saveSchedule && s.getSchedule && s.updateSchedule && s.deleteSchedule && s.listSchedules);
|
|
2096
|
+
}
|
|
2097
|
+
requireStore() {
|
|
2098
|
+
if (!this.supported) {
|
|
2099
|
+
throw new Error("persisted schedules need a store that implements saveSchedule/getSchedule/updateSchedule/deleteSchedule/listSchedules \u2014 every bundled store adapter does; this one does not");
|
|
2100
|
+
}
|
|
2101
|
+
const s = this.store;
|
|
2102
|
+
return {
|
|
2103
|
+
saveSchedule: /* @__PURE__ */ __name((r) => s.saveSchedule.call(s, r), "saveSchedule"),
|
|
2104
|
+
getSchedule: /* @__PURE__ */ __name((id) => s.getSchedule.call(s, id), "getSchedule"),
|
|
2105
|
+
updateSchedule: /* @__PURE__ */ __name((id, patch, expected) => s.updateSchedule.call(s, id, patch, expected), "updateSchedule"),
|
|
2106
|
+
deleteSchedule: /* @__PURE__ */ __name((id) => s.deleteSchedule.call(s, id), "deleteSchedule"),
|
|
2107
|
+
listSchedules: /* @__PURE__ */ __name((q) => s.listSchedules.call(s, q), "listSchedules")
|
|
2108
|
+
};
|
|
2109
|
+
}
|
|
2110
|
+
/** Create a schedule. Throws {@link ScheduleAlreadyExistsError} if the id is taken. */
|
|
2111
|
+
async create(opts) {
|
|
2112
|
+
const store = this.requireStore();
|
|
2113
|
+
if (await store.getSchedule(opts.id)) throw new ScheduleAlreadyExistsError(opts.id);
|
|
2114
|
+
return this.write(opts, null);
|
|
2115
|
+
}
|
|
2116
|
+
/**
|
|
2117
|
+
* Create the schedule, or replace the definition of an existing one (its history — last fire, last
|
|
2118
|
+
* run, fire count — is kept). The next fire is recomputed from now under the new definition, so a
|
|
2119
|
+
* window that already fired is never fired again by an upsert.
|
|
2120
|
+
*/
|
|
2121
|
+
async upsert(opts) {
|
|
2122
|
+
const store = this.requireStore();
|
|
2123
|
+
return this.write(opts, await store.getSchedule(opts.id));
|
|
2124
|
+
}
|
|
2125
|
+
/** The schedule `id`, or null. */
|
|
2126
|
+
async get(id) {
|
|
2127
|
+
const record = await this.requireStore().getSchedule(id);
|
|
2128
|
+
return record ? describe(record) : null;
|
|
2129
|
+
}
|
|
2130
|
+
/** Schedules matching `query`, soonest-due first. */
|
|
2131
|
+
async list(query = {}) {
|
|
2132
|
+
return (await this.requireStore().listSchedules(query)).map(describe);
|
|
2133
|
+
}
|
|
2134
|
+
/** Delete the schedule. Runs it already started are left alone. Returns whether it existed. */
|
|
2135
|
+
delete(id) {
|
|
2136
|
+
return this.requireStore().deleteSchedule(id);
|
|
2137
|
+
}
|
|
2138
|
+
/** Stop firing until {@link resume}. `note` records why (shown by `get`/`list`). */
|
|
2139
|
+
async pause(id, note) {
|
|
2140
|
+
const store = this.requireStore();
|
|
2141
|
+
const record = await this.mustGet(id);
|
|
2142
|
+
const spec = {
|
|
2143
|
+
...record.spec
|
|
2144
|
+
};
|
|
2145
|
+
if (note !== void 0) spec.note = note;
|
|
2146
|
+
await store.updateSchedule(id, {
|
|
2147
|
+
paused: true,
|
|
2148
|
+
spec,
|
|
2149
|
+
updatedAt: new Date(this.now())
|
|
2150
|
+
});
|
|
2151
|
+
return describe(await this.mustGet(id));
|
|
2152
|
+
}
|
|
2153
|
+
/**
|
|
2154
|
+
* Fire again from the next window after now — the windows skipped while paused are NOT caught up.
|
|
2155
|
+
*/
|
|
2156
|
+
async resume(id) {
|
|
2157
|
+
const store = this.requireStore();
|
|
2158
|
+
const record = await this.mustGet(id);
|
|
2159
|
+
const spec = record.spec;
|
|
2160
|
+
const window = nextWindowAfter(spec, this.now());
|
|
2161
|
+
await store.updateSchedule(id, {
|
|
2162
|
+
paused: false,
|
|
2163
|
+
nextFireAt: window === null ? null : window + jitterFor(record.id, window, spec),
|
|
2164
|
+
state: {
|
|
2165
|
+
...record.state,
|
|
2166
|
+
window
|
|
2167
|
+
},
|
|
2168
|
+
updatedAt: new Date(this.now())
|
|
2169
|
+
});
|
|
2170
|
+
return describe(await this.mustGet(id));
|
|
2171
|
+
}
|
|
2172
|
+
/**
|
|
2173
|
+
* Start one run of the schedule right now, outside its cadence (paused or not). The run id is
|
|
2174
|
+
* `sched:<id>:manual:<nowMs>`; it doesn't move the schedule's next fire.
|
|
2175
|
+
*/
|
|
2176
|
+
async trigger(id) {
|
|
2177
|
+
const record = await this.mustGet(id);
|
|
2178
|
+
const runId = `sched:${id}:manual:${this.now()}`;
|
|
2179
|
+
return this.host.start(record.workflow, specOf(record).input, runId, startOptions(record));
|
|
2180
|
+
}
|
|
2181
|
+
/**
|
|
2182
|
+
* Fire every due schedule (in this engine's namespace; all namespaces for an operator engine):
|
|
2183
|
+
* start its window's run and advance it to the next window. Called by the NestJS timer poller on
|
|
2184
|
+
* each tick when `persistedSchedules` is on; call it yourself from any other driver. Returns the
|
|
2185
|
+
* run ids started (or re-found, for an idempotent re-fire).
|
|
2186
|
+
*/
|
|
2187
|
+
async tick(nowMs = this.now()) {
|
|
2188
|
+
const store = this.requireStore();
|
|
2189
|
+
const started = [];
|
|
2190
|
+
for (let round = 0; round < 50; round++) {
|
|
2191
|
+
const due = await store.listSchedules({
|
|
2192
|
+
dueBy: nowMs,
|
|
2193
|
+
namespace: this.options.namespace,
|
|
2194
|
+
limit: TICK_BATCH
|
|
2195
|
+
});
|
|
2196
|
+
for (const record of due) {
|
|
2197
|
+
const runId = await this.fire(record, nowMs);
|
|
2198
|
+
if (runId) started.push(runId);
|
|
2199
|
+
}
|
|
2200
|
+
if (due.length < TICK_BATCH) break;
|
|
2201
|
+
}
|
|
2202
|
+
return started;
|
|
2203
|
+
}
|
|
2204
|
+
/** Fire one due schedule and advance it. Returns the run id started, if any. */
|
|
2205
|
+
async fire(record, nowMs) {
|
|
2206
|
+
const store = this.requireStore();
|
|
2207
|
+
const spec = specOf(record);
|
|
2208
|
+
const state = record.state;
|
|
2209
|
+
let latest;
|
|
2210
|
+
let next;
|
|
2211
|
+
try {
|
|
2212
|
+
latest = latestWindowAtOrBefore(spec, nowMs);
|
|
2213
|
+
next = nextWindowAfter(spec, latest);
|
|
2214
|
+
} catch (error2) {
|
|
2215
|
+
await store.updateSchedule(record.id, {
|
|
2216
|
+
paused: true,
|
|
2217
|
+
nextFireAt: null,
|
|
2218
|
+
state: {
|
|
2219
|
+
...state,
|
|
2220
|
+
lastError: `invalid schedule: ${errorMessage(error2)}`
|
|
2221
|
+
},
|
|
2222
|
+
updatedAt: new Date(nowMs)
|
|
2223
|
+
}, record.nextFireAt);
|
|
2224
|
+
return void 0;
|
|
2225
|
+
}
|
|
2226
|
+
const window = state.window ?? latest;
|
|
2227
|
+
const jitter = spec.jitterMs ?? 0;
|
|
2228
|
+
const onTime = nowMs - latest <= ON_TIME_GRACE_MS + jitter;
|
|
2229
|
+
let runId;
|
|
2230
|
+
let lastError;
|
|
2231
|
+
let fired = false;
|
|
2232
|
+
const skipMissed = spec.catchup === "skip" && !onTime;
|
|
2233
|
+
const beforeWindow = latest < window;
|
|
2234
|
+
if (!skipMissed && !beforeWindow && !await this.overlapping(spec, state)) {
|
|
2235
|
+
runId = `sched:${record.id}:${latest}`;
|
|
2236
|
+
try {
|
|
2237
|
+
await this.host.start(record.workflow, spec.input, runId, startOptions(record));
|
|
2238
|
+
fired = true;
|
|
2239
|
+
} catch (error2) {
|
|
2240
|
+
if (await this.host.getRun(runId)) fired = true;
|
|
2241
|
+
else lastError = errorMessage(error2);
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
const nextState = {
|
|
2245
|
+
...state,
|
|
2246
|
+
window: next,
|
|
2247
|
+
...fired ? {
|
|
2248
|
+
lastFireAt: latest,
|
|
2249
|
+
lastRunId: runId,
|
|
2250
|
+
fires: (state.fires ?? 0) + 1
|
|
2251
|
+
} : {},
|
|
2252
|
+
lastError: fired ? void 0 : lastError ?? state.lastError
|
|
2253
|
+
};
|
|
2254
|
+
await store.updateSchedule(record.id, {
|
|
2255
|
+
nextFireAt: next === null ? null : next + jitterFor(record.id, next, spec),
|
|
2256
|
+
state: stripUndefined(nextState),
|
|
2257
|
+
updatedAt: new Date(nowMs)
|
|
2258
|
+
}, record.nextFireAt);
|
|
2259
|
+
return fired ? runId : void 0;
|
|
2260
|
+
}
|
|
2261
|
+
/** `overlap: 'skip'` and the previous run of this schedule is still in flight. */
|
|
2262
|
+
async overlapping(spec, state) {
|
|
2263
|
+
if (spec.overlap !== "skip" || !state.lastRunId) return false;
|
|
2264
|
+
const prev = await this.host.getRun(state.lastRunId);
|
|
2265
|
+
return !!prev && !TERMINAL_RUN_STATUSES.includes(prev.status);
|
|
2266
|
+
}
|
|
2267
|
+
async mustGet(id) {
|
|
2268
|
+
const record = await this.requireStore().getSchedule(id);
|
|
2269
|
+
if (!record) throw new ScheduleNotFoundError(id);
|
|
2270
|
+
return record;
|
|
2271
|
+
}
|
|
2272
|
+
async write(opts, existing) {
|
|
2273
|
+
const store = this.requireStore();
|
|
2274
|
+
const spec = toSpec(opts);
|
|
2275
|
+
const now = this.now();
|
|
2276
|
+
const paused = opts.paused ?? existing?.paused ?? false;
|
|
2277
|
+
const window = nextWindowAfter(spec, now);
|
|
2278
|
+
const record = {
|
|
2279
|
+
id: opts.id,
|
|
2280
|
+
namespace: opts.namespace ?? this.options.namespace ?? DEFAULT_NAMESPACE,
|
|
2281
|
+
workflow: workflowName(opts.workflow),
|
|
2282
|
+
paused,
|
|
2283
|
+
nextFireAt: window === null ? null : window + jitterFor(opts.id, window, spec),
|
|
2284
|
+
tags: opts.tags,
|
|
2285
|
+
spec,
|
|
2286
|
+
state: stripUndefined({
|
|
2287
|
+
...existing?.state ?? {},
|
|
2288
|
+
window
|
|
2289
|
+
}),
|
|
2290
|
+
createdAt: existing?.createdAt ?? new Date(now),
|
|
2291
|
+
updatedAt: new Date(now)
|
|
2292
|
+
};
|
|
2293
|
+
await store.saveSchedule(record);
|
|
2294
|
+
return describe(record);
|
|
2295
|
+
}
|
|
2296
|
+
};
|
|
2297
|
+
function toSpec(opts) {
|
|
2298
|
+
if (opts.cron == null === (opts.every == null)) {
|
|
2299
|
+
throw new Error(`schedule "${opts.id}" needs exactly one of "cron" or "every"`);
|
|
2300
|
+
}
|
|
2301
|
+
const everyMs = opts.every != null ? parseDuration(opts.every) : void 0;
|
|
2302
|
+
if (everyMs !== void 0 && !(everyMs > 0)) {
|
|
2303
|
+
throw new Error(`schedule "${opts.id}": "every" must be a positive duration`);
|
|
2304
|
+
}
|
|
2305
|
+
const jitterMs = opts.jitter != null ? parseDuration(opts.jitter) : void 0;
|
|
2306
|
+
return stripUndefined({
|
|
2307
|
+
v: 1,
|
|
2308
|
+
input: opts.input,
|
|
2309
|
+
cron: opts.cron,
|
|
2310
|
+
everyMs,
|
|
2311
|
+
timezone: opts.timezone,
|
|
2312
|
+
jitterMs: jitterMs && jitterMs > 0 ? jitterMs : void 0,
|
|
2313
|
+
overlap: opts.overlap ?? "allow",
|
|
2314
|
+
catchup: opts.catchup ?? "latest",
|
|
2315
|
+
note: opts.note,
|
|
2316
|
+
searchAttributes: opts.searchAttributes,
|
|
2317
|
+
priority: opts.priority,
|
|
2318
|
+
concurrency: opts.concurrency
|
|
2319
|
+
});
|
|
2320
|
+
}
|
|
2321
|
+
__name(toSpec, "toSpec");
|
|
2322
|
+
function specOf(record) {
|
|
2323
|
+
return record.spec;
|
|
2324
|
+
}
|
|
2325
|
+
__name(specOf, "specOf");
|
|
2326
|
+
function latestWindowAtOrBefore(spec, atMs) {
|
|
2327
|
+
if (spec.cron != null) return prevCronFireMs(spec.cron, atMs, spec.timezone);
|
|
2328
|
+
const every = spec.everyMs;
|
|
2329
|
+
return Math.floor(atMs / every) * every;
|
|
2330
|
+
}
|
|
2331
|
+
__name(latestWindowAtOrBefore, "latestWindowAtOrBefore");
|
|
2332
|
+
function nextWindowAfter(spec, afterMs) {
|
|
2333
|
+
if (spec.cron != null) return nextCronFireMs(spec.cron, afterMs, spec.timezone);
|
|
2334
|
+
const every = spec.everyMs;
|
|
2335
|
+
return (Math.floor(afterMs / every) + 1) * every;
|
|
2336
|
+
}
|
|
2337
|
+
__name(nextWindowAfter, "nextWindowAfter");
|
|
2338
|
+
function jitterFor(id, window, spec) {
|
|
2339
|
+
const max = spec.jitterMs ?? 0;
|
|
2340
|
+
if (max <= 0) return 0;
|
|
2341
|
+
let h = 2166136261;
|
|
2342
|
+
for (const ch of `${id}@${window}`) {
|
|
2343
|
+
h ^= ch.charCodeAt(0);
|
|
2344
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
2345
|
+
}
|
|
2346
|
+
return h % max;
|
|
2347
|
+
}
|
|
2348
|
+
__name(jitterFor, "jitterFor");
|
|
2349
|
+
function startOptions(record) {
|
|
2350
|
+
const spec = specOf(record);
|
|
2351
|
+
return stripUndefined({
|
|
2352
|
+
tags: [
|
|
2353
|
+
...record.tags ?? [],
|
|
2354
|
+
`schedule:${record.id}`
|
|
2355
|
+
],
|
|
2356
|
+
namespace: record.namespace,
|
|
2357
|
+
searchAttributes: spec.searchAttributes,
|
|
2358
|
+
priority: spec.priority,
|
|
2359
|
+
concurrency: spec.concurrency
|
|
2360
|
+
});
|
|
2361
|
+
}
|
|
2362
|
+
__name(startOptions, "startOptions");
|
|
2363
|
+
function describe(record) {
|
|
2364
|
+
const spec = specOf(record);
|
|
2365
|
+
const state = record.state;
|
|
2366
|
+
return stripUndefined({
|
|
2367
|
+
id: record.id,
|
|
2368
|
+
workflow: record.workflow,
|
|
2369
|
+
input: spec.input,
|
|
2370
|
+
namespace: record.namespace,
|
|
2371
|
+
cron: spec.cron,
|
|
2372
|
+
everyMs: spec.everyMs,
|
|
2373
|
+
timezone: spec.timezone,
|
|
2374
|
+
jitterMs: spec.jitterMs,
|
|
2375
|
+
overlap: spec.overlap ?? "allow",
|
|
2376
|
+
catchup: spec.catchup ?? "latest",
|
|
2377
|
+
paused: record.paused,
|
|
2378
|
+
note: spec.note,
|
|
2379
|
+
tags: record.tags,
|
|
2380
|
+
searchAttributes: spec.searchAttributes,
|
|
2381
|
+
priority: spec.priority,
|
|
2382
|
+
concurrency: spec.concurrency,
|
|
2383
|
+
nextFireAt: record.paused || record.nextFireAt == null ? null : new Date(record.nextFireAt),
|
|
2384
|
+
lastFireAt: state.lastFireAt != null ? new Date(state.lastFireAt) : void 0,
|
|
2385
|
+
lastRunId: state.lastRunId,
|
|
2386
|
+
fires: state.fires ?? 0,
|
|
2387
|
+
lastError: state.lastError,
|
|
2388
|
+
createdAt: record.createdAt,
|
|
2389
|
+
updatedAt: record.updatedAt
|
|
2390
|
+
});
|
|
2391
|
+
}
|
|
2392
|
+
__name(describe, "describe");
|
|
2393
|
+
function stripUndefined(o) {
|
|
2394
|
+
return Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0));
|
|
2395
|
+
}
|
|
2396
|
+
__name(stripUndefined, "stripUndefined");
|
|
2397
|
+
function errorMessage(error2) {
|
|
2398
|
+
return error2 instanceof Error ? error2.message : String(error2);
|
|
2399
|
+
}
|
|
2400
|
+
__name(errorMessage, "errorMessage");
|
|
2401
|
+
|
|
1829
2402
|
// src/singleton-gate.ts
|
|
1830
2403
|
var SINGLETON_RETRY_MS = 1e3;
|
|
1831
2404
|
var SINGLETON_RETRY_JITTER_MS = 250;
|
|
@@ -2894,6 +3467,12 @@ var WorkflowEngine = class {
|
|
|
2894
3467
|
runDispatcher;
|
|
2895
3468
|
/** Per-key serialization for singleton workflows (admission, back-pressure, notify-on-release). */
|
|
2896
3469
|
singletons;
|
|
3470
|
+
/**
|
|
3471
|
+
* Persisted, runtime-managed schedules: create/upsert/pause/resume/delete/list/trigger them, and
|
|
3472
|
+
* `tick()` fires the due ones (the NestJS timer poller does it when `persistedSchedules` is on).
|
|
3473
|
+
* Needs a store that persists schedules — every bundled adapter does. See {@link ScheduleClient}.
|
|
3474
|
+
*/
|
|
3475
|
+
schedules;
|
|
2897
3476
|
/** Every registered workflow, keyed by `name@version` — so old versions stay runnable. */
|
|
2898
3477
|
workflows = /* @__PURE__ */ new Map();
|
|
2899
3478
|
/** The newest registered version per workflow name — used to `start` new runs. */
|
|
@@ -2978,6 +3557,13 @@ var WorkflowEngine = class {
|
|
|
2978
3557
|
}, "dispatch"),
|
|
2979
3558
|
configFor: /* @__PURE__ */ __name((run) => (this.workflows.get(versionKey(run.workflow, run.workflowVersion)) ?? this.latest.get(run.workflow))?.singleton, "configFor")
|
|
2980
3559
|
});
|
|
3560
|
+
this.schedules = new ScheduleClient(this.store, {
|
|
3561
|
+
start: /* @__PURE__ */ __name((workflow, input, runId, opts) => this.start(workflow, input, runId, opts), "start"),
|
|
3562
|
+
getRun: /* @__PURE__ */ __name((runId) => this.store.getRun(runId), "getRun")
|
|
3563
|
+
}, {
|
|
3564
|
+
namespace: this.namespace,
|
|
3565
|
+
clock: this.clock
|
|
3566
|
+
});
|
|
2981
3567
|
this.pool.bind(
|
|
2982
3568
|
async (result) => {
|
|
2983
3569
|
const waiter = this.pending.get(result.stepId);
|
|
@@ -3113,6 +3699,7 @@ var WorkflowEngine = class {
|
|
|
3113
3699
|
fn,
|
|
3114
3700
|
tags: opts?.tags,
|
|
3115
3701
|
singleton: opts?.singleton,
|
|
3702
|
+
concurrency: opts?.concurrency,
|
|
3116
3703
|
executionTimeoutMs: opts?.executionTimeout != null ? parseDuration(opts.executionTimeout) : void 0,
|
|
3117
3704
|
validateInput: opts?.validateInput,
|
|
3118
3705
|
searchAttributesSchema: opts?.searchAttributesSchema,
|
|
@@ -3175,6 +3762,7 @@ var WorkflowEngine = class {
|
|
|
3175
3762
|
}, "fn"),
|
|
3176
3763
|
tags: opts.tags,
|
|
3177
3764
|
singleton: opts.singleton,
|
|
3765
|
+
concurrency: opts.concurrency,
|
|
3178
3766
|
executionTimeoutMs: opts.executionTimeout != null ? parseDuration(opts.executionTimeout) : void 0,
|
|
3179
3767
|
validateInput: opts.validateInput,
|
|
3180
3768
|
requires: opts.requires,
|
|
@@ -3346,12 +3934,17 @@ var WorkflowEngine = class {
|
|
|
3346
3934
|
error: prior.error
|
|
3347
3935
|
};
|
|
3348
3936
|
}
|
|
3937
|
+
const quota = await resolveConcurrency(opts?.concurrency, registered.concurrency, input);
|
|
3938
|
+
if (quota) await assertConcurrency(this.store, name, quota);
|
|
3349
3939
|
const now = /* @__PURE__ */ new Date();
|
|
3350
3940
|
const stampedTags = [
|
|
3351
3941
|
...opts?.tags ?? [],
|
|
3352
3942
|
...registered.singleton ? [
|
|
3353
3943
|
this.singletons.tag(registered.singleton, input)
|
|
3354
3944
|
] : [],
|
|
3945
|
+
...quota ? [
|
|
3946
|
+
concurrencyTag(quota.key)
|
|
3947
|
+
] : [],
|
|
3355
3948
|
...registered.versionDeclared === false ? [
|
|
3356
3949
|
VERSION_UNDECLARED_TAG
|
|
3357
3950
|
] : []
|
|
@@ -4389,6 +4982,123 @@ var WorkflowEngine = class {
|
|
|
4389
4982
|
return deleted + 1;
|
|
4390
4983
|
}
|
|
4391
4984
|
/**
|
|
4985
|
+
* Hard-delete EVERY run matching `scope` — plus, by default, each one's whole child subtree — in
|
|
4986
|
+
* bounded batches. The "forget this tenant" / "drop this feature's history" operation: where
|
|
4987
|
+
* {@link deleteRun} removes one tree you already know the id of, this drains everything a scope
|
|
4988
|
+
* selects (`{ namespace: 'acme' }`, `{ tag: 'tenant:acme' }`, `{ workflows: [...] }`, attribute
|
|
4989
|
+
* predicates …) through the store API, so no caller has to reach into the `durable_*` tables.
|
|
4990
|
+
*
|
|
4991
|
+
* - **Live runs** (not yet terminal) are {@link cancel cancelled} first (plain cancel, no saga
|
|
4992
|
+
* undo), which tells any worker holding one to stop, and then deleted with the rest. Pass
|
|
4993
|
+
* `cancelLive: false` to leave live runs (and live descendants of finished ones) alone and
|
|
4994
|
+
* delete terminal history only.
|
|
4995
|
+
* - **Children** are found through the same parent→child edges as {@link getRunChildren} and deleted
|
|
4996
|
+
* with their root even when they don't match `scope` themselves (children inherit `namespace`,
|
|
4997
|
+
* but not `tags`). `children: false` deletes exactly the matching runs.
|
|
4998
|
+
* - **Batched**: `batchSize` runs (default 500) per round, each round one bulk
|
|
4999
|
+
* {@link StateStore.deleteRuns} (falling back to per-run {@link StateStore.deleteRun}).
|
|
5000
|
+
*
|
|
5001
|
+
* An EMPTY scope is rejected — it would delete every run in the store. Returns how many runs were
|
|
5002
|
+
* deleted (children included).
|
|
5003
|
+
*/
|
|
5004
|
+
async purgeRuns(scope, opts) {
|
|
5005
|
+
if (!isNonEmptyRunScope(scope)) {
|
|
5006
|
+
throw new Error("purgeRuns: refusing an empty scope (it would delete every run) \u2014 pass at least one predicate, e.g. { namespace }");
|
|
5007
|
+
}
|
|
5008
|
+
const batchSize = Math.max(1, opts?.batchSize ?? 500);
|
|
5009
|
+
const cancelLive = opts?.cancelLive ?? true;
|
|
5010
|
+
const withChildren = opts?.children ?? true;
|
|
5011
|
+
const statuses = cancelLive ? void 0 : [
|
|
5012
|
+
...TERMINAL_RUN_STATUSES
|
|
5013
|
+
];
|
|
5014
|
+
let deleted = 0;
|
|
5015
|
+
let previous = "";
|
|
5016
|
+
for (; ; ) {
|
|
5017
|
+
const batch = await this.store.listRuns({
|
|
5018
|
+
...scope,
|
|
5019
|
+
statuses,
|
|
5020
|
+
limit: batchSize
|
|
5021
|
+
});
|
|
5022
|
+
if (batch.length === 0) break;
|
|
5023
|
+
const fingerprint = batch.map((r) => r.id).join("\0");
|
|
5024
|
+
if (fingerprint === previous) {
|
|
5025
|
+
throw new Error("purgeRuns: the store returned the same runs after deleting them");
|
|
5026
|
+
}
|
|
5027
|
+
previous = fingerprint;
|
|
5028
|
+
const known = new Map(batch.map((r) => [
|
|
5029
|
+
r.id,
|
|
5030
|
+
r.status
|
|
5031
|
+
]));
|
|
5032
|
+
const ids = withChildren ? await this.collectSubtrees(batch.map((r) => r.id)) : batch.map((r) => r.id);
|
|
5033
|
+
const doomed = [];
|
|
5034
|
+
for (const id of ids) {
|
|
5035
|
+
const status = known.has(id) ? known.get(id) : (await this.store.getRun(id))?.status;
|
|
5036
|
+
if (status === void 0) continue;
|
|
5037
|
+
if (!TERMINAL_RUN_STATUSES.includes(status)) {
|
|
5038
|
+
if (!cancelLive) continue;
|
|
5039
|
+
await this.cancel(id).catch(() => void 0);
|
|
5040
|
+
}
|
|
5041
|
+
doomed.push(id);
|
|
5042
|
+
}
|
|
5043
|
+
await this.deleteRunIds(doomed);
|
|
5044
|
+
deleted += doomed.length;
|
|
5045
|
+
if (batch.length < batchSize) break;
|
|
5046
|
+
}
|
|
5047
|
+
return deleted;
|
|
5048
|
+
}
|
|
5049
|
+
/**
|
|
5050
|
+
* {@link purgeRuns} for one namespace — every run (and child) a tenant/partition owns. Children
|
|
5051
|
+
* inherit their parent's namespace, so a namespace purge is complete by construction.
|
|
5052
|
+
*/
|
|
5053
|
+
purgeNamespace(namespace, opts) {
|
|
5054
|
+
return this.purgeRuns({
|
|
5055
|
+
namespace
|
|
5056
|
+
}, opts);
|
|
5057
|
+
}
|
|
5058
|
+
/** The given runs plus every descendant, children before parents. One waiter scan for the batch. */
|
|
5059
|
+
async collectSubtrees(rootIds) {
|
|
5060
|
+
const waitersByParent = /* @__PURE__ */ new Map();
|
|
5061
|
+
for (const w of await this.store.listSignalWaiters("child:")) {
|
|
5062
|
+
const list = waitersByParent.get(w.runId) ?? [];
|
|
5063
|
+
list.push(w.token.slice("child:".length));
|
|
5064
|
+
waitersByParent.set(w.runId, list);
|
|
5065
|
+
}
|
|
5066
|
+
const prefixes = [
|
|
5067
|
+
"signal:child:",
|
|
5068
|
+
"spawn:"
|
|
5069
|
+
];
|
|
5070
|
+
const childrenOf = /* @__PURE__ */ __name(async (parent) => {
|
|
5071
|
+
const ids = new Set(waitersByParent.get(parent) ?? []);
|
|
5072
|
+
const cps = this.store.listCheckpointsByNamePrefix ? await this.store.listCheckpointsByNamePrefix(parent, prefixes) : (await this.store.listCheckpoints(parent)).filter((cp) => prefixes.some((p) => cp.name.startsWith(p)));
|
|
5073
|
+
for (const cp of cps) {
|
|
5074
|
+
if (cp.name.startsWith("signal:child:")) ids.add(cp.name.slice("signal:child:".length));
|
|
5075
|
+
if (cp.name.startsWith("spawn:") && typeof cp.output === "string") ids.add(cp.output);
|
|
5076
|
+
}
|
|
5077
|
+
return [
|
|
5078
|
+
...ids
|
|
5079
|
+
];
|
|
5080
|
+
}, "childrenOf");
|
|
5081
|
+
const ordered = [];
|
|
5082
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5083
|
+
const visit = /* @__PURE__ */ __name(async (id) => {
|
|
5084
|
+
if (seen.has(id)) return;
|
|
5085
|
+
seen.add(id);
|
|
5086
|
+
for (const child of await childrenOf(id)) await visit(child);
|
|
5087
|
+
ordered.push(id);
|
|
5088
|
+
}, "visit");
|
|
5089
|
+
for (const id of rootIds) await visit(id);
|
|
5090
|
+
return ordered;
|
|
5091
|
+
}
|
|
5092
|
+
/** Bulk-delete through the store's `deleteRuns`, or one `deleteRun` per id when it has none. */
|
|
5093
|
+
async deleteRunIds(ids) {
|
|
5094
|
+
if (ids.length === 0) return;
|
|
5095
|
+
if (this.store.deleteRuns) {
|
|
5096
|
+
await this.store.deleteRuns(ids);
|
|
5097
|
+
return;
|
|
5098
|
+
}
|
|
5099
|
+
for (const id of ids) await this.store.deleteRun(id);
|
|
5100
|
+
}
|
|
5101
|
+
/**
|
|
4392
5102
|
* Cascade cancellation to a run's children — both awaited (`ctx.child`, found via its live
|
|
4393
5103
|
* `child:<id>` waiter) and fire-and-forget (`ctx.startChild`, found via its `spawn:<id>`
|
|
4394
5104
|
* checkpoint). Recursive, so a whole subtree is cancelled; the terminal guard in `cancel` stops it
|
|
@@ -6409,14 +7119,6 @@ var RemoteStepError = class extends Error {
|
|
|
6409
7119
|
}
|
|
6410
7120
|
};
|
|
6411
7121
|
|
|
6412
|
-
// src/interfaces.ts
|
|
6413
|
-
var TERMINAL_RUN_STATUSES = [
|
|
6414
|
-
"completed",
|
|
6415
|
-
"failed",
|
|
6416
|
-
"cancelled",
|
|
6417
|
-
"dead"
|
|
6418
|
-
];
|
|
6419
|
-
|
|
6420
7122
|
// src/run-facets.ts
|
|
6421
7123
|
function facetOrigin(origin) {
|
|
6422
7124
|
return origin?.trim() ? origin : null;
|
|
@@ -6448,7 +7150,8 @@ __name(mergeRunFacetRows, "mergeRunFacetRows");
|
|
|
6448
7150
|
var RUN_VALUE_FACET_LIMIT = 100;
|
|
6449
7151
|
var RUN_VALUE_FACET_SCAN = 5e3;
|
|
6450
7152
|
var ENGINE_MINTED_TAG_PREFIXES = [
|
|
6451
|
-
"singleton:"
|
|
7153
|
+
"singleton:",
|
|
7154
|
+
"concurrency:"
|
|
6452
7155
|
];
|
|
6453
7156
|
function isEngineMintedTag(value) {
|
|
6454
7157
|
return value !== null && ENGINE_MINTED_TAG_PREFIXES.some((prefix) => value.startsWith(prefix));
|
|
@@ -6553,9 +7256,42 @@ var CodecStateStore = class {
|
|
|
6553
7256
|
* Bound the same conditional way as {@link runFacets} so absence is reported, not faked.
|
|
6554
7257
|
*/
|
|
6555
7258
|
runValueFacets;
|
|
7259
|
+
/** Forwarded verbatim (deletes touch no payload). Bound only when the inner store has it, so the
|
|
7260
|
+
* engine's per-run fallback still kicks in through the wrapper. */
|
|
7261
|
+
deleteRuns;
|
|
7262
|
+
/** Forwarded verbatim — retention selects by status/updatedAt/scope, never by payload. Bound only
|
|
7263
|
+
* when the inner store prunes, so the retention poller still sees (and warns about) its absence. */
|
|
7264
|
+
pruneTerminalRuns;
|
|
7265
|
+
/** Forwarded verbatim — a count reads no payload. Bound only when the inner store counts, so the
|
|
7266
|
+
* engine's facet/listing fallback still applies through the wrapper. */
|
|
7267
|
+
countRuns;
|
|
7268
|
+
/** Schedules persist their run `input` inside `spec` — encoded like any other payload. Bound only
|
|
7269
|
+
* when the inner store persists schedules, so `engine.schedules` reports absence faithfully. */
|
|
7270
|
+
saveSchedule;
|
|
7271
|
+
getSchedule;
|
|
7272
|
+
updateSchedule;
|
|
7273
|
+
deleteSchedule;
|
|
7274
|
+
listSchedules;
|
|
6556
7275
|
constructor(inner, codec) {
|
|
6557
7276
|
this.inner = inner;
|
|
6558
7277
|
this.codec = codec;
|
|
7278
|
+
const deleteRuns = inner.deleteRuns;
|
|
7279
|
+
if (deleteRuns) this.deleteRuns = (ids) => deleteRuns.call(inner, ids);
|
|
7280
|
+
const prune = inner.pruneTerminalRuns;
|
|
7281
|
+
if (prune) this.pruneTerminalRuns = (policy, now, limit) => prune.call(inner, policy, now, limit);
|
|
7282
|
+
const count = inner.countRuns;
|
|
7283
|
+
if (count) this.countRuns = (query) => count.call(inner, query);
|
|
7284
|
+
const { saveSchedule, getSchedule, updateSchedule, deleteSchedule, listSchedules } = inner;
|
|
7285
|
+
if (saveSchedule && getSchedule && updateSchedule && deleteSchedule && listSchedules) {
|
|
7286
|
+
this.saveSchedule = (r) => saveSchedule.call(inner, this.encSchedule(r));
|
|
7287
|
+
this.getSchedule = async (id) => this.decSchedule(await getSchedule.call(inner, id));
|
|
7288
|
+
this.updateSchedule = (id, patch, expected) => updateSchedule.call(inner, id, patch.spec ? {
|
|
7289
|
+
...patch,
|
|
7290
|
+
spec: this.encSpec(patch.spec)
|
|
7291
|
+
} : patch, expected);
|
|
7292
|
+
this.deleteSchedule = (id) => deleteSchedule.call(inner, id);
|
|
7293
|
+
this.listSchedules = async (q) => (await listSchedules.call(inner, q)).map((r) => this.decSchedule(r));
|
|
7294
|
+
}
|
|
6559
7295
|
const facets = inner.runFacets;
|
|
6560
7296
|
if (facets) this.runFacets = (query) => facets.call(inner, query);
|
|
6561
7297
|
const valueFacets = inner.runValueFacets;
|
|
@@ -6597,6 +7333,28 @@ var CodecStateStore = class {
|
|
|
6597
7333
|
output: this.dec(cp.output)
|
|
6598
7334
|
};
|
|
6599
7335
|
}
|
|
7336
|
+
encSpec(spec) {
|
|
7337
|
+
return "input" in spec ? {
|
|
7338
|
+
...spec,
|
|
7339
|
+
input: this.enc(spec.input)
|
|
7340
|
+
} : spec;
|
|
7341
|
+
}
|
|
7342
|
+
encSchedule(r) {
|
|
7343
|
+
return {
|
|
7344
|
+
...r,
|
|
7345
|
+
spec: this.encSpec(r.spec)
|
|
7346
|
+
};
|
|
7347
|
+
}
|
|
7348
|
+
decSchedule(r) {
|
|
7349
|
+
if (!r || !("input" in r.spec)) return r;
|
|
7350
|
+
return {
|
|
7351
|
+
...r,
|
|
7352
|
+
spec: {
|
|
7353
|
+
...r.spec,
|
|
7354
|
+
input: this.dec(r.spec.input)
|
|
7355
|
+
}
|
|
7356
|
+
};
|
|
7357
|
+
}
|
|
6600
7358
|
ensureSchema() {
|
|
6601
7359
|
return this.inner.ensureSchema?.() ?? Promise.resolve();
|
|
6602
7360
|
}
|
|
@@ -6795,126 +7553,6 @@ function collectMetrics(engine) {
|
|
|
6795
7553
|
}
|
|
6796
7554
|
__name(collectMetrics, "collectMetrics");
|
|
6797
7555
|
|
|
6798
|
-
// src/scheduler.ts
|
|
6799
|
-
var import_node_module = require("module");
|
|
6800
|
-
|
|
6801
|
-
// src/cron-compat.ts
|
|
6802
|
-
function isIndexable(value) {
|
|
6803
|
-
return typeof value === "object" && value !== null || typeof value === "function";
|
|
6804
|
-
}
|
|
6805
|
-
__name(isIndexable, "isIndexable");
|
|
6806
|
-
function unwrapDefault(module_) {
|
|
6807
|
-
if (!isIndexable(module_)) return module_;
|
|
6808
|
-
if (Reflect.get(module_, "parseExpression") !== void 0) return module_;
|
|
6809
|
-
if (Reflect.get(module_, "CronExpressionParser") !== void 0) return module_;
|
|
6810
|
-
const inner = Reflect.get(module_, "default");
|
|
6811
|
-
return inner === void 0 ? module_ : inner;
|
|
6812
|
-
}
|
|
6813
|
-
__name(unwrapDefault, "unwrapDefault");
|
|
6814
|
-
function v4Entry(module_) {
|
|
6815
|
-
if (!isIndexable(module_)) return void 0;
|
|
6816
|
-
const parse = Reflect.get(module_, "parseExpression");
|
|
6817
|
-
if (typeof parse !== "function") return void 0;
|
|
6818
|
-
return (expr, options) => parse.call(module_, expr, options);
|
|
6819
|
-
}
|
|
6820
|
-
__name(v4Entry, "v4Entry");
|
|
6821
|
-
function v5Entry(module_) {
|
|
6822
|
-
if (!isIndexable(module_)) return void 0;
|
|
6823
|
-
const parser = Reflect.get(module_, "CronExpressionParser");
|
|
6824
|
-
if (!isIndexable(parser)) return void 0;
|
|
6825
|
-
const parse = Reflect.get(parser, "parse");
|
|
6826
|
-
if (typeof parse !== "function") return void 0;
|
|
6827
|
-
return (expr, options) => parse.call(parser, expr, options);
|
|
6828
|
-
}
|
|
6829
|
-
__name(v5Entry, "v5Entry");
|
|
6830
|
-
function resolveCronParse(module_) {
|
|
6831
|
-
const resolved = unwrapDefault(module_);
|
|
6832
|
-
return v4Entry(resolved) ?? v5Entry(resolved);
|
|
6833
|
-
}
|
|
6834
|
-
__name(resolveCronParse, "resolveCronParse");
|
|
6835
|
-
|
|
6836
|
-
// src/scheduler.ts
|
|
6837
|
-
var nodeRequire = (0, import_node_module.createRequire)(__importMetaUrl);
|
|
6838
|
-
var defaultSleep = /* @__PURE__ */ __name((ms) => new Promise((r) => setTimeout(r, ms)), "defaultSleep");
|
|
6839
|
-
function scheduledRunId(key, everyMs, nowMs) {
|
|
6840
|
-
return `sched:${key}:${Math.floor(nowMs / everyMs)}`;
|
|
6841
|
-
}
|
|
6842
|
-
__name(scheduledRunId, "scheduledRunId");
|
|
6843
|
-
var cronParser;
|
|
6844
|
-
function loadCronParser() {
|
|
6845
|
-
if (cronParser !== void 0) return cronParser;
|
|
6846
|
-
try {
|
|
6847
|
-
cronParser = nodeRequire("cron-parser");
|
|
6848
|
-
} catch {
|
|
6849
|
-
throw new Error('cron schedules need the optional peer dependency "cron-parser" \u2014 install it (e.g. `npm i cron-parser`).');
|
|
6850
|
-
}
|
|
6851
|
-
return cronParser;
|
|
6852
|
-
}
|
|
6853
|
-
__name(loadCronParser, "loadCronParser");
|
|
6854
|
-
function prevCronFireMs(expr, nowMs, timezone = "UTC") {
|
|
6855
|
-
const parse = resolveCronParse(loadCronParser());
|
|
6856
|
-
if (!parse) {
|
|
6857
|
-
throw new Error(`the installed "cron-parser" exposes neither v4's parseExpression nor v5's CronExpressionParser.parse \u2014 the supported range is ^4.0.0 || ^5.0.0.`);
|
|
6858
|
-
}
|
|
6859
|
-
return parse(expr, {
|
|
6860
|
-
currentDate: new Date(nowMs + 1),
|
|
6861
|
-
tz: timezone
|
|
6862
|
-
}).prev().toDate().getTime();
|
|
6863
|
-
}
|
|
6864
|
-
__name(prevCronFireMs, "prevCronFireMs");
|
|
6865
|
-
function scheduleRunIdAt(s, nowMs) {
|
|
6866
|
-
if (s.cron != null) return `sched:${s.key}:${prevCronFireMs(s.cron, nowMs, s.timezone)}`;
|
|
6867
|
-
if (s.everyMs != null) return scheduledRunId(s.key, s.everyMs, nowMs);
|
|
6868
|
-
throw new Error(`schedule "${s.key}" needs either "everyMs" or "cron"`);
|
|
6869
|
-
}
|
|
6870
|
-
__name(scheduleRunIdAt, "scheduleRunIdAt");
|
|
6871
|
-
function scheduleRunIdsAt(s, nowMs) {
|
|
6872
|
-
const current = scheduleRunIdAt(s, nowMs);
|
|
6873
|
-
if (!s.backfill || s.backfill.maxCatchup <= 0) return [
|
|
6874
|
-
current
|
|
6875
|
-
];
|
|
6876
|
-
const ids = [];
|
|
6877
|
-
if (s.everyMs != null) {
|
|
6878
|
-
const bucket = Math.floor(nowMs / s.everyMs);
|
|
6879
|
-
const oldest = Math.max(0, bucket - s.backfill.maxCatchup);
|
|
6880
|
-
for (let b = oldest; b < bucket; b += 1) ids.push(`sched:${s.key}:${b}`);
|
|
6881
|
-
} else if (s.cron != null) {
|
|
6882
|
-
const prior = [];
|
|
6883
|
-
let cursor = prevCronFireMs(s.cron, nowMs, s.timezone);
|
|
6884
|
-
for (let i = 0; i < s.backfill.maxCatchup; i += 1) {
|
|
6885
|
-
const before = prevCronFireMs(s.cron, cursor - 1, s.timezone);
|
|
6886
|
-
prior.push(`sched:${s.key}:${before}`);
|
|
6887
|
-
cursor = before;
|
|
6888
|
-
}
|
|
6889
|
-
ids.push(...prior.reverse());
|
|
6890
|
-
}
|
|
6891
|
-
ids.push(current);
|
|
6892
|
-
return ids;
|
|
6893
|
-
}
|
|
6894
|
-
__name(scheduleRunIdsAt, "scheduleRunIdsAt");
|
|
6895
|
-
async function runSchedules(engine, schedules, nowMs, opts) {
|
|
6896
|
-
const random = opts?.random ?? Math.random;
|
|
6897
|
-
const sleep = opts?.sleep ?? defaultSleep;
|
|
6898
|
-
const ids = [];
|
|
6899
|
-
for (const s of schedules) {
|
|
6900
|
-
if (s.paused) continue;
|
|
6901
|
-
if (s.overlap === "skip" && s.everyMs) {
|
|
6902
|
-
const prevBucket = Math.floor(nowMs / s.everyMs) - 1;
|
|
6903
|
-
if (prevBucket >= 0) {
|
|
6904
|
-
const prev = await engine.getRun(`sched:${s.key}:${prevBucket}`);
|
|
6905
|
-
if (prev && (prev.status === "running" || prev.status === "suspended")) continue;
|
|
6906
|
-
}
|
|
6907
|
-
}
|
|
6908
|
-
if (s.jitter && s.jitter > 0) await sleep(Math.floor(random() * s.jitter));
|
|
6909
|
-
for (const runId of scheduleRunIdsAt(s, nowMs)) {
|
|
6910
|
-
await engine.start(s.workflow, s.input, runId);
|
|
6911
|
-
ids.push(runId);
|
|
6912
|
-
}
|
|
6913
|
-
}
|
|
6914
|
-
return ids;
|
|
6915
|
-
}
|
|
6916
|
-
__name(runSchedules, "runSchedules");
|
|
6917
|
-
|
|
6918
7556
|
// src/search-attributes.ts
|
|
6919
7557
|
function compare(actual, filter) {
|
|
6920
7558
|
if (filter.op === "in") return filter.values.some((v) => actual === v);
|
|
@@ -7122,6 +7760,38 @@ var InMemoryStateStore = class {
|
|
|
7122
7760
|
}
|
|
7123
7761
|
this.reindexAttributes(runId, void 0);
|
|
7124
7762
|
}
|
|
7763
|
+
async deleteRuns(runIds) {
|
|
7764
|
+
for (const runId of runIds) await this.deleteRun(runId);
|
|
7765
|
+
}
|
|
7766
|
+
async pruneTerminalRuns(policy, nowMs, limit) {
|
|
7767
|
+
const statuses = policy.statuses.filter((s) => TERMINAL_RUN_STATUSES.includes(s));
|
|
7768
|
+
if (statuses.length === 0 || limit <= 0) return 0;
|
|
7769
|
+
if (policy.maxAge == null && policy.maxCount == null) return 0;
|
|
7770
|
+
const inScope = this.matching({
|
|
7771
|
+
...policy.scope ?? {},
|
|
7772
|
+
statuses
|
|
7773
|
+
});
|
|
7774
|
+
const ids = /* @__PURE__ */ new Set();
|
|
7775
|
+
if (policy.maxAge != null) {
|
|
7776
|
+
const cutoff = nowMs - parseDuration(policy.maxAge);
|
|
7777
|
+
const expired = inScope.filter((r) => r.updatedAt.getTime() < cutoff).sort((a, b) => a.updatedAt.getTime() - b.updatedAt.getTime()).slice(0, limit);
|
|
7778
|
+
for (const r of expired) ids.add(r.id);
|
|
7779
|
+
}
|
|
7780
|
+
if (policy.maxCount != null && ids.size < limit) {
|
|
7781
|
+
const byRecency = [
|
|
7782
|
+
...inScope
|
|
7783
|
+
].sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime() || b.id.localeCompare(a.id));
|
|
7784
|
+
for (const r of byRecency.slice(policy.maxCount)) {
|
|
7785
|
+
if (ids.size >= limit) break;
|
|
7786
|
+
ids.add(r.id);
|
|
7787
|
+
}
|
|
7788
|
+
}
|
|
7789
|
+
const doomed = [
|
|
7790
|
+
...ids
|
|
7791
|
+
].slice(0, limit);
|
|
7792
|
+
await this.deleteRuns(doomed);
|
|
7793
|
+
return doomed.length;
|
|
7794
|
+
}
|
|
7125
7795
|
async getCheckpoint(runId, seq) {
|
|
7126
7796
|
const cp = this.checkpoints.get(this.key(runId, seq));
|
|
7127
7797
|
return cp ? {
|
|
@@ -7329,6 +7999,9 @@ var InMemoryStateStore = class {
|
|
|
7329
7999
|
...r
|
|
7330
8000
|
}));
|
|
7331
8001
|
}
|
|
8002
|
+
async countRuns(query) {
|
|
8003
|
+
return this.matching(query).length;
|
|
8004
|
+
}
|
|
7332
8005
|
async runFacets(query) {
|
|
7333
8006
|
return mergeRunFacetRows(this.matching(query).map((run) => ({
|
|
7334
8007
|
status: run.status,
|
|
@@ -7346,6 +8019,34 @@ var InMemoryStateStore = class {
|
|
|
7346
8019
|
});
|
|
7347
8020
|
return runValueFacetsFromRuns(runs, axis, opts);
|
|
7348
8021
|
}
|
|
8022
|
+
schedules = /* @__PURE__ */ new Map();
|
|
8023
|
+
async saveSchedule(record) {
|
|
8024
|
+
this.schedules.set(record.id, structuredClone(record));
|
|
8025
|
+
}
|
|
8026
|
+
async getSchedule(id) {
|
|
8027
|
+
const record = this.schedules.get(id);
|
|
8028
|
+
return record ? structuredClone(record) : null;
|
|
8029
|
+
}
|
|
8030
|
+
async updateSchedule(id, patch, expectedNextFireAt) {
|
|
8031
|
+
const record = this.schedules.get(id);
|
|
8032
|
+
if (!record) return false;
|
|
8033
|
+
if (expectedNextFireAt !== void 0 && record.nextFireAt !== expectedNextFireAt) return false;
|
|
8034
|
+
this.schedules.set(id, {
|
|
8035
|
+
...record,
|
|
8036
|
+
...structuredClone(patch)
|
|
8037
|
+
});
|
|
8038
|
+
return true;
|
|
8039
|
+
}
|
|
8040
|
+
async deleteSchedule(id) {
|
|
8041
|
+
return this.schedules.delete(id);
|
|
8042
|
+
}
|
|
8043
|
+
async listSchedules(query) {
|
|
8044
|
+
const rows = [
|
|
8045
|
+
...this.schedules.values()
|
|
8046
|
+
].filter((s) => query.namespace === void 0 || s.namespace === query.namespace).filter((s) => query.workflow === void 0 || s.workflow === query.workflow).filter((s) => query.tag === void 0 || (s.tags ?? []).includes(query.tag)).filter((s) => query.paused === void 0 || s.paused === query.paused).filter((s) => query.dueBy === void 0 || !s.paused && s.nextFireAt !== null && s.nextFireAt <= query.dueBy).sort((a, b) => (a.nextFireAt ?? Number.POSITIVE_INFINITY) - (b.nextFireAt ?? Number.POSITIVE_INFINITY) || a.id.localeCompare(b.id));
|
|
8047
|
+
const offset = query.offset ?? 0;
|
|
8048
|
+
return rows.slice(offset, query.limit != null ? offset + query.limit : void 0).map((s) => structuredClone(s));
|
|
8049
|
+
}
|
|
7349
8050
|
async listCheckpoints(runId) {
|
|
7350
8051
|
const rows = [
|
|
7351
8052
|
...this.checkpoints.values()
|
|
@@ -7427,7 +8128,9 @@ var InMemoryTransport = class {
|
|
|
7427
8128
|
CONTROL_FLOW_SIGNAL,
|
|
7428
8129
|
CURRENT_PROTOCOL_VERSION,
|
|
7429
8130
|
CodecStateStore,
|
|
8131
|
+
ConcurrencyLimitError,
|
|
7430
8132
|
ContinueAsNew,
|
|
8133
|
+
DEFAULT_CONCURRENCY_STATUSES,
|
|
7431
8134
|
DEFAULT_PARTITION,
|
|
7432
8135
|
DURABLE_OPTIONS,
|
|
7433
8136
|
DURABLE_OPTIONS_CANONICAL,
|
|
@@ -7457,6 +8160,9 @@ var InMemoryTransport = class {
|
|
|
7457
8160
|
RunGateway,
|
|
7458
8161
|
STATE_STORE,
|
|
7459
8162
|
STATE_STORE_CANONICAL,
|
|
8163
|
+
ScheduleAlreadyExistsError,
|
|
8164
|
+
ScheduleClient,
|
|
8165
|
+
ScheduleNotFoundError,
|
|
7460
8166
|
SignalTimeoutError,
|
|
7461
8167
|
SingletonQueueFullError,
|
|
7462
8168
|
TERMINAL_RUN_STATUSES,
|
|
@@ -7470,6 +8176,7 @@ var InMemoryTransport = class {
|
|
|
7470
8176
|
aggregateAnnouncements,
|
|
7471
8177
|
announcementsOf,
|
|
7472
8178
|
applyAttributeQuery,
|
|
8179
|
+
assertConcurrency,
|
|
7473
8180
|
attributeColumnFor,
|
|
7474
8181
|
attributeOperand,
|
|
7475
8182
|
attributePredicateOperands,
|
|
@@ -7482,7 +8189,9 @@ var InMemoryTransport = class {
|
|
|
7482
8189
|
classifyWaiterToken,
|
|
7483
8190
|
clearDurableExecutionWrappers,
|
|
7484
8191
|
collectMetrics,
|
|
8192
|
+
concurrencyTag,
|
|
7485
8193
|
controlPlaneDescriptor,
|
|
8194
|
+
countRuns,
|
|
7486
8195
|
coveredTokens,
|
|
7487
8196
|
createStepLogger,
|
|
7488
8197
|
currentStep,
|
|
@@ -7504,6 +8213,7 @@ var InMemoryTransport = class {
|
|
|
7504
8213
|
isEngineMintedTag,
|
|
7505
8214
|
isEventToken,
|
|
7506
8215
|
isLegacyDescriptor,
|
|
8216
|
+
isNonEmptyRunScope,
|
|
7507
8217
|
isWorkflowControlFlowSignal,
|
|
7508
8218
|
log,
|
|
7509
8219
|
matchesAttributes,
|
|
@@ -7511,6 +8221,7 @@ var InMemoryTransport = class {
|
|
|
7511
8221
|
mergeRunValueFacetRows,
|
|
7512
8222
|
missingCapabilities,
|
|
7513
8223
|
negotiate,
|
|
8224
|
+
nextCronFireMs,
|
|
7514
8225
|
normalizeAttributeRows,
|
|
7515
8226
|
normalizeDescriptor,
|
|
7516
8227
|
observedAnnouncements,
|
|
@@ -7520,6 +8231,7 @@ var InMemoryTransport = class {
|
|
|
7520
8231
|
prevCronFireMs,
|
|
7521
8232
|
readSearchAttributes,
|
|
7522
8233
|
requiredCapabilities,
|
|
8234
|
+
resolveConcurrency,
|
|
7523
8235
|
resolveRouting,
|
|
7524
8236
|
resolveRunWaiting,
|
|
7525
8237
|
runDurableExecution,
|