UNPKG

@dudousxd/nestjs-durable

Version:

Durable workflows for NestJS — module, decorators, discovery and boot recovery

2,228 lines 87.9 kB
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);

// src/index.ts
var index_exports = {};
__export(index_exports, {
  CONTEXT_ACCESSOR: () => CONTEXT_ACCESSOR,
  DEAD_LETTER_METADATA: () => DEAD_LETTER_METADATA,
  DURABLE_STEP_METADATA: () => DURABLE_STEP_METADATA,
  DURABLE_WORKER_RUNNERS: () => DURABLE_WORKER_RUNNERS,
  DeadLetter: () => DeadLetter,
  DurableModule: () => DurableModule,
  DurableStartClient: () => DurableStartClient,
  DurableStep: () => DurableStep,
  ENTITY_METADATA: () => ENTITY_METADATA,
  ENTITY_ON_METADATA: () => ENTITY_ON_METADATA,
  Entity: () => Entity,
  EntityService: () => EntityService,
  IN_APP_RUN_REDIS_WORKER: () => IN_APP_RUN_REDIS_WORKER,
  IN_APP_WORKER_BINDING: () => IN_APP_WORKER_BINDING,
  IN_APP_WORKER_RUNNERS: () => IN_APP_WORKER_RUNNERS,
  IN_APP_WORKER_RUNTIME: () => IN_APP_WORKER_RUNTIME,
  InAppWorkerBootstrap: () => InAppWorkerBootstrap,
  ON_EVENT_METADATA: () => ON_EVENT_METADATA,
  On: () => On,
  OnDurableEvent: () => OnDurableEvent,
  OnEvent: () => OnEvent,
  ProxyRunGateway: () => ProxyRunGateway,
  RUN_GATEWAY: () => RUN_GATEWAY,
  RUN_REDIS_WORKER: () => RUN_REDIS_WORKER,
  RunGateway: () => import_nestjs_durable_core15.RunGateway,
  RunRequestResponder: () => RunRequestResponder,
  STEP_INTERCEPTOR_METADATA: () => STEP_INTERCEPTOR_METADATA,
  Step: () => Step,
  StepInterceptor: () => StepInterceptor,
  StoreRunGateway: () => StoreRunGateway,
  TenantEventRepublisher: () => TenantEventRepublisher,
  ThinStepRegistrar: () => ThinStepRegistrar,
  ThinWorkerBootstrap: () => ThinWorkerBootstrap,
  ThinWorkflowRegistrar: () => ThinWorkflowRegistrar,
  WORKFLOW_METADATA: () => WORKFLOW_METADATA,
  Workflow: () => Workflow,
  WorkflowEngine: () => import_nestjs_durable_core15.WorkflowEngine,
  WorkflowService: () => WorkflowService,
  attributesOf: () => attributesOf,
  entityConfigFor: () => entityConfigFor,
  getDurableStepMeta: () => getDurableStepMeta,
  getEntityMeta: () => getEntityMeta,
  getOnEvents: () => getOnEvents,
  getWorkflowMeta: () => getWorkflowMeta,
  inAppWorkerProviders: () => inAppWorkerProviders,
  isDeadLetterHandler: () => isDeadLetterHandler,
  isDrivingOperator: () => isDrivingOperator,
  isOperatorRole: () => isOperatorRole,
  isStepInterceptor: () => isStepInterceptor,
  readSearchAttributes: () => import_nestjs_durable_core15.readSearchAttributes,
  thinWorkerProviders: () => thinWorkerProviders,
  unavailableRunGateway: () => unavailableRunGateway
});
module.exports = __toCommonJS(index_exports);

// src/attributes-of.ts
var import_nestjs_durable_core2 = require("@dudousxd/nestjs-durable-core");

// src/decorators.ts
var import_nestjs_durable_core = require("@dudousxd/nestjs-durable-core");
var import_reflect_metadata = require("reflect-metadata");
var WORKFLOW_METADATA = Symbol("nestjs-durable:workflow");
function Workflow(options) {
  return (target) => {
    const meta = {
      name: options.name,
      version: options.version ?? "1",
      deadLetterWorkflow: options.deadLetterWorkflow,
      tags: options.tags,
      singleton: options.singleton,
      executionTimeout: options.executionTimeout,
      inputSchema: options.inputSchema,
      validateInput: options.validateInput,
      searchAttributes: options.searchAttributes,
      onEvent: options.onEvent,
      debounce: options.debounce,
      batch: options.batch,
      requires: options.requires
    };
    Reflect.defineMetadata(WORKFLOW_METADATA, meta, target);
    Object.defineProperty(target, import_nestjs_durable_core.WORKFLOW_NAME_KEY, {
      value: options.name,
      configurable: true
    });
  };
}
__name(Workflow, "Workflow");
function getWorkflowMeta(target) {
  return Reflect.getMetadata(WORKFLOW_METADATA, target);
}
__name(getWorkflowMeta, "getWorkflowMeta");
var DURABLE_STEP_METADATA = Symbol("nestjs-durable:step-handler");
function stepConfigFrom(options) {
  const config = {
    retries: options.retries,
    backoff: options.backoff,
    backoffMs: options.backoffMs,
    backoffMaxMs: options.backoffMaxMs,
    jitter: options.jitter,
    timeoutMs: options.timeoutMs,
    requires: options.requires
  };
  const hasAnyField = Object.values(config).some((value) => value !== void 0);
  return hasAnyField ? config : void 0;
}
__name(stepConfigFrom, "stepConfigFrom");
function Step(nameOrOptions) {
  return (target, propertyKey, descriptor) => {
    const options = typeof nameOrOptions === "string" ? {
      name: nameOrOptions
    } : nameOrOptions ?? {};
    const derivedName = options.name ?? `${target.constructor.name}.${String(propertyKey)}`;
    const meta = {
      name: derivedName,
      input: options.input,
      output: options.output
    };
    Reflect.defineMetadata(DURABLE_STEP_METADATA, meta, descriptor.value);
    descriptor.value[import_nestjs_durable_core.DURABLE_STEP_NAME] = derivedName;
    const config = stepConfigFrom(options);
    if (config !== void 0) {
      descriptor.value[import_nestjs_durable_core.DURABLE_STEP_CONFIG] = config;
    }
    return descriptor;
  };
}
__name(Step, "Step");
var DurableStep = Step;
function getDurableStepMeta(method) {
  return Reflect.getMetadata(DURABLE_STEP_METADATA, method);
}
__name(getDurableStepMeta, "getDurableStepMeta");
var DEAD_LETTER_METADATA = Symbol("nestjs-durable:dead-letter");
function DeadLetter() {
  return (_target, _propertyKey, descriptor) => {
    Reflect.defineMetadata(DEAD_LETTER_METADATA, true, descriptor.value);
    return descriptor;
  };
}
__name(DeadLetter, "DeadLetter");
function isDeadLetterHandler(method) {
  return Reflect.getMetadata(DEAD_LETTER_METADATA, method) === true;
}
__name(isDeadLetterHandler, "isDeadLetterHandler");
var ON_EVENT_METADATA = Symbol("nestjs-durable:on-event");
function OnDurableEvent(...events) {
  return (target) => {
    const existing = Reflect.getMetadata(ON_EVENT_METADATA, target) ?? [];
    Reflect.defineMetadata(ON_EVENT_METADATA, [
      ...existing,
      ...events
    ], target);
  };
}
__name(OnDurableEvent, "OnDurableEvent");
var OnEvent = OnDurableEvent;
function getOnEvents(meta, target) {
  const fromDecorator = Reflect.getMetadata(ON_EVENT_METADATA, target) ?? [];
  return [
    .../* @__PURE__ */ new Set([
      ...meta.onEvent ?? [],
      ...fromDecorator
    ])
  ];
}
__name(getOnEvents, "getOnEvents");

// src/attributes-of.ts
function attributesOf(workflow, run) {
  const meta = getWorkflowMeta(workflow);
  if (!meta) {
    throw new Error(`attributesOf: ${workflow.name} is not a @Workflow class \u2014 is it decorated with @Workflow({ name, searchAttributes })?`);
  }
  if (!meta.searchAttributes) {
    throw new Error(`attributesOf: workflow '${meta.name}' declares no searchAttributes schema \u2014 reading attributes by class requires the workflow to declare its schema: @Workflow({ name: '${meta.name}', searchAttributes: mySchema }).`);
  }
  return (0, import_nestjs_durable_core2.readSearchAttributes)(meta.searchAttributes, run);
}
__name(attributesOf, "attributesOf");

// src/durable-start-client.ts
var import_durable_worker = require("@dudousxd/durable-worker");
var import_nestjs_durable_core3 = require("@dudousxd/nestjs-durable-core");
var import_common = require("@nestjs/common");
function _ts_decorate(decorators, target, key, desc) {
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
  return c > 3 && r && Object.defineProperty(target, key, r), r;
}
__name(_ts_decorate, "_ts_decorate");
function _ts_metadata(k, v) {
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
}
__name(_ts_metadata, "_ts_metadata");
var DurableStartClient = class {
  static {
    __name(this, "DurableStartClient");
  }
  options;
  deps;
  tenant;
  constructor(options, deps) {
    this.options = options;
    this.deps = deps;
    this.tenant = options.partition ?? "default";
  }
  async start(workflow, input, runId = globalThis.crypto.randomUUID(), opts) {
    const name = (0, import_nestjs_durable_core3.workflowName)(workflow);
    await (0, import_durable_worker.startRun)(this.options.connection, {
      tenant: this.tenant,
      workflow: name,
      input,
      runId,
      // Option B: DO NOT pass `namespace` — the start-run queue stays the shared
      // `durable-start-run` the operator consumes; tenant rides only as message data.
      ...this.options.prefix !== void 0 ? {
        prefix: this.options.prefix
      } : {},
      ...opts?.tags !== void 0 ? {
        tags: opts.tags
      } : {},
      ...opts?.searchAttributes !== void 0 ? {
        searchAttributes: opts.searchAttributes
      } : {},
      ...this.deps !== void 0 ? {
        deps: this.deps
      } : {}
    });
    return {
      runId,
      status: "pending"
    };
  }
  cancel(_runId) {
    return tenantUnsupported("cancel");
  }
  deleteRun(_runId) {
    return tenantUnsupported("deleteRun");
  }
  // — everything below rejects: a tenant worker holds no store/driver, only the start channel. —
  // The remaining WorkflowEngine surface WorkflowService delegates to (resume/waitForRun/signal/
  // signalWithStart/publishEvent) all need the store or driver a tenant does not have. A tenant only
  // ever calls `start`; these exist so a mistaken call fails with a CLEAR, named tenant error instead
  // of a cryptic `this.engine.X is not a function` — the facade is honest about what it cannot do.
  // Params mirror the WorkflowEngine surface being faced; every method rejects without reading them.
  resume(_runId) {
    return tenantUnsupported("resume");
  }
  waitForRun(_runId, _opts) {
    return tenantUnsupported("waitForRun");
  }
  signal(_token, _payload) {
    return tenantUnsupported("signal");
  }
  signalWithStart(_workflow, _input, _runId, _signal, _opts) {
    return tenantUnsupported("signalWithStart");
  }
  publishEvent(_name, _payload, _opts) {
    return tenantUnsupported("publishEvent");
  }
};
DurableStartClient = _ts_decorate([
  (0, import_common.Injectable)(),
  _ts_metadata("design:type", Function),
  _ts_metadata("design:paramtypes", [
    typeof DurableModuleOptions === "undefined" ? Object : DurableModuleOptions,
    typeof StartRunDeps === "undefined" ? Object : StartRunDeps
  ])
], DurableStartClient);
function tenantUnsupported(method) {
  return Promise.reject(new Error(`${method}() is not available on a tenant worker (no store). Use the control plane for it.`));
}
__name(tenantUnsupported, "tenantUnsupported");

