@dudousxd/nestjs-durable 0.51.0 → 0.52.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, StandardSchemaV1, StepError, OnErrorInput, WorkflowRef, SingletonConfig, ConcurrencyConfig, SearchAttributesSchema, StateStore, Transport, NamedTransport, ControlPlane, StalledRunInfo, ScheduledWorkflow, RetentionPolicy, QueueConfig, AdmissionBackend, WorkflowInputOf, StartOptions, RunResult, RunGateway, WorkflowEngine, EntityHandler, DurableTopology, RunDetailOptions, RunDetail, FanPageQuery, FanPage, RunQuery, RunListItem, RunFacetQuery, RunFacetRow, RunValueAxis, RunValueFacetOptions, RunValueFacetRow, GroupHealth, RunWaiting, EngineEvent, RunRequest, RunReply, StepInvocation, WorkflowRun, TenantEvent } from '@dudousxd/nestjs-durable-core';
2
- export { AttributeFilter, DynamicWorkflowCtx, EngineEvent, InferSearchAttributes, OnErrorInput, RunDetail, RunGateway, RunListItem, RunQuery, RunSchedulesOptions, RunStatus, RunWaiting, ScheduledWorkflow, SearchAttributes, SearchAttributesSchema, StepCheckpoint, StepEvent, StepLogger, WorkflowCtx, WorkflowEngine, WorkflowHandler, WorkflowRun, prevCronFireMs, readSearchAttributes, runSchedules, scheduleRunIdAt, scheduledRunId } from '@dudousxd/nestjs-durable-core';
1
+ import { WorkflowCtx, SearchAttributes, WorkflowClass, StandardSchemaV1, StepError, OnErrorInput, WorkflowRef, SingletonConfig, ConcurrencyConfig, SearchAttributesSchema, StateStore, Transport, NamedTransport, ControlPlane, StalledRunInfo, ScheduledWorkflow, RetentionPolicy, QueueConfig, AdmissionBackend, TenantVerifier, TenantTokenSource, WorkflowInputOf, StartOptions, RunResult, RunGateway, WorkflowEngine, EntityHandler, DurableTopology, RunDetailOptions, RunDetail, FanPageQuery, FanPage, RunQuery, RunListItem, RunFacetQuery, RunFacetRow, RunValueAxis, RunValueFacetOptions, RunValueFacetRow, GroupHealth, RunWaiting, EngineEvent, RunRequest, RunReply, StepInvocation, WorkflowRun, TenantEvent } from '@dudousxd/nestjs-durable-core';
2
+ export { AttributeFilter, DynamicWorkflowCtx, EngineEvent, InferSearchAttributes, OnErrorInput, RunDetail, RunGateway, RunListItem, RunQuery, RunSchedulesOptions, RunStatus, RunWaiting, ScheduledWorkflow, SearchAttributes, SearchAttributesSchema, StepCheckpoint, StepEvent, StepLogger, TenantTokenSource, TenantVerifier, VerifiedTenant, WorkflowCtx, WorkflowEngine, WorkflowHandler, WorkflowRun, hmacTenantVerifier, prevCronFireMs, readSearchAttributes, runSchedules, scheduleRunIdAt, scheduledRunId, signTenantToken } 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';
@@ -790,6 +790,26 @@ interface DurableModuleOptions {
790
790
  /** Timeout for a thin worker's `RunGateway` round-trip over `transport` before it rejects. Defaults
791
791
  * to 10_000ms inside `ProxyRunGateway`. Ignored for an operator (bound to `StoreRunGateway`). */
792
792
  runGatewayTimeoutMs?: number;
793
+ /**
794
+ * **Operator.** Verifies the signed tenant token a store-less tenant worker presents in the wire
795
+ * `tenant` field of every run request and start-run message, and DERIVES the tenant from it,
796
+ * discarding the wire claim. An invalid, tampered or absent token is rejected: a run request gets
797
+ * an `unauthorized` error reply, a start-run creates nothing. Use `hmacTenantVerifier(secret)`
798
+ * (tokens minted by `signTenantToken`, wire-compatible with Agora) or supply your own.
799
+ *
800
+ * Optional. Absent: the wire `tenant` is trusted verbatim (prefix/network isolation only), exactly
801
+ * as before. An explicit `topology: { role: 'control-plane' }` without a verifier logs a warning
802
+ * at boot; a plain operator (`store` + `transport`, no `topology`) boots silently.
803
+ */
804
+ verifyTenant?: TenantVerifier;
805
+ /**
806
+ * **Tenant worker.** The signed token (see `signTenantToken`) this worker presents, verbatim, in
807
+ * the `tenant` field of every run request (`ProxyRunGateway`) and start-run (`engine.start`), so
808
+ * an operator with {@link verifyTenant} can authenticate it. A string, or a function called before
809
+ * every request (rotation, short-lived tokens). Optional: absent, the raw {@link partition} is
810
+ * sent, as before. Live-tail stays keyed by the partition, never the token.
811
+ */
812
+ tenantToken?: TenantTokenSource;
793
813
  /**
794
814
  * An explicit preset that NAMES the deployment role instead of leaving it to `store`/`connection`
795
815
  * inference, and VALIDATES the axes that are otherwise only prose (see the interface doc's role
@@ -1091,13 +1111,29 @@ declare class ProxyRunGateway implements RunGateway {
1091
1111
  private readonly tenant;
1092
1112
  private readonly timeoutMs;
1093
1113
  private readonly capabilityTtlMs;
1114
+ /**
1115
+ * The tenant's signed token (see `signTenantToken`), presented VERBATIM in the `tenant` field of
1116
+ * every {@link RunRequest} so an operator with `verifyTenant` can authenticate it and derive the
1117
+ * tenant from it. A function is called before every request (rotation / short-lived tokens).
1118
+ * Omit to send the raw `tenant` (prefix/network isolation only). The live-tail channel always
1119
+ * stays keyed by `tenant`, never the token: the operator republishes by the run's namespace.
1120
+ */
1121
+ private readonly tenantToken?;
1094
1122
  private readonly pending;
1095
1123
  /** Verb → when to ask the control plane for it again, after it answered that it does not know it.
1096
1124
  * During a rolling upgrade the control plane is several pods of mixed versions behind one queue,
1097
1125
  * and a reply does not say which pod sent it, so the whole control plane is assumed old until the
1098
1126
  * entry expires. */
1099
1127
  private readonly unsupportedUntil;
1100
- constructor(transport: Transport, tenant: string, timeoutMs?: number, capabilityTtlMs?: number);
1128
+ constructor(transport: Transport, tenant: string, timeoutMs?: number, capabilityTtlMs?: number,
1129
+ /**
1130
+ * The tenant's signed token (see `signTenantToken`), presented VERBATIM in the `tenant` field of
1131
+ * every {@link RunRequest} so an operator with `verifyTenant` can authenticate it and derive the
1132
+ * tenant from it. A function is called before every request (rotation / short-lived tokens).
1133
+ * Omit to send the raw `tenant` (prefix/network isolation only). The live-tail channel always
1134
+ * stays keyed by `tenant`, never the token: the operator republishes by the run's namespace.
1135
+ */
1136
+ tenantToken?: TenantTokenSource | undefined);
1101
1137
  private handleReply;
1102
1138
  private request;
1103
1139
  topology(): DurableTopology;
@@ -1165,6 +1201,22 @@ interface RunRequestTransport {
1165
1201
  onRunRequest(handler: (msg: RunRequest) => Promise<void>): void;
1166
1202
  publishRunReply(reply: RunReply): Promise<void>;
1167
1203
  }
1204
+ /** Options for {@link RunRequestResponder}. */
1205
+ interface RunRequestResponderOptions {
1206
+ /**
1207
+ * Verifies the signed token a tenant presents in the wire `tenant` field and DERIVES the real
1208
+ * tenant from it (see `hmacTenantVerifier`). Present: an invalid, tampered or absent token is
1209
+ * rejected with an `unauthorized` error reply and the wire claim is discarded in favour of the
1210
+ * verified tenant. Absent: the wire `tenant` is trusted verbatim (prefix/network isolation only).
1211
+ */
1212
+ verifyTenant?: TenantVerifier | undefined;
1213
+ /**
1214
+ * Warn at construction when no {@link verifyTenant} is set. Default `true`. `DurableModule` passes
1215
+ * `true` only for an explicit `topology: { role: 'control-plane' }`, so a plain operator app that
1216
+ * never serves tenants boots silently.
1217
+ */
1218
+ warnWithoutVerifier?: boolean | undefined;
1219
+ }
1168
1220
  /**
1169
1221
  * Operator-side consumer of a tenant's {@link RunRequest}s: answers each one against a
1170
1222
  * `RunGateway`, enforcing the tenant boundary before touching the run. For every runId-bearing
@@ -1173,11 +1225,17 @@ interface RunRequestTransport {
1173
1225
  * verb, so a tenant can never read or act on another tenant's run. `listRuns` is scoped by
1174
1226
  * overwriting the query's `namespace` with the requester's tenant, ignoring whatever the client
1175
1227
  * sent. This is the security boundary of the tenant run gateway — do not weaken it.
1228
+ *
1229
+ * With {@link RunRequestResponderOptions.verifyTenant}, every request is authenticated FIRST: the
1230
+ * wire `tenant` is treated as a signed token, the real tenant is derived from it, and an invalid
1231
+ * token short-circuits into an `unauthorized` error reply before any run is touched. A verifier
1232
+ * that throws becomes an error reply too.
1176
1233
  */
1177
1234
  declare class RunRequestResponder {
1178
1235
  private readonly transport;
1179
1236
  private readonly gateway;
1180
- constructor(transport: RunRequestTransport, gateway: RunGateway);
1237
+ private readonly verifyTenant;
1238
+ constructor(transport: RunRequestTransport, gateway: RunGateway, options?: RunRequestResponderOptions);
1181
1239
  /** Register the consumer on the transport. Each request is answered independently; a handler
1182
1240
  * failure never throws back into the transport (errors are captured into an error reply). */
1183
1241
  start(): void;
@@ -1375,4 +1433,4 @@ declare class WorkflowService {
1375
1433
  }): Promise<number>;
1376
1434
  }
