@dudousxd/nestjs-durable 0.44.0 → 0.45.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.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { WorkflowCtx, SearchAttributes, WorkflowClass, StepError, WorkflowRef, SingletonConfig, SearchAttributesSchema, StateStore, Transport, NamedTransport, ControlPlane, ScheduledWorkflow, RetentionPolicy, QueueConfig, AdmissionBackend, WorkflowInputOf, StartOptions, RunResult, RunGateway, WorkflowEngine, EntityHandler, DurableTopology, RunDetail, RunQuery, RunListItem, RunFacetQuery, RunFacetRow, RunValueAxis, RunValueFacetOptions, RunValueFacetRow, GroupHealth, RunWaiting, EngineEvent, RunRequest, RunReply, StepInvocation, TenantEvent } from '@dudousxd/nestjs-durable-core';
2
- export { AttributeFilter, EngineEvent, InferSearchAttributes, RunDetail, RunGateway, RunListItem, RunQuery, RunStatus, RunWaiting, SearchAttributes, SearchAttributesSchema, StepCheckpoint, StepEvent, StepLogger, WorkflowCtx, WorkflowEngine, WorkflowHandler, WorkflowRun, readSearchAttributes } from '@dudousxd/nestjs-durable-core';
1
+ import { WorkflowCtx, SearchAttributes, WorkflowClass, StepError, WorkflowRef, SingletonConfig, ConcurrencyConfig, SearchAttributesSchema, StateStore, Transport, NamedTransport, ControlPlane, ScheduledWorkflow, RetentionPolicy, QueueConfig, AdmissionBackend, WorkflowInputOf, StartOptions, RunResult, RunGateway, WorkflowEngine, EntityHandler, DurableTopology, RunDetail, RunQuery, RunListItem, RunFacetQuery, RunFacetRow, RunValueAxis, RunValueFacetOptions, RunValueFacetRow, GroupHealth, RunWaiting, EngineEvent, RunRequest, RunReply, StepInvocation, TenantEvent } from '@dudousxd/nestjs-durable-core';
2
+ export { AttributeFilter, DynamicWorkflowCtx, EngineEvent, InferSearchAttributes, RunDetail, RunGateway, RunListItem, RunQuery, RunStatus, RunWaiting, SearchAttributes, SearchAttributesSchema, StepCheckpoint, StepEvent, StepLogger, WorkflowCtx, WorkflowEngine, WorkflowHandler, WorkflowRun, readSearchAttributes } from '@dudousxd/nestjs-durable-core';
3
3
  import { z } from 'zod';
4
4
  import { ConcurrencyOption, StartRunDeps, RunRedisWorkerOptions, RunningWorker, DurableWorkerRuntime } from '@dudousxd/durable-worker';
5
5
  import { DynamicModule, InjectionToken, OnModuleInit, OnApplicationBootstrap, OnApplicationShutdown, Provider } from '@nestjs/common';