// src/durable-worker.module.ts
var import_durable_worker2 = require("@dudousxd/durable-worker");
var import_nestjs_durable_core4 = require("@dudousxd/nestjs-durable-core");
var import_common2 = require("@nestjs/common");
var import_core = require("@nestjs/core");

// src/discovery-helpers.ts
function scanWorkflows(discovery, register) {
  for (const wrapper of discovery.getProviders()) {
    const { instance } = wrapper;
    if (!instance || typeof instance !== "object") continue;
    const meta = getWorkflowMeta(instance.constructor);
    if (!meta) continue;
    const workflow = instance;
    if (typeof workflow.run !== "function") {
      throw new Error(`@Workflow ${meta.name} must define a run(ctx, input) method`);
    }
    register(meta, workflow);
  }
}
__name(scanWorkflows, "scanWorkflows");
function scanSteps(discovery, scanner, register) {
  for (const wrapper of discovery.getProviders()) {
    const { instance } = wrapper;
    if (!instance || typeof instance !== "object") continue;
    const prototype = Object.getPrototypeOf(instance);
    for (const methodName of scanner.getAllMethodNames(prototype)) {
      const method = instance[methodName];
      if (typeof method !== "function") continue;
      const meta = getDurableStepMeta(method);
      if (!meta) continue;
      const boundMethod = method;
      const handler = /* @__PURE__ */ __name(async (input, log) => {
        const validInput = meta.input ? meta.input.parse(input) : input;
        const output = await boundMethod.call(instance, validInput, log);
        return meta.output ? meta.output.parse(output) : output;
      }, "handler");
      register(meta, handler);
    }
  }
}
__name(scanSteps, "scanSteps");

// src/durable-worker.module.ts
function _ts_decorate2(decorators, target, key, desc) {
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
  return c > 3 && r && Object.defineProperty(target, key, r), r;
}
__name(_ts_decorate2, "_ts_decorate");
function _ts_metadata2(k, v) {
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
}
__name(_ts_metadata2, "_ts_metadata");
function _ts_param(paramIndex, decorator) {
  return function(target, key) {
    decorator(target, key, paramIndex);
  };
}
__name(_ts_param, "_ts_param");
var RUN_REDIS_WORKER = Symbol("nestjs-durable:run-redis-worker");
var DURABLE_WORKER_RUNNERS = Symbol("nestjs-durable:worker-runners");
function isPureThinWorker(options) {
  return options.store === void 0 && options.connection !== void 0;
}
__name(isPureThinWorker, "isPureThinWorker");
var ThinWorkflowRegistrar = class {
  static {
    __name(this, "ThinWorkflowRegistrar");
  }
  discovery;
  runtime;
  options;
  constructor(discovery, runtime, options) {
    this.discovery = discovery;
    this.runtime = runtime;
    this.options = options;
  }
  onModuleInit() {
    if (!isPureThinWorker(this.options)) return;
    scanWorkflows(this.discovery, (meta, instance) => this.runtime.registerWorkflow(meta.name, (ctx, input) => instance.run(ctx, input)));
  }
};
ThinWorkflowRegistrar = _ts_decorate2([
  (0, import_common2.Injectable)(),
  _ts_param(2, (0, import_common2.Inject)(import_nestjs_durable_core4.DURABLE_OPTIONS_CANONICAL)),
  _ts_metadata2("design:type", Function),
  _ts_metadata2("design:paramtypes", [
    typeof import_core.DiscoveryService === "undefined" ? Object : import_core.DiscoveryService,
    typeof import_durable_worker2.DurableWorkerRuntime === "undefined" ? Object : import_durable_worker2.DurableWorkerRuntime,
    typeof DurableModuleOptions === "undefined" ? Object : DurableModuleOptions
  ])
], ThinWorkflowRegistrar);
var ThinStepRegistrar = class {
  static {
    __name(this, "ThinStepRegistrar");
  }
  discovery;
  metadataScanner;
  runtime;
  options;
  constructor(discovery, metadataScanner, runtime, options) {
    this.discovery = discovery;
    this.metadataScanner = metadataScanner;
    this.runtime = runtime;
    this.options = options;
  }
  onModuleInit() {
    if (!isPureThinWorker(this.options)) return;
    scanSteps(this.discovery, this.metadataScanner, (meta, handler) => this.runtime.registerStep(meta.name, handler));
  }
};
ThinStepRegistrar = _ts_decorate2([
  (0, import_common2.Injectable)(),
  _ts_param(3, (0, import_common2.Inject)(import_nestjs_durable_core4.DURABLE_OPTIONS_CANONICAL)),
  _ts_metadata2("design:type", Function),
  _ts_metadata2("design:paramtypes", [
    typeof import_core.DiscoveryService === "undefined" ? Object : import_core.DiscoveryService,
    typeof import_core.MetadataScanner === "undefined" ? Object : import_core.MetadataScanner,
    typeof import_durable_worker2.DurableWorkerRuntime === "undefined" ? Object : import_durable_worker2.DurableWorkerRuntime,
    typeof DurableModuleOptions === "undefined" ? Object : DurableModuleOptions
  ])
], ThinStepRegistrar);
var ThinWorkerBootstrap = class {
  static {
    __name(this, "ThinWorkerBootstrap");
  }
  runtime;
  options;
  runRedisWorker;
  runnersSink;
  runners = [];
  constructor(runtime, options, runRedisWorker, runnersSink) {
    this.runtime = runtime;
    this.options = options;
    this.runRedisWorker = runRedisWorker;
    this.runnersSink = runnersSink;
  }
  async onApplicationBootstrap() {
    const options = this.options;
    if (!isPureThinWorker(options) || options.connection === void 0) return;
    const handle = await this.runRedisWorker({
      runtime: this.runtime,
      connection: options.connection,
      ...options.partition !== void 0 ? {
        partition: options.partition
      } : {},
      ...options.prefix !== void 0 ? {
        prefix: options.prefix
      } : {},
      ...options.instanceId !== void 0 ? {
        instanceId: options.instanceId
      } : {},
      ...options.concurrency !== void 0 ? {
        concurrency: options.concurrency
      } : {}
    });
    this.runners.push(handle);
    this.runnersSink.push(handle);
  }
  async onApplicationShutdown() {
    await Promise.allSettled(this.runners.map((h) => h.close()));
  }
};
ThinWorkerBootstrap = _ts_decorate2([
  (0, import_common2.Injectable)(),
  _ts_param(1, (0, import_common2.Inject)(import_nestjs_durable_core4.DURABLE_OPTIONS_CANONICAL)),
  _ts_param(2, (0, import_common2.Inject)(RUN_REDIS_WORKER)),
  _ts_param(3, (0, import_common2.Inject)(DURABLE_WORKER_RUNNERS)),
  _ts_metadata2("design:type", Function),
  _ts_metadata2("design:paramtypes", [
    typeof import_durable_worker2.DurableWorkerRuntime === "undefined" ? Object : import_durable_worker2.DurableWorkerRuntime,
    typeof DurableModuleOptions === "undefined" ? Object : DurableModuleOptions,
    typeof RunRedisWorkerFn === "undefined" ? Object : RunRedisWorkerFn,
    Array
  ])
], ThinWorkerBootstrap);
function thinWorkerProviders() {
  return [
    {
      provide: import_durable_worker2.DurableWorkerRuntime,
      // The runtime's `WorkflowWorker` falls back to its own `group` ctor param as the WORKFLOW's
      // `workflowPartition` for any `ctx.step` call (see `workflow-context.ts`'s `resolveCallGroup`)
      // — that fallback MUST equal this module's own `partition`, or a dispatched step's decision
      // carries a mismatched token and the engine dispatches it to a queue nothing here subscribes
      // to. Explicit `''` (never
      // `undefined`) so it does NOT fall through to `WorkflowWorker`'s unrelated `'workflows'`
      // default parameter.
      useFactory: /* @__PURE__ */ __name((options) => new import_durable_worker2.DurableWorkerRuntime({
        workflowGroup: options.partition ?? ""
      }), "useFactory"),
      inject: [
        import_nestjs_durable_core4.DURABLE_OPTIONS_CANONICAL
      ]
    },
    {
      provide: RUN_REDIS_WORKER,
      useValue: import_durable_worker2.runRedisWorker
    },
    {
      provide: DURABLE_WORKER_RUNNERS,
      useValue: []
    },
    ThinWorkflowRegistrar,
    ThinStepRegistrar,
    ThinWorkerBootstrap
  ];
}
__name(thinWorkerProviders, "thinWorkerProviders");
function tenantGatewayUnavailable(method) {
  return Promise.reject(new Error(`RunGateway.${method}() is not available \u2014 pass \`transport\` to use RunGateway.`));
}
__name(tenantGatewayUnavailable, "tenantGatewayUnavailable");
function unavailableRunGateway() {
  return {
    // Topology is local metadata, always answerable — a thin worker with no transport is still a tenant.
    topology() {
      return {
        role: "tenant"
      };
    },
    getRunDetail(_runId) {
      return tenantGatewayUnavailable("getRunDetail");
    },
    listRuns(_query) {
      return tenantGatewayUnavailable("listRuns");
    },
    waitingFor(_runIds) {
      return tenantGatewayUnavailable("waitingFor");
    },
    workerHealth() {
      return tenantGatewayUnavailable("workerHealth");
    },
    cancel(_runId) {
      return tenantGatewayUnavailable("cancel");
    },
    retry(_runId) {
      return tenantGatewayUnavailable("retry");
    },
    continue(_runId) {
      return tenantGatewayUnavailable("continue");
    },
    retryWithInput(_runId, _input) {
      return tenantGatewayUnavailable("retryWithInput");
    },
    redispatchPending(_runId) {
      return tenantGatewayUnavailable("redispatchPending");
    },
    subscribe(_runId, _onEvent) {
      throw new Error("RunGateway.subscribe() is not available \u2014 pass `transport` to use RunGateway.");
    }
  };
}
__name(unavailableRunGateway, "unavailableRunGateway");