1377
1435
 
1378
- export { CONTEXT_ACCESSOR, type ContextAccessor, type ContextStore, DEAD_LETTER_METADATA, DURABLE_STEP_METADATA, DURABLE_WORKER_RUNNERS, DURABLE_WORKER_RUNTIME, DeadLetter, type DtoClass, DurableModule, type DurableModuleAsyncOptions, type DurableModuleOptions, type DurableRetentionOptions, DurableStartClient, DurableStep, type DurableStepInterceptor, type DurableStepMeta, ENTITY_METADATA, ENTITY_ON_METADATA, Entity, EntityService, type ErrorClassWithCode, IN_APP_RUN_REDIS_WORKER, IN_APP_WORKER_BINDING, IN_APP_WORKER_RUNNERS, IN_APP_WORKER_RUNTIME, type InAppWorkerBinding, InAppWorkerBootstrap, type IoSchema, ON_ERROR_METADATA, ON_EVENT_METADATA, On, OnDurableEvent, OnError, type OnErrorMethod, OnEvent, ProxyRunGateway, RUN_GATEWAY, RUN_REDIS_WORKER, type RunLookupStore, type RunRedisWorkerFn, RunRequestResponder, type RunRequestTransport, STEP_INTERCEPTOR_METADATA, Step, StepInterceptor, type StepOptions, StoreRunGateway, TenantEventRepublisher, ThinStepRegistrar, ThinWorkerBootstrap, ThinWorkflowRegistrar, UNSUPPORTED_RUN_REQUEST, type UserRef, WORKFLOW_METADATA, Workflow, type WorkflowAttributesOf, type WorkflowMeta, type WorkflowOptions, WorkflowService, attributesOf, entityConfigFor, getDurableStepMeta, getEntityMeta, getOnEvents, getWorkflowMeta, inAppWorkerProviders, isDeadLetterHandler, isDrivingOperator, isOperatorRole, isStepInterceptor, onErrorCodesOf, thinWorkerProviders, unavailableRunGateway };
1436
+ export { CONTEXT_ACCESSOR, type ContextAccessor, type ContextStore, DEAD_LETTER_METADATA, DURABLE_STEP_METADATA, DURABLE_WORKER_RUNNERS, DURABLE_WORKER_RUNTIME, DeadLetter, type DtoClass, DurableModule, type DurableModuleAsyncOptions, type DurableModuleOptions, type DurableRetentionOptions, DurableStartClient, DurableStep, type DurableStepInterceptor, type DurableStepMeta, ENTITY_METADATA, ENTITY_ON_METADATA, Entity, EntityService, type ErrorClassWithCode, IN_APP_RUN_REDIS_WORKER, IN_APP_WORKER_BINDING, IN_APP_WORKER_RUNNERS, IN_APP_WORKER_RUNTIME, type InAppWorkerBinding, InAppWorkerBootstrap, type IoSchema, ON_ERROR_METADATA, ON_EVENT_METADATA, On, OnDurableEvent, OnError, type OnErrorMethod, OnEvent, ProxyRunGateway, RUN_GATEWAY, RUN_REDIS_WORKER, type RunLookupStore, type RunRedisWorkerFn, RunRequestResponder, type RunRequestResponderOptions, type RunRequestTransport, STEP_INTERCEPTOR_METADATA, Step, StepInterceptor, type StepOptions, StoreRunGateway, TenantEventRepublisher, ThinStepRegistrar, ThinWorkerBootstrap, ThinWorkflowRegistrar, UNSUPPORTED_RUN_REQUEST, type UserRef, WORKFLOW_METADATA, Workflow, type WorkflowAttributesOf, type WorkflowMeta, type WorkflowOptions, WorkflowService, attributesOf, entityConfigFor, getDurableStepMeta, getEntityMeta, getOnEvents, getWorkflowMeta, inAppWorkerProviders, isDeadLetterHandler, isDrivingOperator, isOperatorRole, isStepInterceptor, onErrorCodesOf, thinWorkerProviders, unavailableRunGateway };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { WorkflowCtx, SearchAttributes, WorkflowClass, StandardSchemaV1, StepError, OnErrorInput, WorkflowRef, SingletonConfig, ConcurrencyConfig, SearchAttributesSchema, StateStore, Transport, NamedTransport, ControlPlane, StalledRunInfo, ScheduledWorkflow, RetentionPolicy, QueueConfig, AdmissionBackend, WorkflowInputOf, StartOptions, RunResult, RunGateway, WorkflowEngine, EntityHandler, DurableTopology, RunDetailOptions, RunDetail, FanPageQuery, FanPage, RunQuery, RunListItem, RunFacetQuery, RunFacetRow, RunValueAxis, RunValueFacetOptions, RunValueFacetRow, GroupHealth, RunWaiting, EngineEvent, RunRequest, RunReply, StepInvocation, WorkflowRun, TenantEvent } from '@dudousxd/nestjs-durable-core';
2
- export { AttributeFilter, DynamicWorkflowCtx, EngineEvent, InferSearchAttributes, OnErrorInput, RunDetail, RunGateway, RunListItem, RunQuery, RunSchedulesOptions, RunStatus, RunWaiting, ScheduledWorkflow, SearchAttributes, SearchAttributesSchema, StepCheckpoint, StepEvent, StepLogger, WorkflowCtx, WorkflowEngine, WorkflowHandler, WorkflowRun, prevCronFireMs, readSearchAttributes, runSchedules, scheduleRunIdAt, scheduledRunId } from '@dudousxd/nestjs-durable-core';
1
+ import { WorkflowCtx, SearchAttributes, WorkflowClass, StandardSchemaV1, StepError, OnErrorInput, WorkflowRef, SingletonConfig, ConcurrencyConfig, SearchAttributesSchema, StateStore, Transport, NamedTransport, ControlPlane, StalledRunInfo, ScheduledWorkflow, RetentionPolicy, QueueConfig, AdmissionBackend, TenantVerifier, TenantTokenSource, WorkflowInputOf, StartOptions, RunResult, RunGateway, WorkflowEngine, EntityHandler, DurableTopology, RunDetailOptions, RunDetail, FanPageQuery, FanPage, RunQuery, RunListItem, RunFacetQuery, RunFacetRow, RunValueAxis, RunValueFacetOptions, RunValueFacetRow, GroupHealth, RunWaiting, EngineEvent, RunRequest, RunReply, StepInvocation, WorkflowRun, TenantEvent } from '@dudousxd/nestjs-durable-core';
2
+ export { AttributeFilter, DynamicWorkflowCtx, EngineEvent, InferSearchAttributes, OnErrorInput, RunDetail, RunGateway, RunListItem, RunQuery, RunSchedulesOptions, RunStatus, RunWaiting, ScheduledWorkflow, SearchAttributes, SearchAttributesSchema, StepCheckpoint, StepEvent, StepLogger, TenantTokenSource, TenantVerifier, VerifiedTenant, WorkflowCtx, WorkflowEngine, WorkflowHandler, WorkflowRun, hmacTenantVerifier, prevCronFireMs, readSearchAttributes, runSchedules, scheduleRunIdAt, scheduledRunId, signTenantToken } 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';
@@ -790,6 +790,26 @@ interface DurableModuleOptions {
790
790
  /** Timeout for a thin worker's `RunGateway` round-trip over `transport` before it rejects. Defaults
791
791
  * to 10_000ms inside `ProxyRunGateway`. Ignored for an operator (bound to `StoreRunGateway`). */
792
792
  runGatewayTimeoutMs?: number;
793
+ /**
794
+ * **Operator.** Verifies the signed tenant token a store-less tenant worker presents in the wire
795
+ * `tenant` field of every run request and start-run message, and DERIVES the tenant from it,
796
+ * discarding the wire claim. An invalid, tampered or absent token is rejected: a run request gets
797
+ * an `unauthorized` error reply, a start-run creates nothing. Use `hmacTenantVerifier(secret)`
798
+ * (tokens minted by `signTenantToken`, wire-compatible with Agora) or supply your own.
799
+ *
800
+ * Optional. Absent: the wire `tenant` is trusted verbatim (prefix/network isolation only), exactly
801
+ * as before. An explicit `topology: { role: 'control-plane' }` without a verifier logs a warning
802
+ * at boot; a plain operator (`store` + `transport`, no `topology`) boots silently.
803
+ */
804
+ verifyTenant?: TenantVerifier;
805
+ /**
806
+ * **Tenant worker.** The signed token (see `signTenantToken`) this worker presents, verbatim, in
807
+ * the `tenant` field of every run request (`ProxyRunGateway`) and start-run (`engine.start`), so
808
+ * an operator with {@link verifyTenant} can authenticate it. A string, or a function called before
809
+ * every request (rotation, short-lived tokens). Optional: absent, the raw {@link partition} is
810
+ * sent, as before. Live-tail stays keyed by the partition, never the token.
811
+ */
812
+ tenantToken?: TenantTokenSource;
793
813
  /**
794
814
  * An explicit preset that NAMES the deployment role instead of leaving it to `store`/`connection`
795
815
  * inference, and VALIDATES the axes that are otherwise only prose (see the interface doc's role
@@ -1091,13 +1111,29 @@ declare class ProxyRunGateway implements RunGateway {
1091
1111
  private readonly tenant;
1092
1112
  private readonly timeoutMs;
1093
1113
  private readonly capabilityTtlMs;
1114
+ /**
1115
+ * The tenant's signed token (see `signTenantToken`), presented VERBATIM in the `tenant` field of
1116
+ * every {@link RunRequest} so an operator with `verifyTenant` can authenticate it and derive the
1117
+ * tenant from it. A function is called before every request (rotation / short-lived tokens).
1118
+ * Omit to send the raw `tenant` (prefix/network isolation only). The live-tail channel always
1119
+ * stays keyed by `tenant`, never the token: the operator republishes by the run's namespace.
1120
+ */
1121
+ private readonly tenantToken?;
1094
1122
  private readonly pending;
1095
1123
  /** Verb → when to ask the control plane for it again, after it answered that it does not know it.
1096
1124
  * During a rolling upgrade the control plane is several pods of mixed versions behind one queue,
1097
1125
  * and a reply does not say which pod sent it, so the whole control plane is assumed old until the
1098
1126
  * entry expires. */
1099
1127
  private readonly unsupportedUntil;
1100
- constructor(transport: Transport, tenant: string, timeoutMs?: number, capabilityTtlMs?: number);
1128
+ constructor(transport: Transport, tenant: string, timeoutMs?: number, capabilityTtlMs?: number,
1129
+ /**
1130
+ * The tenant's signed token (see `signTenantToken`), presented VERBATIM in the `tenant` field of
1131
+ * every {@link RunRequest} so an operator with `verifyTenant` can authenticate it and derive the
1132
+ * tenant from it. A function is called before every request (rotation / short-lived tokens).
1133
+ * Omit to send the raw `tenant` (prefix/network isolation only). The live-tail channel always
1134
+ * stays keyed by `tenant`, never the token: the operator republishes by the run's namespace.
1135
+ */
1136
+ tenantToken?: TenantTokenSource | undefined);
1101
1137
  private handleReply;
1102
1138
  private request;
1103
1139
  topology(): DurableTopology;
@@ -1165,6 +1201,22 @@ interface RunRequestTransport {
1165
1201
  onRunRequest(handler: (msg: RunRequest) => Promise<void>): void;
1166
1202
  publishRunReply(reply: RunReply): Promise<void>;
1167
1203
  }
1204
+ /** Options for {@link RunRequestResponder}. */
1205
+ interface RunRequestResponderOptions {
1206
+ /**
1207
+ * Verifies the signed token a tenant presents in the wire `tenant` field and DERIVES the real
1208
+ * tenant from it (see `hmacTenantVerifier`). Present: an invalid, tampered or absent token is
1209
+ * rejected with an `unauthorized` error reply and the wire claim is discarded in favour of the
1210
+ * verified tenant. Absent: the wire `tenant` is trusted verbatim (prefix/network isolation only).
1211
+ */
1212
+ verifyTenant?: TenantVerifier | undefined;
1213
+ /**
1214
+ * Warn at construction when no {@link verifyTenant} is set. Default `true`. `DurableModule` passes
1215
+ * `true` only for an explicit `topology: { role: 'control-plane' }`, so a plain operator app that
1216
+ * never serves tenants boots silently.
1217
+ */
1218
+ warnWithoutVerifier?: boolean | undefined;
1219
+ }
1168
1220
  /**
1169
1221
  * Operator-side consumer of a tenant's {@link RunRequest}s: answers each one against a
1170
1222
  * `RunGateway`, enforcing the tenant boundary before touching the run. For every runId-bearing
@@ -1173,11 +1225,17 @@ interface RunRequestTransport {
1173
1225
  * verb, so a tenant can never read or act on another tenant's run. `listRuns` is scoped by
1174
1226
  * overwriting the query's `namespace` with the requester's tenant, ignoring whatever the client
1175
1227
  * sent. This is the security boundary of the tenant run gateway — do not weaken it.
1228
+ *
1229
+ * With {@link RunRequestResponderOptions.verifyTenant}, every request is authenticated FIRST: the
1230
+ * wire `tenant` is treated as a signed token, the real tenant is derived from it, and an invalid
1231
+ * token short-circuits into an `unauthorized` error reply before any run is touched. A verifier
1232
+ * that throws becomes an error reply too.
1176
1233
  */
1177
1234
  declare class RunRequestResponder {
1178
1235
  private readonly transport;
1179
1236
  private readonly gateway;
1180
- constructor(transport: RunRequestTransport, gateway: RunGateway);
1237
+ private readonly verifyTenant;
1238
+ constructor(transport: RunRequestTransport, gateway: RunGateway, options?: RunRequestResponderOptions);
1181
1239
  /** Register the consumer on the transport. Each request is answered independently; a handler
1182
1240
  * failure never throws back into the transport (errors are captured into an error reply). */
1183
1241
  start(): void;
@@ -1375,4 +1433,4 @@ declare class WorkflowService {
1375
1433
  }): Promise<number>;
1376
1434
  }
