@dudousxd/nestjs-durable-core 0.71.1 → 0.72.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -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,
@@ -44,6 +46,7 @@ __export(index_exports, {
44
46
  InMemoryTransport: () => InMemoryTransport,
45
47
  LEGACY_V1_CAPABILITIES: () => LEGACY_V1_CAPABILITIES,
46
48
  LEGACY_V1_PROTOCOL: () => LEGACY_V1_PROTOCOL,
49
+ NestedWorkflowCallError: () => NestedWorkflowCallError,
47
50
  NonDeterminismError: () => NonDeterminismError,
48
51
  QueueController: () => QueueController,
49
52
  RUN_VALUE_FACET_LIMIT: () => RUN_VALUE_FACET_LIMIT,
@@ -55,6 +58,9 @@ __export(index_exports, {
55
58
  RunGateway: () => RunGateway,
56
59
  STATE_STORE: () => STATE_STORE,
57
60
  STATE_STORE_CANONICAL: () => STATE_STORE_CANONICAL,
61
+ ScheduleAlreadyExistsError: () => ScheduleAlreadyExistsError,
62
+ ScheduleClient: () => ScheduleClient,
63
+ ScheduleNotFoundError: () => ScheduleNotFoundError,
58
64
  SignalTimeoutError: () => SignalTimeoutError,
59
65
  SingletonQueueFullError: () => SingletonQueueFullError,
60
66
  TERMINAL_RUN_STATUSES: () => TERMINAL_RUN_STATUSES,
@@ -68,6 +74,7 @@ __export(index_exports, {
68
74
  aggregateAnnouncements: () => aggregateAnnouncements,
69
75
  announcementsOf: () => announcementsOf,
70
76
  applyAttributeQuery: () => applyAttributeQuery,
77
+ assertConcurrency: () => assertConcurrency,
71
78
  attributeColumnFor: () => attributeColumnFor,
72
79
  attributeOperand: () => attributeOperand,
73
80
  attributePredicateOperands: () => attributePredicateOperands,
@@ -80,10 +87,13 @@ __export(index_exports, {
80
87
  classifyWaiterToken: () => classifyWaiterToken,
81
88
  clearDurableExecutionWrappers: () => clearDurableExecutionWrappers,
82
89
  collectMetrics: () => collectMetrics,
90
+ concurrencyTag: () => concurrencyTag,
83
91
  controlPlaneDescriptor: () => controlPlaneDescriptor,
92
+ countRuns: () => countRuns,
84
93
  coveredTokens: () => coveredTokens,
85
94
  createStepLogger: () => createStepLogger,
86
95
  currentStep: () => currentStep,
96
+ currentStepScope: () => currentStepScope,
87
97
  currentWorkflowCtx: () => currentWorkflowCtx,
88
98
  debug: () => debug,
89
99
  describeWorker: () => describeWorker,
@@ -102,6 +112,7 @@ __export(index_exports, {
102
112
  isEngineMintedTag: () => isEngineMintedTag,
103
113
  isEventToken: () => isEventToken,
104
114
  isLegacyDescriptor: () => isLegacyDescriptor,
115
+ isNonEmptyRunScope: () => isNonEmptyRunScope,
105
116
  isWorkflowControlFlowSignal: () => isWorkflowControlFlowSignal,
106
117
  log: () => log,
107
118
  matchesAttributes: () => matchesAttributes,
@@ -109,6 +120,7 @@ __export(index_exports, {
109
120
  mergeRunValueFacetRows: () => mergeRunValueFacetRows,
110
121
  missingCapabilities: () => missingCapabilities,
111
122
  negotiate: () => negotiate,
123
+ nextCronFireMs: () => nextCronFireMs,
112
124
  normalizeAttributeRows: () => normalizeAttributeRows,
113
125
  normalizeDescriptor: () => normalizeDescriptor,
114
126
  observedAnnouncements: () => observedAnnouncements,
@@ -118,11 +130,14 @@ __export(index_exports, {
118
130
  prevCronFireMs: () => prevCronFireMs,
119
131
  readSearchAttributes: () => readSearchAttributes,
120
132
  requiredCapabilities: () => requiredCapabilities,
133
+ resolveConcurrency: () => resolveConcurrency,
121
134
  resolveRouting: () => resolveRouting,
122
135
  resolveRunWaiting: () => resolveRunWaiting,
123
136
  runDurableExecution: () => runDurableExecution,
124
137
  runInStepLogger: () => runInStepLogger,
138
+ runInStepScope: () => runInStepScope,
125
139
  runInWorkflowCtx: () => runInWorkflowCtx,
140
+ runOutsideWorkflowCtx: () => runOutsideWorkflowCtx,
126
141
  runSchedules: () => runSchedules,
127
142
  runStepHandler: () => runStepHandler,
128
143
  runValueFacetsFromRuns: () => runValueFacetsFromRuns,
@@ -313,6 +328,27 @@ function currentWorkflowCtx() {
313
328
  return storage.getStore();
314
329
  }
315
330
  __name(currentWorkflowCtx, "currentWorkflowCtx");
331
+ var STEP_SCOPE_KEY = Symbol.for("nestjs-durable:ambient-step-scope");
332
+ var stepScopeRef = globalThis;
333
+ if (!stepScopeRef[STEP_SCOPE_KEY]) {
334
+ stepScopeRef[STEP_SCOPE_KEY] = new import_node_async_hooks.AsyncLocalStorage();
335
+ }
336
+ var stepScopeStorage = stepScopeRef[STEP_SCOPE_KEY];
337
+ function runInStepScope(owner, step, fn) {
338
+ return stepScopeStorage.run({
339
+ owner,
340
+ step
341
+ }, fn);
342
+ }
343
+ __name(runInStepScope, "runInStepScope");
344
+ function currentStepScope() {
345
+ return stepScopeStorage.getStore();
346
+ }
347
+ __name(currentStepScope, "currentStepScope");
348
+ function runOutsideWorkflowCtx(fn) {
349
+ return storage.exit(() => stepScopeStorage.exit(fn));
350
+ }
351
+ __name(runOutsideWorkflowCtx, "runOutsideWorkflowCtx");
316
352
 
317
353
  // src/ambient-step.ts
318
354
  var import_node_async_hooks2 = require("async_hooks");
@@ -391,6 +427,23 @@ var FatalError = class extends Error {
391
427
  this.code = code;
392
428
  }
393
429
  };
430
+ var ConcurrencyLimitError = class extends Error {
431
+ static {
432
+ __name(this, "ConcurrencyLimitError");
433
+ }
434
+ workflow;
435
+ key;
436
+ limit;
437
+ active;
438
+ constructor(workflow, key, limit, active) {
439
+ super(`concurrency limit reached for key "${key}" (${active}/${limit} in flight); ${workflow} was not started \u2014 retry later`);
440
+ this.name = "ConcurrencyLimitError";
441
+ this.workflow = workflow;
442
+ this.key = key;
443
+ this.limit = limit;
444
+ this.active = active;
445
+ }
446
+ };
394
447
  var SingletonQueueFullError = class extends Error {
395
448
  static {
396
449
  __name(this, "SingletonQueueFullError");
@@ -458,6 +511,21 @@ var NonDeterminismError = class extends Error {
458
511
  this.seq = seq;
459
512
  }
460
513
  };
514
+ var NestedWorkflowCallError = class extends FatalError {
515
+ static {
516
+ __name(this, "NestedWorkflowCallError");
517
+ }
518
+ runId;
519
+ primitive;
520
+ step;
521
+ constructor(runId, primitive, step) {
522
+ super(`ctx.${primitive}() was called inside the body of step "${step}" (run ${runId}). Workflow primitives (child/startChild/step/sleep/signals/sideEffect/\u2026, and MyWorkflow.start()/.execute() via the ambient ctx) must be called from the workflow body, never from inside a step body: the step's checkpoint replays without re-running it, so the nested call would shift every later journal position and corrupt the run on replay. Move the call out of the step.`, "nested_workflow_call");
523
+ this.name = "NestedWorkflowCallError";
524
+ this.runId = runId;
525
+ this.primitive = primitive;
526
+ this.step = step;
527
+ }
528
+ };
461
529
  var GatherError = class extends Error {
462
530
  static {
463
531
  __name(this, "GatherError");
@@ -814,14 +882,19 @@ async function runStepHandler(task, handler) {
814
882
  } : result, "withEvents");
815
883
  try {
816
884
  const logger = createStepLogger(events, Date.now);
817
- const output = await runDurableExecution({
818
- unit: "step",
819
- runId: task.runId,
820
- workflow: void 0,
821
- name: task.name,
822
- seq: task.seq,
823
- attempt: task.attempt
824
- }, async () => runInStepLogger(logger, () => handler(task.input, logger)));
885
+ const output = await runDurableExecution(
886
+ {
887
+ unit: "step",
888
+ runId: task.runId,
889
+ workflow: void 0,
890
+ name: task.name,
891
+ seq: task.seq,
892
+ attempt: task.attempt
893
+ },
894
+ // Outside any ambient workflow ctx: an in-process transport runs the handler on the parent
895
+ // body's async path, but a handler is not the body (see `runOutsideWorkflowCtx`).
896
+ async () => runOutsideWorkflowCtx(() => runInStepLogger(logger, () => handler(task.input, logger)))
897
+ );
825
898
  return withEvents({
826
899
  ...base,
827
900
  status: "completed",
@@ -904,6 +977,58 @@ function instantCheckpoint(p) {
904
977
  }
905
978
  __name(instantCheckpoint, "instantCheckpoint");
906
979
 
980
+ // src/concurrency.ts
981
+ var DEFAULT_CONCURRENCY_STATUSES = [
982
+ "pending",
983
+ "running",
984
+ "suspended",
985
+ "cancelling",
986
+ "blocked"
987
+ ];
988
+ function concurrencyTag(key) {
989
+ return `concurrency:${key}`;
990
+ }
991
+ __name(concurrencyTag, "concurrencyTag");
992
+ async function resolveConcurrency(override, config, input) {
993
+ if (override) return override;
994
+ if (!config) return void 0;
995
+ const key = config.key(input);
996
+ if (key === void 0) return void 0;
997
+ const limit = typeof config.limit === "function" ? await config.limit(key) : config.limit;
998
+ return {
999
+ key,
1000
+ limit,
1001
+ countStatuses: config.countStatuses
1002
+ };
1003
+ }
1004
+ __name(resolveConcurrency, "resolveConcurrency");
1005
+ async function countRuns(store, query) {
1006
+ if (store.countRuns) return store.countRuns(query);
1007
+ if (store.runFacets && !query.status && !query.statuses && query.origin === void 0) {
1008
+ const cells = await store.runFacets(query);
1009
+ return cells.reduce((sum, c) => sum + c.count, 0);
1010
+ }
1011
+ if (store.runFacets && query.statuses && !query.status && query.origin === void 0) {
1012
+ const { statuses, ...rest } = query;
1013
+ const cells = await store.runFacets(rest);
1014
+ return cells.filter((c) => statuses.includes(c.status)).reduce((sum, c) => sum + c.count, 0);
1015
+ }
1016
+ return (await store.listRuns(query)).length;
1017
+ }
1018
+ __name(countRuns, "countRuns");
1019
+ async function assertConcurrency(store, workflow, quota) {
1020
+ const active = quota.limit <= 0 ? 0 : await countRuns(store, {
1021
+ tag: concurrencyTag(quota.key),
1022
+ statuses: [
1023
+ ...quota.countStatuses ?? DEFAULT_CONCURRENCY_STATUSES
1024
+ ]
1025
+ });
1026
+ if (quota.limit <= 0 || active >= quota.limit) {
1027
+ throw new ConcurrencyLimitError(workflow, quota.key, quota.limit, active);
1028
+ }
1029
+ }
1030
+ __name(assertConcurrency, "assertConcurrency");
1031
+
907
1032
  // src/entities.ts
908
1033
  var ENTITY_WORKFLOW = "__entity";
909
1034
  var entityRunId = /* @__PURE__ */ __name((name, key) => `entity:${name}:${key}`, "entityRunId");
@@ -1736,6 +1861,19 @@ function planDispatch(requires, liveDescriptors, cp, token) {
1736
1861
  }
1737
1862
  __name(planDispatch, "planDispatch");
1738
1863
 
1864
+ // src/interfaces.ts
1865
+ var TERMINAL_RUN_STATUSES = [
1866
+ "completed",
1867
+ "failed",
1868
+ "cancelled",
1869
+ "dead"
1870
+ ];
1871
+ function isNonEmptyRunScope(scope) {
1872
+ if (!scope) return false;
1873
+ 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;
1874
+ }
1875
+ __name(isNonEmptyRunScope, "isNonEmptyRunScope");
1876
+
1739
1877
  // src/tenant-group.ts
1740
1878
  function tenantGroup(baseGroup, tenant) {
1741
1879
  return tenant !== void 0 && tenant !== "" && tenant !== "default" ? `${baseGroup}@${tenant}` : baseGroup;
@@ -1826,6 +1964,486 @@ function indexWaitersByRun(waiters) {
1826
1964
  }
1827
1965
  __name(indexWaitersByRun, "indexWaitersByRun");
1828
1966
 
1967
+ // src/scheduler.ts
1968
+ var import_node_module = require("module");
1969
+
1970
+ // src/cron-compat.ts
1971
+ function isIndexable(value) {
1972
+ return typeof value === "object" && value !== null || typeof value === "function";
1973
+ }
1974
+ __name(isIndexable, "isIndexable");
1975
+ function unwrapDefault(module_) {
1976
+ if (!isIndexable(module_)) return module_;
1977
+ if (Reflect.get(module_, "parseExpression") !== void 0) return module_;
1978
+ if (Reflect.get(module_, "CronExpressionParser") !== void 0) return module_;
1979
+ const inner = Reflect.get(module_, "default");
1980
+ return inner === void 0 ? module_ : inner;
1981
+ }
1982
+ __name(unwrapDefault, "unwrapDefault");
1983
+ function v4Entry(module_) {
1984
+ if (!isIndexable(module_)) return void 0;
1985
+ const parse = Reflect.get(module_, "parseExpression");
1986
+ if (typeof parse !== "function") return void 0;
1987
+ return (expr, options) => parse.call(module_, expr, options);
1988
+ }
1989
+ __name(v4Entry, "v4Entry");
1990
+ function v5Entry(module_) {
1991
+ if (!isIndexable(module_)) return void 0;
1992
+ const parser = Reflect.get(module_, "CronExpressionParser");
1993
+ if (!isIndexable(parser)) return void 0;
1994
+ const parse = Reflect.get(parser, "parse");
1995
+ if (typeof parse !== "function") return void 0;
1996
+ return (expr, options) => parse.call(parser, expr, options);
1997
+ }
1998
+ __name(v5Entry, "v5Entry");
1999
+ function resolveCronParse(module_) {
2000
+ const resolved = unwrapDefault(module_);
2001
+ return v4Entry(resolved) ?? v5Entry(resolved);
2002
+ }
2003
+ __name(resolveCronParse, "resolveCronParse");
2004
+
2005
+ // src/scheduler.ts
2006
+ var nodeRequire = (0, import_node_module.createRequire)(__importMetaUrl);
2007
+ var defaultSleep = /* @__PURE__ */ __name((ms) => new Promise((r) => setTimeout(r, ms)), "defaultSleep");
2008
+ function scheduledRunId(key, everyMs, nowMs) {
2009
+ return `sched:${key}:${Math.floor(nowMs / everyMs)}`;
2010
+ }
2011
+ __name(scheduledRunId, "scheduledRunId");
2012
+ var cronParser;
2013
+ function loadCronParser() {
2014
+ if (cronParser !== void 0) return cronParser;
2015
+ try {
2016
+ cronParser = nodeRequire("cron-parser");
2017
+ } catch {
2018
+ throw new Error('cron schedules need the optional peer dependency "cron-parser" \u2014 install it (e.g. `npm i cron-parser`).');
2019
+ }
2020
+ return cronParser;
2021
+ }
2022
+ __name(loadCronParser, "loadCronParser");
2023
+ function prevCronFireMs(expr, nowMs, timezone = "UTC") {
2024
+ const parse = resolveCronParse(loadCronParser());
2025
+ if (!parse) {
2026
+ 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.`);
2027
+ }
2028
+ return parse(expr, {
2029
+ currentDate: new Date(nowMs + 1),
2030
+ tz: timezone
2031
+ }).prev().toDate().getTime();
2032
+ }
2033
+ __name(prevCronFireMs, "prevCronFireMs");
2034
+ function nextCronFireMs(expr, afterMs, timezone = "UTC") {
2035
+ const parse = resolveCronParse(loadCronParser());
2036
+ if (!parse) {
2037
+ 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.`);
2038
+ }
2039
+ return parse(expr, {
2040
+ currentDate: new Date(afterMs),
2041
+ tz: timezone
2042
+ }).next().toDate().getTime();
2043
+ }
2044
+ __name(nextCronFireMs, "nextCronFireMs");
2045
+ function scheduleRunIdAt(s, nowMs) {
2046
+ if (s.cron != null) return `sched:${s.key}:${prevCronFireMs(s.cron, nowMs, s.timezone)}`;
2047
+ if (s.everyMs != null) return scheduledRunId(s.key, s.everyMs, nowMs);
2048
+ throw new Error(`schedule "${s.key}" needs either "everyMs" or "cron"`);
2049
+ }
2050
+ __name(scheduleRunIdAt, "scheduleRunIdAt");
2051
+ function scheduleRunIdsAt(s, nowMs) {
2052
+ const current = scheduleRunIdAt(s, nowMs);
2053
+ if (!s.backfill || s.backfill.maxCatchup <= 0) return [
2054
+ current
2055
+ ];
2056
+ const ids = [];
2057
+ if (s.everyMs != null) {
2058
+ const bucket = Math.floor(nowMs / s.everyMs);
2059
+ const oldest = Math.max(0, bucket - s.backfill.maxCatchup);
2060
+ for (let b = oldest; b < bucket; b += 1) ids.push(`sched:${s.key}:${b}`);
2061
+ } else if (s.cron != null) {
2062
+ const prior = [];
2063
+ let cursor = prevCronFireMs(s.cron, nowMs, s.timezone);
2064
+ for (let i = 0; i < s.backfill.maxCatchup; i += 1) {
2065
+ const before = prevCronFireMs(s.cron, cursor - 1, s.timezone);
2066
+ prior.push(`sched:${s.key}:${before}`);
2067
+ cursor = before;
2068
+ }
2069
+ ids.push(...prior.reverse());
2070
+ }
2071
+ ids.push(current);
2072
+ return ids;
2073
+ }
2074
+ __name(scheduleRunIdsAt, "scheduleRunIdsAt");
2075
+ async function runSchedules(engine, schedules, nowMs, opts) {
2076
+ const random = opts?.random ?? Math.random;
2077
+ const sleep = opts?.sleep ?? defaultSleep;
2078
+ const ids = [];
2079
+ for (const s of schedules) {
2080
+ if (s.paused) continue;
2081
+ if (s.overlap === "skip" && s.everyMs) {
2082
+ const prevBucket = Math.floor(nowMs / s.everyMs) - 1;
2083
+ if (prevBucket >= 0) {
2084
+ const prev = await engine.getRun(`sched:${s.key}:${prevBucket}`);
2085
+ if (prev && (prev.status === "running" || prev.status === "suspended")) continue;
2086
+ }
2087
+ }
2088
+ if (s.jitter && s.jitter > 0) await sleep(Math.floor(random() * s.jitter));
2089
+ for (const runId of scheduleRunIdsAt(s, nowMs)) {
2090
+ await engine.start(s.workflow, s.input, runId);
2091
+ ids.push(runId);
2092
+ }
2093
+ }
2094
+ return ids;
2095
+ }
2096
+ __name(runSchedules, "runSchedules");
2097
+
2098
+ // src/schedules.ts
2099
+ var ScheduleAlreadyExistsError = class extends Error {
2100
+ static {
2101
+ __name(this, "ScheduleAlreadyExistsError");
2102
+ }
2103
+ scheduleId;
2104
+ constructor(scheduleId) {
2105
+ super(`schedule "${scheduleId}" already exists`), this.scheduleId = scheduleId;
2106
+ this.name = "ScheduleAlreadyExistsError";
2107
+ }
2108
+ };
2109
+ var ScheduleNotFoundError = class extends Error {
2110
+ static {
2111
+ __name(this, "ScheduleNotFoundError");
2112
+ }
2113
+ scheduleId;
2114
+ constructor(scheduleId) {
2115
+ super(`schedule "${scheduleId}" not found`), this.scheduleId = scheduleId;
2116
+ this.name = "ScheduleNotFoundError";
2117
+ }
2118
+ };
2119
+ var DEFAULT_NAMESPACE = "default";
2120
+ var ON_TIME_GRACE_MS = 6e4;
2121
+ var TICK_BATCH = 100;
2122
+ var ScheduleClient = class {
2123
+ static {
2124
+ __name(this, "ScheduleClient");
2125
+ }
2126
+ store;
2127
+ host;
2128
+ options;
2129
+ constructor(store, host, options = {}) {
2130
+ this.store = store;
2131
+ this.host = host;
2132
+ this.options = options;
2133
+ }
2134
+ now() {
2135
+ return (this.options.clock ?? Date.now)();
2136
+ }
2137
+ /** Whether the store persists schedules (implements the schedule methods). */
2138
+ get supported() {
2139
+ const s = this.store;
2140
+ return !!(s.saveSchedule && s.getSchedule && s.updateSchedule && s.deleteSchedule && s.listSchedules);
2141
+ }
2142
+ requireStore() {
2143
+ if (!this.supported) {
2144
+ 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");
2145
+ }
2146
+ const s = this.store;
2147
+ return {
2148
+ saveSchedule: /* @__PURE__ */ __name((r) => s.saveSchedule.call(s, r), "saveSchedule"),
2149
+ getSchedule: /* @__PURE__ */ __name((id) => s.getSchedule.call(s, id), "getSchedule"),
2150
+ updateSchedule: /* @__PURE__ */ __name((id, patch, expected) => s.updateSchedule.call(s, id, patch, expected), "updateSchedule"),
2151
+ deleteSchedule: /* @__PURE__ */ __name((id) => s.deleteSchedule.call(s, id), "deleteSchedule"),
2152
+ listSchedules: /* @__PURE__ */ __name((q) => s.listSchedules.call(s, q), "listSchedules")
2153
+ };
2154
+ }
2155
+ /** Create a schedule. Throws {@link ScheduleAlreadyExistsError} if the id is taken. */
2156
+ async create(opts) {
2157
+ const store = this.requireStore();
2158
+ if (await store.getSchedule(opts.id)) throw new ScheduleAlreadyExistsError(opts.id);
2159
+ return this.write(opts, null);
2160
+ }
2161
+ /**
2162
+ * Create the schedule, or replace the definition of an existing one (its history — last fire, last
2163
+ * run, fire count — is kept). The next fire is recomputed from now under the new definition, so a
2164
+ * window that already fired is never fired again by an upsert.
2165
+ */
2166
+ async upsert(opts) {
2167
+ const store = this.requireStore();
2168
+ return this.write(opts, await store.getSchedule(opts.id));
2169
+ }
2170
+ /** The schedule `id`, or null. */
2171
+ async get(id) {
2172
+ const record = await this.requireStore().getSchedule(id);
2173
+ return record ? describe(record) : null;
2174
+ }
2175
+ /** Schedules matching `query`, soonest-due first. */
2176
+ async list(query = {}) {
2177
+ return (await this.requireStore().listSchedules(query)).map(describe);
2178
+ }
2179
+ /** Delete the schedule. Runs it already started are left alone. Returns whether it existed. */
2180
+ delete(id) {
2181
+ return this.requireStore().deleteSchedule(id);
2182
+ }
2183
+ /** Stop firing until {@link resume}. `note` records why (shown by `get`/`list`). */
2184
+ async pause(id, note) {
2185
+ const store = this.requireStore();
2186
+ const record = await this.mustGet(id);
2187
+ const spec = {
2188
+ ...record.spec
2189
+ };
2190
+ if (note !== void 0) spec.note = note;
2191
+ await store.updateSchedule(id, {
2192
+ paused: true,
2193
+ spec,
2194
+ updatedAt: new Date(this.now())
2195
+ });
2196
+ return describe(await this.mustGet(id));
2197
+ }
2198
+ /**
2199
+ * Fire again from the next window after now — the windows skipped while paused are NOT caught up.
2200
+ */
2201
+ async resume(id) {
2202
+ const store = this.requireStore();
2203
+ const record = await this.mustGet(id);
2204
+ const spec = record.spec;
2205
+ const window = nextWindowAfter(spec, this.now());
2206
+ await store.updateSchedule(id, {
2207
+ paused: false,
2208
+ nextFireAt: window === null ? null : window + jitterFor(record.id, window, spec),
2209
+ state: {
2210
+ ...record.state,
2211
+ window
2212
+ },
2213
+ updatedAt: new Date(this.now())
2214
+ });
2215
+ return describe(await this.mustGet(id));
2216
+ }
2217
+ /**
2218
+ * Start one run of the schedule right now, outside its cadence (paused or not). The run id is
2219
+ * `sched:<id>:manual:<nowMs>`; it doesn't move the schedule's next fire.
2220
+ */
2221
+ async trigger(id) {
2222
+ const record = await this.mustGet(id);
2223
+ const runId = `sched:${id}:manual:${this.now()}`;
2224
+ return this.host.start(record.workflow, specOf(record).input, runId, startOptions(record));
2225
+ }
2226
+ /**
2227
+ * Fire every due schedule (in this engine's namespace; all namespaces for an operator engine):
2228
+ * start its window's run and advance it to the next window. Called by the NestJS timer poller on
2229
+ * each tick when `persistedSchedules` is on; call it yourself from any other driver. Returns the
2230
+ * run ids started (or re-found, for an idempotent re-fire).
2231
+ */
2232
+ async tick(nowMs = this.now()) {
2233
+ const store = this.requireStore();
2234
+ const started = [];
2235
+ for (let round = 0; round < 50; round++) {
2236
+ const due = await store.listSchedules({
2237
+ dueBy: nowMs,
2238
+ namespace: this.options.namespace,
2239
+ limit: TICK_BATCH
2240
+ });
2241
+ for (const record of due) {
2242
+ const runId = await this.fire(record, nowMs);
2243
+ if (runId) started.push(runId);
2244
+ }
2245
+ if (due.length < TICK_BATCH) break;
2246
+ }
2247
+ return started;
2248
+ }
2249
+ /** Fire one due schedule and advance it. Returns the run id started, if any. */
2250
+ async fire(record, nowMs) {
2251
+ const store = this.requireStore();
2252
+ const spec = specOf(record);
2253
+ const state = record.state;
2254
+ let latest;
2255
+ let next;
2256
+ try {
2257
+ latest = latestWindowAtOrBefore(spec, nowMs);
2258
+ next = nextWindowAfter(spec, latest);
2259
+ } catch (error2) {
2260
+ await store.updateSchedule(record.id, {
2261
+ paused: true,
2262
+ nextFireAt: null,
2263
+ state: {
2264
+ ...state,
2265
+ lastError: `invalid schedule: ${errorMessage(error2)}`
2266
+ },
2267
+ updatedAt: new Date(nowMs)
2268
+ }, record.nextFireAt);
2269
+ return void 0;
2270
+ }
2271
+ const window = state.window ?? latest;
2272
+ const jitter = spec.jitterMs ?? 0;
2273
+ const onTime = nowMs - latest <= ON_TIME_GRACE_MS + jitter;
2274
+ let runId;
2275
+ let lastError;
2276
+ let fired = false;
2277
+ const skipMissed = spec.catchup === "skip" && !onTime;
2278
+ const beforeWindow = latest < window;
2279
+ if (!skipMissed && !beforeWindow && !await this.overlapping(spec, state)) {
2280
+ runId = `sched:${record.id}:${latest}`;
2281
+ try {
2282
+ await this.host.start(record.workflow, spec.input, runId, startOptions(record));
2283
+ fired = true;
2284
+ } catch (error2) {
2285
+ if (await this.host.getRun(runId)) fired = true;
2286
+ else lastError = errorMessage(error2);
2287
+ }
2288
+ }
2289
+ const nextState = {
2290
+ ...state,
2291
+ window: next,
2292
+ ...fired ? {
2293
+ lastFireAt: latest,
2294
+ lastRunId: runId,
2295
+ fires: (state.fires ?? 0) + 1
2296
+ } : {},
2297
+ lastError: fired ? void 0 : lastError ?? state.lastError
2298
+ };
2299
+ await store.updateSchedule(record.id, {
2300
+ nextFireAt: next === null ? null : next + jitterFor(record.id, next, spec),
2301
+ state: stripUndefined(nextState),
2302
+ updatedAt: new Date(nowMs)
2303
+ }, record.nextFireAt);
2304
+ return fired ? runId : void 0;
2305
+ }
2306
+ /** `overlap: 'skip'` and the previous run of this schedule is still in flight. */
2307
+ async overlapping(spec, state) {
2308
+ if (spec.overlap !== "skip" || !state.lastRunId) return false;
2309
+ const prev = await this.host.getRun(state.lastRunId);
2310
+ return !!prev && !TERMINAL_RUN_STATUSES.includes(prev.status);
2311
+ }
2312
+ async mustGet(id) {
2313
+ const record = await this.requireStore().getSchedule(id);
2314
+ if (!record) throw new ScheduleNotFoundError(id);
2315
+ return record;
2316
+ }
2317
+ async write(opts, existing) {
2318
+ const store = this.requireStore();
2319
+ const spec = toSpec(opts);
2320
+ const now = this.now();
2321
+ const paused = opts.paused ?? existing?.paused ?? false;
2322
+ const window = nextWindowAfter(spec, now);
2323
+ const record = {
2324
+ id: opts.id,
2325
+ namespace: opts.namespace ?? this.options.namespace ?? DEFAULT_NAMESPACE,
2326
+ workflow: workflowName(opts.workflow),
2327
+ paused,
2328
+ nextFireAt: window === null ? null : window + jitterFor(opts.id, window, spec),
2329
+ tags: opts.tags,
2330
+ spec,
2331
+ state: stripUndefined({
2332
+ ...existing?.state ?? {},
2333
+ window
2334
+ }),
2335
+ createdAt: existing?.createdAt ?? new Date(now),
2336
+ updatedAt: new Date(now)
2337
+ };
2338
+ await store.saveSchedule(record);
2339
+ return describe(record);
2340
+ }
2341
+ };
2342
+ function toSpec(opts) {
2343
+ if (opts.cron == null === (opts.every == null)) {
2344
+ throw new Error(`schedule "${opts.id}" needs exactly one of "cron" or "every"`);
2345
+ }
2346
+ const everyMs = opts.every != null ? parseDuration(opts.every) : void 0;
2347
+ if (everyMs !== void 0 && !(everyMs > 0)) {
2348
+ throw new Error(`schedule "${opts.id}": "every" must be a positive duration`);
2349
+ }
2350
+ const jitterMs = opts.jitter != null ? parseDuration(opts.jitter) : void 0;
2351
+ return stripUndefined({
2352
+ v: 1,
2353
+ input: opts.input,
2354
+ cron: opts.cron,
2355
+ everyMs,
2356
+ timezone: opts.timezone,
2357
+ jitterMs: jitterMs && jitterMs > 0 ? jitterMs : void 0,
2358
+ overlap: opts.overlap ?? "allow",
2359
+ catchup: opts.catchup ?? "latest",
2360
+ note: opts.note,
2361
+ searchAttributes: opts.searchAttributes,
2362
+ priority: opts.priority,
2363
+ concurrency: opts.concurrency
2364
+ });
2365
+ }
2366
+ __name(toSpec, "toSpec");
2367
+ function specOf(record) {
2368
+ return record.spec;
2369
+ }
2370
+ __name(specOf, "specOf");
2371
+ function latestWindowAtOrBefore(spec, atMs) {
2372
+ if (spec.cron != null) return prevCronFireMs(spec.cron, atMs, spec.timezone);
2373
+ const every = spec.everyMs;
2374
+ return Math.floor(atMs / every) * every;
2375
+ }
2376
+ __name(latestWindowAtOrBefore, "latestWindowAtOrBefore");
2377
+ function nextWindowAfter(spec, afterMs) {
2378
+ if (spec.cron != null) return nextCronFireMs(spec.cron, afterMs, spec.timezone);
2379
+ const every = spec.everyMs;
2380
+ return (Math.floor(afterMs / every) + 1) * every;
2381
+ }
2382
+ __name(nextWindowAfter, "nextWindowAfter");
2383
+ function jitterFor(id, window, spec) {
2384
+ const max = spec.jitterMs ?? 0;
2385
+ if (max <= 0) return 0;
2386
+ let h = 2166136261;
2387
+ for (const ch of `${id}@${window}`) {
2388
+ h ^= ch.charCodeAt(0);
2389
+ h = Math.imul(h, 16777619) >>> 0;
2390
+ }
2391
+ return h % max;
2392
+ }
2393
+ __name(jitterFor, "jitterFor");
2394
+ function startOptions(record) {
2395
+ const spec = specOf(record);
2396
+ return stripUndefined({
2397
+ tags: [
2398
+ ...record.tags ?? [],
2399
+ `schedule:${record.id}`
2400
+ ],
2401
+ namespace: record.namespace,
2402
+ searchAttributes: spec.searchAttributes,
2403
+ priority: spec.priority,
2404
+ concurrency: spec.concurrency
2405
+ });
2406
+ }
2407
+ __name(startOptions, "startOptions");
2408
+ function describe(record) {
2409
+ const spec = specOf(record);
2410
+ const state = record.state;
2411
+ return stripUndefined({
2412
+ id: record.id,
2413
+ workflow: record.workflow,
2414
+ input: spec.input,
2415
+ namespace: record.namespace,
2416
+ cron: spec.cron,
2417
+ everyMs: spec.everyMs,
2418
+ timezone: spec.timezone,
2419
+ jitterMs: spec.jitterMs,
2420
+ overlap: spec.overlap ?? "allow",
2421
+ catchup: spec.catchup ?? "latest",
2422
+ paused: record.paused,
2423
+ note: spec.note,
2424
+ tags: record.tags,
2425
+ searchAttributes: spec.searchAttributes,
2426
+ priority: spec.priority,
2427
+ concurrency: spec.concurrency,
2428
+ nextFireAt: record.paused || record.nextFireAt == null ? null : new Date(record.nextFireAt),
2429
+ lastFireAt: state.lastFireAt != null ? new Date(state.lastFireAt) : void 0,
2430
+ lastRunId: state.lastRunId,
2431
+ fires: state.fires ?? 0,
2432
+ lastError: state.lastError,
2433
+ createdAt: record.createdAt,
2434
+ updatedAt: record.updatedAt
2435
+ });
2436
+ }
2437
+ __name(describe, "describe");
2438
+ function stripUndefined(o) {
2439
+ return Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0));
2440
+ }
2441
+ __name(stripUndefined, "stripUndefined");
2442
+ function errorMessage(error2) {
2443
+ return error2 instanceof Error ? error2.message : String(error2);
2444
+ }
2445
+ __name(errorMessage, "errorMessage");
2446
+
1829
2447
  // src/singleton-gate.ts
1830
2448
  var SINGLETON_RETRY_MS = 1e3;
1831
2449
  var SINGLETON_RETRY_JITTER_MS = 250;
@@ -2180,10 +2798,15 @@ var Position = class Position2 {
2180
2798
  rewind() {
2181
2799
  this.seq -= 1;
2182
2800
  }
2801
+ /** The position the NEXT primitive would claim, without claiming it. */
2802
+ peek() {
2803
+ return this.seq + 1;
2804
+ }
2183
2805
  };
2184
2806
  function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttributesSchema) {
2185
2807
  const { store, replay } = host;
2186
2808
  const pos = new Position();
2809
+ const scopeOwner = {};
2187
2810
  const readCheckpoint = /* @__PURE__ */ __name((seq) => {
2188
2811
  const hit = replay?.get(seq);
2189
2812
  if (hit !== void 0) return hit;
@@ -2229,7 +2852,7 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
2229
2852
  };
2230
2853
  const body = /* @__PURE__ */ __name(() => {
2231
2854
  const logger = createStepLogger(events, host.clock);
2232
- return runInStepLogger(logger, () => fn(logger));
2855
+ return runInStepScope(scopeOwner, name, () => runInStepLogger(logger, () => fn(logger)));
2233
2856
  }, "body");
2234
2857
  const output = host.interceptStep ? await host.interceptStep(invocation, body) : await body();
2235
2858
  await host.completeStep({
@@ -2282,7 +2905,7 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
2282
2905
  }
2283
2906
  if (existing && existing.status === "completed") return existing.output;
2284
2907
  return store.transaction(async (tx) => {
2285
- const output = await fn(tx.raw);
2908
+ const output = await runInStepScope(scopeOwner, name, () => fn(tx.raw));
2286
2909
  const cp = instantCheckpoint({
2287
2910
  runId,
2288
2911
  seq: current,
@@ -2791,30 +3414,49 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
2791
3414
  });
2792
3415
  }
2793
3416
  __name(step, "step");
3417
+ const nestedStep = /* @__PURE__ */ __name(() => {
3418
+ const scope = currentStepScope();
3419
+ return scope !== void 0 && scope.owner === scopeOwner ? scope.step : void 0;
3420
+ }, "nestedStep");
3421
+ const guard = /* @__PURE__ */ __name((primitive, fn) => (...args) => {
3422
+ const stepName = nestedStep();
3423
+ if (stepName === void 0) return fn(...args);
3424
+ return (async () => {
3425
+ if (await readCheckpoint(pos.peek())) return fn(...args);
3426
+ throw new NestedWorkflowCallError(runId, primitive, stepName);
3427
+ })();
3428
+ }, "guard");
3429
+ const guardedWebhook = /* @__PURE__ */ __name(() => {
3430
+ const stepName = nestedStep();
3431
+ if (stepName !== void 0 && !replay?.get(pos.peek())) {
3432
+ throw new NestedWorkflowCallError(runId, "webhook", stepName);
3433
+ }
3434
+ return webhook();
3435
+ }, "guardedWebhook");
2794
3436
  return {
2795
3437
  runId,
2796
- step,
2797
- localStep,
2798
- upsertSearchAttributes,
2799
- transaction,
2800
- callEntity,
2801
- signalEntity,
2802
- sleep,
2803
- sleepUntil,
3438
+ step: guard("step", step),
3439
+ localStep: guard("localStep", localStep),
3440
+ upsertSearchAttributes: guard("upsertSearchAttributes", upsertSearchAttributes),
3441
+ transaction: guard("transaction", transaction),
3442
+ callEntity: guard("callEntity", callEntity),
3443
+ signalEntity: guard("signalEntity", signalEntity),
3444
+ sleep: guard("sleep", sleep),
3445
+ sleepUntil: guard("sleepUntil", sleepUntil),
2804
3446
  continueAsNew,
2805
- waitForSignal,
2806
- waitForEvent,
2807
- task,
2808
- child,
2809
- all,
2810
- startChild,
2811
- breakpoint,
2812
- webhook,
2813
- setEvent,
2814
- onUpdate,
2815
- patched,
2816
- now,
2817
- sideEffect
3447
+ waitForSignal: guard("waitForSignal", waitForSignal),
3448
+ waitForEvent: guard("waitForEvent", waitForEvent),
3449
+ task: guard("task", task),
3450
+ child: guard("child", child),
3451
+ all: guard("all", all),
3452
+ startChild: guard("startChild", startChild),
3453
+ breakpoint: guard("breakpoint", breakpoint),
3454
+ webhook: guardedWebhook,
3455
+ setEvent: guard("setEvent", setEvent),
3456
+ onUpdate: guard("onUpdate", onUpdate),
3457
+ patched: guard("patched", patched),
3458
+ now: guard("now", now),
3459
+ sideEffect: guard("sideEffect", sideEffect)
2818
3460
  };
2819
3461
  }
2820
3462
  __name(createWorkflowCtx, "createWorkflowCtx");
@@ -2894,6 +3536,12 @@ var WorkflowEngine = class {
2894
3536
  runDispatcher;
2895
3537
  /** Per-key serialization for singleton workflows (admission, back-pressure, notify-on-release). */
2896
3538
  singletons;
3539
+ /**
3540
+ * Persisted, runtime-managed schedules: create/upsert/pause/resume/delete/list/trigger them, and
3541
+ * `tick()` fires the due ones (the NestJS timer poller does it when `persistedSchedules` is on).
3542
+ * Needs a store that persists schedules — every bundled adapter does. See {@link ScheduleClient}.
3543
+ */
3544
+ schedules;
2897
3545
  /** Every registered workflow, keyed by `name@version` — so old versions stay runnable. */
2898
3546
  workflows = /* @__PURE__ */ new Map();
2899
3547
  /** The newest registered version per workflow name — used to `start` new runs. */
@@ -2978,6 +3626,13 @@ var WorkflowEngine = class {
2978
3626
  }, "dispatch"),
2979
3627
  configFor: /* @__PURE__ */ __name((run) => (this.workflows.get(versionKey(run.workflow, run.workflowVersion)) ?? this.latest.get(run.workflow))?.singleton, "configFor")
2980
3628
  });
3629
+ this.schedules = new ScheduleClient(this.store, {
3630
+ start: /* @__PURE__ */ __name((workflow, input, runId, opts) => this.start(workflow, input, runId, opts), "start"),
3631
+ getRun: /* @__PURE__ */ __name((runId) => this.store.getRun(runId), "getRun")
3632
+ }, {
3633
+ namespace: this.namespace,
3634
+ clock: this.clock
3635
+ });
2981
3636
  this.pool.bind(
2982
3637
  async (result) => {
2983
3638
  const waiter = this.pending.get(result.stepId);
@@ -3113,6 +3768,7 @@ var WorkflowEngine = class {
3113
3768
  fn,
3114
3769
  tags: opts?.tags,
3115
3770
  singleton: opts?.singleton,
3771
+ concurrency: opts?.concurrency,
3116
3772
  executionTimeoutMs: opts?.executionTimeout != null ? parseDuration(opts.executionTimeout) : void 0,
3117
3773
  validateInput: opts?.validateInput,
3118
3774
  searchAttributesSchema: opts?.searchAttributesSchema,
@@ -3175,6 +3831,7 @@ var WorkflowEngine = class {
3175
3831
  }, "fn"),
3176
3832
  tags: opts.tags,
3177
3833
  singleton: opts.singleton,
3834
+ concurrency: opts.concurrency,
3178
3835
  executionTimeoutMs: opts.executionTimeout != null ? parseDuration(opts.executionTimeout) : void 0,
3179
3836
  validateInput: opts.validateInput,
3180
3837
  requires: opts.requires,
@@ -3346,12 +4003,17 @@ var WorkflowEngine = class {
3346
4003
  error: prior.error
3347
4004
  };
3348
4005
  }
4006
+ const quota = await resolveConcurrency(opts?.concurrency, registered.concurrency, input);
4007
+ if (quota) await assertConcurrency(this.store, name, quota);
3349
4008
  const now = /* @__PURE__ */ new Date();
3350
4009
  const stampedTags = [
3351
4010
  ...opts?.tags ?? [],
3352
4011
  ...registered.singleton ? [
3353
4012
  this.singletons.tag(registered.singleton, input)
3354
4013
  ] : [],
4014
+ ...quota ? [
4015
+ concurrencyTag(quota.key)
4016
+ ] : [],
3355
4017
  ...registered.versionDeclared === false ? [
3356
4018
  VERSION_UNDECLARED_TAG
3357
4019
  ] : []
@@ -3396,10 +4058,30 @@ var WorkflowEngine = class {
3396
4058
  getRun(runId) {
3397
4059
  return this.store.getRun(runId);
3398
4060
  }
3399
- async resume(runId) {
4061
+ /**
4062
+ * Re-execute a run's body (replaying its checkpoints). This is the EXPLICIT entry point: besides
4063
+ * driving a live run, it re-drives a `failed` run — the manual retry (prefer {@link requeue}, which
4064
+ * also resets the failure state so replay re-attempts the failed parts). Completed / cancelled /
4065
+ * dead runs are never re-executed. The engine's own implicit wake-ups (signals, step results,
4066
+ * timers, redelivered dispatches) go through {@link resumeImplicit}, which treats `failed` as
4067
+ * terminal too.
4068
+ */
4069
+ resume(runId) {
4070
+ return this.resumeRun(runId, true);
4071
+ }
4072
+ /**
4073
+ * {@link resume} for every IMPLICIT wake-up — a delivered signal/child completion, a late step
4074
+ * result, a due timer, a redelivered dispatch, a queue-slot wake. A `failed` run is terminal to
4075
+ * these: only an explicit retry ({@link requeue} / {@link resume}) may re-drive it. Otherwise a
4076
+ * stale waiter or a late result left behind by a failed run silently re-executes its body.
4077
+ */
4078
+ resumeImplicit(runId) {
4079
+ return this.resumeRun(runId, false);
4080
+ }
4081
+ async resumeRun(runId, allowFailed) {
3400
4082
  const run = await this.store.getRun(runId);
3401
4083
  if (!run) throw new Error(`run ${runId} not found`);
3402
- if (run.status === "cancelled" || run.status === "completed" || run.status === "dead") {
4084
+ if (run.status === "cancelled" || run.status === "completed" || run.status === "dead" || run.status === "failed" && !allowFailed) {
3403
4085
  return {
3404
4086
  runId,
3405
4087
  status: run.status,
@@ -3848,7 +4530,7 @@ var WorkflowEngine = class {
3848
4530
  const nowMs = this.clock();
3849
4531
  const acquired = await this.store.tryLockRun(runId, this.instanceId, nowMs + this.leaseMs, nowMs);
3850
4532
  if (!acquired) return null;
3851
- return this.resume(runId).catch((err) => {
4533
+ return this.resumeImplicit(runId).catch((err) => {
3852
4534
  if (err instanceof NamespaceMismatch) return null;
3853
4535
  throw err;
3854
4536
  });
@@ -4069,7 +4751,7 @@ var WorkflowEngine = class {
4069
4751
  if (!acquired) continue;
4070
4752
  try {
4071
4753
  const settled = onLocked ? await onLocked(run) : void 0;
4072
- results.push(settled ?? await this.resume(run.id));
4754
+ results.push(settled ?? await this.resumeImplicit(run.id));
4073
4755
  } catch (error2) {
4074
4756
  console.error(`[nestjs-durable] could not resume run ${run.id} (${run.workflow}@${run.workflowVersion}); leaving it for another instance:`, error2);
4075
4757
  }
@@ -4166,7 +4848,7 @@ var WorkflowEngine = class {
4166
4848
  // for an ordinary (non-fan) signal/child await.
4167
4849
  parallelGroup: waiter.parallelGroup
4168
4850
  }));
4169
- return this.resume(waiter.runId).catch((err) => {
4851
+ return this.resumeImplicit(waiter.runId).catch((err) => {
4170
4852
  if (err instanceof NamespaceMismatch) return null;
4171
4853
  throw err;
4172
4854
  });
@@ -4282,7 +4964,7 @@ var WorkflowEngine = class {
4282
4964
  async cancel(runId, opts) {
4283
4965
  const run = await this.store.getRun(runId);
4284
4966
  if (!run) return null;
4285
- if (run.status === "completed" || run.status === "cancelled" || run.status === "dead") {
4967
+ if (TERMINAL_RUN_STATUSES.includes(run.status)) {
4286
4968
  return {
4287
4969
  runId,
4288
4970
  status: run.status,
@@ -4311,7 +4993,7 @@ var WorkflowEngine = class {
4311
4993
  }).catch(() => void 0);
4312
4994
  }
4313
4995
  queueMicrotask(() => {
4314
- void this.resume(runId).then(() => this.notifyCancelled(runId)).catch(() => void 0);
4996
+ void this.resumeImplicit(runId).then(() => this.notifyCancelled(runId)).catch(() => void 0);
4315
4997
  });
4316
4998
  await this.cancelChildren(runId, opts);
4317
4999
  return {
@@ -4389,6 +5071,123 @@ var WorkflowEngine = class {
4389
5071
  return deleted + 1;
4390
5072
  }
4391
5073
  /**
5074
+ * Hard-delete EVERY run matching `scope` — plus, by default, each one's whole child subtree — in
5075
+ * bounded batches. The "forget this tenant" / "drop this feature's history" operation: where
5076
+ * {@link deleteRun} removes one tree you already know the id of, this drains everything a scope
5077
+ * selects (`{ namespace: 'acme' }`, `{ tag: 'tenant:acme' }`, `{ workflows: [...] }`, attribute
5078
+ * predicates …) through the store API, so no caller has to reach into the `durable_*` tables.
5079
+ *
5080
+ * - **Live runs** (not yet terminal) are {@link cancel cancelled} first (plain cancel, no saga
5081
+ * undo), which tells any worker holding one to stop, and then deleted with the rest. Pass
5082
+ * `cancelLive: false` to leave live runs (and live descendants of finished ones) alone and
5083
+ * delete terminal history only.
5084
+ * - **Children** are found through the same parent→child edges as {@link getRunChildren} and deleted
5085
+ * with their root even when they don't match `scope` themselves (children inherit `namespace`,
5086
+ * but not `tags`). `children: false` deletes exactly the matching runs.
5087
+ * - **Batched**: `batchSize` runs (default 500) per round, each round one bulk
5088
+ * {@link StateStore.deleteRuns} (falling back to per-run {@link StateStore.deleteRun}).
5089
+ *
5090
+ * An EMPTY scope is rejected — it would delete every run in the store. Returns how many runs were
5091
+ * deleted (children included).
5092
+ */
5093
+ async purgeRuns(scope, opts) {
5094
+ if (!isNonEmptyRunScope(scope)) {
5095
+ throw new Error("purgeRuns: refusing an empty scope (it would delete every run) \u2014 pass at least one predicate, e.g. { namespace }");
5096
+ }
5097
+ const batchSize = Math.max(1, opts?.batchSize ?? 500);
5098
+ const cancelLive = opts?.cancelLive ?? true;
5099
+ const withChildren = opts?.children ?? true;
5100
+ const statuses = cancelLive ? void 0 : [
5101
+ ...TERMINAL_RUN_STATUSES
5102
+ ];
5103
+ let deleted = 0;
5104
+ let previous = "";
5105
+ for (; ; ) {
5106
+ const batch = await this.store.listRuns({
5107
+ ...scope,
5108
+ statuses,
5109
+ limit: batchSize
5110
+ });
5111
+ if (batch.length === 0) break;
5112
+ const fingerprint = batch.map((r) => r.id).join("\0");
5113
+ if (fingerprint === previous) {
5114
+ throw new Error("purgeRuns: the store returned the same runs after deleting them");
5115
+ }
5116
+ previous = fingerprint;
5117
+ const known = new Map(batch.map((r) => [
5118
+ r.id,
5119
+ r.status
5120
+ ]));
5121
+ const ids = withChildren ? await this.collectSubtrees(batch.map((r) => r.id)) : batch.map((r) => r.id);
5122
+ const doomed = [];
5123
+ for (const id of ids) {
5124
+ const status = known.has(id) ? known.get(id) : (await this.store.getRun(id))?.status;
5125
+ if (status === void 0) continue;
5126
+ if (!TERMINAL_RUN_STATUSES.includes(status)) {
5127
+ if (!cancelLive) continue;
5128
+ await this.cancel(id).catch(() => void 0);
5129
+ }
5130
+ doomed.push(id);
5131
+ }
5132
+ await this.deleteRunIds(doomed);
5133
+ deleted += doomed.length;
5134
+ if (batch.length < batchSize) break;
5135
+ }
5136
+ return deleted;
5137
+ }
5138
+ /**
5139
+ * {@link purgeRuns} for one namespace — every run (and child) a tenant/partition owns. Children
5140
+ * inherit their parent's namespace, so a namespace purge is complete by construction.
5141
+ */
5142
+ purgeNamespace(namespace, opts) {
5143
+ return this.purgeRuns({
5144
+ namespace
5145
+ }, opts);
5146
+ }
5147
+ /** The given runs plus every descendant, children before parents. One waiter scan for the batch. */
5148
+ async collectSubtrees(rootIds) {
5149
+ const waitersByParent = /* @__PURE__ */ new Map();
5150
+ for (const w of await this.store.listSignalWaiters("child:")) {
5151
+ const list = waitersByParent.get(w.runId) ?? [];
5152
+ list.push(w.token.slice("child:".length));
5153
+ waitersByParent.set(w.runId, list);
5154
+ }
5155
+ const prefixes = [
5156
+ "signal:child:",
5157
+ "spawn:"
5158
+ ];
5159
+ const childrenOf = /* @__PURE__ */ __name(async (parent) => {
5160
+ const ids = new Set(waitersByParent.get(parent) ?? []);
5161
+ 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)));
5162
+ for (const cp of cps) {
5163
+ if (cp.name.startsWith("signal:child:")) ids.add(cp.name.slice("signal:child:".length));
5164
+ if (cp.name.startsWith("spawn:") && typeof cp.output === "string") ids.add(cp.output);
5165
+ }
5166
+ return [
5167
+ ...ids
5168
+ ];
5169
+ }, "childrenOf");
5170
+ const ordered = [];
5171
+ const seen = /* @__PURE__ */ new Set();
5172
+ const visit = /* @__PURE__ */ __name(async (id) => {
5173
+ if (seen.has(id)) return;
5174
+ seen.add(id);
5175
+ for (const child of await childrenOf(id)) await visit(child);
5176
+ ordered.push(id);
5177
+ }, "visit");
5178
+ for (const id of rootIds) await visit(id);
5179
+ return ordered;
5180
+ }
5181
+ /** Bulk-delete through the store's `deleteRuns`, or one `deleteRun` per id when it has none. */
5182
+ async deleteRunIds(ids) {
5183
+ if (ids.length === 0) return;
5184
+ if (this.store.deleteRuns) {
5185
+ await this.store.deleteRuns(ids);
5186
+ return;
5187
+ }
5188
+ for (const id of ids) await this.store.deleteRun(id);
5189
+ }
5190
+ /**
4392
5191
  * Cascade cancellation to a run's children — both awaited (`ctx.child`, found via its live
4393
5192
  * `child:<id>` waiter) and fire-and-forget (`ctx.startChild`, found via its `spawn:<id>`
4394
5193
  * checkpoint). Recursive, so a whole subtree is cancelled; the terminal guard in `cancel` stops it
@@ -5029,7 +5828,7 @@ var WorkflowEngine = class {
5029
5828
  const checkpoint = await this.store.getCheckpoint(runId, cmd.seq);
5030
5829
  if (checkpoint && checkpoint.status !== "pending") {
5031
5830
  setTimeout(() => {
5032
- void this.resume(runId).catch(() => void 0);
5831
+ void this.resumeImplicit(runId).catch(() => void 0);
5033
5832
  }, 0).unref?.();
5034
5833
  return;
5035
5834
  }
@@ -5371,7 +6170,7 @@ var WorkflowEngine = class {
5371
6170
  kind: "signal",
5372
6171
  output: payload
5373
6172
  }));
5374
- setTimeout(() => void this.resume(run.id).catch(() => void 0), 0);
6173
+ setTimeout(() => void this.resumeImplicit(run.id).catch(() => void 0), 0);
5375
6174
  }, "deliverBuffered");
5376
6175
  const buffered = await this.store.takeBufferedSignal(cmd.signal);
5377
6176
  if (buffered) {
@@ -6185,7 +6984,7 @@ var WorkflowEngine = class {
6185
6984
  queueMs: startedAt.getTime() - cp.enqueuedAt.getTime(),
6186
6985
  durationMs: finishedAt.getTime() - startedAt.getTime()
6187
6986
  });
6188
- await this.resume(result.runId).catch((err) => {
6987
+ await this.resumeImplicit(result.runId).catch((err) => {
6189
6988
  if (err instanceof NamespaceMismatch) return null;
6190
6989
  throw err;
6191
6990
  });
@@ -6286,7 +7085,7 @@ var WorkflowEngine = class {
6286
7085
  ...waiters
6287
7086
  ];
6288
7087
  waiters.clear();
6289
- for (const runId of runIds) void this.resume(runId).catch(() => void 0);
7088
+ for (const runId of runIds) void this.resumeImplicit(runId).catch(() => void 0);
6290
7089
  }
6291
7090
  /** Release the flow-control slot a dispatched step held (if any), by its stepId. */
6292
7091
  async releaseQueueSlot(id) {
@@ -6409,14 +7208,6 @@ var RemoteStepError = class extends Error {
6409
7208
  }
6410
7209
  };
6411
7210
 
6412
- // src/interfaces.ts
6413
- var TERMINAL_RUN_STATUSES = [
6414
- "completed",
6415
- "failed",
6416
- "cancelled",
6417
- "dead"
6418
- ];
6419
-
6420
7211
  // src/run-facets.ts
6421
7212
  function facetOrigin(origin) {
6422
7213
  return origin?.trim() ? origin : null;
@@ -6448,7 +7239,8 @@ __name(mergeRunFacetRows, "mergeRunFacetRows");
6448
7239
  var RUN_VALUE_FACET_LIMIT = 100;
6449
7240
  var RUN_VALUE_FACET_SCAN = 5e3;
6450
7241
  var ENGINE_MINTED_TAG_PREFIXES = [
6451
- "singleton:"
7242
+ "singleton:",
7243
+ "concurrency:"
6452
7244
  ];
6453
7245
  function isEngineMintedTag(value) {
6454
7246
  return value !== null && ENGINE_MINTED_TAG_PREFIXES.some((prefix) => value.startsWith(prefix));
@@ -6553,9 +7345,42 @@ var CodecStateStore = class {
6553
7345
  * Bound the same conditional way as {@link runFacets} so absence is reported, not faked.
6554
7346
  */
6555
7347
  runValueFacets;
7348
+ /** Forwarded verbatim (deletes touch no payload). Bound only when the inner store has it, so the
7349
+ * engine's per-run fallback still kicks in through the wrapper. */
7350
+ deleteRuns;
7351
+ /** Forwarded verbatim — retention selects by status/updatedAt/scope, never by payload. Bound only
7352
+ * when the inner store prunes, so the retention poller still sees (and warns about) its absence. */
7353
+ pruneTerminalRuns;
7354
+ /** Forwarded verbatim — a count reads no payload. Bound only when the inner store counts, so the
7355
+ * engine's facet/listing fallback still applies through the wrapper. */
7356
+ countRuns;
7357
+ /** Schedules persist their run `input` inside `spec` — encoded like any other payload. Bound only
7358
+ * when the inner store persists schedules, so `engine.schedules` reports absence faithfully. */
7359
+ saveSchedule;
7360
+ getSchedule;
7361
+ updateSchedule;
7362
+ deleteSchedule;
7363
+ listSchedules;
6556
7364
  constructor(inner, codec) {
6557
7365
  this.inner = inner;
6558
7366
  this.codec = codec;
7367
+ const deleteRuns = inner.deleteRuns;
7368
+ if (deleteRuns) this.deleteRuns = (ids) => deleteRuns.call(inner, ids);
7369
+ const prune = inner.pruneTerminalRuns;
7370
+ if (prune) this.pruneTerminalRuns = (policy, now, limit) => prune.call(inner, policy, now, limit);
7371
+ const count = inner.countRuns;
7372
+ if (count) this.countRuns = (query) => count.call(inner, query);
7373
+ const { saveSchedule, getSchedule, updateSchedule, deleteSchedule, listSchedules } = inner;
7374
+ if (saveSchedule && getSchedule && updateSchedule && deleteSchedule && listSchedules) {
7375
+ this.saveSchedule = (r) => saveSchedule.call(inner, this.encSchedule(r));
7376
+ this.getSchedule = async (id) => this.decSchedule(await getSchedule.call(inner, id));
7377
+ this.updateSchedule = (id, patch, expected) => updateSchedule.call(inner, id, patch.spec ? {
7378
+ ...patch,
7379
+ spec: this.encSpec(patch.spec)
7380
+ } : patch, expected);
7381
+ this.deleteSchedule = (id) => deleteSchedule.call(inner, id);
7382
+ this.listSchedules = async (q) => (await listSchedules.call(inner, q)).map((r) => this.decSchedule(r));
7383
+ }
6559
7384
  const facets = inner.runFacets;
6560
7385
  if (facets) this.runFacets = (query) => facets.call(inner, query);
6561
7386
  const valueFacets = inner.runValueFacets;
@@ -6597,6 +7422,28 @@ var CodecStateStore = class {
6597
7422
  output: this.dec(cp.output)
6598
7423
  };
6599
7424
  }
7425
+ encSpec(spec) {
7426
+ return "input" in spec ? {
7427
+ ...spec,
7428
+ input: this.enc(spec.input)
7429
+ } : spec;
7430
+ }
7431
+ encSchedule(r) {
7432
+ return {
7433
+ ...r,
7434
+ spec: this.encSpec(r.spec)
7435
+ };
7436
+ }
7437
+ decSchedule(r) {
7438
+ if (!r || !("input" in r.spec)) return r;
7439
+ return {
7440
+ ...r,
7441
+ spec: {
7442
+ ...r.spec,
7443
+ input: this.dec(r.spec.input)
7444
+ }
7445
+ };
7446
+ }
6600
7447
  ensureSchema() {
6601
7448
  return this.inner.ensureSchema?.() ?? Promise.resolve();
6602
7449
  }
@@ -6795,126 +7642,6 @@ function collectMetrics(engine) {
6795
7642
  }
6796
7643
  __name(collectMetrics, "collectMetrics");
6797
7644
 
6798
- // src/scheduler.ts
6799
- var import_node_module = require("module");
6800
-
6801
- // src/cron-compat.ts
6802
- function isIndexable(value) {
6803
- return typeof value === "object" && value !== null || typeof value === "function";
6804
- }
6805
- __name(isIndexable, "isIndexable");
6806
- function unwrapDefault(module_) {
6807
- if (!isIndexable(module_)) return module_;
6808
- if (Reflect.get(module_, "parseExpression") !== void 0) return module_;
6809
- if (Reflect.get(module_, "CronExpressionParser") !== void 0) return module_;
6810
- const inner = Reflect.get(module_, "default");
6811
- return inner === void 0 ? module_ : inner;
6812
- }
6813
- __name(unwrapDefault, "unwrapDefault");
6814
- function v4Entry(module_) {
6815
- if (!isIndexable(module_)) return void 0;
6816
- const parse = Reflect.get(module_, "parseExpression");
6817
- if (typeof parse !== "function") return void 0;
6818
- return (expr, options) => parse.call(module_, expr, options);
6819
- }
6820
- __name(v4Entry, "v4Entry");
6821
- function v5Entry(module_) {
6822
- if (!isIndexable(module_)) return void 0;
6823
- const parser = Reflect.get(module_, "CronExpressionParser");
6824
- if (!isIndexable(parser)) return void 0;
6825
- const parse = Reflect.get(parser, "parse");
6826
- if (typeof parse !== "function") return void 0;
6827
- return (expr, options) => parse.call(parser, expr, options);
6828
- }
6829
- __name(v5Entry, "v5Entry");
6830
- function resolveCronParse(module_) {
6831
- const resolved = unwrapDefault(module_);
6832
- return v4Entry(resolved) ?? v5Entry(resolved);
6833
- }
6834
- __name(resolveCronParse, "resolveCronParse");
6835
-
6836
- // src/scheduler.ts
6837
- var nodeRequire = (0, import_node_module.createRequire)(__importMetaUrl);
6838
- var defaultSleep = /* @__PURE__ */ __name((ms) => new Promise((r) => setTimeout(r, ms)), "defaultSleep");
6839
- function scheduledRunId(key, everyMs, nowMs) {
6840
- return `sched:${key}:${Math.floor(nowMs / everyMs)}`;
6841
- }
6842
- __name(scheduledRunId, "scheduledRunId");
6843
- var cronParser;
6844
- function loadCronParser() {
6845
- if (cronParser !== void 0) return cronParser;
6846
- try {
6847
- cronParser = nodeRequire("cron-parser");
6848
- } catch {
6849
- throw new Error('cron schedules need the optional peer dependency "cron-parser" \u2014 install it (e.g. `npm i cron-parser`).');
6850
- }
6851
- return cronParser;
6852
- }
6853
- __name(loadCronParser, "loadCronParser");
6854
- function prevCronFireMs(expr, nowMs, timezone = "UTC") {
6855
- const parse = resolveCronParse(loadCronParser());
6856
- if (!parse) {
6857
- throw new Error(`the installed "cron-parser" exposes neither v4's parseExpression nor v5's CronExpressionParser.parse \u2014 the supported range is ^4.0.0 || ^5.0.0.`);
6858
- }
6859
- return parse(expr, {
6860
- currentDate: new Date(nowMs + 1),
6861
- tz: timezone
6862
- }).prev().toDate().getTime();
6863
- }
6864
- __name(prevCronFireMs, "prevCronFireMs");
6865
- function scheduleRunIdAt(s, nowMs) {
6866
- if (s.cron != null) return `sched:${s.key}:${prevCronFireMs(s.cron, nowMs, s.timezone)}`;
6867
- if (s.everyMs != null) return scheduledRunId(s.key, s.everyMs, nowMs);
6868
- throw new Error(`schedule "${s.key}" needs either "everyMs" or "cron"`);
6869
- }
6870
- __name(scheduleRunIdAt, "scheduleRunIdAt");
6871
- function scheduleRunIdsAt(s, nowMs) {
6872
- const current = scheduleRunIdAt(s, nowMs);
6873
- if (!s.backfill || s.backfill.maxCatchup <= 0) return [
6874
- current
6875
- ];
6876
- const ids = [];
6877
- if (s.everyMs != null) {
6878
- const bucket = Math.floor(nowMs / s.everyMs);
6879
- const oldest = Math.max(0, bucket - s.backfill.maxCatchup);
6880
- for (let b = oldest; b < bucket; b += 1) ids.push(`sched:${s.key}:${b}`);
6881
- } else if (s.cron != null) {
6882
- const prior = [];
6883
- let cursor = prevCronFireMs(s.cron, nowMs, s.timezone);
6884
- for (let i = 0; i < s.backfill.maxCatchup; i += 1) {
6885
- const before = prevCronFireMs(s.cron, cursor - 1, s.timezone);
6886
- prior.push(`sched:${s.key}:${before}`);
6887
- cursor = before;
6888
- }
6889
- ids.push(...prior.reverse());
6890
- }
6891
- ids.push(current);
6892
- return ids;
6893
- }
6894
- __name(scheduleRunIdsAt, "scheduleRunIdsAt");
6895
- async function runSchedules(engine, schedules, nowMs, opts) {
6896
- const random = opts?.random ?? Math.random;
6897
- const sleep = opts?.sleep ?? defaultSleep;
6898
- const ids = [];
6899
- for (const s of schedules) {
6900
- if (s.paused) continue;
6901
- if (s.overlap === "skip" && s.everyMs) {
6902
- const prevBucket = Math.floor(nowMs / s.everyMs) - 1;
6903
- if (prevBucket >= 0) {
6904
- const prev = await engine.getRun(`sched:${s.key}:${prevBucket}`);
6905
- if (prev && (prev.status === "running" || prev.status === "suspended")) continue;
6906
- }
6907
- }
6908
- if (s.jitter && s.jitter > 0) await sleep(Math.floor(random() * s.jitter));
6909
- for (const runId of scheduleRunIdsAt(s, nowMs)) {
6910
- await engine.start(s.workflow, s.input, runId);
6911
- ids.push(runId);
6912
- }
6913
- }
6914
- return ids;
6915
- }
6916
- __name(runSchedules, "runSchedules");
6917
-
6918
7645
  // src/search-attributes.ts
6919
7646
  function compare(actual, filter) {
6920
7647
  if (filter.op === "in") return filter.values.some((v) => actual === v);
@@ -7122,6 +7849,38 @@ var InMemoryStateStore = class {
7122
7849
  }
7123
7850
  this.reindexAttributes(runId, void 0);
7124
7851
  }
7852
+ async deleteRuns(runIds) {
7853
+ for (const runId of runIds) await this.deleteRun(runId);
7854
+ }
7855
+ async pruneTerminalRuns(policy, nowMs, limit) {
7856
+ const statuses = policy.statuses.filter((s) => TERMINAL_RUN_STATUSES.includes(s));
7857
+ if (statuses.length === 0 || limit <= 0) return 0;
7858
+ if (policy.maxAge == null && policy.maxCount == null) return 0;
7859
+ const inScope = this.matching({
7860
+ ...policy.scope ?? {},
7861
+ statuses
7862
+ });
7863
+ const ids = /* @__PURE__ */ new Set();
7864
+ if (policy.maxAge != null) {
7865
+ const cutoff = nowMs - parseDuration(policy.maxAge);
7866
+ const expired = inScope.filter((r) => r.updatedAt.getTime() < cutoff).sort((a, b) => a.updatedAt.getTime() - b.updatedAt.getTime()).slice(0, limit);
7867
+ for (const r of expired) ids.add(r.id);
7868
+ }
7869
+ if (policy.maxCount != null && ids.size < limit) {
7870
+ const byRecency = [
7871
+ ...inScope
7872
+ ].sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime() || b.id.localeCompare(a.id));
7873
+ for (const r of byRecency.slice(policy.maxCount)) {
7874
+ if (ids.size >= limit) break;
7875
+ ids.add(r.id);
7876
+ }
7877
+ }
7878
+ const doomed = [
7879
+ ...ids
7880
+ ].slice(0, limit);
7881
+ await this.deleteRuns(doomed);
7882
+ return doomed.length;
7883
+ }
7125
7884
  async getCheckpoint(runId, seq) {
7126
7885
  const cp = this.checkpoints.get(this.key(runId, seq));
7127
7886
  return cp ? {
@@ -7329,6 +8088,9 @@ var InMemoryStateStore = class {
7329
8088
  ...r
7330
8089
  }));
7331
8090
  }
8091
+ async countRuns(query) {
8092
+ return this.matching(query).length;
8093
+ }
7332
8094
  async runFacets(query) {
7333
8095
  return mergeRunFacetRows(this.matching(query).map((run) => ({
7334
8096
  status: run.status,
@@ -7346,6 +8108,34 @@ var InMemoryStateStore = class {
7346
8108
  });
7347
8109
  return runValueFacetsFromRuns(runs, axis, opts);
7348
8110
  }
8111
+ schedules = /* @__PURE__ */ new Map();
8112
+ async saveSchedule(record) {
8113
+ this.schedules.set(record.id, structuredClone(record));
8114
+ }
8115
+ async getSchedule(id) {
8116
+ const record = this.schedules.get(id);
8117
+ return record ? structuredClone(record) : null;
8118
+ }
8119
+ async updateSchedule(id, patch, expectedNextFireAt) {
8120
+ const record = this.schedules.get(id);
8121
+ if (!record) return false;
8122
+ if (expectedNextFireAt !== void 0 && record.nextFireAt !== expectedNextFireAt) return false;
8123
+ this.schedules.set(id, {
8124
+ ...record,
8125
+ ...structuredClone(patch)
8126
+ });
8127
+ return true;
8128
+ }
8129
+ async deleteSchedule(id) {
8130
+ return this.schedules.delete(id);
8131
+ }
8132
+ async listSchedules(query) {
8133
+ const rows = [
8134
+ ...this.schedules.values()
8135
+ ].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));
8136
+ const offset = query.offset ?? 0;
8137
+ return rows.slice(offset, query.limit != null ? offset + query.limit : void 0).map((s) => structuredClone(s));
8138
+ }
7349
8139
  async listCheckpoints(runId) {
7350
8140
  const rows = [
7351
8141
  ...this.checkpoints.values()
@@ -7427,7 +8217,9 @@ var InMemoryTransport = class {
7427
8217
  CONTROL_FLOW_SIGNAL,
7428
8218
  CURRENT_PROTOCOL_VERSION,
7429
8219
  CodecStateStore,
8220
+ ConcurrencyLimitError,
7430
8221
  ContinueAsNew,
8222
+ DEFAULT_CONCURRENCY_STATUSES,
7431
8223
  DEFAULT_PARTITION,
7432
8224
  DURABLE_OPTIONS,
7433
8225
  DURABLE_OPTIONS_CANONICAL,
@@ -7446,6 +8238,7 @@ var InMemoryTransport = class {
7446
8238
  InMemoryTransport,
7447
8239
  LEGACY_V1_CAPABILITIES,
7448
8240
  LEGACY_V1_PROTOCOL,
8241
+ NestedWorkflowCallError,
7449
8242
  NonDeterminismError,
7450
8243
  QueueController,
7451
8244
  RUN_VALUE_FACET_LIMIT,
@@ -7457,6 +8250,9 @@ var InMemoryTransport = class {
7457
8250
  RunGateway,
7458
8251
  STATE_STORE,
7459
8252
  STATE_STORE_CANONICAL,
8253
+ ScheduleAlreadyExistsError,
8254
+ ScheduleClient,
8255
+ ScheduleNotFoundError,
7460
8256
  SignalTimeoutError,
7461
8257
  SingletonQueueFullError,
7462
8258
  TERMINAL_RUN_STATUSES,
@@ -7470,6 +8266,7 @@ var InMemoryTransport = class {
7470
8266
  aggregateAnnouncements,
7471
8267
  announcementsOf,
7472
8268
  applyAttributeQuery,
8269
+ assertConcurrency,
7473
8270
  attributeColumnFor,
7474
8271
  attributeOperand,
7475
8272
  attributePredicateOperands,
@@ -7482,10 +8279,13 @@ var InMemoryTransport = class {
7482
8279
  classifyWaiterToken,
7483
8280
  clearDurableExecutionWrappers,
7484
8281
  collectMetrics,
8282
+ concurrencyTag,
7485
8283
  controlPlaneDescriptor,
8284
+ countRuns,
7486
8285
  coveredTokens,
7487
8286
  createStepLogger,
7488
8287
  currentStep,
8288
+ currentStepScope,
7489
8289
  currentWorkflowCtx,
7490
8290
  debug,
7491
8291
  describeWorker,
@@ -7504,6 +8304,7 @@ var InMemoryTransport = class {
7504
8304
  isEngineMintedTag,
7505
8305
  isEventToken,
7506
8306
  isLegacyDescriptor,
8307
+ isNonEmptyRunScope,
7507
8308
  isWorkflowControlFlowSignal,
7508
8309
  log,
7509
8310
  matchesAttributes,
@@ -7511,6 +8312,7 @@ var InMemoryTransport = class {
7511
8312
  mergeRunValueFacetRows,
7512
8313
  missingCapabilities,
7513
8314
  negotiate,
8315
+ nextCronFireMs,
7514
8316
  normalizeAttributeRows,
7515
8317
  normalizeDescriptor,
7516
8318
  observedAnnouncements,
@@ -7520,11 +8322,14 @@ var InMemoryTransport = class {
7520
8322
  prevCronFireMs,
7521
8323
  readSearchAttributes,
7522
8324
  requiredCapabilities,
8325
+ resolveConcurrency,
7523
8326
  resolveRouting,
7524
8327
  resolveRunWaiting,
7525
8328
  runDurableExecution,
7526
8329
  runInStepLogger,
8330
+ runInStepScope,
7527
8331
  runInWorkflowCtx,
8332
+ runOutsideWorkflowCtx,
7528
8333
  runSchedules,
7529
8334
  runStepHandler,
7530
8335
  runValueFacetsFromRuns,