// src/durable.module.ts
var import_nestjs_durable_core14 = require("@dudousxd/nestjs-durable-core");
var import_common12 = require("@nestjs/common");
var import_core5 = require("@nestjs/core");

// src/durable-step.registrar.ts
var import_nestjs_durable_core5 = require("@dudousxd/nestjs-durable-core");
var import_common3 = require("@nestjs/common");
var import_core2 = require("@nestjs/core");

// src/role.ts
function isOperatorRole(options) {
  return options.store !== void 0;
}
__name(isOperatorRole, "isOperatorRole");
function isDrivingOperator(options) {
  return isOperatorRole(options) && options.drive !== false;
}
__name(isDrivingOperator, "isDrivingOperator");

// src/durable-step.registrar.ts
function _ts_decorate3(decorators, target, key, desc) {
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
  return c > 3 && r && Object.defineProperty(target, key, r), r;
}
__name(_ts_decorate3, "_ts_decorate");
function _ts_metadata3(k, v) {
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
}
__name(_ts_metadata3, "_ts_metadata");
function _ts_param2(paramIndex, decorator) {
  return function(target, key) {
    decorator(target, key, paramIndex);
  };
}
__name(_ts_param2, "_ts_param");
function supportsHandle(transport) {
  return typeof transport?.handle === "function";
}
__name(supportsHandle, "supportsHandle");
var DurableStepRegistrar = class {
  static {
    __name(this, "DurableStepRegistrar");
  }
  discovery;
  metadataScanner;
  transport;
  options;
  constructor(discovery, metadataScanner, transport, options) {
    this.discovery = discovery;
    this.metadataScanner = metadataScanner;
    this.transport = transport;
    this.options = options;
  }
  onModuleInit() {
    if (!isDrivingOperator(this.options)) return;
    if (!supportsHandle(this.transport)) return;
    const transport = this.transport;
    scanSteps(this.discovery, this.metadataScanner, (meta, handler) => transport.handle(meta.name, handler, this.options.partition));
  }
};
DurableStepRegistrar = _ts_decorate3([
  (0, import_common3.Injectable)(),
  _ts_param2(2, (0, import_common3.Inject)(import_nestjs_durable_core5.TRANSPORT_CANONICAL)),
  _ts_param2(3, (0, import_common3.Inject)(import_nestjs_durable_core5.DURABLE_OPTIONS_CANONICAL)),
  _ts_metadata3("design:type", Function),
  _ts_metadata3("design:paramtypes", [
    typeof import_core2.DiscoveryService === "undefined" ? Object : import_core2.DiscoveryService,
    typeof import_core2.MetadataScanner === "undefined" ? Object : import_core2.MetadataScanner,
    Object,
    typeof DurableModuleOptions === "undefined" ? Object : DurableModuleOptions
  ])
], DurableStepRegistrar);

// src/entity.ts
var import_nestjs_durable_core6 = require("@dudousxd/nestjs-durable-core");
var import_common4 = require("@nestjs/common");
var import_reflect_metadata2 = require("reflect-metadata");
function _ts_decorate4(decorators, target, key, desc) {
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
  return c > 3 && r && Object.defineProperty(target, key, r), r;
}
__name(_ts_decorate4, "_ts_decorate");
function _ts_metadata4(k, v) {
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
}
__name(_ts_metadata4, "_ts_metadata");
var ENTITY_METADATA = Symbol("nestjs-durable:entity");
var ENTITY_ON_METADATA = Symbol("nestjs-durable:entity-on");
function Entity(options) {
  return (target) => {
    Reflect.defineMetadata(ENTITY_METADATA, options, target);
  };
}
__name(Entity, "Entity");
function On(op) {
  return (target, propertyKey) => {
    const ctor = target.constructor;
    const ops = Reflect.getMetadata(ENTITY_ON_METADATA, ctor) ?? /* @__PURE__ */ new Map();
    ops.set(op, propertyKey);
    Reflect.defineMetadata(ENTITY_ON_METADATA, ops, ctor);
  };
}
__name(On, "On");
function getEntityMeta(target) {
  return Reflect.getMetadata(ENTITY_METADATA, target);
}
__name(getEntityMeta, "getEntityMeta");
function entityConfigFor(ctor) {
  const ops = Reflect.getMetadata(ENTITY_ON_METADATA, ctor) ?? /* @__PURE__ */ new Map();
  const Cls = ctor;
  const handlers = {};
  for (const [op, method] of ops) {
    handlers[op] = (state, arg) => {
      Object.setPrototypeOf(state, Cls.prototype);
      const fn = state[method];
      if (typeof fn !== "function") throw new Error(`entity handler "${method}" is not a method`);
      return fn.call(state, arg);
    };
  }
  return {
    initialState: /* @__PURE__ */ __name(() => new Cls(), "initialState"),
    handlers
  };
}
__name(entityConfigFor, "entityConfigFor");
var EntityService = class {
  static {
    __name(this, "EntityService");
  }
  engine;
  constructor(engine) {
    this.engine = engine;
  }
  /** Send an operation to an entity (fire-and-forget; ordered + exactly-once per key). */
  signal(name, key, op, arg) {
    return this.engine.signalEntity(name, key, op, arg);
  }
  /** Read an entity's current durable state (or undefined if it has none yet). */
  getState(name, key) {
    return this.engine.getEntityState(name, key);
  }
};
EntityService = _ts_decorate4([
  (0, import_common4.Injectable)(),
  _ts_metadata4("design:type", Function),
  _ts_metadata4("design:paramtypes", [
    typeof import_nestjs_durable_core6.WorkflowEngine === "undefined" ? Object : import_nestjs_durable_core6.WorkflowEngine
  ])
], EntityService);

// src/in-app-worker.ts
var import_durable_worker3 = require("@dudousxd/durable-worker");
var import_nestjs_durable_core7 = require("@dudousxd/nestjs-durable-core");
var import_common5 = require("@nestjs/common");
var import_core3 = require("@nestjs/core");
function _ts_decorate5(decorators, target, key, desc) {
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
  return c > 3 && r && Object.defineProperty(target, key, r), r;
}
__name(_ts_decorate5, "_ts_decorate");
function _ts_metadata5(k, v) {
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
}
__name(_ts_metadata5, "_ts_metadata");
function _ts_param3(paramIndex, decorator) {
  return function(target, key) {
    decorator(target, key, paramIndex);
  };
}
__name(_ts_param3, "_ts_param");
var IN_APP_WORKER_BINDING = Symbol("nestjs-durable:in-app-worker-binding");
var IN_APP_WORKER_RUNTIME = Symbol("nestjs-durable:in-app-worker-runtime");
var IN_APP_RUN_REDIS_WORKER = Symbol("nestjs-durable:in-app-run-redis-worker");
var IN_APP_WORKER_RUNNERS = Symbol("nestjs-durable:in-app-worker-runners");
function isCoLocatedWorker(options) {
  return options.store !== void 0 && options.connection !== void 0;
}
__name(isCoLocatedWorker, "isCoLocatedWorker");
function inAppWorkerBinding(transport, options) {
  if (!isCoLocatedWorker(options)) return null;
  if (!transport?.dispatchWorkflowTask || !transport.onDecision) {
    throw new Error("a co-located worker (store + connection) requires a transport that carries workflow tasks (dispatchWorkflowTask + onDecision), e.g. BullMQTransport. An in-process transport cannot serve a group-served workflow.");
  }
  return {
    transport,
    ...options.partition !== void 0 ? {
      partition: options.partition
    } : {}
  };
}
__name(inAppWorkerBinding, "inAppWorkerBinding");
var InAppWorkerBootstrap = class {
  static {
    __name(this, "InAppWorkerBootstrap");
  }
  discovery;
  metadataScanner;
  options;
  runtime;
  runRedisWorker;
  runnersSink;
  runners = [];
  constructor(discovery, metadataScanner, options, runtime, runRedisWorker, runnersSink) {
    this.discovery = discovery;
    this.metadataScanner = metadataScanner;
    this.options = options;
    this.runtime = runtime;
    this.runRedisWorker = runRedisWorker;
    this.runnersSink = runnersSink;
  }
  onModuleInit() {
    if (!isCoLocatedWorker(this.options)) return;
    scanWorkflows(this.discovery, (meta, instance) => this.runtime.registerWorkflow(meta.name, (ctx, input) => instance.run(ctx, input)));
    scanSteps(this.discovery, this.metadataScanner, (meta, handler) => this.runtime.registerStep(meta.name, handler));
  }
  async onApplicationBootstrap() {
    const options = this.options;
    if (!isCoLocatedWorker(options) || options.connection === void 0) return;
    const handle = await this.runRedisWorker({
      runtime: this.runtime,
      connection: options.connection,
      ...options.partition !== void 0 ? {
        partition: options.partition
      } : {},
      ...options.prefix !== void 0 ? {
        prefix: options.prefix
      } : {},
      ...options.instanceId !== void 0 ? {
        instanceId: options.instanceId
      } : {},
      ...options.concurrency !== void 0 ? {
        concurrency: options.concurrency
      } : {}
    });
    this.runners.push(handle);
    this.runnersSink.push(handle);
  }
  async onApplicationShutdown() {
    await Promise.allSettled(this.runners.map((handle) => handle.close()));
  }
};
InAppWorkerBootstrap = _ts_decorate5([
  (0, import_common5.Injectable)(),
  _ts_param3(2, (0, import_common5.Inject)(import_nestjs_durable_core7.DURABLE_OPTIONS_CANONICAL)),
  _ts_param3(3, (0, import_common5.Inject)(IN_APP_WORKER_RUNTIME)),
  _ts_param3(4, (0, import_common5.Inject)(IN_APP_RUN_REDIS_WORKER)),
  _ts_param3(5, (0, import_common5.Inject)(IN_APP_WORKER_RUNNERS)),
  _ts_metadata5("design:type", Function),
  _ts_metadata5("design:paramtypes", [
    typeof import_core3.DiscoveryService === "undefined" ? Object : import_core3.DiscoveryService,
    typeof import_core3.MetadataScanner === "undefined" ? Object : import_core3.MetadataScanner,
    typeof DurableModuleOptions === "undefined" ? Object : DurableModuleOptions,
    typeof import_durable_worker3.DurableWorkerRuntime === "undefined" ? Object : import_durable_worker3.DurableWorkerRuntime,
    typeof RunRedisWorkerFn === "undefined" ? Object : RunRedisWorkerFn,
    Array
  ])
], InAppWorkerBootstrap);
function inAppWorkerProviders() {
  return [
    {
      provide: IN_APP_WORKER_BINDING,
      useFactory: /* @__PURE__ */ __name((transport, options) => inAppWorkerBinding(transport, options), "useFactory"),
      inject: [
        import_nestjs_durable_core7.TRANSPORT_CANONICAL,
        import_nestjs_durable_core7.DURABLE_OPTIONS_CANONICAL
      ]
    },
    {
      provide: IN_APP_WORKER_RUNTIME,
      // The runtime's `WorkflowWorker` uses its `group` ctor param as the WORKFLOW's
      // `workflowPartition` for any `ctx.step` call (see `workflow-context.ts`'s `resolveCallGroup`)
      // — that fallback MUST equal this app's own `partition`, or a dispatched step's decision
      // carries a mismatched token and the engine dispatches it to a queue nothing here subscribes
      // to. Explicit `''` (never
      // `undefined`) so it does NOT fall through to `WorkflowWorker`'s unrelated `'workflows'`
      // default parameter.
      useFactory: /* @__PURE__ */ __name((options) => new import_durable_worker3.DurableWorkerRuntime({
        workflowGroup: options.partition ?? ""
      }), "useFactory"),
      inject: [
        import_nestjs_durable_core7.DURABLE_OPTIONS_CANONICAL
      ]
    },
    {
      provide: IN_APP_RUN_REDIS_WORKER,
      useValue: import_durable_worker3.runRedisWorker
    },
    {
      provide: IN_APP_WORKER_RUNNERS,
      useValue: []
    },
    InAppWorkerBootstrap
  ];
}
__name(inAppWorkerProviders, "inAppWorkerProviders");

