@dudousxd/nestjs-durable-core 0.71.0 → 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 +862 -142
- 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 +851 -142
- 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
|
] : []
|
|
@@ -3657,9 +4250,14 @@ var WorkflowEngine = class {
|
|
|
3657
4250
|
async recoverIncomplete(nowMs = this.clock()) {
|
|
3658
4251
|
if (this.draining) return [];
|
|
3659
4252
|
const results = [];
|
|
3660
|
-
for (const
|
|
3661
|
-
const acquired = await this.store.tryLockRun(
|
|
4253
|
+
for (const listed of await this.store.listIncompleteRuns(this.namespace)) {
|
|
4254
|
+
const acquired = await this.store.tryLockRun(listed.id, this.instanceId, nowMs + this.leaseMs, nowMs);
|
|
3662
4255
|
if (!acquired) continue;
|
|
4256
|
+
const run = await this.store.getRun(listed.id);
|
|
4257
|
+
if (!run || run.status !== "running" && run.status !== "cancelling") {
|
|
4258
|
+
await this.store.releaseRunLock(listed.id);
|
|
4259
|
+
continue;
|
|
4260
|
+
}
|
|
3663
4261
|
if (run.status === "running") {
|
|
3664
4262
|
const dispatched = await this.inFlightRemoteStep(run.id);
|
|
3665
4263
|
if (dispatched) {
|
|
@@ -4384,6 +4982,123 @@ var WorkflowEngine = class {
|
|
|
4384
4982
|
return deleted + 1;
|
|
4385
4983
|
}
|
|
4386
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
|
+
/**
|
|
4387
5102
|
* Cascade cancellation to a run's children — both awaited (`ctx.child`, found via its live
|
|
4388
5103
|
* `child:<id>` waiter) and fire-and-forget (`ctx.startChild`, found via its `spawn:<id>`
|
|
4389
5104
|
* checkpoint). Recursive, so a whole subtree is cancelled; the terminal guard in `cancel` stops it
|
|
@@ -5519,16 +6234,16 @@ var WorkflowEngine = class {
|
|
|
5519
6234
|
try {
|
|
5520
6235
|
const output = await runInWorkflowCtx(ctx, () => fn(ctx, run.input));
|
|
5521
6236
|
if (this.cancelRequested.has(run.id)) {
|
|
5522
|
-
return this.compensateAndCancel(run, compensations, replay);
|
|
6237
|
+
return await this.compensateAndCancel(run, compensations, replay);
|
|
5523
6238
|
}
|
|
5524
|
-
return this.settleRun(run, {
|
|
6239
|
+
return await this.settleRun(run, {
|
|
5525
6240
|
kind: "completed",
|
|
5526
6241
|
output
|
|
5527
6242
|
});
|
|
5528
6243
|
} catch (err) {
|
|
5529
6244
|
if (err instanceof ContinueAsNew) {
|
|
5530
6245
|
if (this.cancelRequested.has(run.id)) {
|
|
5531
|
-
return this.compensateAndCancel(run, compensations, replay);
|
|
6246
|
+
return await this.compensateAndCancel(run, compensations, replay);
|
|
5532
6247
|
}
|
|
5533
6248
|
await this.store.updateRun(run.id, {
|
|
5534
6249
|
status: "completed",
|
|
@@ -5555,35 +6270,35 @@ var WorkflowEngine = class {
|
|
|
5555
6270
|
}
|
|
5556
6271
|
if (err instanceof WorkflowBlocked) {
|
|
5557
6272
|
if (this.cancelRequested.has(run.id)) {
|
|
5558
|
-
return this.compensateAndCancel(run, compensations, replay);
|
|
6273
|
+
return await this.compensateAndCancel(run, compensations, replay);
|
|
5559
6274
|
}
|
|
5560
|
-
return this.settleRun(run, {
|
|
6275
|
+
return await this.settleRun(run, {
|
|
5561
6276
|
kind: "blocked",
|
|
5562
6277
|
blocked: err.plan
|
|
5563
6278
|
});
|
|
5564
6279
|
}
|
|
5565
6280
|
if (err instanceof WorkflowSuspended) {
|
|
5566
6281
|
if (this.cancelRequested.has(run.id)) {
|
|
5567
|
-
return this.compensateAndCancel(run, compensations, replay);
|
|
6282
|
+
return await this.compensateAndCancel(run, compensations, replay);
|
|
5568
6283
|
}
|
|
5569
|
-
return this.settleRun(run, {
|
|
6284
|
+
return await this.settleRun(run, {
|
|
5570
6285
|
kind: "suspended",
|
|
5571
6286
|
wakeAt: err.wakeAt
|
|
5572
6287
|
});
|
|
5573
6288
|
}
|
|
5574
6289
|
if (this.cancelRequested.has(run.id)) {
|
|
5575
|
-
return this.compensateAndCancel(run, compensations, replay);
|
|
6290
|
+
return await this.compensateAndCancel(run, compensations, replay);
|
|
5576
6291
|
}
|
|
5577
6292
|
const error2 = {
|
|
5578
6293
|
message: err instanceof Error ? err.message : String(err),
|
|
5579
6294
|
stack: err instanceof Error ? err.stack : void 0
|
|
5580
6295
|
};
|
|
5581
6296
|
const pending = await this.unwindCompensations(run, compensations, replay);
|
|
5582
|
-
if (pending) return this.settleRun(run, {
|
|
6297
|
+
if (pending) return await this.settleRun(run, {
|
|
5583
6298
|
kind: "suspended",
|
|
5584
6299
|
wakeAt: pending.wakeAt
|
|
5585
6300
|
});
|
|
5586
|
-
return this.settleRun(run, {
|
|
6301
|
+
return await this.settleRun(run, {
|
|
5587
6302
|
kind: "failed",
|
|
5588
6303
|
error: error2
|
|
5589
6304
|
});
|
|
@@ -5997,7 +6712,10 @@ var WorkflowEngine = class {
|
|
|
5997
6712
|
}
|
|
5998
6713
|
async callRemote(runId, seq, step, input, queue, transport, replay, admission, routing) {
|
|
5999
6714
|
const group = stepGroup(step, routing?.namespace);
|
|
6000
|
-
|
|
6715
|
+
let existing = replay?.get(seq) ?? await this.store.getCheckpoint(runId, seq);
|
|
6716
|
+
if (existing?.status === "pending" && replay?.has(seq)) {
|
|
6717
|
+
existing = await this.store.getCheckpoint(runId, seq) ?? existing;
|
|
6718
|
+
}
|
|
6001
6719
|
if (existing && existing.name !== step.name) {
|
|
6002
6720
|
throw new NonDeterminismError(runId, seq, step.name, existing.name);
|
|
6003
6721
|
}
|
|
@@ -6401,14 +7119,6 @@ var RemoteStepError = class extends Error {
|
|
|
6401
7119
|
}
|
|
6402
7120
|
};
|
|
6403
7121
|
|
|
6404
|
-
// src/interfaces.ts
|
|
6405
|
-
var TERMINAL_RUN_STATUSES = [
|
|
6406
|
-
"completed",
|
|
6407
|
-
"failed",
|
|
6408
|
-
"cancelled",
|
|
6409
|
-
"dead"
|
|
6410
|
-
];
|
|
6411
|
-
|
|
6412
7122
|
// src/run-facets.ts
|
|
6413
7123
|
function facetOrigin(origin) {
|
|
6414
7124
|
return origin?.trim() ? origin : null;
|
|
@@ -6440,7 +7150,8 @@ __name(mergeRunFacetRows, "mergeRunFacetRows");
|
|
|
6440
7150
|
var RUN_VALUE_FACET_LIMIT = 100;
|
|
6441
7151
|
var RUN_VALUE_FACET_SCAN = 5e3;
|
|
6442
7152
|
var ENGINE_MINTED_TAG_PREFIXES = [
|
|
6443
|
-
"singleton:"
|
|
7153
|
+
"singleton:",
|
|
7154
|
+
"concurrency:"
|
|
6444
7155
|
];
|
|
6445
7156
|
function isEngineMintedTag(value) {
|
|
6446
7157
|
return value !== null && ENGINE_MINTED_TAG_PREFIXES.some((prefix) => value.startsWith(prefix));
|
|
@@ -6545,9 +7256,42 @@ var CodecStateStore = class {
|
|
|
6545
7256
|
* Bound the same conditional way as {@link runFacets} so absence is reported, not faked.
|
|
6546
7257
|
*/
|
|
6547
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;
|
|
6548
7275
|
constructor(inner, codec) {
|
|
6549
7276
|
this.inner = inner;
|
|
6550
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
|
+
}
|
|
6551
7295
|
const facets = inner.runFacets;
|
|
6552
7296
|
if (facets) this.runFacets = (query) => facets.call(inner, query);
|
|
6553
7297
|
const valueFacets = inner.runValueFacets;
|
|
@@ -6589,6 +7333,28 @@ var CodecStateStore = class {
|
|
|
6589
7333
|
output: this.dec(cp.output)
|
|
6590
7334
|
};
|
|
6591
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
|
+
}
|
|
6592
7358
|
ensureSchema() {
|
|
6593
7359
|
return this.inner.ensureSchema?.() ?? Promise.resolve();
|
|
6594
7360
|
}
|
|
@@ -6787,126 +7553,6 @@ function collectMetrics(engine) {
|
|
|
6787
7553
|
}
|
|
6788
7554
|
__name(collectMetrics, "collectMetrics");
|
|
6789
7555
|
|
|
6790
|
-
// src/scheduler.ts
|
|
6791
|
-
var import_node_module = require("module");
|
|
6792
|
-
|
|
6793
|
-
// src/cron-compat.ts
|
|
6794
|
-
function isIndexable(value) {
|
|
6795
|
-
return typeof value === "object" && value !== null || typeof value === "function";
|
|
6796
|
-
}
|
|
6797
|
-
__name(isIndexable, "isIndexable");
|
|
6798
|
-
function unwrapDefault(module_) {
|
|
6799
|
-
if (!isIndexable(module_)) return module_;
|
|
6800
|
-
if (Reflect.get(module_, "parseExpression") !== void 0) return module_;
|
|
6801
|
-
if (Reflect.get(module_, "CronExpressionParser") !== void 0) return module_;
|
|
6802
|
-
const inner = Reflect.get(module_, "default");
|
|
6803
|
-
return inner === void 0 ? module_ : inner;
|
|
6804
|
-
}
|
|
6805
|
-
__name(unwrapDefault, "unwrapDefault");
|
|
6806
|
-
function v4Entry(module_) {
|
|
6807
|
-
if (!isIndexable(module_)) return void 0;
|
|
6808
|
-
const parse = Reflect.get(module_, "parseExpression");
|
|
6809
|
-
if (typeof parse !== "function") return void 0;
|
|
6810
|
-
return (expr, options) => parse.call(module_, expr, options);
|
|
6811
|
-
}
|
|
6812
|
-
__name(v4Entry, "v4Entry");
|
|
6813
|
-
function v5Entry(module_) {
|
|
6814
|
-
if (!isIndexable(module_)) return void 0;
|
|
6815
|
-
const parser = Reflect.get(module_, "CronExpressionParser");
|
|
6816
|
-
if (!isIndexable(parser)) return void 0;
|
|
6817
|
-
const parse = Reflect.get(parser, "parse");
|
|
6818
|
-
if (typeof parse !== "function") return void 0;
|
|
6819
|
-
return (expr, options) => parse.call(parser, expr, options);
|
|
6820
|
-
}
|
|
6821
|
-
__name(v5Entry, "v5Entry");
|
|
6822
|
-
function resolveCronParse(module_) {
|
|
6823
|
-
const resolved = unwrapDefault(module_);
|
|
6824
|
-
return v4Entry(resolved) ?? v5Entry(resolved);
|
|
6825
|
-
}
|
|
6826
|
-
__name(resolveCronParse, "resolveCronParse");
|
|
6827
|
-
|
|
6828
|
-
// src/scheduler.ts
|
|
6829
|
-
var nodeRequire = (0, import_node_module.createRequire)(__importMetaUrl);
|
|
6830
|
-
var defaultSleep = /* @__PURE__ */ __name((ms) => new Promise((r) => setTimeout(r, ms)), "defaultSleep");
|
|
6831
|
-
function scheduledRunId(key, everyMs, nowMs) {
|
|
6832
|
-
return `sched:${key}:${Math.floor(nowMs / everyMs)}`;
|
|
6833
|
-
}
|
|
6834
|
-
__name(scheduledRunId, "scheduledRunId");
|
|
6835
|
-
var cronParser;
|
|
6836
|
-
function loadCronParser() {
|
|
6837
|
-
if (cronParser !== void 0) return cronParser;
|
|
6838
|
-
try {
|
|
6839
|
-
cronParser = nodeRequire("cron-parser");
|
|
6840
|
-
} catch {
|
|
6841
|
-
throw new Error('cron schedules need the optional peer dependency "cron-parser" \u2014 install it (e.g. `npm i cron-parser`).');
|
|
6842
|
-
}
|
|
6843
|
-
return cronParser;
|
|
6844
|
-
}
|
|
6845
|
-
__name(loadCronParser, "loadCronParser");
|
|
6846
|
-
function prevCronFireMs(expr, nowMs, timezone = "UTC") {
|
|
6847
|
-
const parse = resolveCronParse(loadCronParser());
|
|
6848
|
-
if (!parse) {
|
|
6849
|
-
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.`);
|
|
6850
|
-
}
|
|
6851
|
-
return parse(expr, {
|
|
6852
|
-
currentDate: new Date(nowMs + 1),
|
|
6853
|
-
tz: timezone
|
|
6854
|
-
}).prev().toDate().getTime();
|
|
6855
|
-
}
|
|
6856
|
-
__name(prevCronFireMs, "prevCronFireMs");
|
|
6857
|
-
function scheduleRunIdAt(s, nowMs) {
|
|
6858
|
-
if (s.cron != null) return `sched:${s.key}:${prevCronFireMs(s.cron, nowMs, s.timezone)}`;
|
|
6859
|
-
if (s.everyMs != null) return scheduledRunId(s.key, s.everyMs, nowMs);
|
|
6860
|
-
throw new Error(`schedule "${s.key}" needs either "everyMs" or "cron"`);
|
|
6861
|
-
}
|
|
6862
|
-
__name(scheduleRunIdAt, "scheduleRunIdAt");
|
|
6863
|
-
function scheduleRunIdsAt(s, nowMs) {
|
|
6864
|
-
const current = scheduleRunIdAt(s, nowMs);
|
|
6865
|
-
if (!s.backfill || s.backfill.maxCatchup <= 0) return [
|
|
6866
|
-
current
|
|
6867
|
-
];
|
|
6868
|
-
const ids = [];
|
|
6869
|
-
if (s.everyMs != null) {
|
|
6870
|
-
const bucket = Math.floor(nowMs / s.everyMs);
|
|
6871
|
-
const oldest = Math.max(0, bucket - s.backfill.maxCatchup);
|
|
6872
|
-
for (let b = oldest; b < bucket; b += 1) ids.push(`sched:${s.key}:${b}`);
|
|
6873
|
-
} else if (s.cron != null) {
|
|
6874
|
-
const prior = [];
|
|
6875
|
-
let cursor = prevCronFireMs(s.cron, nowMs, s.timezone);
|
|
6876
|
-
for (let i = 0; i < s.backfill.maxCatchup; i += 1) {
|
|
6877
|
-
const before = prevCronFireMs(s.cron, cursor - 1, s.timezone);
|
|
6878
|
-
prior.push(`sched:${s.key}:${before}`);
|
|
6879
|
-
cursor = before;
|
|
6880
|
-
}
|
|
6881
|
-
ids.push(...prior.reverse());
|
|
6882
|
-
}
|
|
6883
|
-
ids.push(current);
|
|
6884
|
-
return ids;
|
|
6885
|
-
}
|
|
6886
|
-
__name(scheduleRunIdsAt, "scheduleRunIdsAt");
|
|
6887
|
-
async function runSchedules(engine, schedules, nowMs, opts) {
|
|
6888
|
-
const random = opts?.random ?? Math.random;
|
|
6889
|
-
const sleep = opts?.sleep ?? defaultSleep;
|
|
6890
|
-
const ids = [];
|
|
6891
|
-
for (const s of schedules) {
|
|
6892
|
-
if (s.paused) continue;
|
|
6893
|
-
if (s.overlap === "skip" && s.everyMs) {
|
|
6894
|
-
const prevBucket = Math.floor(nowMs / s.everyMs) - 1;
|
|
6895
|
-
if (prevBucket >= 0) {
|
|
6896
|
-
const prev = await engine.getRun(`sched:${s.key}:${prevBucket}`);
|
|
6897
|
-
if (prev && (prev.status === "running" || prev.status === "suspended")) continue;
|
|
6898
|
-
}
|
|
6899
|
-
}
|
|
6900
|
-
if (s.jitter && s.jitter > 0) await sleep(Math.floor(random() * s.jitter));
|
|
6901
|
-
for (const runId of scheduleRunIdsAt(s, nowMs)) {
|
|
6902
|
-
await engine.start(s.workflow, s.input, runId);
|
|
6903
|
-
ids.push(runId);
|
|
6904
|
-
}
|
|
6905
|
-
}
|
|
6906
|
-
return ids;
|
|
6907
|
-
}
|
|
6908
|
-
__name(runSchedules, "runSchedules");
|
|
6909
|
-
|
|
6910
7556
|
// src/search-attributes.ts
|
|
6911
7557
|
function compare(actual, filter) {
|
|
6912
7558
|
if (filter.op === "in") return filter.values.some((v) => actual === v);
|
|
@@ -7114,6 +7760,38 @@ var InMemoryStateStore = class {
|
|
|
7114
7760
|
}
|
|
7115
7761
|
this.reindexAttributes(runId, void 0);
|
|
7116
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
|
+
}
|
|
7117
7795
|
async getCheckpoint(runId, seq) {
|
|
7118
7796
|
const cp = this.checkpoints.get(this.key(runId, seq));
|
|
7119
7797
|
return cp ? {
|
|
@@ -7321,6 +7999,9 @@ var InMemoryStateStore = class {
|
|
|
7321
7999
|
...r
|
|
7322
8000
|
}));
|
|
7323
8001
|
}
|
|
8002
|
+
async countRuns(query) {
|
|
8003
|
+
return this.matching(query).length;
|
|
8004
|
+
}
|
|
7324
8005
|
async runFacets(query) {
|
|
7325
8006
|
return mergeRunFacetRows(this.matching(query).map((run) => ({
|
|
7326
8007
|
status: run.status,
|
|
@@ -7338,6 +8019,34 @@ var InMemoryStateStore = class {
|
|
|
7338
8019
|
});
|
|
7339
8020
|
return runValueFacetsFromRuns(runs, axis, opts);
|
|
7340
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
|
+
}
|
|
7341
8050
|
async listCheckpoints(runId) {
|
|
7342
8051
|
const rows = [
|
|
7343
8052
|
...this.checkpoints.values()
|
|
@@ -7419,7 +8128,9 @@ var InMemoryTransport = class {
|
|
|
7419
8128
|
CONTROL_FLOW_SIGNAL,
|
|
7420
8129
|
CURRENT_PROTOCOL_VERSION,
|
|
7421
8130
|
CodecStateStore,
|
|
8131
|
+
ConcurrencyLimitError,
|
|
7422
8132
|
ContinueAsNew,
|
|
8133
|
+
DEFAULT_CONCURRENCY_STATUSES,
|
|
7423
8134
|
DEFAULT_PARTITION,
|
|
7424
8135
|
DURABLE_OPTIONS,
|
|
7425
8136
|
DURABLE_OPTIONS_CANONICAL,
|
|
@@ -7449,6 +8160,9 @@ var InMemoryTransport = class {
|
|
|
7449
8160
|
RunGateway,
|
|
7450
8161
|
STATE_STORE,
|
|
7451
8162
|
STATE_STORE_CANONICAL,
|
|
8163
|
+
ScheduleAlreadyExistsError,
|
|
8164
|
+
ScheduleClient,
|
|
8165
|
+
ScheduleNotFoundError,
|
|
7452
8166
|
SignalTimeoutError,
|
|
7453
8167
|
SingletonQueueFullError,
|
|
7454
8168
|
TERMINAL_RUN_STATUSES,
|
|
@@ -7462,6 +8176,7 @@ var InMemoryTransport = class {
|
|
|
7462
8176
|
aggregateAnnouncements,
|
|
7463
8177
|
announcementsOf,
|
|
7464
8178
|
applyAttributeQuery,
|
|
8179
|
+
assertConcurrency,
|
|
7465
8180
|
attributeColumnFor,
|
|
7466
8181
|
attributeOperand,
|
|
7467
8182
|
attributePredicateOperands,
|
|
@@ -7474,7 +8189,9 @@ var InMemoryTransport = class {
|
|
|
7474
8189
|
classifyWaiterToken,
|
|
7475
8190
|
clearDurableExecutionWrappers,
|
|
7476
8191
|
collectMetrics,
|
|
8192
|
+
concurrencyTag,
|
|
7477
8193
|
controlPlaneDescriptor,
|
|
8194
|
+
countRuns,
|
|
7478
8195
|
coveredTokens,
|
|
7479
8196
|
createStepLogger,
|
|
7480
8197
|
currentStep,
|
|
@@ -7496,6 +8213,7 @@ var InMemoryTransport = class {
|
|
|
7496
8213
|
isEngineMintedTag,
|
|
7497
8214
|
isEventToken,
|
|
7498
8215
|
isLegacyDescriptor,
|
|
8216
|
+
isNonEmptyRunScope,
|
|
7499
8217
|
isWorkflowControlFlowSignal,
|
|
7500
8218
|
log,
|
|
7501
8219
|
matchesAttributes,
|
|
@@ -7503,6 +8221,7 @@ var InMemoryTransport = class {
|
|
|
7503
8221
|
mergeRunValueFacetRows,
|
|
7504
8222
|
missingCapabilities,
|
|
7505
8223
|
negotiate,
|
|
8224
|
+
nextCronFireMs,
|
|
7506
8225
|
normalizeAttributeRows,
|
|
7507
8226
|
normalizeDescriptor,
|
|
7508
8227
|
observedAnnouncements,
|
|
@@ -7512,6 +8231,7 @@ var InMemoryTransport = class {
|
|
|
7512
8231
|
prevCronFireMs,
|
|
7513
8232
|
readSearchAttributes,
|
|
7514
8233
|
requiredCapabilities,
|
|
8234
|
+
resolveConcurrency,
|
|
7515
8235
|
resolveRouting,
|
|
7516
8236
|
resolveRunWaiting,
|
|
7517
8237
|
runDurableExecution,
|