1377
1435
 
1378
- export { CONTEXT_ACCESSOR, type ContextAccessor, type ContextStore, DEAD_LETTER_METADATA, DURABLE_STEP_METADATA, DURABLE_WORKER_RUNNERS, DURABLE_WORKER_RUNTIME, DeadLetter, type DtoClass, DurableModule, type DurableModuleAsyncOptions, type DurableModuleOptions, type DurableRetentionOptions, DurableStartClient, DurableStep, type DurableStepInterceptor, type DurableStepMeta, ENTITY_METADATA, ENTITY_ON_METADATA, Entity, EntityService, type ErrorClassWithCode, IN_APP_RUN_REDIS_WORKER, IN_APP_WORKER_BINDING, IN_APP_WORKER_RUNNERS, IN_APP_WORKER_RUNTIME, type InAppWorkerBinding, InAppWorkerBootstrap, type IoSchema, ON_ERROR_METADATA, ON_EVENT_METADATA, On, OnDurableEvent, OnError, type OnErrorMethod, OnEvent, ProxyRunGateway, RUN_GATEWAY, RUN_REDIS_WORKER, type RunLookupStore, type RunRedisWorkerFn, RunRequestResponder, type RunRequestTransport, STEP_INTERCEPTOR_METADATA, Step, StepInterceptor, type StepOptions, StoreRunGateway, TenantEventRepublisher, ThinStepRegistrar, ThinWorkerBootstrap, ThinWorkflowRegistrar, UNSUPPORTED_RUN_REQUEST, type UserRef, WORKFLOW_METADATA, Workflow, type WorkflowAttributesOf, type WorkflowMeta, type WorkflowOptions, WorkflowService, attributesOf, entityConfigFor, getDurableStepMeta, getEntityMeta, getOnEvents, getWorkflowMeta, inAppWorkerProviders, isDeadLetterHandler, isDrivingOperator, isOperatorRole, isStepInterceptor, onErrorCodesOf, thinWorkerProviders, unavailableRunGateway };
1436
+ export { CONTEXT_ACCESSOR, type ContextAccessor, type ContextStore, DEAD_LETTER_METADATA, DURABLE_STEP_METADATA, DURABLE_WORKER_RUNNERS, DURABLE_WORKER_RUNTIME, DeadLetter, type DtoClass, DurableModule, type DurableModuleAsyncOptions, type DurableModuleOptions, type DurableRetentionOptions, DurableStartClient, DurableStep, type DurableStepInterceptor, type DurableStepMeta, ENTITY_METADATA, ENTITY_ON_METADATA, Entity, EntityService, type ErrorClassWithCode, IN_APP_RUN_REDIS_WORKER, IN_APP_WORKER_BINDING, IN_APP_WORKER_RUNNERS, IN_APP_WORKER_RUNTIME, type InAppWorkerBinding, InAppWorkerBootstrap, type IoSchema, ON_ERROR_METADATA, ON_EVENT_METADATA, On, OnDurableEvent, OnError, type OnErrorMethod, OnEvent, ProxyRunGateway, RUN_GATEWAY, RUN_REDIS_WORKER, type RunLookupStore, type RunRedisWorkerFn, RunRequestResponder, type RunRequestResponderOptions, type RunRequestTransport, STEP_INTERCEPTOR_METADATA, Step, StepInterceptor, type StepOptions, StoreRunGateway, TenantEventRepublisher, ThinStepRegistrar, ThinWorkerBootstrap, ThinWorkflowRegistrar, UNSUPPORTED_RUN_REQUEST, type UserRef, WORKFLOW_METADATA, Workflow, type WorkflowAttributesOf, type WorkflowMeta, type WorkflowOptions, WorkflowService, attributesOf, entityConfigFor, getDurableStepMeta, getEntityMeta, getOnEvents, getWorkflowMeta, inAppWorkerProviders, isDeadLetterHandler, isDrivingOperator, isOperatorRole, isStepInterceptor, onErrorCodesOf, thinWorkerProviders, unavailableRunGateway };
package/dist/index.js CHANGED
@@ -264,7 +264,7 @@ function attributesOf(workflow, run) {
264
264
  __name(attributesOf, "attributesOf");
265
265
 
266
266
  // src/durable-start-client.ts
267
- import { workflowName } from "@dudousxd/nestjs-durable-core";
267
+ import { resolveTenantToken, workflowName } from "@dudousxd/nestjs-durable-core";
268
268
  import { Injectable } from "@nestjs/common";
269
269
 
270
270
  // src/worker-sdk.ts
@@ -322,7 +322,9 @@ var DurableStartClient = class {
322
322
  async start(workflow, input, runId = globalThis.crypto.randomUUID(), opts) {
323
323
  const name = workflowName(workflow);
324
324
  await startRun(this.options.connection, {
325
- tenant: this.tenant,
325
+ // The signed token (when configured) rides in the `tenant` field, as on every run request; an
326
+ // operator with `verifyTenant` derives the run's namespace from it.
327
+ tenant: this.options.tenantToken !== void 0 ? await resolveTenantToken(this.options.tenantToken) : this.tenant,
326
328
  workflow: name,
327
329
  input,
328
330
  runId,
@@ -1159,10 +1161,11 @@ function inAppWorkerProviders() {
1159
1161
  __name(inAppWorkerProviders, "inAppWorkerProviders");
1160
1162
 
1161
1163
  // src/proxy-run-gateway.ts
1162
- import { SUMMARY_CHILD_LIMIT, childIdsOf, pageFan, summarizeTimeline } from "@dudousxd/nestjs-durable-core";
1164
+ import { SUMMARY_CHILD_LIMIT, childIdsOf, pageFan, resolveTenantToken as resolveTenantToken2, summarizeTimeline } from "@dudousxd/nestjs-durable-core";
1163
1165
  import { Injectable as Injectable6 } from "@nestjs/common";
1164
1166
 
1165
1167
  // src/run-request-responder.ts
1168
+ import { resolveWireTenant } from "@dudousxd/nestjs-durable-core";
1166
1169
  var UNSUPPORTED_RUN_REQUEST = "unsupported-request";
1167
1170
  var KNOWN_KINDS = /* @__PURE__ */ new Set([
1168
1171
  "getRunDetail",
@@ -1184,9 +1187,14 @@ var RunRequestResponder = class {
1184
1187
  }
1185
1188
  transport;
1186
1189
  gateway;
1187
- constructor(transport, gateway) {
1190
+ verifyTenant;
1191
+ constructor(transport, gateway, options = {}) {
1188
1192
  this.transport = transport;
1189
1193
  this.gateway = gateway;
1194
+ this.verifyTenant = options.verifyTenant;
1195
+ if (!this.verifyTenant && options.warnWithoutVerifier !== false) {
1196
+ console.warn("[nestjs-durable] run-request responder started WITHOUT a tenant verifier: the wire `tenant` claim is trusted verbatim (prefix/network isolation only). Any broker client can act as any tenant. Configure `verifyTenant` (e.g. hmacTenantVerifier(secret)) and signed tenant tokens unless your broker is fully isolated per tenant.");
1197
+ }
1190
1198
  }
1191
1199
  /** Register the consumer on the transport. Each request is answered independently; a handler
1192
1200
  * failure never throws back into the transport (errors are captured into an error reply). */
@@ -1209,7 +1217,24 @@ var RunRequestResponder = class {
1209
1217
  await this.transport.publishRunReply(reply);
1210
1218
  });
1211
1219
  }
1212
- async handle(msg) {
1220
+ async handle(wire) {
1221
+ const tenant = await resolveWireTenant(wire.tenant, this.verifyTenant);
1222
+ if (tenant === null) {
1223
+ return {
1224
+ requestId: wire.requestId,
1225
+ result: {
1226
+ ok: false,
1227
+ error: {
1228
+ message: "invalid or unauthorized tenant token",
1229
+ code: "unauthorized"
1230
+ }
1231
+ }
1232
+ };
1233
+ }
1234
+ const msg = {
1235
+ ...wire,
1236
+ tenant
1237
+ };
1213
1238
  const { body } = msg;
1214
1239
  if (!KNOWN_KINDS.has(body.kind)) {
1215
1240
  return {
@@ -1402,17 +1427,19 @@ var ProxyRunGateway = class {
1402
1427
  tenant;
1403
1428
  timeoutMs;
1404
1429
  capabilityTtlMs;
1430
+ tenantToken;
1405
1431
  pending = /* @__PURE__ */ new Map();
1406
1432
  /** Verb → when to ask the control plane for it again, after it answered that it does not know it.
1407
1433
  * During a rolling upgrade the control plane is several pods of mixed versions behind one queue,
1408
1434
  * and a reply does not say which pod sent it, so the whole control plane is assumed old until the
1409
1435
  * entry expires. */
1410
1436
  unsupportedUntil = /* @__PURE__ */ new Map();
1411
- constructor(transport, tenant, timeoutMs = 1e4, capabilityTtlMs = 6e4) {
1437
+ constructor(transport, tenant, timeoutMs = 1e4, capabilityTtlMs = 6e4, tenantToken) {
1412
1438
  this.transport = transport;
1413
1439
  this.tenant = tenant;
1414
1440
  this.timeoutMs = timeoutMs;
1415
1441
  this.capabilityTtlMs = capabilityTtlMs;
1442
+ this.tenantToken = tenantToken;
1416
1443
  this.transport.onRunReply?.((reply) => this.handleReply(reply));
1417
1444
  }
1418
1445
  handleReply(reply) {
@@ -1425,7 +1452,10 @@ var ProxyRunGateway = class {
1425
1452
  } else if (reply.result.error.code === UNSUPPORTED_RUN_REQUEST) {
1426
1453
  pending.reject(new UnsupportedRunRequestError(reply.result.error.message));
1427
1454
  } else {
1428
- pending.reject(new Error(reply.result.error.message));
1455
+ const { message, code } = reply.result.error;
1456
+ pending.reject(code !== void 0 ? Object.assign(new Error(message), {
1457
+ code
1458
+ }) : new Error(message));
1429
1459
  }
1430
1460
  }
1431
1461
  request(body) {
@@ -1440,11 +1470,17 @@ var ProxyRunGateway = class {
1440
1470
  reject,
1441
1471
  timer
1442
1472
  });
1443
- this.transport.dispatchRunRequest?.({
1473
+ const source = this.tenantToken;
1474
+ const dispatched = typeof source === "function" ? resolveTenantToken2(source).then((tenant) => this.transport.dispatchRunRequest?.({
1444
1475
  requestId,
1445
- tenant: this.tenant,
1476
+ tenant,
1446
1477
  body
1447
- }).catch((error) => {
1478
+ })) : this.transport.dispatchRunRequest?.({
1479
+ requestId,
1480
+ tenant: source ?? this.tenant,
1481
+ body
1482
+ });
1483
+ dispatched?.catch((error) => {
1448
1484
  const stillPending = this.pending.get(requestId);
1449
1485
  if (!stillPending) return;
1450
1486
  clearTimeout(stillPending.timer);
@@ -1602,7 +1638,8 @@ ProxyRunGateway = _ts_decorate6([
1602
1638
  typeof Transport === "undefined" ? Object : Transport,
1603
1639
  String,
1604
1640
  void 0,
1605
- void 0
1641
+ void 0,
1642
+ typeof TenantTokenSource === "undefined" ? Object : TenantTokenSource
1606
1643
  ])
1607
1644
  ], ProxyRunGateway);
1608
1645
 
@@ -2727,7 +2764,15 @@ var RunGatewayBootstrap = class RunGatewayBootstrap2 {
2727
2764
  onRunRequest: onRunRequest.bind(this.transport),
2728
2765
  publishRunReply: publishRunReply.bind(this.transport)
2729
2766
  };
2730
- new RunRequestResponder(runRequestTransport, this.gateway).start();
2767
+ new RunRequestResponder(runRequestTransport, this.gateway, {
2768
+ verifyTenant: this.options.verifyTenant,
2769
+ // Mirrors Agora's rule (`warnWithoutVerifier: role === 'control-plane'`): only a deployment
2770
+ // that EXPLICITLY names itself the control plane is told it runs without tenant auth. A plain
2771
+ // operator (role inferred from `store`) is Agora's `standalone`: anyone with broker access
2772
+ // can already enqueue steps and results there, so the warning would be noise on every boot
2773
+ // of an app that never serves tenants.
2774
+ warnWithoutVerifier: this.options.topology?.role === "control-plane"
2775
+ }).start();
2731
2776
  }
2732
2777
  if (typeof publishTenantEvent === "function") {
2733
2778
  const republisher = new TenantEventRepublisher(store, publishTenantEvent.bind(this.transport));
@@ -2879,6 +2924,7 @@ var DurableModule = class _DurableModule {
2879
2924
  context,
2880
2925
  rehydrate: rehydrate || void 0,
2881
2926
  compensationRetries: options.compensationRetries,
2927
+ verifyTenant: options.verifyTenant,
2882
2928
  // A non-driving (dashboard/API) operator must not run workflows: enqueue-only, leaving
2883
2929
  // each `pending` run in the store for a DRIVING instance's poll. A driving operator gets
2884
2930
  // the engine's default in-process dispatcher: for a body registered inline, it runs
@@ -2919,7 +2965,7 @@ var DurableModule = class _DurableModule {
2919
2965
  }
2920
2966
  return new StoreRunGateway(store, engine);
2921
2967
  }
2922
- return options.transport ? new ProxyRunGateway(options.transport, options.partition ?? "default", options.runGatewayTimeoutMs) : unavailableRunGateway();
2968
+ return options.transport ? new ProxyRunGateway(options.transport, options.partition ?? "default", options.runGatewayTimeoutMs, void 0, options.tenantToken) : unavailableRunGateway();
2923
2969
  }, "useFactory"),
2924
2970
  inject: [
2925
2971
  DURABLE_OPTIONS_CANONICAL7,
@@ -2954,7 +3000,7 @@ DurableModule = _ts_decorate12([
2954
3000
  ], DurableModule);
2955
3001
 
2956
3002
  // src/index.ts
2957
- import { prevCronFireMs, readSearchAttributes as readSearchAttributes2, RunGateway as RunGateway3, runSchedules as runSchedules2, scheduledRunId, scheduleRunIdAt, WorkflowEngine as WorkflowEngine7 } from "@dudousxd/nestjs-durable-core";
3003
+ import { hmacTenantVerifier, prevCronFireMs, readSearchAttributes as readSearchAttributes2, RunGateway as RunGateway3, runSchedules as runSchedules2, scheduledRunId, scheduleRunIdAt, signTenantToken, WorkflowEngine as WorkflowEngine7 } from "@dudousxd/nestjs-durable-core";
2958
3004
  export {
2959
3005
  CONTEXT_ACCESSOR,
2960
3006
  DEAD_LETTER_METADATA,
@@ -3004,6 +3050,7 @@ export {
3004
3050
  getEntityMeta,
3005
3051
  getOnEvents,
3006
3052
  getWorkflowMeta,
3053
+ hmacTenantVerifier,
3007
3054
  inAppWorkerProviders,
3008
3055
  isDeadLetterHandler,
3009
3056
  isDrivingOperator,
@@ -3015,6 +3062,7 @@ export {
3015
3062
  runSchedules2 as runSchedules,
3016
3063
  scheduleRunIdAt,
3017
3064
  scheduledRunId,
3065
+ signTenantToken,
3018
3066
  thinWorkerProviders,
3019
3067
  unavailableRunGateway
3020
3068
  };