// src/proxy-run-gateway.ts
var import_common6 = require("@nestjs/common");
function _ts_decorate6(decorators, target, key, desc) {
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
  return c > 3 && r && Object.defineProperty(target, key, r), r;
}
__name(_ts_decorate6, "_ts_decorate");
function _ts_metadata6(k, v) {
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
}
__name(_ts_metadata6, "_ts_metadata");
var ProxyRunGateway = class {
  static {
    __name(this, "ProxyRunGateway");
  }
  transport;
  tenant;
  timeoutMs;
  pending = /* @__PURE__ */ new Map();
  constructor(transport, tenant, timeoutMs = 1e4) {
    this.transport = transport;
    this.tenant = tenant;
    this.timeoutMs = timeoutMs;
    this.transport.onRunReply?.((reply) => this.handleReply(reply));
  }
  handleReply(reply) {
    const pending = this.pending.get(reply.requestId);
    if (!pending) return;
    clearTimeout(pending.timer);
    this.pending.delete(reply.requestId);
    if (reply.result.ok) {
      pending.resolve(reply.result.data);
    } else {
      pending.reject(new Error(reply.result.error.message));
    }
  }
  request(body) {
    return new Promise((resolve, reject) => {
      const requestId = globalThis.crypto.randomUUID();
      const timer = setTimeout(() => {
        this.pending.delete(requestId);
        reject(new Error(`control plane did not respond to ${body.kind} within ${this.timeoutMs}ms`));
      }, this.timeoutMs);
      this.pending.set(requestId, {
        resolve,
        reject,
        timer
      });
      this.transport.dispatchRunRequest?.({
        requestId,
        tenant: this.tenant,
        body
      }).catch((error) => {
        const stillPending = this.pending.get(requestId);
        if (!stillPending) return;
        clearTimeout(stillPending.timer);
        this.pending.delete(requestId);
        stillPending.reject(error instanceof Error ? error : new Error(String(error)));
      });
    });
  }
  topology() {
    return {
      role: "tenant",
      tenant: this.tenant
    };
  }
  getRunDetail(runId) {
    return this.request({
      kind: "getRunDetail",
      runId
    });
  }
  /** The control plane's `StoreRunGateway` resolves each run's `waiting` descriptor; the reply carries
  *  it through as plain JSON, so a tenant's list rows name the wait too. */
  listRuns(query) {
    return this.request({
      kind: "listRuns",
      query
    });
  }
  /** Round-trips to the operator, which scopes the result to this tenant's own `@<tenant>` groups. */
  workerHealth() {
    return this.request({
      kind: "workerHealth"
    });
  }
  /** Bulk, one request for the whole id list (like `listRuns`, not one request per id). The operator
  *  filters the reply to runs this tenant actually owns (see `RunRequestResponder`). */
  waitingFor(runIds) {
    return this.request({
      kind: "waitingFor",
      runIds
    });
  }
  cancel(runId, opts) {
    return this.request(opts === void 0 ? {
      kind: "cancel",
      runId
    } : {
      kind: "cancel",
      runId,
      opts
    });
  }
  retry(runId) {
    return this.request({
      kind: "retry",
      runId
    });
  }
  continue(runId) {
    return this.request({
      kind: "continue",
      runId
    });
  }
  retryWithInput(runId, input) {
    return this.request({
      kind: "retryWithInput",
      runId,
      input
    });
  }
  redispatchPending(runId) {
    return this.request({
      kind: "redispatch",
      runId
    });
  }
  subscribe(runId, onEvent) {
    const unsubscribe = this.transport.onTenantEvent?.(this.tenant, (evt) => {
      if (evt.event.runId === runId) onEvent(evt.event);
    });
    return unsubscribe ?? (() => {
    });
  }
};
ProxyRunGateway = _ts_decorate6([
  (0, import_common6.Injectable)(),
  _ts_metadata6("design:type", Function),
  _ts_metadata6("design:paramtypes", [
    typeof Transport === "undefined" ? Object : Transport,
    String,
    void 0
  ])
], ProxyRunGateway);

// src/retention-poller.ts
var import_nestjs_durable_core8 = require("@dudousxd/nestjs-durable-core");
var import_common7 = require("@nestjs/common");
function _ts_decorate7(decorators, target, key, desc) {
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
  return c > 3 && r && Object.defineProperty(target, key, r), r;
}
__name(_ts_decorate7, "_ts_decorate");
function _ts_metadata7(k, v) {
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
}
__name(_ts_metadata7, "_ts_metadata");
function _ts_param4(paramIndex, decorator) {
  return function(target, key) {
    decorator(target, key, paramIndex);
  };
}
__name(_ts_param4, "_ts_param");
var DEFAULT_SWEEP_INTERVAL_MS = 6e4;
var DEFAULT_BATCH_SIZE = 1e3;
var MAX_BATCHES_PER_POLICY = 100;
function validateRetention(retention) {
  const seen = /* @__PURE__ */ new Set();
  for (const policy of retention.policies) {
    if (policy.statuses.length === 0) {
      throw new Error("durable retention: each policy must list at least one status");
    }
    if (policy.maxAge == null && policy.maxCount == null) {
      throw new Error("durable retention: each policy must set maxAge and/or maxCount");
    }
    if (policy.maxAge != null) (0, import_nestjs_durable_core8.parseDuration)(policy.maxAge);
    for (const status of policy.statuses) {
      if (!import_nestjs_durable_core8.TERMINAL_RUN_STATUSES.includes(status)) {
        throw new Error(`durable retention: status "${status}" is not terminal; only ${import_nestjs_durable_core8.TERMINAL_RUN_STATUSES.join(", ")} can be pruned`);
      }
      if (seen.has(status)) {
        throw new Error(`durable retention: status "${status}" appears in more than one policy; status sets must be disjoint`);
      }
      seen.add(status);
    }
  }
}
__name(validateRetention, "validateRetention");
var RetentionPoller = class {
  static {
    __name(this, "RetentionPoller");
  }
  store;
  options;
  timer;
  sweeping = false;
  constructor(store, options) {
    this.store = store;
    this.options = options;
  }
  /** Non-null once past the `isDrivingOperator` guard (which requires `options.store` to be set). */
  requireStore() {
    if (!this.store) {
      throw new Error("unreachable: STATE_STORE_CANONICAL must resolve a store when options.store is set");
    }
    return this.store;
  }
  async onApplicationBootstrap() {
    if (!isDrivingOperator(this.options)) return;
    const retention = this.options.retention;
    if (!retention || retention.policies.length === 0) return;
    validateRetention(retention);
    if (typeof this.requireStore().pruneTerminalRuns !== "function") {
      console.warn("[nestjs-durable] `retention` is configured but the store adapter does not implement pruneTerminalRuns; retention is disabled.");
      return;
    }
    await this.sweep();
    const intervalMs = retention.sweepInterval != null ? (0, import_nestjs_durable_core8.parseDuration)(retention.sweepInterval) : DEFAULT_SWEEP_INTERVAL_MS;
    if (intervalMs > 0) {
      this.timer = setInterval(() => void this.sweep(), intervalMs);
      this.timer.unref?.();
    }
  }
  onModuleDestroy() {
    if (this.timer) clearInterval(this.timer);
  }
  async sweep() {
    if (this.sweeping) return;
    const retention = this.options.retention;
    const store = this.requireStore();
    const prune = store.pruneTerminalRuns;
    if (!retention || typeof prune !== "function") return;
    this.sweeping = true;
    try {
      const batchSize = retention.batchSize ?? DEFAULT_BATCH_SIZE;
      const now = Date.now();
      for (const policy of retention.policies) {
        for (let batch = 0; batch < MAX_BATCHES_PER_POLICY; batch++) {
          const deleted = await prune.call(store, policy, now, batchSize);
          if (deleted < batchSize) break;
        }
      }
    } finally {
      this.sweeping = false;
    }
  }
};
RetentionPoller = _ts_decorate7([
  (0, import_common7.Injectable)(),
  _ts_param4(0, (0, import_common7.Inject)(import_nestjs_durable_core8.STATE_STORE_CANONICAL)),
  _ts_param4(1, (0, import_common7.Inject)(import_nestjs_durable_core8.DURABLE_OPTIONS_CANONICAL)),
  _ts_metadata7("design:type", Function),
  _ts_metadata7("design:paramtypes", [
    Object,
    typeof DurableModuleOptions === "undefined" ? Object : DurableModuleOptions
  ])
], RetentionPoller);

