@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.js
CHANGED
|
@@ -250,6 +250,23 @@ var FatalError = class extends Error {
|
|
|
250
250
|
this.code = code;
|
|
251
251
|
}
|
|
252
252
|
};
|
|
253
|
+
var ConcurrencyLimitError = class extends Error {
|
|
254
|
+
static {
|
|
255
|
+
__name(this, "ConcurrencyLimitError");
|
|
256
|
+
}
|
|
257
|
+
workflow;
|
|
258
|
+
key;
|
|
259
|
+
limit;
|
|
260
|
+
active;
|
|
261
|
+
constructor(workflow, key, limit, active) {
|
|
262
|
+
super(`concurrency limit reached for key "${key}" (${active}/${limit} in flight); ${workflow} was not started \u2014 retry later`);
|
|
263
|
+
this.name = "ConcurrencyLimitError";
|
|
264
|
+
this.workflow = workflow;
|
|
265
|
+
this.key = key;
|
|
266
|
+
this.limit = limit;
|
|
267
|
+
this.active = active;
|
|
268
|
+
}
|
|
269
|
+
};
|
|
253
270
|
var SingletonQueueFullError = class extends Error {
|
|
254
271
|
static {
|
|
255
272
|
__name(this, "SingletonQueueFullError");
|
|
@@ -763,6 +780,58 @@ function instantCheckpoint(p) {
|
|
|
763
780
|
}
|
|
764
781
|
__name(instantCheckpoint, "instantCheckpoint");
|
|
765
782
|
|
|
783
|
+
// src/concurrency.ts
|
|
784
|
+
var DEFAULT_CONCURRENCY_STATUSES = [
|
|
785
|
+
"pending",
|
|
786
|
+
"running",
|
|
787
|
+
"suspended",
|
|
788
|
+
"cancelling",
|
|
789
|
+
"blocked"
|
|
790
|
+
];
|
|
791
|
+
function concurrencyTag(key) {
|
|
792
|
+
return `concurrency:${key}`;
|
|
793
|
+
}
|
|
794
|
+
__name(concurrencyTag, "concurrencyTag");
|
|
795
|
+
async function resolveConcurrency(override, config, input) {
|
|
796
|
+
if (override) return override;
|
|
797
|
+
if (!config) return void 0;
|
|
798
|
+
const key = config.key(input);
|
|
799
|
+
if (key === void 0) return void 0;
|
|
800
|
+
const limit = typeof config.limit === "function" ? await config.limit(key) : config.limit;
|
|
801
|
+
return {
|
|
802
|
+
key,
|
|
803
|
+
limit,
|
|
804
|
+
countStatuses: config.countStatuses
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
__name(resolveConcurrency, "resolveConcurrency");
|
|
808
|
+
async function countRuns(store, query) {
|
|
809
|
+
if (store.countRuns) return store.countRuns(query);
|
|
810
|
+
if (store.runFacets && !query.status && !query.statuses && query.origin === void 0) {
|
|
811
|
+
const cells = await store.runFacets(query);
|
|
812
|
+
return cells.reduce((sum, c) => sum + c.count, 0);
|
|
813
|
+
}
|
|
814
|
+
if (store.runFacets && query.statuses && !query.status && query.origin === void 0) {
|
|
815
|
+
const { statuses, ...rest } = query;
|
|
816
|
+
const cells = await store.runFacets(rest);
|
|
817
|
+
return cells.filter((c) => statuses.includes(c.status)).reduce((sum, c) => sum + c.count, 0);
|
|
818
|
+
}
|
|
819
|
+
return (await store.listRuns(query)).length;
|
|
820
|
+
}
|
|
821
|
+
__name(countRuns, "countRuns");
|
|
822
|
+
async function assertConcurrency(store, workflow, quota) {
|
|
823
|
+
const active = quota.limit <= 0 ? 0 : await countRuns(store, {
|
|
824
|
+
tag: concurrencyTag(quota.key),
|
|
825
|
+
statuses: [
|
|
826
|
+
...quota.countStatuses ?? DEFAULT_CONCURRENCY_STATUSES
|
|
827
|
+
]
|
|
828
|
+
});
|
|
829
|
+
if (quota.limit <= 0 || active >= quota.limit) {
|
|
830
|
+
throw new ConcurrencyLimitError(workflow, quota.key, quota.limit, active);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
__name(assertConcurrency, "assertConcurrency");
|
|
834
|
+
|
|
766
835
|
// src/entities.ts
|
|
767
836
|
var ENTITY_WORKFLOW = "__entity";
|
|
768
837
|
var entityRunId = /* @__PURE__ */ __name((name, key) => `entity:${name}:${key}`, "entityRunId");
|
|
@@ -1595,6 +1664,19 @@ function planDispatch(requires, liveDescriptors, cp, token) {
|
|
|
1595
1664
|
}
|
|
1596
1665
|
__name(planDispatch, "planDispatch");
|
|
1597
1666
|
|
|
1667
|
+
// src/interfaces.ts
|
|
1668
|
+
var TERMINAL_RUN_STATUSES = [
|
|
1669
|
+
"completed",
|
|
1670
|
+
"failed",
|
|
1671
|
+
"cancelled",
|
|
1672
|
+
"dead"
|
|
1673
|
+
];
|
|
1674
|
+
function isNonEmptyRunScope(scope) {
|
|
1675
|
+
if (!scope) return false;
|
|
1676
|
+
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;
|
|
1677
|
+
}
|
|
1678
|
+
__name(isNonEmptyRunScope, "isNonEmptyRunScope");
|
|
1679
|
+
|
|
1598
1680
|
// src/tenant-group.ts
|
|
1599
1681
|
function tenantGroup(baseGroup, tenant) {
|
|
1600
1682
|
return tenant !== void 0 && tenant !== "" && tenant !== "default" ? `${baseGroup}@${tenant}` : baseGroup;
|
|
@@ -1685,6 +1767,486 @@ function indexWaitersByRun(waiters) {
|
|
|
1685
1767
|
}
|
|
1686
1768
|
__name(indexWaitersByRun, "indexWaitersByRun");
|
|
1687
1769
|
|
|
1770
|
+
// src/scheduler.ts
|
|
1771
|
+
import { createRequire } from "node:module";
|
|
1772
|
+
|
|
1773
|
+
// src/cron-compat.ts
|
|
1774
|
+
function isIndexable(value) {
|
|
1775
|
+
return typeof value === "object" && value !== null || typeof value === "function";
|
|
1776
|
+
}
|
|
1777
|
+
__name(isIndexable, "isIndexable");
|
|
1778
|
+
function unwrapDefault(module_) {
|
|
1779
|
+
if (!isIndexable(module_)) return module_;
|
|
1780
|
+
if (Reflect.get(module_, "parseExpression") !== void 0) return module_;
|
|
1781
|
+
if (Reflect.get(module_, "CronExpressionParser") !== void 0) return module_;
|
|
1782
|
+
const inner = Reflect.get(module_, "default");
|
|
1783
|
+
return inner === void 0 ? module_ : inner;
|
|
1784
|
+
}
|
|
1785
|
+
__name(unwrapDefault, "unwrapDefault");
|
|
1786
|
+
function v4Entry(module_) {
|
|
1787
|
+
if (!isIndexable(module_)) return void 0;
|
|
1788
|
+
const parse = Reflect.get(module_, "parseExpression");
|
|
1789
|
+
if (typeof parse !== "function") return void 0;
|
|
1790
|
+
return (expr, options) => parse.call(module_, expr, options);
|
|
1791
|
+
}
|
|
1792
|
+
__name(v4Entry, "v4Entry");
|
|
1793
|
+
function v5Entry(module_) {
|
|
1794
|
+
if (!isIndexable(module_)) return void 0;
|
|
1795
|
+
const parser = Reflect.get(module_, "CronExpressionParser");
|
|
1796
|
+
if (!isIndexable(parser)) return void 0;
|
|
1797
|
+
const parse = Reflect.get(parser, "parse");
|
|
1798
|
+
if (typeof parse !== "function") return void 0;
|
|
1799
|
+
return (expr, options) => parse.call(parser, expr, options);
|
|
1800
|
+
}
|
|
1801
|
+
__name(v5Entry, "v5Entry");
|
|
1802
|
+
function resolveCronParse(module_) {
|
|
1803
|
+
const resolved = unwrapDefault(module_);
|
|
1804
|
+
return v4Entry(resolved) ?? v5Entry(resolved);
|
|
1805
|
+
}
|
|
1806
|
+
__name(resolveCronParse, "resolveCronParse");
|
|
1807
|
+
|
|
1808
|
+
// src/scheduler.ts
|
|
1809
|
+
var nodeRequire = createRequire(import.meta.url);
|
|
1810
|
+
var defaultSleep = /* @__PURE__ */ __name((ms) => new Promise((r) => setTimeout(r, ms)), "defaultSleep");
|
|
1811
|
+
function scheduledRunId(key, everyMs, nowMs) {
|
|
1812
|
+
return `sched:${key}:${Math.floor(nowMs / everyMs)}`;
|
|
1813
|
+
}
|
|
1814
|
+
__name(scheduledRunId, "scheduledRunId");
|
|
1815
|
+
var cronParser;
|
|
1816
|
+
function loadCronParser() {
|
|
1817
|
+
if (cronParser !== void 0) return cronParser;
|
|
1818
|
+
try {
|
|
1819
|
+
cronParser = nodeRequire("cron-parser");
|
|
1820
|
+
} catch {
|
|
1821
|
+
throw new Error('cron schedules need the optional peer dependency "cron-parser" \u2014 install it (e.g. `npm i cron-parser`).');
|
|
1822
|
+
}
|
|
1823
|
+
return cronParser;
|
|
1824
|
+
}
|
|
1825
|
+
__name(loadCronParser, "loadCronParser");
|
|
1826
|
+
function prevCronFireMs(expr, nowMs, timezone = "UTC") {
|
|
1827
|
+
const parse = resolveCronParse(loadCronParser());
|
|
1828
|
+
if (!parse) {
|
|
1829
|
+
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.`);
|
|
1830
|
+
}
|
|
1831
|
+
return parse(expr, {
|
|
1832
|
+
currentDate: new Date(nowMs + 1),
|
|
1833
|
+
tz: timezone
|
|
1834
|
+
}).prev().toDate().getTime();
|
|
1835
|
+
}
|
|
1836
|
+
__name(prevCronFireMs, "prevCronFireMs");
|
|
1837
|
+
function nextCronFireMs(expr, afterMs, timezone = "UTC") {
|
|
1838
|
+
const parse = resolveCronParse(loadCronParser());
|
|
1839
|
+
if (!parse) {
|
|
1840
|
+
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.`);
|
|
1841
|
+
}
|
|
1842
|
+
return parse(expr, {
|
|
1843
|
+
currentDate: new Date(afterMs),
|
|
1844
|
+
tz: timezone
|
|
1845
|
+
}).next().toDate().getTime();
|
|
1846
|
+
}
|
|
1847
|
+
__name(nextCronFireMs, "nextCronFireMs");
|
|
1848
|
+
function scheduleRunIdAt(s, nowMs) {
|
|
1849
|
+
if (s.cron != null) return `sched:${s.key}:${prevCronFireMs(s.cron, nowMs, s.timezone)}`;
|
|
1850
|
+
if (s.everyMs != null) return scheduledRunId(s.key, s.everyMs, nowMs);
|
|
1851
|
+
throw new Error(`schedule "${s.key}" needs either "everyMs" or "cron"`);
|
|
1852
|
+
}
|
|
1853
|
+
__name(scheduleRunIdAt, "scheduleRunIdAt");
|
|
1854
|
+
function scheduleRunIdsAt(s, nowMs) {
|
|
1855
|
+
const current = scheduleRunIdAt(s, nowMs);
|
|
1856
|
+
if (!s.backfill || s.backfill.maxCatchup <= 0) return [
|
|
1857
|
+
current
|
|
1858
|
+
];
|
|
1859
|
+
const ids = [];
|
|
1860
|
+
if (s.everyMs != null) {
|
|
1861
|
+
const bucket = Math.floor(nowMs / s.everyMs);
|
|
1862
|
+
const oldest = Math.max(0, bucket - s.backfill.maxCatchup);
|
|
1863
|
+
for (let b = oldest; b < bucket; b += 1) ids.push(`sched:${s.key}:${b}`);
|
|
1864
|
+
} else if (s.cron != null) {
|
|
1865
|
+
const prior = [];
|
|
1866
|
+
let cursor = prevCronFireMs(s.cron, nowMs, s.timezone);
|
|
1867
|
+
for (let i = 0; i < s.backfill.maxCatchup; i += 1) {
|
|
1868
|
+
const before = prevCronFireMs(s.cron, cursor - 1, s.timezone);
|
|
1869
|
+
prior.push(`sched:${s.key}:${before}`);
|
|
1870
|
+
cursor = before;
|
|
1871
|
+
}
|
|
1872
|
+
ids.push(...prior.reverse());
|
|
1873
|
+
}
|
|
1874
|
+
ids.push(current);
|
|
1875
|
+
return ids;
|
|
1876
|
+
}
|
|
1877
|
+
__name(scheduleRunIdsAt, "scheduleRunIdsAt");
|
|
1878
|
+
async function runSchedules(engine, schedules, nowMs, opts) {
|
|
1879
|
+
const random = opts?.random ?? Math.random;
|
|
1880
|
+
const sleep = opts?.sleep ?? defaultSleep;
|
|
1881
|
+
const ids = [];
|
|
1882
|
+
for (const s of schedules) {
|
|
1883
|
+
if (s.paused) continue;
|
|
1884
|
+
if (s.overlap === "skip" && s.everyMs) {
|
|
1885
|
+
const prevBucket = Math.floor(nowMs / s.everyMs) - 1;
|
|
1886
|
+
if (prevBucket >= 0) {
|
|
1887
|
+
const prev = await engine.getRun(`sched:${s.key}:${prevBucket}`);
|
|
1888
|
+
if (prev && (prev.status === "running" || prev.status === "suspended")) continue;
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
if (s.jitter && s.jitter > 0) await sleep(Math.floor(random() * s.jitter));
|
|
1892
|
+
for (const runId of scheduleRunIdsAt(s, nowMs)) {
|
|
1893
|
+
await engine.start(s.workflow, s.input, runId);
|
|
1894
|
+
ids.push(runId);
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
return ids;
|
|
1898
|
+
}
|
|
1899
|
+
__name(runSchedules, "runSchedules");
|
|
1900
|
+
|
|
1901
|
+
// src/schedules.ts
|
|
1902
|
+
var ScheduleAlreadyExistsError = class extends Error {
|
|
1903
|
+
static {
|
|
1904
|
+
__name(this, "ScheduleAlreadyExistsError");
|
|
1905
|
+
}
|
|
1906
|
+
scheduleId;
|
|
1907
|
+
constructor(scheduleId) {
|
|
1908
|
+
super(`schedule "${scheduleId}" already exists`), this.scheduleId = scheduleId;
|
|
1909
|
+
this.name = "ScheduleAlreadyExistsError";
|
|
1910
|
+
}
|
|
1911
|
+
};
|
|
1912
|
+
var ScheduleNotFoundError = class extends Error {
|
|
1913
|
+
static {
|
|
1914
|
+
__name(this, "ScheduleNotFoundError");
|
|
1915
|
+
}
|
|
1916
|
+
scheduleId;
|
|
1917
|
+
constructor(scheduleId) {
|
|
1918
|
+
super(`schedule "${scheduleId}" not found`), this.scheduleId = scheduleId;
|
|
1919
|
+
this.name = "ScheduleNotFoundError";
|
|
1920
|
+
}
|
|
1921
|
+
};
|
|
1922
|
+
var DEFAULT_NAMESPACE = "default";
|
|
1923
|
+
var ON_TIME_GRACE_MS = 6e4;
|
|
1924
|
+
var TICK_BATCH = 100;
|
|
1925
|
+
var ScheduleClient = class {
|
|
1926
|
+
static {
|
|
1927
|
+
__name(this, "ScheduleClient");
|
|
1928
|
+
}
|
|
1929
|
+
store;
|
|
1930
|
+
host;
|
|
1931
|
+
options;
|
|
1932
|
+
constructor(store, host, options = {}) {
|
|
1933
|
+
this.store = store;
|
|
1934
|
+
this.host = host;
|
|
1935
|
+
this.options = options;
|
|
1936
|
+
}
|
|
1937
|
+
now() {
|
|
1938
|
+
return (this.options.clock ?? Date.now)();
|
|
1939
|
+
}
|
|
1940
|
+
/** Whether the store persists schedules (implements the schedule methods). */
|
|
1941
|
+
get supported() {
|
|
1942
|
+
const s = this.store;
|
|
1943
|
+
return !!(s.saveSchedule && s.getSchedule && s.updateSchedule && s.deleteSchedule && s.listSchedules);
|
|
1944
|
+
}
|
|
1945
|
+
requireStore() {
|
|
1946
|
+
if (!this.supported) {
|
|
1947
|
+
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");
|
|
1948
|
+
}
|
|
1949
|
+
const s = this.store;
|
|
1950
|
+
return {
|
|
1951
|
+
saveSchedule: /* @__PURE__ */ __name((r) => s.saveSchedule.call(s, r), "saveSchedule"),
|
|
1952
|
+
getSchedule: /* @__PURE__ */ __name((id) => s.getSchedule.call(s, id), "getSchedule"),
|
|
1953
|
+
updateSchedule: /* @__PURE__ */ __name((id, patch, expected) => s.updateSchedule.call(s, id, patch, expected), "updateSchedule"),
|
|
1954
|
+
deleteSchedule: /* @__PURE__ */ __name((id) => s.deleteSchedule.call(s, id), "deleteSchedule"),
|
|
1955
|
+
listSchedules: /* @__PURE__ */ __name((q) => s.listSchedules.call(s, q), "listSchedules")
|
|
1956
|
+
};
|
|
1957
|
+
}
|
|
1958
|
+
/** Create a schedule. Throws {@link ScheduleAlreadyExistsError} if the id is taken. */
|
|
1959
|
+
async create(opts) {
|
|
1960
|
+
const store = this.requireStore();
|
|
1961
|
+
if (await store.getSchedule(opts.id)) throw new ScheduleAlreadyExistsError(opts.id);
|
|
1962
|
+
return this.write(opts, null);
|
|
1963
|
+
}
|
|
1964
|
+
/**
|
|
1965
|
+
* Create the schedule, or replace the definition of an existing one (its history — last fire, last
|
|
1966
|
+
* run, fire count — is kept). The next fire is recomputed from now under the new definition, so a
|
|
1967
|
+
* window that already fired is never fired again by an upsert.
|
|
1968
|
+
*/
|
|
1969
|
+
async upsert(opts) {
|
|
1970
|
+
const store = this.requireStore();
|
|
1971
|
+
return this.write(opts, await store.getSchedule(opts.id));
|
|
1972
|
+
}
|
|
1973
|
+
/** The schedule `id`, or null. */
|
|
1974
|
+
async get(id) {
|
|
1975
|
+
const record = await this.requireStore().getSchedule(id);
|
|
1976
|
+
return record ? describe(record) : null;
|
|
1977
|
+
}
|
|
1978
|
+
/** Schedules matching `query`, soonest-due first. */
|
|
1979
|
+
async list(query = {}) {
|
|
1980
|
+
return (await this.requireStore().listSchedules(query)).map(describe);
|
|
1981
|
+
}
|
|
1982
|
+
/** Delete the schedule. Runs it already started are left alone. Returns whether it existed. */
|
|
1983
|
+
delete(id) {
|
|
1984
|
+
return this.requireStore().deleteSchedule(id);
|
|
1985
|
+
}
|
|
1986
|
+
/** Stop firing until {@link resume}. `note` records why (shown by `get`/`list`). */
|
|
1987
|
+
async pause(id, note) {
|
|
1988
|
+
const store = this.requireStore();
|
|
1989
|
+
const record = await this.mustGet(id);
|
|
1990
|
+
const spec = {
|
|
1991
|
+
...record.spec
|
|
1992
|
+
};
|
|
1993
|
+
if (note !== void 0) spec.note = note;
|
|
1994
|
+
await store.updateSchedule(id, {
|
|
1995
|
+
paused: true,
|
|
1996
|
+
spec,
|
|
1997
|
+
updatedAt: new Date(this.now())
|
|
1998
|
+
});
|
|
1999
|
+
return describe(await this.mustGet(id));
|
|
2000
|
+
}
|
|
2001
|
+
/**
|
|
2002
|
+
* Fire again from the next window after now — the windows skipped while paused are NOT caught up.
|
|
2003
|
+
*/
|
|
2004
|
+
async resume(id) {
|
|
2005
|
+
const store = this.requireStore();
|
|
2006
|
+
const record = await this.mustGet(id);
|
|
2007
|
+
const spec = record.spec;
|
|
2008
|
+
const window = nextWindowAfter(spec, this.now());
|
|
2009
|
+
await store.updateSchedule(id, {
|
|
2010
|
+
paused: false,
|
|
2011
|
+
nextFireAt: window === null ? null : window + jitterFor(record.id, window, spec),
|
|
2012
|
+
state: {
|
|
2013
|
+
...record.state,
|
|
2014
|
+
window
|
|
2015
|
+
},
|
|
2016
|
+
updatedAt: new Date(this.now())
|
|
2017
|
+
});
|
|
2018
|
+
return describe(await this.mustGet(id));
|
|
2019
|
+
}
|
|
2020
|
+
/**
|
|
2021
|
+
* Start one run of the schedule right now, outside its cadence (paused or not). The run id is
|
|
2022
|
+
* `sched:<id>:manual:<nowMs>`; it doesn't move the schedule's next fire.
|
|
2023
|
+
*/
|
|
2024
|
+
async trigger(id) {
|
|
2025
|
+
const record = await this.mustGet(id);
|
|
2026
|
+
const runId = `sched:${id}:manual:${this.now()}`;
|
|
2027
|
+
return this.host.start(record.workflow, specOf(record).input, runId, startOptions(record));
|
|
2028
|
+
}
|
|
2029
|
+
/**
|
|
2030
|
+
* Fire every due schedule (in this engine's namespace; all namespaces for an operator engine):
|
|
2031
|
+
* start its window's run and advance it to the next window. Called by the NestJS timer poller on
|
|
2032
|
+
* each tick when `persistedSchedules` is on; call it yourself from any other driver. Returns the
|
|
2033
|
+
* run ids started (or re-found, for an idempotent re-fire).
|
|
2034
|
+
*/
|
|
2035
|
+
async tick(nowMs = this.now()) {
|
|
2036
|
+
const store = this.requireStore();
|
|
2037
|
+
const started = [];
|
|
2038
|
+
for (let round = 0; round < 50; round++) {
|
|
2039
|
+
const due = await store.listSchedules({
|
|
2040
|
+
dueBy: nowMs,
|
|
2041
|
+
namespace: this.options.namespace,
|
|
2042
|
+
limit: TICK_BATCH
|
|
2043
|
+
});
|
|
2044
|
+
for (const record of due) {
|
|
2045
|
+
const runId = await this.fire(record, nowMs);
|
|
2046
|
+
if (runId) started.push(runId);
|
|
2047
|
+
}
|
|
2048
|
+
if (due.length < TICK_BATCH) break;
|
|
2049
|
+
}
|
|
2050
|
+
return started;
|
|
2051
|
+
}
|
|
2052
|
+
/** Fire one due schedule and advance it. Returns the run id started, if any. */
|
|
2053
|
+
async fire(record, nowMs) {
|
|
2054
|
+
const store = this.requireStore();
|
|
2055
|
+
const spec = specOf(record);
|
|
2056
|
+
const state = record.state;
|
|
2057
|
+
let latest;
|
|
2058
|
+
let next;
|
|
2059
|
+
try {
|
|
2060
|
+
latest = latestWindowAtOrBefore(spec, nowMs);
|
|
2061
|
+
next = nextWindowAfter(spec, latest);
|
|
2062
|
+
} catch (error2) {
|
|
2063
|
+
await store.updateSchedule(record.id, {
|
|
2064
|
+
paused: true,
|
|
2065
|
+
nextFireAt: null,
|
|
2066
|
+
state: {
|
|
2067
|
+
...state,
|
|
2068
|
+
lastError: `invalid schedule: ${errorMessage(error2)}`
|
|
2069
|
+
},
|
|
2070
|
+
updatedAt: new Date(nowMs)
|
|
2071
|
+
}, record.nextFireAt);
|
|
2072
|
+
return void 0;
|
|
2073
|
+
}
|
|
2074
|
+
const window = state.window ?? latest;
|
|
2075
|
+
const jitter = spec.jitterMs ?? 0;
|
|
2076
|
+
const onTime = nowMs - latest <= ON_TIME_GRACE_MS + jitter;
|
|
2077
|
+
let runId;
|
|
2078
|
+
let lastError;
|
|
2079
|
+
let fired = false;
|
|
2080
|
+
const skipMissed = spec.catchup === "skip" && !onTime;
|
|
2081
|
+
const beforeWindow = latest < window;
|
|
2082
|
+
if (!skipMissed && !beforeWindow && !await this.overlapping(spec, state)) {
|
|
2083
|
+
runId = `sched:${record.id}:${latest}`;
|
|
2084
|
+
try {
|
|
2085
|
+
await this.host.start(record.workflow, spec.input, runId, startOptions(record));
|
|
2086
|
+
fired = true;
|
|
2087
|
+
} catch (error2) {
|
|
2088
|
+
if (await this.host.getRun(runId)) fired = true;
|
|
2089
|
+
else lastError = errorMessage(error2);
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
const nextState = {
|
|
2093
|
+
...state,
|
|
2094
|
+
window: next,
|
|
2095
|
+
...fired ? {
|
|
2096
|
+
lastFireAt: latest,
|
|
2097
|
+
lastRunId: runId,
|
|
2098
|
+
fires: (state.fires ?? 0) + 1
|
|
2099
|
+
} : {},
|
|
2100
|
+
lastError: fired ? void 0 : lastError ?? state.lastError
|
|
2101
|
+
};
|
|
2102
|
+
await store.updateSchedule(record.id, {
|
|
2103
|
+
nextFireAt: next === null ? null : next + jitterFor(record.id, next, spec),
|
|
2104
|
+
state: stripUndefined(nextState),
|
|
2105
|
+
updatedAt: new Date(nowMs)
|
|
2106
|
+
}, record.nextFireAt);
|
|
2107
|
+
return fired ? runId : void 0;
|
|
2108
|
+
}
|
|
2109
|
+
/** `overlap: 'skip'` and the previous run of this schedule is still in flight. */
|
|
2110
|
+
async overlapping(spec, state) {
|
|
2111
|
+
if (spec.overlap !== "skip" || !state.lastRunId) return false;
|
|
2112
|
+
const prev = await this.host.getRun(state.lastRunId);
|
|
2113
|
+
return !!prev && !TERMINAL_RUN_STATUSES.includes(prev.status);
|
|
2114
|
+
}
|
|
2115
|
+
async mustGet(id) {
|
|
2116
|
+
const record = await this.requireStore().getSchedule(id);
|
|
2117
|
+
if (!record) throw new ScheduleNotFoundError(id);
|
|
2118
|
+
return record;
|
|
2119
|
+
}
|
|
2120
|
+
async write(opts, existing) {
|
|
2121
|
+
const store = this.requireStore();
|
|
2122
|
+
const spec = toSpec(opts);
|
|
2123
|
+
const now = this.now();
|
|
2124
|
+
const paused = opts.paused ?? existing?.paused ?? false;
|
|
2125
|
+
const window = nextWindowAfter(spec, now);
|
|
2126
|
+
const record = {
|
|
2127
|
+
id: opts.id,
|
|
2128
|
+
namespace: opts.namespace ?? this.options.namespace ?? DEFAULT_NAMESPACE,
|
|
2129
|
+
workflow: workflowName(opts.workflow),
|
|
2130
|
+
paused,
|
|
2131
|
+
nextFireAt: window === null ? null : window + jitterFor(opts.id, window, spec),
|
|
2132
|
+
tags: opts.tags,
|
|
2133
|
+
spec,
|
|
2134
|
+
state: stripUndefined({
|
|
2135
|
+
...existing?.state ?? {},
|
|
2136
|
+
window
|
|
2137
|
+
}),
|
|
2138
|
+
createdAt: existing?.createdAt ?? new Date(now),
|
|
2139
|
+
updatedAt: new Date(now)
|
|
2140
|
+
};
|
|
2141
|
+
await store.saveSchedule(record);
|
|
2142
|
+
return describe(record);
|
|
2143
|
+
}
|
|
2144
|
+
};
|
|
2145
|
+
function toSpec(opts) {
|
|
2146
|
+
if (opts.cron == null === (opts.every == null)) {
|
|
2147
|
+
throw new Error(`schedule "${opts.id}" needs exactly one of "cron" or "every"`);
|
|
2148
|
+
}
|
|
2149
|
+
const everyMs = opts.every != null ? parseDuration(opts.every) : void 0;
|
|
2150
|
+
if (everyMs !== void 0 && !(everyMs > 0)) {
|
|
2151
|
+
throw new Error(`schedule "${opts.id}": "every" must be a positive duration`);
|
|
2152
|
+
}
|
|
2153
|
+
const jitterMs = opts.jitter != null ? parseDuration(opts.jitter) : void 0;
|
|
2154
|
+
return stripUndefined({
|
|
2155
|
+
v: 1,
|
|
2156
|
+
input: opts.input,
|
|
2157
|
+
cron: opts.cron,
|
|
2158
|
+
everyMs,
|
|
2159
|
+
timezone: opts.timezone,
|
|
2160
|
+
jitterMs: jitterMs && jitterMs > 0 ? jitterMs : void 0,
|
|
2161
|
+
overlap: opts.overlap ?? "allow",
|
|
2162
|
+
catchup: opts.catchup ?? "latest",
|
|
2163
|
+
note: opts.note,
|
|
2164
|
+
searchAttributes: opts.searchAttributes,
|
|
2165
|
+
priority: opts.priority,
|
|
2166
|
+
concurrency: opts.concurrency
|
|
2167
|
+
});
|
|
2168
|
+
}
|
|
2169
|
+
__name(toSpec, "toSpec");
|
|
2170
|
+
function specOf(record) {
|
|
2171
|
+
return record.spec;
|
|
2172
|
+
}
|
|
2173
|
+
__name(specOf, "specOf");
|
|
2174
|
+
function latestWindowAtOrBefore(spec, atMs) {
|
|
2175
|
+
if (spec.cron != null) return prevCronFireMs(spec.cron, atMs, spec.timezone);
|
|
2176
|
+
const every = spec.everyMs;
|
|
2177
|
+
return Math.floor(atMs / every) * every;
|
|
2178
|
+
}
|
|
2179
|
+
__name(latestWindowAtOrBefore, "latestWindowAtOrBefore");
|
|
2180
|
+
function nextWindowAfter(spec, afterMs) {
|
|
2181
|
+
if (spec.cron != null) return nextCronFireMs(spec.cron, afterMs, spec.timezone);
|
|
2182
|
+
const every = spec.everyMs;
|
|
2183
|
+
return (Math.floor(afterMs / every) + 1) * every;
|
|
2184
|
+
}
|
|
2185
|
+
__name(nextWindowAfter, "nextWindowAfter");
|
|
2186
|
+
function jitterFor(id, window, spec) {
|
|
2187
|
+
const max = spec.jitterMs ?? 0;
|
|
2188
|
+
if (max <= 0) return 0;
|
|
2189
|
+
let h = 2166136261;
|
|
2190
|
+
for (const ch of `${id}@${window}`) {
|
|
2191
|
+
h ^= ch.charCodeAt(0);
|
|
2192
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
2193
|
+
}
|
|
2194
|
+
return h % max;
|
|
2195
|
+
}
|
|
2196
|
+
__name(jitterFor, "jitterFor");
|
|
2197
|
+
function startOptions(record) {
|
|
2198
|
+
const spec = specOf(record);
|
|
2199
|
+
return stripUndefined({
|
|
2200
|
+
tags: [
|
|
2201
|
+
...record.tags ?? [],
|
|
2202
|
+
`schedule:${record.id}`
|
|
2203
|
+
],
|
|
2204
|
+
namespace: record.namespace,
|
|
2205
|
+
searchAttributes: spec.searchAttributes,
|
|
2206
|
+
priority: spec.priority,
|
|
2207
|
+
concurrency: spec.concurrency
|
|
2208
|
+
});
|
|
2209
|
+
}
|
|
2210
|
+
__name(startOptions, "startOptions");
|
|
2211
|
+
function describe(record) {
|
|
2212
|
+
const spec = specOf(record);
|
|
2213
|
+
const state = record.state;
|
|
2214
|
+
return stripUndefined({
|
|
2215
|
+
id: record.id,
|
|
2216
|
+
workflow: record.workflow,
|
|
2217
|
+
input: spec.input,
|
|
2218
|
+
namespace: record.namespace,
|
|
2219
|
+
cron: spec.cron,
|
|
2220
|
+
everyMs: spec.everyMs,
|
|
2221
|
+
timezone: spec.timezone,
|
|
2222
|
+
jitterMs: spec.jitterMs,
|
|
2223
|
+
overlap: spec.overlap ?? "allow",
|
|
2224
|
+
catchup: spec.catchup ?? "latest",
|
|
2225
|
+
paused: record.paused,
|
|
2226
|
+
note: spec.note,
|
|
2227
|
+
tags: record.tags,
|
|
2228
|
+
searchAttributes: spec.searchAttributes,
|
|
2229
|
+
priority: spec.priority,
|
|
2230
|
+
concurrency: spec.concurrency,
|
|
2231
|
+
nextFireAt: record.paused || record.nextFireAt == null ? null : new Date(record.nextFireAt),
|
|
2232
|
+
lastFireAt: state.lastFireAt != null ? new Date(state.lastFireAt) : void 0,
|
|
2233
|
+
lastRunId: state.lastRunId,
|
|
2234
|
+
fires: state.fires ?? 0,
|
|
2235
|
+
lastError: state.lastError,
|
|
2236
|
+
createdAt: record.createdAt,
|
|
2237
|
+
updatedAt: record.updatedAt
|
|
2238
|
+
});
|
|
2239
|
+
}
|
|
2240
|
+
__name(describe, "describe");
|
|
2241
|
+
function stripUndefined(o) {
|
|
2242
|
+
return Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0));
|
|
2243
|
+
}
|
|
2244
|
+
__name(stripUndefined, "stripUndefined");
|
|
2245
|
+
function errorMessage(error2) {
|
|
2246
|
+
return error2 instanceof Error ? error2.message : String(error2);
|
|
2247
|
+
}
|
|
2248
|
+
__name(errorMessage, "errorMessage");
|
|
2249
|
+
|
|
1688
2250
|
// src/singleton-gate.ts
|
|
1689
2251
|
var SINGLETON_RETRY_MS = 1e3;
|
|
1690
2252
|
var SINGLETON_RETRY_JITTER_MS = 250;
|
|
@@ -2753,6 +3315,12 @@ var WorkflowEngine = class {
|
|
|
2753
3315
|
runDispatcher;
|
|
2754
3316
|
/** Per-key serialization for singleton workflows (admission, back-pressure, notify-on-release). */
|
|
2755
3317
|
singletons;
|
|
3318
|
+
/**
|
|
3319
|
+
* Persisted, runtime-managed schedules: create/upsert/pause/resume/delete/list/trigger them, and
|
|
3320
|
+
* `tick()` fires the due ones (the NestJS timer poller does it when `persistedSchedules` is on).
|
|
3321
|
+
* Needs a store that persists schedules — every bundled adapter does. See {@link ScheduleClient}.
|
|
3322
|
+
*/
|
|
3323
|
+
schedules;
|
|
2756
3324
|
/** Every registered workflow, keyed by `name@version` — so old versions stay runnable. */
|
|
2757
3325
|
workflows = /* @__PURE__ */ new Map();
|
|
2758
3326
|
/** The newest registered version per workflow name — used to `start` new runs. */
|
|
@@ -2837,6 +3405,13 @@ var WorkflowEngine = class {
|
|
|
2837
3405
|
}, "dispatch"),
|
|
2838
3406
|
configFor: /* @__PURE__ */ __name((run) => (this.workflows.get(versionKey(run.workflow, run.workflowVersion)) ?? this.latest.get(run.workflow))?.singleton, "configFor")
|
|
2839
3407
|
});
|
|
3408
|
+
this.schedules = new ScheduleClient(this.store, {
|
|
3409
|
+
start: /* @__PURE__ */ __name((workflow, input, runId, opts) => this.start(workflow, input, runId, opts), "start"),
|
|
3410
|
+
getRun: /* @__PURE__ */ __name((runId) => this.store.getRun(runId), "getRun")
|
|
3411
|
+
}, {
|
|
3412
|
+
namespace: this.namespace,
|
|
3413
|
+
clock: this.clock
|
|
3414
|
+
});
|
|
2840
3415
|
this.pool.bind(
|
|
2841
3416
|
async (result) => {
|
|
2842
3417
|
const waiter = this.pending.get(result.stepId);
|
|
@@ -2972,6 +3547,7 @@ var WorkflowEngine = class {
|
|
|
2972
3547
|
fn,
|
|
2973
3548
|
tags: opts?.tags,
|
|
2974
3549
|
singleton: opts?.singleton,
|
|
3550
|
+
concurrency: opts?.concurrency,
|
|
2975
3551
|
executionTimeoutMs: opts?.executionTimeout != null ? parseDuration(opts.executionTimeout) : void 0,
|
|
2976
3552
|
validateInput: opts?.validateInput,
|
|
2977
3553
|
searchAttributesSchema: opts?.searchAttributesSchema,
|
|
@@ -3034,6 +3610,7 @@ var WorkflowEngine = class {
|
|
|
3034
3610
|
}, "fn"),
|
|
3035
3611
|
tags: opts.tags,
|
|
3036
3612
|
singleton: opts.singleton,
|
|
3613
|
+
concurrency: opts.concurrency,
|
|
3037
3614
|
executionTimeoutMs: opts.executionTimeout != null ? parseDuration(opts.executionTimeout) : void 0,
|
|
3038
3615
|
validateInput: opts.validateInput,
|
|
3039
3616
|
requires: opts.requires,
|
|
@@ -3205,12 +3782,17 @@ var WorkflowEngine = class {
|
|
|
3205
3782
|
error: prior.error
|
|
3206
3783
|
};
|
|
3207
3784
|
}
|
|
3785
|
+
const quota = await resolveConcurrency(opts?.concurrency, registered.concurrency, input);
|
|
3786
|
+
if (quota) await assertConcurrency(this.store, name, quota);
|
|
3208
3787
|
const now = /* @__PURE__ */ new Date();
|
|
3209
3788
|
const stampedTags = [
|
|
3210
3789
|
...opts?.tags ?? [],
|
|
3211
3790
|
...registered.singleton ? [
|
|
3212
3791
|
this.singletons.tag(registered.singleton, input)
|
|
3213
3792
|
] : [],
|
|
3793
|
+
...quota ? [
|
|
3794
|
+
concurrencyTag(quota.key)
|
|
3795
|
+
] : [],
|
|
3214
3796
|
...registered.versionDeclared === false ? [
|
|
3215
3797
|
VERSION_UNDECLARED_TAG
|
|
3216
3798
|
] : []
|
|
@@ -3516,9 +4098,14 @@ var WorkflowEngine = class {
|
|
|
3516
4098
|
async recoverIncomplete(nowMs = this.clock()) {
|
|
3517
4099
|
if (this.draining) return [];
|
|
3518
4100
|
const results = [];
|
|
3519
|
-
for (const
|
|
3520
|
-
const acquired = await this.store.tryLockRun(
|
|
4101
|
+
for (const listed of await this.store.listIncompleteRuns(this.namespace)) {
|
|
4102
|
+
const acquired = await this.store.tryLockRun(listed.id, this.instanceId, nowMs + this.leaseMs, nowMs);
|
|
3521
4103
|
if (!acquired) continue;
|
|
4104
|
+
const run = await this.store.getRun(listed.id);
|
|
4105
|
+
if (!run || run.status !== "running" && run.status !== "cancelling") {
|
|
4106
|
+
await this.store.releaseRunLock(listed.id);
|
|
4107
|
+
continue;
|
|
4108
|
+
}
|
|
3522
4109
|
if (run.status === "running") {
|
|
3523
4110
|
const dispatched = await this.inFlightRemoteStep(run.id);
|
|
3524
4111
|
if (dispatched) {
|
|
@@ -4243,6 +4830,123 @@ var WorkflowEngine = class {
|
|
|
4243
4830
|
return deleted + 1;
|
|
4244
4831
|
}
|
|
4245
4832
|
/**
|
|
4833
|
+
* Hard-delete EVERY run matching `scope` — plus, by default, each one's whole child subtree — in
|
|
4834
|
+
* bounded batches. The "forget this tenant" / "drop this feature's history" operation: where
|
|
4835
|
+
* {@link deleteRun} removes one tree you already know the id of, this drains everything a scope
|
|
4836
|
+
* selects (`{ namespace: 'acme' }`, `{ tag: 'tenant:acme' }`, `{ workflows: [...] }`, attribute
|
|
4837
|
+
* predicates …) through the store API, so no caller has to reach into the `durable_*` tables.
|
|
4838
|
+
*
|
|
4839
|
+
* - **Live runs** (not yet terminal) are {@link cancel cancelled} first (plain cancel, no saga
|
|
4840
|
+
* undo), which tells any worker holding one to stop, and then deleted with the rest. Pass
|
|
4841
|
+
* `cancelLive: false` to leave live runs (and live descendants of finished ones) alone and
|
|
4842
|
+
* delete terminal history only.
|
|
4843
|
+
* - **Children** are found through the same parent→child edges as {@link getRunChildren} and deleted
|
|
4844
|
+
* with their root even when they don't match `scope` themselves (children inherit `namespace`,
|
|
4845
|
+
* but not `tags`). `children: false` deletes exactly the matching runs.
|
|
4846
|
+
* - **Batched**: `batchSize` runs (default 500) per round, each round one bulk
|
|
4847
|
+
* {@link StateStore.deleteRuns} (falling back to per-run {@link StateStore.deleteRun}).
|
|
4848
|
+
*
|
|
4849
|
+
* An EMPTY scope is rejected — it would delete every run in the store. Returns how many runs were
|
|
4850
|
+
* deleted (children included).
|
|
4851
|
+
*/
|
|
4852
|
+
async purgeRuns(scope, opts) {
|
|
4853
|
+
if (!isNonEmptyRunScope(scope)) {
|
|
4854
|
+
throw new Error("purgeRuns: refusing an empty scope (it would delete every run) \u2014 pass at least one predicate, e.g. { namespace }");
|
|
4855
|
+
}
|
|
4856
|
+
const batchSize = Math.max(1, opts?.batchSize ?? 500);
|
|
4857
|
+
const cancelLive = opts?.cancelLive ?? true;
|
|
4858
|
+
const withChildren = opts?.children ?? true;
|
|
4859
|
+
const statuses = cancelLive ? void 0 : [
|
|
4860
|
+
...TERMINAL_RUN_STATUSES
|
|
4861
|
+
];
|
|
4862
|
+
let deleted = 0;
|
|
4863
|
+
let previous = "";
|
|
4864
|
+
for (; ; ) {
|
|
4865
|
+
const batch = await this.store.listRuns({
|
|
4866
|
+
...scope,
|
|
4867
|
+
statuses,
|
|
4868
|
+
limit: batchSize
|
|
4869
|
+
});
|
|
4870
|
+
if (batch.length === 0) break;
|
|
4871
|
+
const fingerprint = batch.map((r) => r.id).join("\0");
|
|
4872
|
+
if (fingerprint === previous) {
|
|
4873
|
+
throw new Error("purgeRuns: the store returned the same runs after deleting them");
|
|
4874
|
+
}
|
|
4875
|
+
previous = fingerprint;
|
|
4876
|
+
const known = new Map(batch.map((r) => [
|
|
4877
|
+
r.id,
|
|
4878
|
+
r.status
|
|
4879
|
+
]));
|
|
4880
|
+
const ids = withChildren ? await this.collectSubtrees(batch.map((r) => r.id)) : batch.map((r) => r.id);
|
|
4881
|
+
const doomed = [];
|
|
4882
|
+
for (const id of ids) {
|
|
4883
|
+
const status = known.has(id) ? known.get(id) : (await this.store.getRun(id))?.status;
|
|
4884
|
+
if (status === void 0) continue;
|
|
4885
|
+
if (!TERMINAL_RUN_STATUSES.includes(status)) {
|
|
4886
|
+
if (!cancelLive) continue;
|
|
4887
|
+
await this.cancel(id).catch(() => void 0);
|
|
4888
|
+
}
|
|
4889
|
+
doomed.push(id);
|
|
4890
|
+
}
|
|
4891
|
+
await this.deleteRunIds(doomed);
|
|
4892
|
+
deleted += doomed.length;
|
|
4893
|
+
if (batch.length < batchSize) break;
|
|
4894
|
+
}
|
|
4895
|
+
return deleted;
|
|
4896
|
+
}
|
|
4897
|
+
/**
|
|
4898
|
+
* {@link purgeRuns} for one namespace — every run (and child) a tenant/partition owns. Children
|
|
4899
|
+
* inherit their parent's namespace, so a namespace purge is complete by construction.
|
|
4900
|
+
*/
|
|
4901
|
+
purgeNamespace(namespace, opts) {
|
|
4902
|
+
return this.purgeRuns({
|
|
4903
|
+
namespace
|
|
4904
|
+
}, opts);
|
|
4905
|
+
}
|
|
4906
|
+
/** The given runs plus every descendant, children before parents. One waiter scan for the batch. */
|
|
4907
|
+
async collectSubtrees(rootIds) {
|
|
4908
|
+
const waitersByParent = /* @__PURE__ */ new Map();
|
|
4909
|
+
for (const w of await this.store.listSignalWaiters("child:")) {
|
|
4910
|
+
const list = waitersByParent.get(w.runId) ?? [];
|
|
4911
|
+
list.push(w.token.slice("child:".length));
|
|
4912
|
+
waitersByParent.set(w.runId, list);
|
|
4913
|
+
}
|
|
4914
|
+
const prefixes = [
|
|
4915
|
+
"signal:child:",
|
|
4916
|
+
"spawn:"
|
|
4917
|
+
];
|
|
4918
|
+
const childrenOf = /* @__PURE__ */ __name(async (parent) => {
|
|
4919
|
+
const ids = new Set(waitersByParent.get(parent) ?? []);
|
|
4920
|
+
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)));
|
|
4921
|
+
for (const cp of cps) {
|
|
4922
|
+
if (cp.name.startsWith("signal:child:")) ids.add(cp.name.slice("signal:child:".length));
|
|
4923
|
+
if (cp.name.startsWith("spawn:") && typeof cp.output === "string") ids.add(cp.output);
|
|
4924
|
+
}
|
|
4925
|
+
return [
|
|
4926
|
+
...ids
|
|
4927
|
+
];
|
|
4928
|
+
}, "childrenOf");
|
|
4929
|
+
const ordered = [];
|
|
4930
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4931
|
+
const visit = /* @__PURE__ */ __name(async (id) => {
|
|
4932
|
+
if (seen.has(id)) return;
|
|
4933
|
+
seen.add(id);
|
|
4934
|
+
for (const child of await childrenOf(id)) await visit(child);
|
|
4935
|
+
ordered.push(id);
|
|
4936
|
+
}, "visit");
|
|
4937
|
+
for (const id of rootIds) await visit(id);
|
|
4938
|
+
return ordered;
|
|
4939
|
+
}
|
|
4940
|
+
/** Bulk-delete through the store's `deleteRuns`, or one `deleteRun` per id when it has none. */
|
|
4941
|
+
async deleteRunIds(ids) {
|
|
4942
|
+
if (ids.length === 0) return;
|
|
4943
|
+
if (this.store.deleteRuns) {
|
|
4944
|
+
await this.store.deleteRuns(ids);
|
|
4945
|
+
return;
|
|
4946
|
+
}
|
|
4947
|
+
for (const id of ids) await this.store.deleteRun(id);
|
|
4948
|
+
}
|
|
4949
|
+
/**
|
|
4246
4950
|
* Cascade cancellation to a run's children — both awaited (`ctx.child`, found via its live
|
|
4247
4951
|
* `child:<id>` waiter) and fire-and-forget (`ctx.startChild`, found via its `spawn:<id>`
|
|
4248
4952
|
* checkpoint). Recursive, so a whole subtree is cancelled; the terminal guard in `cancel` stops it
|
|
@@ -5378,16 +6082,16 @@ var WorkflowEngine = class {
|
|
|
5378
6082
|
try {
|
|
5379
6083
|
const output = await runInWorkflowCtx(ctx, () => fn(ctx, run.input));
|
|
5380
6084
|
if (this.cancelRequested.has(run.id)) {
|
|
5381
|
-
return this.compensateAndCancel(run, compensations, replay);
|
|
6085
|
+
return await this.compensateAndCancel(run, compensations, replay);
|
|
5382
6086
|
}
|
|
5383
|
-
return this.settleRun(run, {
|
|
6087
|
+
return await this.settleRun(run, {
|
|
5384
6088
|
kind: "completed",
|
|
5385
6089
|
output
|
|
5386
6090
|
});
|
|
5387
6091
|
} catch (err) {
|
|
5388
6092
|
if (err instanceof ContinueAsNew) {
|
|
5389
6093
|
if (this.cancelRequested.has(run.id)) {
|
|
5390
|
-
return this.compensateAndCancel(run, compensations, replay);
|
|
6094
|
+
return await this.compensateAndCancel(run, compensations, replay);
|
|
5391
6095
|
}
|
|
5392
6096
|
await this.store.updateRun(run.id, {
|
|
5393
6097
|
status: "completed",
|
|
@@ -5414,35 +6118,35 @@ var WorkflowEngine = class {
|
|
|
5414
6118
|
}
|
|
5415
6119
|
if (err instanceof WorkflowBlocked) {
|
|
5416
6120
|
if (this.cancelRequested.has(run.id)) {
|
|
5417
|
-
return this.compensateAndCancel(run, compensations, replay);
|
|
6121
|
+
return await this.compensateAndCancel(run, compensations, replay);
|
|
5418
6122
|
}
|
|
5419
|
-
return this.settleRun(run, {
|
|
6123
|
+
return await this.settleRun(run, {
|
|
5420
6124
|
kind: "blocked",
|
|
5421
6125
|
blocked: err.plan
|
|
5422
6126
|
});
|
|
5423
6127
|
}
|
|
5424
6128
|
if (err instanceof WorkflowSuspended) {
|
|
5425
6129
|
if (this.cancelRequested.has(run.id)) {
|
|
5426
|
-
return this.compensateAndCancel(run, compensations, replay);
|
|
6130
|
+
return await this.compensateAndCancel(run, compensations, replay);
|
|
5427
6131
|
}
|
|
5428
|
-
return this.settleRun(run, {
|
|
6132
|
+
return await this.settleRun(run, {
|
|
5429
6133
|
kind: "suspended",
|
|
5430
6134
|
wakeAt: err.wakeAt
|
|
5431
6135
|
});
|
|
5432
6136
|
}
|
|
5433
6137
|
if (this.cancelRequested.has(run.id)) {
|
|
5434
|
-
return this.compensateAndCancel(run, compensations, replay);
|
|
6138
|
+
return await this.compensateAndCancel(run, compensations, replay);
|
|
5435
6139
|
}
|
|
5436
6140
|
const error2 = {
|
|
5437
6141
|
message: err instanceof Error ? err.message : String(err),
|
|
5438
6142
|
stack: err instanceof Error ? err.stack : void 0
|
|
5439
6143
|
};
|
|
5440
6144
|
const pending = await this.unwindCompensations(run, compensations, replay);
|
|
5441
|
-
if (pending) return this.settleRun(run, {
|
|
6145
|
+
if (pending) return await this.settleRun(run, {
|
|
5442
6146
|
kind: "suspended",
|
|
5443
6147
|
wakeAt: pending.wakeAt
|
|
5444
6148
|
});
|
|
5445
|
-
return this.settleRun(run, {
|
|
6149
|
+
return await this.settleRun(run, {
|
|
5446
6150
|
kind: "failed",
|
|
5447
6151
|
error: error2
|
|
5448
6152
|
});
|
|
@@ -5856,7 +6560,10 @@ var WorkflowEngine = class {
|
|
|
5856
6560
|
}
|
|
5857
6561
|
async callRemote(runId, seq, step, input, queue, transport, replay, admission, routing) {
|
|
5858
6562
|
const group = stepGroup(step, routing?.namespace);
|
|
5859
|
-
|
|
6563
|
+
let existing = replay?.get(seq) ?? await this.store.getCheckpoint(runId, seq);
|
|
6564
|
+
if (existing?.status === "pending" && replay?.has(seq)) {
|
|
6565
|
+
existing = await this.store.getCheckpoint(runId, seq) ?? existing;
|
|
6566
|
+
}
|
|
5860
6567
|
if (existing && existing.name !== step.name) {
|
|
5861
6568
|
throw new NonDeterminismError(runId, seq, step.name, existing.name);
|
|
5862
6569
|
}
|
|
@@ -6260,14 +6967,6 @@ var RemoteStepError = class extends Error {
|
|
|
6260
6967
|
}
|
|
6261
6968
|
};
|
|
6262
6969
|
|
|
6263
|
-
// src/interfaces.ts
|
|
6264
|
-
var TERMINAL_RUN_STATUSES = [
|
|
6265
|
-
"completed",
|
|
6266
|
-
"failed",
|
|
6267
|
-
"cancelled",
|
|
6268
|
-
"dead"
|
|
6269
|
-
];
|
|
6270
|
-
|
|
6271
6970
|
// src/run-facets.ts
|
|
6272
6971
|
function facetOrigin(origin) {
|
|
6273
6972
|
return origin?.trim() ? origin : null;
|
|
@@ -6299,7 +6998,8 @@ __name(mergeRunFacetRows, "mergeRunFacetRows");
|
|
|
6299
6998
|
var RUN_VALUE_FACET_LIMIT = 100;
|
|
6300
6999
|
var RUN_VALUE_FACET_SCAN = 5e3;
|
|
6301
7000
|
var ENGINE_MINTED_TAG_PREFIXES = [
|
|
6302
|
-
"singleton:"
|
|
7001
|
+
"singleton:",
|
|
7002
|
+
"concurrency:"
|
|
6303
7003
|
];
|
|
6304
7004
|
function isEngineMintedTag(value) {
|
|
6305
7005
|
return value !== null && ENGINE_MINTED_TAG_PREFIXES.some((prefix) => value.startsWith(prefix));
|
|
@@ -6404,9 +7104,42 @@ var CodecStateStore = class {
|
|
|
6404
7104
|
* Bound the same conditional way as {@link runFacets} so absence is reported, not faked.
|
|
6405
7105
|
*/
|
|
6406
7106
|
runValueFacets;
|
|
7107
|
+
/** Forwarded verbatim (deletes touch no payload). Bound only when the inner store has it, so the
|
|
7108
|
+
* engine's per-run fallback still kicks in through the wrapper. */
|
|
7109
|
+
deleteRuns;
|
|
7110
|
+
/** Forwarded verbatim — retention selects by status/updatedAt/scope, never by payload. Bound only
|
|
7111
|
+
* when the inner store prunes, so the retention poller still sees (and warns about) its absence. */
|
|
7112
|
+
pruneTerminalRuns;
|
|
7113
|
+
/** Forwarded verbatim — a count reads no payload. Bound only when the inner store counts, so the
|
|
7114
|
+
* engine's facet/listing fallback still applies through the wrapper. */
|
|
7115
|
+
countRuns;
|
|
7116
|
+
/** Schedules persist their run `input` inside `spec` — encoded like any other payload. Bound only
|
|
7117
|
+
* when the inner store persists schedules, so `engine.schedules` reports absence faithfully. */
|
|
7118
|
+
saveSchedule;
|
|
7119
|
+
getSchedule;
|
|
7120
|
+
updateSchedule;
|
|
7121
|
+
deleteSchedule;
|
|
7122
|
+
listSchedules;
|
|
6407
7123
|
constructor(inner, codec) {
|
|
6408
7124
|
this.inner = inner;
|
|
6409
7125
|
this.codec = codec;
|
|
7126
|
+
const deleteRuns = inner.deleteRuns;
|
|
7127
|
+
if (deleteRuns) this.deleteRuns = (ids) => deleteRuns.call(inner, ids);
|
|
7128
|
+
const prune = inner.pruneTerminalRuns;
|
|
7129
|
+
if (prune) this.pruneTerminalRuns = (policy, now, limit) => prune.call(inner, policy, now, limit);
|
|
7130
|
+
const count = inner.countRuns;
|
|
7131
|
+
if (count) this.countRuns = (query) => count.call(inner, query);
|
|
7132
|
+
const { saveSchedule, getSchedule, updateSchedule, deleteSchedule, listSchedules } = inner;
|
|
7133
|
+
if (saveSchedule && getSchedule && updateSchedule && deleteSchedule && listSchedules) {
|
|
7134
|
+
this.saveSchedule = (r) => saveSchedule.call(inner, this.encSchedule(r));
|
|
7135
|
+
this.getSchedule = async (id) => this.decSchedule(await getSchedule.call(inner, id));
|
|
7136
|
+
this.updateSchedule = (id, patch, expected) => updateSchedule.call(inner, id, patch.spec ? {
|
|
7137
|
+
...patch,
|
|
7138
|
+
spec: this.encSpec(patch.spec)
|
|
7139
|
+
} : patch, expected);
|
|
7140
|
+
this.deleteSchedule = (id) => deleteSchedule.call(inner, id);
|
|
7141
|
+
this.listSchedules = async (q) => (await listSchedules.call(inner, q)).map((r) => this.decSchedule(r));
|
|
7142
|
+
}
|
|
6410
7143
|
const facets = inner.runFacets;
|
|
6411
7144
|
if (facets) this.runFacets = (query) => facets.call(inner, query);
|
|
6412
7145
|
const valueFacets = inner.runValueFacets;
|
|
@@ -6448,6 +7181,28 @@ var CodecStateStore = class {
|
|
|
6448
7181
|
output: this.dec(cp.output)
|
|
6449
7182
|
};
|
|
6450
7183
|
}
|
|
7184
|
+
encSpec(spec) {
|
|
7185
|
+
return "input" in spec ? {
|
|
7186
|
+
...spec,
|
|
7187
|
+
input: this.enc(spec.input)
|
|
7188
|
+
} : spec;
|
|
7189
|
+
}
|
|
7190
|
+
encSchedule(r) {
|
|
7191
|
+
return {
|
|
7192
|
+
...r,
|
|
7193
|
+
spec: this.encSpec(r.spec)
|
|
7194
|
+
};
|
|
7195
|
+
}
|
|
7196
|
+
decSchedule(r) {
|
|
7197
|
+
if (!r || !("input" in r.spec)) return r;
|
|
7198
|
+
return {
|
|
7199
|
+
...r,
|
|
7200
|
+
spec: {
|
|
7201
|
+
...r.spec,
|
|
7202
|
+
input: this.dec(r.spec.input)
|
|
7203
|
+
}
|
|
7204
|
+
};
|
|
7205
|
+
}
|
|
6451
7206
|
ensureSchema() {
|
|
6452
7207
|
return this.inner.ensureSchema?.() ?? Promise.resolve();
|
|
6453
7208
|
}
|
|
@@ -6646,126 +7401,6 @@ function collectMetrics(engine) {
|
|
|
6646
7401
|
}
|
|
6647
7402
|
__name(collectMetrics, "collectMetrics");
|
|
6648
7403
|
|
|
6649
|
-
// src/scheduler.ts
|
|
6650
|
-
import { createRequire } from "node:module";
|
|
6651
|
-
|
|
6652
|
-
// src/cron-compat.ts
|
|
6653
|
-
function isIndexable(value) {
|
|
6654
|
-
return typeof value === "object" && value !== null || typeof value === "function";
|
|
6655
|
-
}
|
|
6656
|
-
__name(isIndexable, "isIndexable");
|
|
6657
|
-
function unwrapDefault(module_) {
|
|
6658
|
-
if (!isIndexable(module_)) return module_;
|
|
6659
|
-
if (Reflect.get(module_, "parseExpression") !== void 0) return module_;
|
|
6660
|
-
if (Reflect.get(module_, "CronExpressionParser") !== void 0) return module_;
|
|
6661
|
-
const inner = Reflect.get(module_, "default");
|
|
6662
|
-
return inner === void 0 ? module_ : inner;
|
|
6663
|
-
}
|
|
6664
|
-
__name(unwrapDefault, "unwrapDefault");
|
|
6665
|
-
function v4Entry(module_) {
|
|
6666
|
-
if (!isIndexable(module_)) return void 0;
|
|
6667
|
-
const parse = Reflect.get(module_, "parseExpression");
|
|
6668
|
-
if (typeof parse !== "function") return void 0;
|
|
6669
|
-
return (expr, options) => parse.call(module_, expr, options);
|
|
6670
|
-
}
|
|
6671
|
-
__name(v4Entry, "v4Entry");
|
|
6672
|
-
function v5Entry(module_) {
|
|
6673
|
-
if (!isIndexable(module_)) return void 0;
|
|
6674
|
-
const parser = Reflect.get(module_, "CronExpressionParser");
|
|
6675
|
-
if (!isIndexable(parser)) return void 0;
|
|
6676
|
-
const parse = Reflect.get(parser, "parse");
|
|
6677
|
-
if (typeof parse !== "function") return void 0;
|
|
6678
|
-
return (expr, options) => parse.call(parser, expr, options);
|
|
6679
|
-
}
|
|
6680
|
-
__name(v5Entry, "v5Entry");
|
|
6681
|
-
function resolveCronParse(module_) {
|
|
6682
|
-
const resolved = unwrapDefault(module_);
|
|
6683
|
-
return v4Entry(resolved) ?? v5Entry(resolved);
|
|
6684
|
-
}
|
|
6685
|
-
__name(resolveCronParse, "resolveCronParse");
|
|
6686
|
-
|
|
6687
|
-
// src/scheduler.ts
|
|
6688
|
-
var nodeRequire = createRequire(import.meta.url);
|
|
6689
|
-
var defaultSleep = /* @__PURE__ */ __name((ms) => new Promise((r) => setTimeout(r, ms)), "defaultSleep");
|
|
6690
|
-
function scheduledRunId(key, everyMs, nowMs) {
|
|
6691
|
-
return `sched:${key}:${Math.floor(nowMs / everyMs)}`;
|
|
6692
|
-
}
|
|
6693
|
-
__name(scheduledRunId, "scheduledRunId");
|
|
6694
|
-
var cronParser;
|
|
6695
|
-
function loadCronParser() {
|
|
6696
|
-
if (cronParser !== void 0) return cronParser;
|
|
6697
|
-
try {
|
|
6698
|
-
cronParser = nodeRequire("cron-parser");
|
|
6699
|
-
} catch {
|
|
6700
|
-
throw new Error('cron schedules need the optional peer dependency "cron-parser" \u2014 install it (e.g. `npm i cron-parser`).');
|
|
6701
|
-
}
|
|
6702
|
-
return cronParser;
|
|
6703
|
-
}
|
|
6704
|
-
__name(loadCronParser, "loadCronParser");
|
|
6705
|
-
function prevCronFireMs(expr, nowMs, timezone = "UTC") {
|
|
6706
|
-
const parse = resolveCronParse(loadCronParser());
|
|
6707
|
-
if (!parse) {
|
|
6708
|
-
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.`);
|
|
6709
|
-
}
|
|
6710
|
-
return parse(expr, {
|
|
6711
|
-
currentDate: new Date(nowMs + 1),
|
|
6712
|
-
tz: timezone
|
|
6713
|
-
}).prev().toDate().getTime();
|
|
6714
|
-
}
|
|
6715
|
-
__name(prevCronFireMs, "prevCronFireMs");
|
|
6716
|
-
function scheduleRunIdAt(s, nowMs) {
|
|
6717
|
-
if (s.cron != null) return `sched:${s.key}:${prevCronFireMs(s.cron, nowMs, s.timezone)}`;
|
|
6718
|
-
if (s.everyMs != null) return scheduledRunId(s.key, s.everyMs, nowMs);
|
|
6719
|
-
throw new Error(`schedule "${s.key}" needs either "everyMs" or "cron"`);
|
|
6720
|
-
}
|
|
6721
|
-
__name(scheduleRunIdAt, "scheduleRunIdAt");
|
|
6722
|
-
function scheduleRunIdsAt(s, nowMs) {
|
|
6723
|
-
const current = scheduleRunIdAt(s, nowMs);
|
|
6724
|
-
if (!s.backfill || s.backfill.maxCatchup <= 0) return [
|
|
6725
|
-
current
|
|
6726
|
-
];
|
|
6727
|
-
const ids = [];
|
|
6728
|
-
if (s.everyMs != null) {
|
|
6729
|
-
const bucket = Math.floor(nowMs / s.everyMs);
|
|
6730
|
-
const oldest = Math.max(0, bucket - s.backfill.maxCatchup);
|
|
6731
|
-
for (let b = oldest; b < bucket; b += 1) ids.push(`sched:${s.key}:${b}`);
|
|
6732
|
-
} else if (s.cron != null) {
|
|
6733
|
-
const prior = [];
|
|
6734
|
-
let cursor = prevCronFireMs(s.cron, nowMs, s.timezone);
|
|
6735
|
-
for (let i = 0; i < s.backfill.maxCatchup; i += 1) {
|
|
6736
|
-
const before = prevCronFireMs(s.cron, cursor - 1, s.timezone);
|
|
6737
|
-
prior.push(`sched:${s.key}:${before}`);
|
|
6738
|
-
cursor = before;
|
|
6739
|
-
}
|
|
6740
|
-
ids.push(...prior.reverse());
|
|
6741
|
-
}
|
|
6742
|
-
ids.push(current);
|
|
6743
|
-
return ids;
|
|
6744
|
-
}
|
|
6745
|
-
__name(scheduleRunIdsAt, "scheduleRunIdsAt");
|
|
6746
|
-
async function runSchedules(engine, schedules, nowMs, opts) {
|
|
6747
|
-
const random = opts?.random ?? Math.random;
|
|
6748
|
-
const sleep = opts?.sleep ?? defaultSleep;
|
|
6749
|
-
const ids = [];
|
|
6750
|
-
for (const s of schedules) {
|
|
6751
|
-
if (s.paused) continue;
|
|
6752
|
-
if (s.overlap === "skip" && s.everyMs) {
|
|
6753
|
-
const prevBucket = Math.floor(nowMs / s.everyMs) - 1;
|
|
6754
|
-
if (prevBucket >= 0) {
|
|
6755
|
-
const prev = await engine.getRun(`sched:${s.key}:${prevBucket}`);
|
|
6756
|
-
if (prev && (prev.status === "running" || prev.status === "suspended")) continue;
|
|
6757
|
-
}
|
|
6758
|
-
}
|
|
6759
|
-
if (s.jitter && s.jitter > 0) await sleep(Math.floor(random() * s.jitter));
|
|
6760
|
-
for (const runId of scheduleRunIdsAt(s, nowMs)) {
|
|
6761
|
-
await engine.start(s.workflow, s.input, runId);
|
|
6762
|
-
ids.push(runId);
|
|
6763
|
-
}
|
|
6764
|
-
}
|
|
6765
|
-
return ids;
|
|
6766
|
-
}
|
|
6767
|
-
__name(runSchedules, "runSchedules");
|
|
6768
|
-
|
|
6769
7404
|
// src/search-attributes.ts
|
|
6770
7405
|
function compare(actual, filter) {
|
|
6771
7406
|
if (filter.op === "in") return filter.values.some((v) => actual === v);
|
|
@@ -6973,6 +7608,38 @@ var InMemoryStateStore = class {
|
|
|
6973
7608
|
}
|
|
6974
7609
|
this.reindexAttributes(runId, void 0);
|
|
6975
7610
|
}
|
|
7611
|
+
async deleteRuns(runIds) {
|
|
7612
|
+
for (const runId of runIds) await this.deleteRun(runId);
|
|
7613
|
+
}
|
|
7614
|
+
async pruneTerminalRuns(policy, nowMs, limit) {
|
|
7615
|
+
const statuses = policy.statuses.filter((s) => TERMINAL_RUN_STATUSES.includes(s));
|
|
7616
|
+
if (statuses.length === 0 || limit <= 0) return 0;
|
|
7617
|
+
if (policy.maxAge == null && policy.maxCount == null) return 0;
|
|
7618
|
+
const inScope = this.matching({
|
|
7619
|
+
...policy.scope ?? {},
|
|
7620
|
+
statuses
|
|
7621
|
+
});
|
|
7622
|
+
const ids = /* @__PURE__ */ new Set();
|
|
7623
|
+
if (policy.maxAge != null) {
|
|
7624
|
+
const cutoff = nowMs - parseDuration(policy.maxAge);
|
|
7625
|
+
const expired = inScope.filter((r) => r.updatedAt.getTime() < cutoff).sort((a, b) => a.updatedAt.getTime() - b.updatedAt.getTime()).slice(0, limit);
|
|
7626
|
+
for (const r of expired) ids.add(r.id);
|
|
7627
|
+
}
|
|
7628
|
+
if (policy.maxCount != null && ids.size < limit) {
|
|
7629
|
+
const byRecency = [
|
|
7630
|
+
...inScope
|
|
7631
|
+
].sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime() || b.id.localeCompare(a.id));
|
|
7632
|
+
for (const r of byRecency.slice(policy.maxCount)) {
|
|
7633
|
+
if (ids.size >= limit) break;
|
|
7634
|
+
ids.add(r.id);
|
|
7635
|
+
}
|
|
7636
|
+
}
|
|
7637
|
+
const doomed = [
|
|
7638
|
+
...ids
|
|
7639
|
+
].slice(0, limit);
|
|
7640
|
+
await this.deleteRuns(doomed);
|
|
7641
|
+
return doomed.length;
|
|
7642
|
+
}
|
|
6976
7643
|
async getCheckpoint(runId, seq) {
|
|
6977
7644
|
const cp = this.checkpoints.get(this.key(runId, seq));
|
|
6978
7645
|
return cp ? {
|
|
@@ -7180,6 +7847,9 @@ var InMemoryStateStore = class {
|
|
|
7180
7847
|
...r
|
|
7181
7848
|
}));
|
|
7182
7849
|
}
|
|
7850
|
+
async countRuns(query) {
|
|
7851
|
+
return this.matching(query).length;
|
|
7852
|
+
}
|
|
7183
7853
|
async runFacets(query) {
|
|
7184
7854
|
return mergeRunFacetRows(this.matching(query).map((run) => ({
|
|
7185
7855
|
status: run.status,
|
|
@@ -7197,6 +7867,34 @@ var InMemoryStateStore = class {
|
|
|
7197
7867
|
});
|
|
7198
7868
|
return runValueFacetsFromRuns(runs, axis, opts);
|
|
7199
7869
|
}
|
|
7870
|
+
schedules = /* @__PURE__ */ new Map();
|
|
7871
|
+
async saveSchedule(record) {
|
|
7872
|
+
this.schedules.set(record.id, structuredClone(record));
|
|
7873
|
+
}
|
|
7874
|
+
async getSchedule(id) {
|
|
7875
|
+
const record = this.schedules.get(id);
|
|
7876
|
+
return record ? structuredClone(record) : null;
|
|
7877
|
+
}
|
|
7878
|
+
async updateSchedule(id, patch, expectedNextFireAt) {
|
|
7879
|
+
const record = this.schedules.get(id);
|
|
7880
|
+
if (!record) return false;
|
|
7881
|
+
if (expectedNextFireAt !== void 0 && record.nextFireAt !== expectedNextFireAt) return false;
|
|
7882
|
+
this.schedules.set(id, {
|
|
7883
|
+
...record,
|
|
7884
|
+
...structuredClone(patch)
|
|
7885
|
+
});
|
|
7886
|
+
return true;
|
|
7887
|
+
}
|
|
7888
|
+
async deleteSchedule(id) {
|
|
7889
|
+
return this.schedules.delete(id);
|
|
7890
|
+
}
|
|
7891
|
+
async listSchedules(query) {
|
|
7892
|
+
const rows = [
|
|
7893
|
+
...this.schedules.values()
|
|
7894
|
+
].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));
|
|
7895
|
+
const offset = query.offset ?? 0;
|
|
7896
|
+
return rows.slice(offset, query.limit != null ? offset + query.limit : void 0).map((s) => structuredClone(s));
|
|
7897
|
+
}
|
|
7200
7898
|
async listCheckpoints(runId) {
|
|
7201
7899
|
const rows = [
|
|
7202
7900
|
...this.checkpoints.values()
|
|
@@ -7277,7 +7975,9 @@ export {
|
|
|
7277
7975
|
CONTROL_FLOW_SIGNAL,
|
|
7278
7976
|
CURRENT_PROTOCOL_VERSION,
|
|
7279
7977
|
CodecStateStore,
|
|
7978
|
+
ConcurrencyLimitError,
|
|
7280
7979
|
ContinueAsNew,
|
|
7980
|
+
DEFAULT_CONCURRENCY_STATUSES,
|
|
7281
7981
|
DEFAULT_PARTITION,
|
|
7282
7982
|
DURABLE_OPTIONS,
|
|
7283
7983
|
DURABLE_OPTIONS_CANONICAL,
|
|
@@ -7307,6 +8007,9 @@ export {
|
|
|
7307
8007
|
RunGateway,
|
|
7308
8008
|
STATE_STORE,
|
|
7309
8009
|
STATE_STORE_CANONICAL,
|
|
8010
|
+
ScheduleAlreadyExistsError,
|
|
8011
|
+
ScheduleClient,
|
|
8012
|
+
ScheduleNotFoundError,
|
|
7310
8013
|
SignalTimeoutError,
|
|
7311
8014
|
SingletonQueueFullError,
|
|
7312
8015
|
TERMINAL_RUN_STATUSES,
|
|
@@ -7320,6 +8023,7 @@ export {
|
|
|
7320
8023
|
aggregateAnnouncements,
|
|
7321
8024
|
announcementsOf,
|
|
7322
8025
|
applyAttributeQuery,
|
|
8026
|
+
assertConcurrency,
|
|
7323
8027
|
attributeColumnFor,
|
|
7324
8028
|
attributeOperand,
|
|
7325
8029
|
attributePredicateOperands,
|
|
@@ -7332,7 +8036,9 @@ export {
|
|
|
7332
8036
|
classifyWaiterToken,
|
|
7333
8037
|
clearDurableExecutionWrappers,
|
|
7334
8038
|
collectMetrics,
|
|
8039
|
+
concurrencyTag,
|
|
7335
8040
|
controlPlaneDescriptor,
|
|
8041
|
+
countRuns,
|
|
7336
8042
|
coveredTokens,
|
|
7337
8043
|
createStepLogger,
|
|
7338
8044
|
currentStep,
|
|
@@ -7354,6 +8060,7 @@ export {
|
|
|
7354
8060
|
isEngineMintedTag,
|
|
7355
8061
|
isEventToken,
|
|
7356
8062
|
isLegacyDescriptor,
|
|
8063
|
+
isNonEmptyRunScope,
|
|
7357
8064
|
isWorkflowControlFlowSignal,
|
|
7358
8065
|
log,
|
|
7359
8066
|
matchesAttributes,
|
|
@@ -7361,6 +8068,7 @@ export {
|
|
|
7361
8068
|
mergeRunValueFacetRows,
|
|
7362
8069
|
missingCapabilities,
|
|
7363
8070
|
negotiate,
|
|
8071
|
+
nextCronFireMs,
|
|
7364
8072
|
normalizeAttributeRows,
|
|
7365
8073
|
normalizeDescriptor,
|
|
7366
8074
|
observedAnnouncements,
|
|
@@ -7370,6 +8078,7 @@ export {
|
|
|
7370
8078
|
prevCronFireMs,
|
|
7371
8079
|
readSearchAttributes,
|
|
7372
8080
|
requiredCapabilities,
|
|
8081
|
+
resolveConcurrency,
|
|
7373
8082
|
resolveRouting,
|
|
7374
8083
|
resolveRunWaiting,
|
|
7375
8084
|
runDurableExecution,
|