@@ -112,6 +112,8 @@ interface WorkflowMeta {
112
112
  tags?: string[] | undefined;
113
113
  /** Per-key serialization (a durable mutex). See `WorkflowOptions`. */
114
114
  singleton?: SingletonConfig | undefined;
115
+ /** Start-time concurrency quota (rejecting). See `WorkflowOptions`. */
116
+ concurrency?: ConcurrencyConfig | undefined;
115
117
  /** Max wall-clock lifetime before a run is cancelled (e.g. `'2h'`). See `WorkflowOptions`. */
116
118
  executionTimeout?: string | number | undefined;
117
119
  /** class-validator DTO validated at start. See `WorkflowOptions`. */
@@ -156,6 +158,15 @@ interface WorkflowOptions {
156
158
  * 1) raises the concurrency.
157
159
  */
158
160
  singleton?: SingletonConfig;
161
+ /**
162
+ * A start-time concurrency QUOTA: at most `limit` runs sharing a key in flight, and a start over it
163
+ * is REJECTED (`ConcurrencyLimitError`, nothing created) rather than queued. e.g.
164
+ * `concurrency: { key: (input) => `tenant:${input.tenantId}`, limit: 8 }` caps what one tenant can
165
+ * have executing at once. The key is shared across workflows; `limit` may be a (async) function of
166
+ * the key for per-plan limits; `countStatuses` narrows what occupies a slot (default: every
167
+ * non-terminal status). Use `singleton` instead when excess runs should wait their turn.
168
+ */
169
+ concurrency?: ConcurrencyConfig;
159
170
  /**
160
171
  * Max wall-clock lifetime for a run of this workflow (e.g. `'2h'`, `'7 days'`, or ms). A run that
161
172
  * outlives it is moved to `cancelled` (`execution_timeout`) by the timer poller — a backstop for
@@ -363,7 +374,7 @@ declare function getOnEvents(meta: WorkflowMeta, target: object): string[];
363
374
 
364
375
  /**
365
376
  * Retention config for the {@link RetentionPoller}. One or more {@link RetentionPolicy policies}
366
- * (status sets must be disjoint — validated at boot), swept together on a shared interval.
377
+ * (status sets must be disjoint per scope — validated at boot), swept together on a shared interval.
367
378
  *
368
379
  * ```ts
369
380
  * retention: {
@@ -372,9 +383,14 @@ declare function getOnEvents(meta: WorkflowMeta, target: object): string[];
372
383
  * policies: [
373
384
  * { statuses: ['completed', 'cancelled'], maxAge: '14d', maxCount: 200 },
374
385
  * { statuses: ['failed'], maxAge: '90d' }, // keep failures longer for debugging
386
+ * // Scoped: chat turns age out after a day, whatever the defaults above say.
387
+ * { statuses: ['completed', 'failed', 'cancelled'], maxAge: '1d', scope: { tags: ['chat'] } },
375
388
  * ],
376
389
  * }
377
390
  * ```
391
+ *
392
+ * Policies are applied one after another, so a run matched by several scopes is pruned by the
393
+ * strictest of them.
378
394
  */
379
395
  interface DurableRetentionOptions {
380
396
  /** The retention rules, one per (disjoint) status group. */
@@ -513,12 +529,21 @@ interface DurableModuleOptions {
513
529
  * optional `cron-parser` peer dependency. Operator only.
514
530
  */
515
531
  schedules?: ScheduledWorkflow[];
532
+ /**
533
+ * Fire the PERSISTED schedules managed at runtime through `engine.schedules` (create/upsert/pause/
534
+ * resume/delete/list) on every timer-poll tick of a driving instance — one indexed "due by now"
535
+ * query per tick. Off by default, so a deployment that never uses them doesn't need the
536
+ * `durable_schedules` table. Safe with any number of driving instances: each window's run id is
537
+ * deterministic and advancing a schedule is a compare-and-set. Operator only.
538
+ */
539
+ persistedSchedules?: boolean;
516
540
  /**
517
541
  * Hard-prune terminal run history on an interval so `durable_workflow_runs` (and its child tables)
518
542
  * stays bounded — without it, completed/failed/cancelled runs accumulate forever and the timer
519
543
  * poller's per-tick status scans get linearly slower. Driving instances only. Omit to keep all
520
- * history (the default). Requires a store adapter that implements `pruneTerminalRuns` (the
521
- * MikroORM adapter does); other adapters no-op with a warning. See {@link DurableRetentionOptions}.
544
+ * history (the default). Requires a store adapter that implements `pruneTerminalRuns` (every
545
+ * first-party adapter does); a custom store without it no-ops with a warning. See
546
+ * {@link DurableRetentionOptions}.
522
547
  * Operator only.
523
548
  */
524
549
  retention?: DurableRetentionOptions;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { WorkflowCtx, SearchAttributes, WorkflowClass, StepError, WorkflowRef, SingletonConfig, SearchAttributesSchema, StateStore, Transport, NamedTransport, ControlPlane, ScheduledWorkflow, RetentionPolicy, QueueConfig, AdmissionBackend, WorkflowInputOf, StartOptions, RunResult, RunGateway, WorkflowEngine, EntityHandler, DurableTopology, RunDetail, RunQuery, RunListItem, RunFacetQuery, RunFacetRow, RunValueAxis, RunValueFacetOptions, RunValueFacetRow, GroupHealth, RunWaiting, EngineEvent, RunRequest, RunReply, StepInvocation, TenantEvent } from '@dudousxd/nestjs-durable-core';
2
- export { AttributeFilter, EngineEvent, InferSearchAttributes, RunDetail, RunGateway, RunListItem, RunQuery, RunStatus, RunWaiting, SearchAttributes, SearchAttributesSchema, StepCheckpoint, StepEvent, StepLogger, WorkflowCtx, WorkflowEngine, WorkflowHandler, WorkflowRun, readSearchAttributes } from '@dudousxd/nestjs-durable-core';
1
+ import { WorkflowCtx, SearchAttributes, WorkflowClass, StepError, WorkflowRef, SingletonConfig, ConcurrencyConfig, SearchAttributesSchema, StateStore, Transport, NamedTransport, ControlPlane, ScheduledWorkflow, RetentionPolicy, QueueConfig, AdmissionBackend, WorkflowInputOf, StartOptions, RunResult, RunGateway, WorkflowEngine, EntityHandler, DurableTopology, RunDetail, RunQuery, RunListItem, RunFacetQuery, RunFacetRow, RunValueAxis, RunValueFacetOptions, RunValueFacetRow, GroupHealth, RunWaiting, EngineEvent, RunRequest, RunReply, StepInvocation, TenantEvent } from '@dudousxd/nestjs-durable-core';
2
+ export { AttributeFilter, DynamicWorkflowCtx, EngineEvent, InferSearchAttributes, RunDetail, RunGateway, RunListItem, RunQuery, RunStatus, RunWaiting, SearchAttributes, SearchAttributesSchema, StepCheckpoint, StepEvent, StepLogger, WorkflowCtx, WorkflowEngine, WorkflowHandler, WorkflowRun, readSearchAttributes } from '@dudousxd/nestjs-durable-core';
3
3
  import { z } from 'zod';
4
4
  import { ConcurrencyOption, StartRunDeps, RunRedisWorkerOptions, RunningWorker, DurableWorkerRuntime } from '@dudousxd/durable-worker';
5
5
  import { DynamicModule, InjectionToken, OnModuleInit, OnApplicationBootstrap, OnApplicationShutdown, Provider } from '@nestjs/common';
@@ -112,6 +112,8 @@ interface WorkflowMeta {
112
112
  tags?: string[] | undefined;
113
113
  /** Per-key serialization (a durable mutex). See `WorkflowOptions`. */
114
114
  singleton?: SingletonConfig | undefined;
115
+ /** Start-time concurrency quota (rejecting). See `WorkflowOptions`. */
116
+ concurrency?: ConcurrencyConfig | undefined;
115
117
  /** Max wall-clock lifetime before a run is cancelled (e.g. `'2h'`). See `WorkflowOptions`. */
116
118
  executionTimeout?: string | number | undefined;
117
119
  /** class-validator DTO validated at start. See `WorkflowOptions`. */
@@ -156,6 +158,15 @@ interface WorkflowOptions {
156
158
  * 1) raises the concurrency.
157
159
  */
158
160
  singleton?: SingletonConfig;
161
+ /**
162
+ * A start-time concurrency QUOTA: at most `limit` runs sharing a key in flight, and a start over it
163
+ * is REJECTED (`ConcurrencyLimitError`, nothing created) rather than queued. e.g.
164
+ * `concurrency: { key: (input) => `tenant:${input.tenantId}`, limit: 8 }` caps what one tenant can
165
+ * have executing at once. The key is shared across workflows; `limit` may be a (async) function of
166
+ * the key for per-plan limits; `countStatuses` narrows what occupies a slot (default: every
167
+ * non-terminal status). Use `singleton` instead when excess runs should wait their turn.
168
+ */
169
+ concurrency?: ConcurrencyConfig;
159
170
  /**
160
171
  * Max wall-clock lifetime for a run of this workflow (e.g. `'2h'`, `'7 days'`, or ms). A run that
161
172
  * outlives it is moved to `cancelled` (`execution_timeout`) by the timer poller — a backstop for
@@ -363,7 +374,7 @@ declare function getOnEvents(meta: WorkflowMeta, target: object): string[];
363
374
 
364
375
  /**
365
376
  * Retention config for the {@link RetentionPoller}. One or more {@link RetentionPolicy policies}
366
- * (status sets must be disjoint — validated at boot), swept together on a shared interval.
377
+ * (status sets must be disjoint per scope — validated at boot), swept together on a shared interval.
367
378
  *
368
379
  * ```ts
369
380
  * retention: {
@@ -372,9 +383,14 @@ declare function getOnEvents(meta: WorkflowMeta, target: object): string[];
372
383
  * policies: [
373
384
  * { statuses: ['completed', 'cancelled'], maxAge: '14d', maxCount: 200 },
374
385
  * { statuses: ['failed'], maxAge: '90d' }, // keep failures longer for debugging
386
+ * // Scoped: chat turns age out after a day, whatever the defaults above say.
387
+ * { statuses: ['completed', 'failed', 'cancelled'], maxAge: '1d', scope: { tags: ['chat'] } },
375
388
  * ],
376
389
  * }
377
390
  * ```
391
+ *
392
+ * Policies are applied one after another, so a run matched by several scopes is pruned by the
393
+ * strictest of them.
378
394
  */
379
395
  interface DurableRetentionOptions {
380
396
  /** The retention rules, one per (disjoint) status group. */
@@ -513,12 +529,21 @@ interface DurableModuleOptions {
513
529
  * optional `cron-parser` peer dependency. Operator only.
514
530
  */
515
531
  schedules?: ScheduledWorkflow[];
532
+ /**
533
+ * Fire the PERSISTED schedules managed at runtime through `engine.schedules` (create/upsert/pause/
534
+ * resume/delete/list) on every timer-poll tick of a driving instance — one indexed "due by now"
535
+ * query per tick. Off by default, so a deployment that never uses them doesn't need the
536
+ * `durable_schedules` table. Safe with any number of driving instances: each window's run id is
537
+ * deterministic and advancing a schedule is a compare-and-set. Operator only.
538
+ */
539
+ persistedSchedules?: boolean;
516
540
  /**
517
541
  * Hard-prune terminal run history on an interval so `durable_workflow_runs` (and its child tables)
518
542
  * stays bounded — without it, completed/failed/cancelled runs accumulate forever and the timer
519
543
  * poller's per-tick status scans get linearly slower. Driving instances only. Omit to keep all
520
- * history (the default). Requires a store adapter that implements `pruneTerminalRuns` (the
521
- * MikroORM adapter does); other adapters no-op with a warning. See {@link DurableRetentionOptions}.
544
+ * history (the default). Requires a store adapter that implements `pruneTerminalRuns` (every
545
+ * first-party adapter does); a custom store without it no-ops with a warning. See
546
+ * {@link DurableRetentionOptions}.
522
547
  * Operator only.
523
548
  */
524
549
  retention?: DurableRetentionOptions;
package/dist/index.js CHANGED
@@ -127,6 +127,7 @@ function Workflow(options) {
127
127
  deadLetterWorkflow: options.deadLetterWorkflow,
128
128
  tags: options.tags,
129
129
  singleton: options.singleton,
130
+ concurrency: options.concurrency,
130
131
  executionTimeout: options.executionTimeout,
131
132
  inputSchema: options.inputSchema,
132
133
  validateInput: options.validateInput,
@@ -1204,6 +1205,7 @@ var MAX_BATCHES_PER_POLICY = 100;
1204
1205
  function validateRetention(retention) {
1205
1206
  const seen = /* @__PURE__ */ new Set();
1206
1207
  for (const policy of retention.policies) {
1208
+ const scopeKey = policy.scope ? stableKey(policy.scope) : "";
1207
1209
  if (policy.statuses.length === 0) {
1208
1210
  throw new Error("durable retention: each policy must list at least one status");
1209
1211
  }
@@ -1215,14 +1217,24 @@ function validateRetention(retention) {
1215
1217
  if (!TERMINAL_RUN_STATUSES.includes(status)) {
1216
1218
  throw new Error(`durable retention: status "${status}" is not terminal; only ${TERMINAL_RUN_STATUSES.join(", ")} can be pruned`);
1217
1219
  }
1218
- if (seen.has(status)) {
1219
- throw new Error(`durable retention: status "${status}" appears in more than one policy; status sets must be disjoint`);
1220
+ const key = `${scopeKey}|${status}`;
1221
+ if (seen.has(key)) {
1222
+ throw new Error(`durable retention: status "${status}" appears in more than one policy with the same scope; status sets must be disjoint per scope`);
1220
1223
  }
1221
- seen.add(status);
1224
+ seen.add(key);
1222
1225
  }
1223
1226
  }
1224
1227
  }
1225
1228
  __name(validateRetention, "validateRetention");
1229
+ function stableKey(value) {
1230
+ if (Array.isArray(value)) return `[${value.map(stableKey).join(",")}]`;
1231
+ if (value && typeof value === "object") {
1232
+ const entries = Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a.localeCompare(b));
1233
+ return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${stableKey(v)}`).join(",")}}`;
1234
+ }
1235
+ return JSON.stringify(value);
1236
+ }
1237
+ __name(stableKey, "stableKey");
1226
1238
  var RetentionPoller = class {
1227
1239
  static {
1228
1240
  __name(this, "RetentionPoller");
@@ -1743,6 +1755,9 @@ var TimerPoller = class {
1743
1755
  if (schedules && schedules.length > 0) {
1744
1756
  await this.sweep("schedules", () => runSchedules(this.engine, schedules, Date.now()));
1745
1757
  }
1758
+ if (this.options.persistedSchedules) {
1759
+ await this.sweep("persistedSchedules", () => this.engine.schedules.tick());
1760
+ }
1746
1761
  } finally {
1747
1762
  this.polling = false;
1748
1763
  }
@@ -1919,6 +1934,7 @@ var WorkflowRegistrar = class {
1919
1934
  origin,
1920
1935
  tags: meta.tags,
1921
1936
  singleton: meta.singleton,
1937
+ concurrency: meta.concurrency,
1922
1938
  executionTimeout: meta.executionTimeout,
1923
1939
  requires: meta.requires,
1924
1940
  validateInput,