// src/run-request-responder.ts
var RunRequestResponder = class {
  static {
    __name(this, "RunRequestResponder");
  }
  transport;
  gateway;
  constructor(transport, gateway) {
    this.transport = transport;
    this.gateway = gateway;
  }
  /** Register the consumer on the transport. Each request is answered independently; a handler
  *  failure never throws back into the transport (errors are captured into an error reply). */
  start() {
    this.transport.onRunRequest(async (msg) => {
      const reply = await this.handle(msg);
      await this.transport.publishRunReply(reply);
    });
  }
  async handle(msg) {
    const { body } = msg;
    if (body.kind === "listRuns") {
      const data = await this.gateway.listRuns({
        ...body.query,
        namespace: msg.tenant
      });
      return {
        requestId: msg.requestId,
        result: {
          ok: true,
          data
        }
      };
    }
    if (body.kind === "workerHealth") {
      const all = await this.gateway.workerHealth();
      const data = all.filter((h) => h.group.endsWith(`@${msg.tenant}`));
      return {
        requestId: msg.requestId,
        result: {
          ok: true,
          data
        }
      };
    }
    if (body.kind === "waitingFor") {
      const all = await this.gateway.waitingFor(body.runIds);
      const owned = await Promise.all(Object.entries(all).map(async ([runId, waiting]) => {
        const runDetail = await this.gateway.getRunDetail(runId);
        return runDetail && runDetail.run.namespace === msg.tenant ? [
          runId,
          waiting
        ] : void 0;
      }));
      const data = {};
      for (const entry of owned) {
        if (entry) data[entry[0]] = entry[1];
      }
      return {
        requestId: msg.requestId,
        result: {
          ok: true,
          data
        }
      };
    }
    const detail = await this.gateway.getRunDetail(body.runId);
    if (detail && detail.run.namespace !== msg.tenant) {
      return {
        requestId: msg.requestId,
        result: {
          ok: false,
          error: {
            message: "run belongs to another tenant",
            code: "cross-tenant"
          }
        }
      };
    }
    if (body.kind === "getRunDetail") {
      return {
        requestId: msg.requestId,
        result: {
          ok: true,
          data: detail
        }
      };
    }
    try {
      const data = await this.callVerb(body);
      return {
        requestId: msg.requestId,
        result: {
          ok: true,
          data
        }
      };
    } catch (err) {
      return {
        requestId: msg.requestId,
        result: {
          ok: false,
          error: {
            message: err instanceof Error ? err.message : String(err)
          }
        }
      };
    }
  }
  callVerb(body) {
    switch (body.kind) {
      case "cancel":
        return this.gateway.cancel(body.runId, body.opts);
      case "retry":
        return this.gateway.retry(body.runId);
      case "continue":
        return this.gateway.continue(body.runId);
      case "retryWithInput":
        return this.gateway.retryWithInput(body.runId, body.input);
      case "redispatch":
        return this.gateway.redispatchPending(body.runId);
    }
  }
};

// src/store-run-gateway.ts
var import_nestjs_durable_core9 = require("@dudousxd/nestjs-durable-core");
var import_common8 = require("@nestjs/common");
function _ts_decorate8(decorators, target, key, desc) {
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
  return c > 3 && r && Object.defineProperty(target, key, r), r;
}
__name(_ts_decorate8, "_ts_decorate");
function _ts_metadata8(k, v) {
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
}
__name(_ts_metadata8, "_ts_metadata");
function _ts_param5(paramIndex, decorator) {
  return function(target, key) {
    decorator(target, key, paramIndex);
  };
}
__name(_ts_param5, "_ts_param");
var StoreRunGateway = class {
  static {
    __name(this, "StoreRunGateway");
  }
  store;
  engine;
  constructor(store, engine) {
    this.store = store;
    this.engine = engine;
  }
  topology() {
    return {
      role: "control-plane"
    };
  }
  async getRunDetail(runId) {
    const run = await this.store.getRun(runId);
    if (!run) return null;
    const [timeline, children] = await Promise.all([
      this.store.listCheckpoints(runId),
      this.engine.getRunChildren(runId)
    ]);
    return {
      run,
      timeline,
      children
    };
  }
  async listRuns(query) {
    const runs = await this.store.listRuns(query);
    const waiterByRun = (0, import_nestjs_durable_core9.indexWaitersByRun)(await this.store.listSignalWaiters(""));
    return runs.map((run) => {
      const waiting = (0, import_nestjs_durable_core9.resolveRunWaiting)(run, waiterByRun);
      return waiting ? {
        ...run,
        waiting
      } : run;
    });
  }
  /**
  * Bulk-resolve what each of `runIds` is currently parked on — for a consumer with its own filtered/
  * paginated run listing (e.g. "which of MY suspended runs are stuck at a breakpoint") without
  * re-deriving `listRuns`' waiter scan or querying `durable_step_checkpoints` directly. Mirrors
  * `listRuns`' waiting computation (the SAME bulk signal-waiter scan + `resolveRunWaiting`), but ALSO
  * bulk-fetches the currently-suspended runs to check real status: `engine.cancel` (the non-compensate
  * path) doesn't clear a run's signal waiter row, so a cancelled run can leave an ORPHANED waiter
  * behind — trusting waiter presence alone would wrongly report a terminal run as still waiting.
  * Two bulk scans total (never one query per requested id), same as `listRuns`.
  */
  async waitingFor(runIds) {
    if (runIds.length === 0) return {};
    const idSet = new Set(runIds);
    const [suspended, waiters] = await Promise.all([
      this.store.listRuns({
        statuses: [
          "suspended"
        ]
      }),
      this.store.listSignalWaiters("")
    ]);
    const waiterByRun = (0, import_nestjs_durable_core9.indexWaitersByRun)(waiters);
    const result = {};
    for (const run of suspended) {
      if (!idSet.has(run.id)) continue;
      const waiting = (0, import_nestjs_durable_core9.resolveRunWaiting)(run, waiterByRun);
      if (waiting) result[run.id] = waiting;
    }
    return result;
  }
  /** Every group the engine knows about — unscoped. A tenant proxy's request is scoped by the
  *  `RunRequestResponder` (to the requester's `@<tenant>` groups); the operator's own UI sees all. */
  workerHealth() {
    return this.engine.workerHealth();
  }
  cancel(runId, opts) {
    return this.engine.cancel(runId, opts);
  }
  /** Re-enqueue (dispatch model) instead of resuming inline — a worker picks the run up and replays it. */
  retry(runId) {
    return this.engine.requeue(runId);
  }
  continue(runId) {
    return this.engine.continue(runId);
  }
  retryWithInput(runId, input) {
    return this.engine.retryWithInput(runId, input);
  }
  redispatchPending(runId) {
    return this.engine.redispatchPending(runId);
  }
  subscribe(runId, onEvent) {
    return this.engine.subscribe((event) => {
      if (event.runId === runId) onEvent(event);
    });
  }
};
StoreRunGateway = _ts_decorate8([
  (0, import_common8.Injectable)(),
  _ts_param5(0, (0, import_common8.Inject)(import_nestjs_durable_core9.STATE_STORE_CANONICAL)),
  _ts_metadata8("design:type", Function),
  _ts_metadata8("design:paramtypes", [
    typeof StateStore === "undefined" ? Object : StateStore,
    typeof import_nestjs_durable_core9.WorkflowEngine === "undefined" ? Object : import_nestjs_durable_core9.WorkflowEngine
  ])
], StoreRunGateway);

// src/tenant-event-republisher.ts
function isTerminalRunEvent(event) {
  return event.type === "run.completed" || event.type === "run.failed";
}
__name(isTerminalRunEvent, "isTerminalRunEvent");
var TenantEventRepublisher = class {
  static {
    __name(this, "TenantEventRepublisher");
  }
  store;
  publish;
  runNamespaces = /* @__PURE__ */ new Map();
  constructor(store, publish) {
    this.store = store;
    this.publish = publish;
  }
  async handle(event) {
    const namespace = await this.namespaceFor(event);
    if (isTerminalRunEvent(event)) this.runNamespaces.delete(event.runId);
    if (!namespace || namespace === "default") return;
    await this.publish({
      tenant: namespace,
      event
    }).catch(() => void 0);
  }
  async namespaceFor(event) {
    if (this.runNamespaces.has(event.runId)) {
      const cached = this.runNamespaces.get(event.runId);
      return cached === null ? void 0 : cached;
    }
    if (event.namespace !== void 0) {
      this.runNamespaces.set(event.runId, event.namespace === "default" ? null : event.namespace);
      return event.namespace;
    }
    const run = await this.store.getRun(event.runId);
    const tenantNamespace = run?.namespace !== void 0 && run.namespace !== "default" ? run.namespace : null;
    this.runNamespaces.set(event.runId, tenantNamespace);
    return tenantNamespace === null ? void 0 : tenantNamespace;
  }
};

// src/timer-poller.ts
var import_nestjs_durable_core10 = require("@dudousxd/nestjs-durable-core");
var import_common9 = require("@nestjs/common");
function _ts_decorate9(decorators, target, key, desc) {
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
  return c > 3 && r && Object.defineProperty(target, key, r), r;
}
__name(_ts_decorate9, "_ts_decorate");
function _ts_metadata9(k, v) {
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
}
__name(_ts_metadata9, "_ts_metadata");
function _ts_param6(paramIndex, decorator) {
  return function(target, key) {
    decorator(target, key, paramIndex);
  };
}
__name(_ts_param6, "_ts_param");
var TimerPoller = class {
  static {
    __name(this, "TimerPoller");
  }
  engine;
  options;
  timer;
  polling = false;
  unsubscribeEnqueued;
  constructor(engine, options) {
    this.engine = engine;
    this.options = options;
  }
  async onApplicationBootstrap() {
    if (!isDrivingOperator(this.options)) return;
    this.unsubscribeEnqueued = this.engine.onEnqueued((runId) => void this.engine.runOne(runId));
    await this.poll();
    const intervalMs = this.options.timerPollMs ?? 1e3;
    if (intervalMs > 0) {
      this.timer = setInterval(() => void this.poll(), intervalMs);
      this.timer.unref?.();
    }
  }
  onModuleDestroy() {
    if (this.timer) clearInterval(this.timer);
    this.unsubscribeEnqueued?.();
  }
  async poll() {
    if (this.polling) return;
    this.polling = true;
    try {
      await this.engine.runPending();
      await this.engine.recoverIncomplete();
      await this.engine.resumeDueTimers();
      await this.engine.sweepTimeouts();
      const schedules = this.options.schedules;
      if (schedules && schedules.length > 0) {
        await (0, import_nestjs_durable_core10.runSchedules)(this.engine, schedules, Date.now());
      }
    } finally {
      this.polling = false;
    }
  }
};
TimerPoller = _ts_decorate9([
  (0, import_common9.Injectable)(),
  _ts_param6(1, (0, import_common9.Inject)(import_nestjs_durable_core10.DURABLE_OPTIONS_CANONICAL)),
  _ts_metadata9("design:type", Function),
  _ts_metadata9("design:paramtypes", [
    typeof import_nestjs_durable_core10.WorkflowEngine === "undefined" ? Object : import_nestjs_durable_core10.WorkflowEngine,
    typeof DurableModuleOptions === "undefined" ? Object : DurableModuleOptions
  ])
], TimerPoller);

// src/tokens.ts
var import_nestjs_durable_core11 = require("@dudousxd/nestjs-durable-core");
var CONTEXT_ACCESSOR = Symbol.for("@dudousxd/nestjs-context:accessor");
var RUN_GATEWAY = import_nestjs_durable_core11.RunGateway;

// src/workflow.registrar.ts
var import_nestjs_durable_core12 = require("@dudousxd/nestjs-durable-core");
var import_common10 = require("@nestjs/common");
var import_core4 = require("@nestjs/core");

// src/input-validation.ts
function classValidatorInput(cls) {
  let cv;
  let ct;
  try {
    cv = require("class-validator");
    ct = require("class-transformer");
  } catch {
    throw new Error('@Workflow({ inputSchema }) needs the optional peers "class-validator" and "class-transformer" \u2014 install them, or pass a `validateInput` function instead.');
  }
  return async (input) => {
    const instance = ct.plainToInstance(cls, input);
    const errors = await cv.validate(instance, {
      whitelist: true
    });
    if (errors.length > 0) {
      const message = errors.map((e) => Object.values(e.constraints ?? {
        _: e.property
      }).join(", ")).join("; ");
      throw new Error(`invalid input for workflow: ${message}`);
    }
  };
}
__name(classValidatorInput, "classValidatorInput");

// src/step-interceptor.ts
var import_reflect_metadata3 = require("reflect-metadata");
var STEP_INTERCEPTOR_METADATA = Symbol("nestjs-durable:step-interceptor");
function StepInterceptor() {
  return (target) => {
    Reflect.defineMetadata(STEP_INTERCEPTOR_METADATA, true, target);
  };
}
__name(StepInterceptor, "StepInterceptor");
function isStepInterceptor(target) {
  return Reflect.getMetadata(STEP_INTERCEPTOR_METADATA, target) === true;
}
__name(isStepInterceptor, "isStepInterceptor");

// src/workflow.registrar.ts
function _ts_decorate10(decorators, target, key, desc) {
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
  return c > 3 && r && Object.defineProperty(target, key, r), r;
}
__name(_ts_decorate10, "_ts_decorate");
function _ts_metadata10(k, v) {
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
}
__name(_ts_metadata10, "_ts_metadata");
function _ts_param7(paramIndex, decorator) {
  return function(target, key) {
    decorator(target, key, paramIndex);
  };
}
__name(_ts_param7, "_ts_param");
var WorkflowRegistrar = class {
  static {
    __name(this, "WorkflowRegistrar");
  }
  discovery;
  metadataScanner;
  engine;
  store;
  options;
  inAppWorker;
  constructor(discovery, metadataScanner, engine, store, options, inAppWorker) {
    this.discovery = discovery;
    this.metadataScanner = metadataScanner;
    this.engine = engine;
    this.store = store;
    this.options = options;
    this.inAppWorker = inAppWorker;
  }
  async onApplicationBootstrap() {
    if (!isDrivingOperator(this.options)) return;
    await this.engine.recoverIncomplete();
  }
  /** On deploy/shutdown: stop picking up new runs and wait for in-flight ones to settle, then close
  *  the transport(s) so the broker workers stop consuming and connections are released. Closing
  *  AFTER the drain keeps the transport alive while in-flight runs dispatch/await their remote steps.
  *  Operator only — a thin worker (no `store`) holds no engine to drain. */
  async onApplicationShutdown() {
    if (!isOperatorRole(this.options)) return;
    await this.engine.drain(this.options.shutdownTimeoutMs);
    const transports = [
      this.options.transport,
      ...(this.options.transports ?? []).map((t) => t.transport)
    ];
    await Promise.allSettled(transports.map((t) => t?.close?.()));
  }
  async onModuleInit() {
    if (!isOperatorRole(this.options)) return;
    const store = this.store;
    if (!store) {
      throw new Error("unreachable: STATE_STORE_CANONICAL must resolve a store when options.store is set");
    }
    if (this.options.autoSchema !== false) {
      await store.ensureSchema?.();
    }
    const deadLetterByWorkflow = /* @__PURE__ */ new Map();
    for (const wrapper of this.discovery.getProviders()) {
      const { instance } = wrapper;
      if (!instance || typeof instance !== "object") continue;
      if (isStepInterceptor(instance.constructor)) {
        const interceptor = instance;
        this.engine.use((invocation, next) => interceptor.intercept(invocation, next));
      }
      const entityMeta = getEntityMeta(instance.constructor);
      if (entityMeta) {
        this.engine.registerEntity(entityMeta.name, entityConfigFor(instance.constructor));
      }
    }
    scanWorkflows(this.discovery, (meta, workflow) => {
      const validateInput = meta.validateInput ?? (meta.inputSchema ? classValidatorInput(meta.inputSchema) : void 0);
      const eventBatch = meta.debounce ? {
        mode: "debounce",
        windowMs: (0, import_nestjs_durable_core12.parseDuration)(meta.debounce)
      } : meta.batch ? {
        mode: "batch",
        maxSize: meta.batch.maxSize,
        windowMs: (0, import_nestjs_durable_core12.parseDuration)(meta.batch.within)
      } : void 0;
      const workflowCtor = workflow.constructor;
      this.engine.register(meta.name, meta.version, (ctx, input) => workflow.run(ctx, input), {
        tags: meta.tags,
        singleton: meta.singleton,
        executionTimeout: meta.executionTimeout,
        requires: meta.requires,
        validateInput,
        searchAttributesSchema: meta.searchAttributes,
        onEvent: getOnEvents(meta, workflowCtor),
        eventBatch,
        // Uniform dispatch (opt-in): when an in-app worker is configured, register the body GROUP-SERVED
        // so the engine dispatches its turns over the transport instead of running it inline; the
        // co-located worker consumer (Task 5: one queue PER REGISTERED NAME) replays the same body.
        // The routing token — and the executor that dispatches under it — MUST be keyed by THIS
        // workflow's own name (`tenantGroup(sanitizeQueueToken(meta.name), partition)`), not a single
        // group shared across every discovered `@Workflow`: a fixed shared token would dispatch every
        // workflow's turns to one queue while the co-located worker subscribes one queue per name,
        // so a turn dispatched under the wrong token would never be consumed. Absent → the inline fast
        // path (unchanged).
        ...this.inAppWorker ? {
          group: (0, import_nestjs_durable_core12.tenantGroup)((0, import_nestjs_durable_core12.sanitizeQueueToken)(meta.name), this.inAppWorker.partition),
          executor: new import_nestjs_durable_core12.RemoteWorkflowExecutor(this.inAppWorker.transport, meta.name, this.inAppWorker.partition)
        } : {}
      });
      (0, import_nestjs_durable_core12.bindWorkflowClass)(workflowCtor, {
        start: /* @__PURE__ */ __name((name, input, runId, opts) => this.engine.start(name, input, runId, opts), "start"),
        waitForRun: /* @__PURE__ */ __name((runId, opts) => this.engine.waitForRun(runId, opts), "waitForRun")
      });
      const inline = this.findDeadLetterHandler(workflow);
      if (inline && meta.deadLetterWorkflow) {
        throw new Error(`@Workflow ${meta.name} declares both an inline @DeadLetter() method and a deadLetterWorkflow option. Use one: the inline handler, or the reference.`);
      }
      if (inline) {
        const dlqName = `${meta.name}.dlq`;
        this.engine.register(dlqName, meta.version, inline);
        deadLetterByWorkflow.set(meta.name, dlqName);
      } else if (meta.deadLetterWorkflow) {
        deadLetterByWorkflow.set(meta.name, (0, import_nestjs_durable_core12.workflowName)(meta.deadLetterWorkflow));
      }
    });
    this.installDeadLetterRouting(deadLetterByWorkflow);
  }
  /** Returns the instance's `@DeadLetter()` method bound to the instance, or undefined if none. */
  findDeadLetterHandler(instance) {
    const prototype = Object.getPrototypeOf(instance);
    for (const methodName of this.metadataScanner.getAllMethodNames(prototype)) {
      const method = instance[methodName];
      if (typeof method === "function" && isDeadLetterHandler(method)) {
        return (ctx, input) => method.call(instance, ctx, input);
      }
    }
    return void 0;
  }
  /**
  * Installs a single onDead listener that routes a dead run to its workflow's handler (from the
  * map) or the module-level `deadLetterWorkflow` default. The handler is started idempotently with
  * a `dlq:<runId>` id, so re-recovery never double-dispatches.
  */
  installDeadLetterRouting(byWorkflow) {
    const fallback = this.options.deadLetterWorkflow ? (0, import_nestjs_durable_core12.workflowName)(this.options.deadLetterWorkflow) : void 0;
    if (byWorkflow.size === 0 && !fallback) return;
    this.engine.onDead((run) => {
      const target = byWorkflow.get(run.workflow) ?? fallback;
      if (!target) return;
      void this.engine.start(
        target,
        {
          deadRunId: run.id,
          workflow: run.workflow,
          input: run.input,
          error: run.error
        },
        `dlq:${run.id}`,
        // Route the dead-letter handler to the dead run's OWN tenant so an operator dispatches it to
        // that tenant's worker group (`target@<tenant>`), not the bare group.
        {
          namespace: run.namespace
        }
      ).catch(() => void 0);
    });
  }
};
WorkflowRegistrar = _ts_decorate10([
  (0, import_common10.Injectable)(),
  _ts_param7(3, (0, import_common10.Inject)(import_nestjs_durable_core12.STATE_STORE_CANONICAL)),
  _ts_param7(4, (0, import_common10.Inject)(import_nestjs_durable_core12.DURABLE_OPTIONS_CANONICAL)),
  _ts_param7(5, (0, import_common10.Inject)(IN_APP_WORKER_BINDING)),
  _ts_metadata10("design:type", Function),
  _ts_metadata10("design:paramtypes", [
    typeof import_core4.DiscoveryService === "undefined" ? Object : import_core4.DiscoveryService,
    typeof import_core4.MetadataScanner === "undefined" ? Object : import_core4.MetadataScanner,
    typeof import_nestjs_durable_core12.WorkflowEngine === "undefined" ? Object : import_nestjs_durable_core12.WorkflowEngine,
    Object,
    typeof DurableModuleOptions === "undefined" ? Object : DurableModuleOptions,
    Object
  ])
], WorkflowRegistrar);

// src/workflow.service.ts
var import_node_crypto = require("crypto");
var import_nestjs_durable_core13 = require("@dudousxd/nestjs-durable-core");
var import_common11 = require("@nestjs/common");
function _ts_decorate11(decorators, target, key, desc) {
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
  return c > 3 && r && Object.defineProperty(target, key, r), r;
}
__name(_ts_decorate11, "_ts_decorate");
function _ts_metadata11(k, v) {
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
}
__name(_ts_metadata11, "_ts_metadata");
var WorkflowService = class {
  static {
    __name(this, "WorkflowService");
  }
  engine;
  constructor(engine) {
    this.engine = engine;
  }
  start(workflow, input, runId = (0, import_node_crypto.randomUUID)(), opts) {
    return this.engine.start(workflow, input, runId, opts);
  }
  resume(runId) {
    return this.engine.resume(runId);
  }
  /**
  * Resolve once a run settles — terminal (completed/failed/cancelled/dead) or suspended. `start`
  * only enqueues (a worker runs the body), so pair them when a request needs the outcome:
  * `const { runId } = await svc.start(...); const result = await svc.waitForRun(runId)`.
  */
  waitForRun(runId, opts) {
    return this.engine.waitForRun(runId, opts);
  }
  /** Deliver an external signal (e.g. from a webhook) to the run waiting on `token`. */
  signal(token, payload) {
    return this.engine.signal(token, payload);
  }
  signalWithStart(workflow, input, runId, signal, opts) {
    return this.engine.signalWithStart(workflow, input, runId, signal, opts);
  }
  /**
  * Publish a named event. Resumes runs waiting on it via `ctx.waitForEvent(name, { match })` and
  * starts a fresh run of every workflow subscribed via `@Workflow({ onEvent })` / `@OnDurableEvent` (the
  * payload becomes its input). Pass `opts.id` to dedupe redeliveries. Returns how many runs it
  * touched (resumed + started).
  *
  * Reliable by default: a publish that touches NOBODY (no live waiter, no subscriber) buffers ONE
  * copy so a LATER `waitForEvent(name, { match })` still consumes it instead of it being dropped —
  * see {@link WorkflowEngine.publishEvent}'s full semantics doc. Pass `opts.buffer: false` to opt out.
  */
  publishEvent(name, payload, opts) {
    return this.engine.publishEvent(name, payload, opts);
  }
};
WorkflowService = _ts_decorate11([
  (0, import_common11.Injectable)(),
  _ts_metadata11("design:type", Function),
  _ts_metadata11("design:paramtypes", [
    typeof import_nestjs_durable_core13.WorkflowEngine === "undefined" ? Object : import_nestjs_durable_core13.WorkflowEngine
  ])
], WorkflowService);

// src/durable.module.ts
function _ts_decorate12(decorators, target, key, desc) {
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
  return c > 3 && r && Object.defineProperty(target, key, r), r;
}
__name(_ts_decorate12, "_ts_decorate");
function _ts_metadata12(k, v) {
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
}
__name(_ts_metadata12, "_ts_metadata");
function _ts_param8(paramIndex, decorator) {
  return function(target, key) {
    decorator(target, key, paramIndex);
  };
}
__name(_ts_param8, "_ts_param");
function resolveAccessor(moduleRef) {
  try {
    return moduleRef.get(CONTEXT_ACCESSOR, {
      strict: false
    });
  } catch {
    return void 0;
  }
}
__name(resolveAccessor, "resolveAccessor");
function carrierFromAccessor(accessor) {
  const carrier = {};
  const traceId = accessor.traceId();
  if (traceId !== void 0) carrier.traceId = traceId;
  const tenantId = accessor.tenantId();
  if (tenantId !== void 0) carrier.tenantId = tenantId;
  const userRef = accessor.userRef();
  if (userRef !== void 0) carrier.userRef = userRef;
  return carrier;
}
__name(carrierFromAccessor, "carrierFromAccessor");
function isContextRuntime(x) {
  return !!x && typeof x.deserialize === "function";
}
__name(isContextRuntime, "isContextRuntime");
async function resolveContextRuntime() {
  try {
    const specifier = "@dudousxd/nestjs-context";
    const mod = await import(specifier);
    return isContextRuntime(mod.Context) ? mod.Context : void 0;
  } catch {
    return void 0;
  }
}
__name(resolveContextRuntime, "resolveContextRuntime");
function isControlPlane(x) {
  return !!x && typeof x.publishControl === "function" && typeof x.onControl === "function";
}
__name(isControlPlane, "isControlPlane");
function isScopeableStore(store) {
  return typeof store.withScope === "function";
}
__name(isScopeableStore, "isScopeableStore");
function scopedStore(store, options) {
  if (options.scopeReads !== true || options.namespace === void 0) return store;
  if (!isScopeableStore(store)) return store;
  return store.withScope({
    namespace: options.namespace
  });
}
__name(scopedStore, "scopedStore");
function assertValidRole(options) {
  const hasStore = options.store !== void 0;
  const hasConnection = options.connection !== void 0;
  if (!hasStore && !hasConnection) {
    throw new Error("a durable module needs either a `store` (operator) or a `connection` (worker)");
  }
  if (hasStore && options.transport === void 0 && options.transports === void 0) {
    throw new Error("an operator (`store`) needs a `transport` (or `transports`)");
  }
}
__name(assertValidRole, "assertValidRole");
function assertValidTopology(options) {
  const topology = options.topology;
  if (topology === void 0) return;
  if (topology.role === "control-plane") {
    if (options.store === void 0 || options.transport === void 0 && options.transports === void 0) {
      throw new Error("topology: { role: 'control-plane' } needs `store` AND (`transport` or `transports`).\nA control-plane node is the durable operator: it owns the state store and dispatches runs to workers over a transport \u2014 without both, there is nothing for it to operate.");
    }
    if (options.partition !== void 0) {
      throw new Error("topology: { role: 'control-plane' } forbids `partition`.\npartition is the WORKER queue-routing suffix \u2014 on a control-plane node, runs are dispatched to tenant partitions via each run's namespace, not via this option. Set `partition` (or `topology: { role: 'tenant', tenant }`) on the WORKER node instead.");
    }
    if (topology.tenant !== void 0 && options.namespace !== void 0 && options.namespace !== topology.tenant) {
      throw new Error(`topology: { role: 'control-plane', tenant: '${topology.tenant}' } conflicts with \`namespace: '${options.namespace}'\`.
tenant maps 1:1 onto namespace (the operator's poll-scoping axis) \u2014 set only \`tenant\`, or set \`namespace\` to the same value.`);
    }
    return;
  }
  if (options.connection === void 0) {
    throw new Error(`topology: { role: 'tenant', tenant: '${topology.tenant}' } needs \`connection\`.
A tenant is a store-less worker: it connects to the broker directly to pull its own partition of tasks instead of polling a store it does not have.`);
  }
  if (options.store !== void 0) {
    throw new Error("topology: { role: 'tenant' } forbids `store`.\nA tenant is store-less BY DEFINITION \u2014 a `store` present means you actually wanted `topology: { role: 'control-plane' }` (the store-owning role).");
  }
  if (options.namespace !== void 0) {
    throw new Error("topology: { role: 'tenant' } forbids `namespace`.\nnamespace is the OPERATOR's poll-scoping axis (which runs a control-plane instance drives/recovers/resumes) \u2014 a tenant worker has no poll loop for it to scope. Use this preset's `tenant` field for the worker's own routing axis instead.");
  }
  if (options.partition !== void 0 && options.partition !== topology.tenant) {
    throw new Error(`topology: { role: 'tenant', tenant: '${topology.tenant}' } conflicts with \`partition: '${options.partition}'\`.
tenant maps 1:1 onto partition (the worker queue-routing suffix) \u2014 set only \`tenant\`, or set \`partition\` to the same value.`);
  }
}
__name(assertValidTopology, "assertValidTopology");
function resolveTopology(options) {
  return servingPartition(resolveTopologyPreset(options));
}
__name(resolveTopology, "resolveTopology");
function servingPartition(options) {
  if (options.partition !== void 0 || options.namespace === void 0) return options;
  return {
    ...options,
    partition: options.namespace
  };
}
__name(servingPartition, "servingPartition");
function resolveTopologyPreset(options) {
  const topology = options.topology;
  if (topology === void 0) return options;
  if (topology.role === "control-plane") {
    if (topology.tenant === void 0) return options;
    return {
      ...options,
      // An explicit `namespace` is already validated equal to `tenant` (assertValidTopology), so
      // keeping it is a no-op rather than a conflict.
      namespace: options.namespace ?? topology.tenant,
      // The tenant is ALSO this node's worker-routing suffix: its engine now dispatches a run's steps
      // to `<name>@<tenant>` (core's `stepGroup`), so its own co-located worker must SUBSCRIBE those
      // same tokens. Without this the node would dispatch into queues it is not itself listening on.
      // `partition` is user-forbidden on this role (assertValidTopology), so we own it here.
      partition: topology.tenant
    };
  }
  if (options.partition !== void 0) return options;
  return {
    ...options,
    partition: topology.tenant
  };
}
__name(resolveTopologyPreset, "resolveTopologyPreset");
var RunGatewayBootstrap = class RunGatewayBootstrap2 {
  static {
    __name(this, "RunGatewayBootstrap");
  }
  engine;
  transport;
  gateway;
  store;
  options;
  unsubscribe;
  constructor(engine, transport, gateway, store, options) {
    this.engine = engine;
    this.transport = transport;
    this.gateway = gateway;
    this.store = store;
    this.options = options;
  }
  onApplicationBootstrap() {
    if (!isDrivingOperator(this.options) || !this.transport) return;
    const store = this.store;
    if (!store) return;
    const { onRunRequest, publishRunReply, publishTenantEvent } = this.transport;
    if (typeof onRunRequest === "function" && typeof publishRunReply === "function") {
      const runRequestTransport = {
        onRunRequest: onRunRequest.bind(this.transport),
        publishRunReply: publishRunReply.bind(this.transport)
      };
      new RunRequestResponder(runRequestTransport, this.gateway).start();
    }
    if (typeof publishTenantEvent === "function") {
      const republisher = new TenantEventRepublisher(store, publishTenantEvent.bind(this.transport));
      this.unsubscribe = this.engine.subscribe((event) => {
        void republisher.handle(event);
      });
    }
  }
  onModuleDestroy() {
    this.unsubscribe?.();
  }
};
RunGatewayBootstrap = _ts_decorate12([
  (0, import_common12.Injectable)(),
  _ts_param8(1, (0, import_common12.Inject)(import_nestjs_durable_core14.TRANSPORT_CANONICAL)),
  _ts_param8(3, (0, import_common12.Inject)(import_nestjs_durable_core14.STATE_STORE_CANONICAL)),
  _ts_param8(4, (0, import_common12.Inject)(import_nestjs_durable_core14.DURABLE_OPTIONS_CANONICAL)),
  _ts_metadata12("design:type", Function),
  _ts_metadata12("design:paramtypes", [
    typeof import_nestjs_durable_core14.WorkflowEngine === "undefined" ? Object : import_nestjs_durable_core14.WorkflowEngine,
    Object,
    typeof import_nestjs_durable_core14.RunGateway === "undefined" ? Object : import_nestjs_durable_core14.RunGateway,
    Object,
    typeof DurableModuleOptions === "undefined" ? Object : DurableModuleOptions
  ])
], RunGatewayBootstrap);
var DurableModule = class _DurableModule {
  static {
    __name(this, "DurableModule");
  }
  static forRoot(options) {
    _DurableModule.assertValid(options);
    return _DurableModule.build({
      provide: import_nestjs_durable_core14.DURABLE_OPTIONS_CANONICAL,
      useValue: resolveTopology(options)
    });
  }
  static forRootAsync(options) {
    return _DurableModule.build({
      provide: import_nestjs_durable_core14.DURABLE_OPTIONS_CANONICAL,
      useFactory: /* @__PURE__ */ __name(async (...args) => {
        const resolved = await options.useFactory(...args);
        _DurableModule.assertValid(resolved);
        return resolveTopology(resolved);
      }, "useFactory"),
      inject: options.inject ?? []
    });
  }
  /**
  * Validates role/axis constraints, in resolution order. When {@link DurableModuleOptions.topology}
  * is set, it OWNS validation — {@link assertValidTopology}'s own store/transport/connection checks
  * are strictly stronger than {@link assertValidRole}'s, so the topology-specific, axis-teaching
  * message is what a `topology`-opted-in consumer sees (not the older generic one). Falls back to
  * {@link assertValidRole} unchanged when `topology` is absent — zero behavior change.
  */
  static assertValid(options) {
    if (options.topology !== void 0) {
      assertValidTopology(options);
      return;
    }
    assertValidRole(options);
  }
  static build(optionsProvider) {
    return {
      module: _DurableModule,
      global: true,
      imports: [
        import_core5.DiscoveryModule
      ],
      providers: [
        optionsProvider,
        {
          provide: import_nestjs_durable_core14.STATE_STORE_CANONICAL,
          useFactory: /* @__PURE__ */ __name((options) => options.store !== void 0 ? scopedStore(options.store, options) : null, "useFactory"),
          inject: [
            import_nestjs_durable_core14.DURABLE_OPTIONS_CANONICAL
          ]
        },
        {
          provide: import_nestjs_durable_core14.TRANSPORT_CANONICAL,
          // With a POOL (`transports`) and no singular `transport`, the canonical transport is the
          // pool's PRIMARY — the same one the engine dispatches on first. Leaving it null starved
          // the step registrar / in-app worker of a transport, so a pool-configured operator
          // registered NO step handlers and its own steps parked in `wait` with no consumer.
          useFactory: /* @__PURE__ */ __name((options) => options.transport ?? options.transports?.[0]?.transport ?? null, "useFactory"),
          inject: [
            import_nestjs_durable_core14.DURABLE_OPTIONS_CANONICAL
          ]
        },
        // Legacy back-compat aliases (deprecated tokens still resolve to the same instances):
        {
          provide: import_nestjs_durable_core14.STATE_STORE,
          useExisting: import_nestjs_durable_core14.STATE_STORE_CANONICAL
        },
        {
          provide: import_nestjs_durable_core14.TRANSPORT,
          useExisting: import_nestjs_durable_core14.TRANSPORT_CANONICAL
        },
        {
          provide: import_nestjs_durable_core14.DURABLE_OPTIONS,
          useExisting: import_nestjs_durable_core14.DURABLE_OPTIONS_CANONICAL
        },
        {
          // Shared token, bound EXACTLY once: a real engine for the operator role, or a store-less
          // `DurableStartClient` facade for a thin worker (no `store`) — either way, `WorkflowService`
          // and app code call `engine.start(...)` unchanged.
          provide: import_nestjs_durable_core14.WorkflowEngine,
          useFactory: /* @__PURE__ */ __name(async (options, store, transport, moduleRef) => {
            if (options.store === void 0) {
              return new DurableStartClient(options);
            }
            if (!store) {
              throw new Error("unreachable: STATE_STORE_CANONICAL must resolve a store when options.store is set");
            }
            const primary = transport ?? options.transports?.[0]?.transport;
            const accessor = resolveAccessor(moduleRef);
            const context = options.context ?? (accessor ? () => carrierFromAccessor(accessor) : void 0);
            const runtime = accessor ? await resolveContextRuntime() : void 0;
            const rehydrate = runtime && ((carrier, fn) => carrier && Object.keys(carrier).length > 0 ? runtime.deserialize(carrier, fn) : fn());
            const engine = new import_nestjs_durable_core14.WorkflowEngine({
              store,
              transport: transport ?? void 0,
              transports: options.transports,
              controlPlane: options.controlPlane ?? (isControlPlane(primary) ? primary : void 0),
              leaseMs: options.leaseMs,
              admission: options.admission,
              maxRecoveryAttempts: options.maxRecoveryAttempts,
              remoteAdvanceSilenceMs: options.remoteAdvanceSilenceMs,
              instanceId: options.instanceId,
              namespace: options.namespace,
              webhookUrl: options.webhookUrl,
              traceparent: options.traceparent,
              context,
              rehydrate: rehydrate || void 0,
              compensationRetries: options.compensationRetries,
              // A non-driving (dashboard/API) operator must not run workflows: enqueue-only, leaving
              // each `pending` run in the store for a DRIVING instance's poll. A driving operator gets
              // the engine's default in-process dispatcher: for a body registered inline, it runs
              // locally; for one registered GROUP-SERVED (co-located worker) or left unregistered, the
              // SAME default dispatcher routes it out remotely (group-served executor, or convention
              // dispatch — always on) instead.
              runDispatcher: options.drive === false ? {
                dispatch: /* @__PURE__ */ __name(() => {
                }, "dispatch")
              } : void 0
            });
            for (const queue of options.queues ?? []) engine.registerQueue(queue);
            return engine;
          }, "useFactory"),
          inject: [
            import_nestjs_durable_core14.DURABLE_OPTIONS_CANONICAL,
            import_nestjs_durable_core14.STATE_STORE_CANONICAL,
            import_nestjs_durable_core14.TRANSPORT_CANONICAL,
            import_core5.ModuleRef
          ]
        },
        WorkflowService,
        EntityService,
        WorkflowRegistrar,
        DurableStepRegistrar,
        TimerPoller,
        RetentionPoller,
        // Tenant run gateway: bound EXACTLY once — the store-backed `StoreRunGateway` for an operator,
        // or (for a thin worker, no `store`) a `ProxyRunGateway` over `transport` when given, else a
        // gateway whose every method rejects with a clear tenant error.
        {
          provide: import_nestjs_durable_core14.RunGateway,
          useFactory: /* @__PURE__ */ __name((options, store, engine) => {
            if (options.store !== void 0) {
              if (!store) {
                throw new Error("unreachable: STATE_STORE_CANONICAL must resolve a store when options.store is set");
              }
              return new StoreRunGateway(store, engine);
            }
            return options.transport ? new ProxyRunGateway(options.transport, options.partition ?? "default", options.runGatewayTimeoutMs) : unavailableRunGateway();
          }, "useFactory"),
          inject: [
            import_nestjs_durable_core14.DURABLE_OPTIONS_CANONICAL,
            import_nestjs_durable_core14.STATE_STORE_CANONICAL,
            import_nestjs_durable_core14.WorkflowEngine
          ]
        },
        RunGatewayBootstrap,
        // Co-located in-app worker (uniform dispatch): inert unless BOTH `store` and `connection` are
        // set — see `in-app-worker.ts`.
        ...inAppWorkerProviders(),
        // Pure thin-worker consumer: inert unless `connection` is set WITHOUT `store` — see
        // `durable-worker.module.ts`.
        ...thinWorkerProviders()
      ],
      exports: [
        WorkflowService,
        EntityService,
        import_nestjs_durable_core14.WorkflowEngine,
        import_nestjs_durable_core14.STATE_STORE,
        import_nestjs_durable_core14.STATE_STORE_CANONICAL,
        import_nestjs_durable_core14.TRANSPORT,
        import_nestjs_durable_core14.TRANSPORT_CANONICAL,
        import_nestjs_durable_core14.DURABLE_OPTIONS_CANONICAL,
        import_nestjs_durable_core14.RunGateway
      ]
    };
  }
};
DurableModule = _ts_decorate12([
  (0, import_common12.Module)({})
], DurableModule);

// src/index.ts
var import_nestjs_durable_core15 = require("@dudousxd/nestjs-durable-core");
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  CONTEXT_ACCESSOR,
  DEAD_LETTER_METADATA,
  DURABLE_STEP_METADATA,
  DURABLE_WORKER_RUNNERS,
  DeadLetter,
  DurableModule,
  DurableStartClient,
  DurableStep,
  ENTITY_METADATA,
  ENTITY_ON_METADATA,
  Entity,
  EntityService,
  IN_APP_RUN_REDIS_WORKER,
  IN_APP_WORKER_BINDING,
  IN_APP_WORKER_RUNNERS,
  IN_APP_WORKER_RUNTIME,
  InAppWorkerBootstrap,
  ON_EVENT_METADATA,
  On,
  OnDurableEvent,
  OnEvent,
  ProxyRunGateway,
  RUN_GATEWAY,
  RUN_REDIS_WORKER,
  RunGateway,
  RunRequestResponder,
  STEP_INTERCEPTOR_METADATA,
  Step,
  StepInterceptor,
  StoreRunGateway,
  TenantEventRepublisher,
  ThinStepRegistrar,
  ThinWorkerBootstrap,
  ThinWorkflowRegistrar,
  WORKFLOW_METADATA,
  Workflow,
  WorkflowEngine,
  WorkflowService,
  attributesOf,
  entityConfigFor,
  getDurableStepMeta,
  getEntityMeta,
  getOnEvents,
  getWorkflowMeta,
  inAppWorkerProviders,
  isDeadLetterHandler,
  isDrivingOperator,
  isOperatorRole,
  isStepInterceptor,
  readSearchAttributes,
  thinWorkerProviders,
  unavailableRunGateway
});
//# sourceMappingURL=index.cjs.map