@horizon-republic/nestjs-jetstream 2.9.1 → 2.10.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,7 +1,8 @@
1
- import { ModuleMetadata, FactoryProvider, Type, Logger, OnApplicationShutdown, DynamicModule } from '@nestjs/common';
2
- import { MsgHdrs, ConnectionOptions, NatsConnection, Status, Msg } from '@nats-io/transport-node';
3
- import { StreamConfig, ConsumerConfig, ConsumeOptions, DeliverPolicy, ReplayPolicy, JetStreamManager, JetStreamClient, ConsumerInfo, JsMsg } from '@nats-io/jetstream';
4
- import { MessageHandler, Server, CustomTransportStrategy, ClientProxy, ReadPacket, WritePacket, BaseRpcContext } from '@nestjs/microservices';
1
+ import { Logger, ModuleMetadata, FactoryProvider, Type, OnApplicationShutdown, DynamicModule } from '@nestjs/common';
2
+ import { MsgHdrs, NatsConnection, Status, ConnectionOptions, Msg } from '@nats-io/transport-node';
3
+ import { JetStreamManager, JetStreamClient, StreamConfig, ConsumerConfig, ConsumeOptions, DeliverPolicy, ReplayPolicy, ConsumerInfo, JsMsg } from '@nats-io/jetstream';
4
+ import { Span } from '@opentelemetry/api';
5
+ import { ClientProxy, ReadPacket, WritePacket, MessageHandler, Server, CustomTransportStrategy, BaseRpcContext } from '@nestjs/microservices';
5
6
  import { Observable } from 'rxjs';
6
7
 
7
8
  /**
@@ -118,176 +119,859 @@ interface JetstreamHealthStatus {
118
119
  }
119
120
 
120
121
  /**
121
- * Stream config overrides exposed to users.
122
- *
123
- * `retention` is excluded because it is controlled by the transport layer
124
- * (Workqueue for events/commands, Limits for broadcast/ordered).
125
- * Any `retention` value provided at runtime is silently stripped.
122
+ * Instrumentation scope name reported on every span the library emits.
123
+ * Matches the npm package name — the convention used by
124
+ * `@opentelemetry/instrumentation-*` and most third-party instrumentations.
126
125
  */
127
- type StreamConfigOverrides = Partial<Omit<StreamConfig, 'retention'>>;
126
+ declare const TRACER_NAME = "@horizon-republic/nestjs-jetstream";
127
+
128
128
  /**
129
- * RPC transport configuration.
129
+ * Enumeration of OpenTelemetry trace kinds this library can emit.
130
130
  *
131
- * Discriminated union on `mode`:
132
- * - `'core'` — NATS native request/reply. Lowest latency.
133
- * - `'jetstream'` — Commands persisted in JetStream. Responses via Core NATS inbox.
131
+ * Each identifier is toggleable via `otel.traces`. Functional kinds (publish,
132
+ * consume, RPC client round-trip, dead letter) are enabled by default;
133
+ * infrastructure kinds (connection lifecycle, self-healing, provisioning,
134
+ * migration, shutdown) are opt-in.
134
135
  *
135
- * When `mode` is `'core'`, only `timeout` is available.
136
- * When `mode` is `'jetstream'`, additional stream/consumer overrides are exposed.
136
+ * @example
137
+ * JetstreamModule.forRoot({
138
+ * otel: {
139
+ * traces: [JetstreamTrace.Publish, JetstreamTrace.Consume, JetstreamTrace.ConnectionLifecycle],
140
+ * },
141
+ * })
137
142
  */
138
- type RpcConfig = {
139
- mode: 'core';
140
- /** Request timeout in ms. Default: 30_000. */
141
- timeout?: number;
142
- } | {
143
- mode: 'jetstream';
144
- /** Handler timeout in ms. Default: 180_000 (3 min). */
145
- timeout?: number;
146
- /** Raw NATS StreamConfig overrides for the command stream. */
147
- stream?: StreamConfigOverrides;
148
- /** Raw NATS ConsumerConfig overrides for the command consumer. */
149
- consumer?: Partial<ConsumerConfig>;
150
- /** Options passed to the nats.js `consumer.consume()` call for the command consumer. */
151
- consume?: Partial<ConsumeOptions>;
152
- /** Maximum number of concurrent RPC handler executions. */
153
- concurrency?: number;
143
+ declare enum JetstreamTrace {
144
+ /** `PRODUCER` span around each `emit()` / `send()` publish operation. Default: ON. */
145
+ Publish = "publish",
154
146
  /**
155
- * Auto-extend ack deadline via `msg.working()` during RPC handler execution.
156
- * The RPC handler timeout (`setTimeout` + `msg.term()`) still acts as the hard cap.
147
+ * `CONSUMER` span around each message delivery to a handler. Retries
148
+ * produce separate spans with `messaging.nats.message.delivery_count > 1`.
149
+ * Default: ON.
157
150
  */
158
- ackExtension?: boolean | number;
159
- };
160
- /** Overrides for JetStream stream and consumer configuration. */
161
- interface StreamConsumerOverrides {
162
- stream?: StreamConfigOverrides;
163
- consumer?: Partial<ConsumerConfig>;
151
+ Consume = "consume",
164
152
  /**
165
- * Options passed to the nats.js `consumer.consume()` call.
166
- * Controls prefetch buffer size, idle heartbeat interval, and auto-refill thresholds.
167
- *
168
- * nats.js supports two consumption modes (message-based and byte-based).
169
- * Do not mix `max_bytes`/`threshold_bytes` with `threshold_messages` —
170
- * use one mode or the other.
171
- *
172
- * @see https://github.com/nats-io/nats.js — ConsumeOptions
153
+ * `CLIENT` span covering a full RPC round-trip on the caller side
154
+ * (`client.send()` reply received). Wraps the inner publish.
155
+ * Default: ON.
173
156
  */
174
- consume?: Partial<ConsumeOptions>;
157
+ RpcClientSend = "rpc.client.send",
175
158
  /**
176
- * Maximum number of concurrent handler executions (RxJS `mergeMap` limit).
177
- *
178
- * Default: `undefined` (unlimited — naturally bounded by `max_ack_pending`).
179
- * Set this to protect downstream systems from overload.
180
- *
181
- * **Important:** if `concurrency < max_ack_pending`, messages buffer in RxJS
182
- * while their NATS ack timer ticks. Increase `ack_wait` proportionally to
183
- * prevent unnecessary redeliveries.
159
+ * `INTERNAL` span emitted when a message exhausts `maxDeliver` and is
160
+ * dead-lettered. Captures the duration of the `onDeadLetter` callback.
161
+ * Default: ON.
184
162
  */
185
- concurrency?: number;
163
+ DeadLetter = "dead_letter",
186
164
  /**
187
- * Auto-extend the NATS ack deadline via `msg.working()` during handler execution.
188
- *
189
- * - `false` (default): disabled — NATS redelivers after `ack_wait` if not acked.
190
- * - `true`: auto-extend at `ack_wait / 2` interval (calculated from consumer config).
191
- * - `number`: explicit extension interval in milliseconds.
165
+ * `INTERNAL` span spanning one NATS connection session. Emits events on
166
+ * connect, reconnect, disconnect. Default: OFF.
192
167
  */
193
- ackExtension?: boolean | number;
168
+ ConnectionLifecycle = "connection.lifecycle",
169
+ /**
170
+ * `INTERNAL` span per consumer recovery attempt following a transient
171
+ * failure (deleted consumer, missed heartbeat). Default: OFF.
172
+ */
173
+ SelfHealing = "self_healing",
174
+ /** `INTERNAL` span per stream or consumer provisioning / update at startup. Default: OFF. */
175
+ Provisioning = "provisioning",
176
+ /** `INTERNAL` span covering a destructive migration flow. Default: OFF. */
177
+ Migration = "migration",
178
+ /** `INTERNAL` span covering the graceful shutdown sequence. Default: OFF. */
179
+ Shutdown = "shutdown"
194
180
  }
195
181
  /**
196
- * Configuration for ordered event consumers.
182
+ * The set of trace kinds enabled when `otel.traces` is not configured
183
+ * (or set to `'default'`). Covers message-flow operations; infrastructure
184
+ * spans are opt-in.
185
+ */
186
+ declare const DEFAULT_TRACES: readonly JetstreamTrace[];
187
+
188
+ /**
189
+ * Central event bus for transport lifecycle notifications.
197
190
  *
198
- * Ordered consumers use Limits retention and deliver messages in strict
199
- * sequential order with at-most-once delivery. No ack/nak/DLQ.
191
+ * Dispatches events to user-provided hooks. Events without a
192
+ * registered hook are silently ignored no default logging.
200
193
  *
201
- * Only a subset of consumer options applies — ordered consumers are
202
- * ephemeral and auto-managed by nats.js.
194
+ * @example
195
+ * ```typescript
196
+ * const bus = new EventBus(logger, {
197
+ * [TransportEvent.Error]: (err) => sentry.captureException(err),
198
+ * });
199
+ *
200
+ * bus.emit(TransportEvent.Error, new Error('timeout'), 'rpc-router');
201
+ * // → calls sentry
202
+ *
203
+ * bus.emit(TransportEvent.Connect, 'nats://localhost:4222');
204
+ * // → no-op (no hook registered)
205
+ * ```
203
206
  */
204
- interface OrderedEventOverrides {
205
- /** Stream overrides (e.g. `max_age`, `max_bytes`). */
206
- stream?: StreamConfigOverrides;
207
- /**
208
- * Where to start reading when the consumer is (re)created.
209
- * @default DeliverPolicy.All
210
- */
211
- deliverPolicy?: DeliverPolicy;
207
+ declare class EventBus {
208
+ private readonly hooks;
209
+ private readonly logger;
210
+ constructor(logger: Logger, hooks?: Partial<TransportHooks>);
212
211
  /**
213
- * Start sequence number. Only used when `deliverPolicy` is `StartSequence`.
212
+ * Emit a lifecycle event. Dispatches to custom hook if registered, otherwise no-op.
213
+ *
214
+ * @param event - The {@link TransportEvent} to emit.
215
+ * @param args - Arguments matching the hook signature for this event.
214
216
  */
215
- optStartSeq?: number;
217
+ emit<K extends keyof TransportHooks>(event: K, ...args: Parameters<TransportHooks[K]>): void;
216
218
  /**
217
- * Start time (ISO string). Only used when `deliverPolicy` is `StartTime`.
219
+ * Hot-path optimized emit for MessageRouted events.
220
+ * Avoids rest/spread overhead of the generic `emit()`.
218
221
  */
219
- optStartTime?: string;
222
+ emitMessageRouted(subject: string, kind: MessageKind): void;
220
223
  /**
221
- * Replay policy for historical messages.
222
- * @default ReplayPolicy.Instant
224
+ * Check whether a hook is registered for the given event.
225
+ *
226
+ * Used by the routing hot path to elide the emit call entirely when the
227
+ * transport owner did not register a listener.
223
228
  */
224
- replayPolicy?: ReplayPolicy;
229
+ hasHook(event: keyof TransportHooks): boolean;
230
+ private callHook;
225
231
  }
232
+
226
233
  /**
227
- * Configuration for the handler metadata KV registry.
234
+ * Manages the lifecycle of a single NATS connection shared across the application.
228
235
  *
229
- * When any handler has `meta` in its extras, the transport writes metadata
230
- * entries to a NATS KV bucket at startup. External services (API gateways,
231
- * dashboards) can watch the bucket for service discovery.
236
+ * Provides both Promise-based and Observable-based access to the connection:
237
+ * - `connect()` / `getConnection()` async/await for one-time setup
238
+ * - `nc$` cached observable (shareReplay) for reactive consumers
239
+ * - `status$` — live connection status event stream
232
240
  *
233
- * All fields are optional sensible defaults are applied.
241
+ * One instance per application, created by `JetstreamModule.forRoot()`.
234
242
  */
235
- interface MetadataRegistryOptions {
243
+ declare class ConnectionProvider {
244
+ private readonly options;
245
+ private readonly eventBus;
246
+ /** Cached observable that replays the established connection to new subscribers. */
247
+ readonly nc$: Observable<NatsConnection>;
248
+ /** Live stream of connection status events (no replay). */
249
+ readonly status$: Observable<Status>;
250
+ private readonly logger;
251
+ private connection;
252
+ private connectionPromise;
253
+ private jsClient;
254
+ private jsmInstance;
255
+ private jsmPromise;
256
+ private readonly otel;
257
+ private readonly otelServiceName;
258
+ private readonly otelEndpoint;
259
+ private lifecycleSpan;
260
+ constructor(options: JetstreamModuleOptions, eventBus: EventBus);
236
261
  /**
237
- * KV bucket name.
238
- * @default 'handler_registry'
262
+ * Establish NATS connection. Idempotent — returns cached connection on subsequent calls.
263
+ *
264
+ * @throws Error if connection is refused (fail fast).
239
265
  */
240
- bucket?: string;
266
+ getConnection(): Promise<NatsConnection>;
241
267
  /**
242
- * Number of KV bucket replicas. Must be an odd number (1, 3, 5, 7, ...).
243
- * Requires a NATS cluster with at least this many nodes.
244
- * @default 1
268
+ * Get the JetStream manager. Cached after first call.
269
+ *
270
+ * @returns The JetStreamManager for stream/consumer administration.
245
271
  */
246
- replicas?: number;
272
+ getJetStreamManager(): Promise<JetStreamManager>;
247
273
  /**
248
- * KV bucket TTL in milliseconds.
274
+ * Get a cached JetStream client.
249
275
  *
250
- * Entries expire automatically unless refreshed by a heartbeat.
251
- * The transport refreshes entries every `ttl / 2` while the pod is alive.
252
- * When the pod stops (graceful or crash), entries expire after this duration.
276
+ * Invalidated automatically on reconnect and shutdown so consumers always
277
+ * operate against the live connection.
253
278
  *
254
- * @default 30_000 (30 seconds)
279
+ * @returns The cached JetStreamClient.
280
+ * @throws Error if the connection has not been established yet.
255
281
  */
256
- ttl?: number;
282
+ getJetStreamClient(): JetStreamClient;
283
+ /** Direct access to the raw NATS connection, or `null` if not yet connected. */
284
+ get unwrap(): NatsConnection | null;
285
+ /**
286
+ * Gracefully shut down the connection.
287
+ *
288
+ * Sequence: drain → wait for close. Falls back to force-close on error.
289
+ */
290
+ shutdown(): Promise<void>;
291
+ private initJetStreamManager;
292
+ /** Internal: establish the physical connection with reconnect monitoring. */
293
+ private establish;
294
+ /** Handle a single `nc.status()` event, emitting hooks and span events. */
295
+ private handleStatusEvent;
296
+ /** Subscribe to connection status events and emit hooks. */
297
+ private monitorStatus;
257
298
  }
299
+
258
300
  /**
259
- * Root module configuration for `JetstreamModule.forRoot()`.
301
+ * NestJS ClientProxy implementation for the JetStream transport.
260
302
  *
261
- * Minimal usage requires only `name` and `servers`.
262
- * All other fields have production-ready defaults.
303
+ * Supports two operational modes:
304
+ * - **Core mode** (default): Uses `nc.request()` for RPC, `nc.publish()` for events.
305
+ * - **JetStream mode**: Uses `js.publish()` for RPC commands + inbox for responses.
306
+ *
307
+ * Events always go through JetStream publish for guaranteed delivery.
308
+ * The mode only affects RPC (request/reply) behavior.
309
+ *
310
+ * Clients are lightweight — they share the NATS connection from `forRoot()`.
263
311
  */
264
- interface JetstreamModuleOptions {
265
- /** Service name. Used for stream/consumer/subject naming. */
266
- name: string;
267
- /** NATS server URLs. */
268
- servers: string[];
312
+ declare class JetstreamClient extends ClientProxy {
313
+ private readonly rootOptions;
314
+ private readonly connection;
315
+ private readonly codec;
316
+ private readonly eventBus;
317
+ private readonly logger;
318
+ /** Target service name this client sends messages to. */
319
+ private readonly targetName;
320
+ /** Pre-cached caller name derived from rootOptions.name, computed once in constructor. */
321
+ private readonly callerName;
269
322
  /**
270
- * Global message codec.
271
- * @default JsonCodec
323
+ * Subject prefixes of the form `{serviceName}__microservice.{kind}.` — one
324
+ * per stream kind this client may publish to. Built once in the constructor
325
+ * so producing a full subject is a single string concat with the user pattern.
272
326
  */
273
- codec?: Codec;
327
+ private readonly eventSubjectPrefix;
328
+ private readonly commandSubjectPrefix;
329
+ private readonly orderedSubjectPrefix;
274
330
  /**
275
- * RPC transport mode and configuration.
276
- * @default { mode: 'core' }
331
+ * RPC configuration snapshots. The values are derived from rootOptions at
332
+ * construction time so the publish hot path never has to re-run
333
+ * isCoreRpcMode / getRpcTimeout on every call.
277
334
  */
278
- rpc?: RpcConfig;
335
+ private readonly isCoreMode;
336
+ private readonly defaultRpcTimeout;
337
+ /** Resolved OpenTelemetry configuration, computed once in the constructor. */
338
+ private readonly otel;
339
+ /** Server endpoint parts used for `server.address` / `server.port` span attributes. */
340
+ private readonly serverEndpoint;
341
+ /** Shared inbox for JetStream-mode RPC responses. */
342
+ private inbox;
343
+ private inboxSubscription;
344
+ /** Pending JetStream-mode RPC callbacks, keyed by correlation ID. */
345
+ private readonly pendingMessages;
346
+ /** Pending JetStream-mode RPC timeouts, keyed by correlation ID. */
347
+ private readonly pendingTimeouts;
348
+ /** Subscription to connection status events for disconnect handling. */
349
+ private statusSubscription;
279
350
  /**
280
- * Enable consumer infrastructure (streams, consumers, message routing).
281
- * Set to `false` for publisher-only services (e.g., API gateways).
282
- * @default true
351
+ * Cached readiness flag. Once `connect()` has wired the inbox and status
352
+ * subscription, subsequent publishes skip the `await connect()` microtask
353
+ * and reach for the underlying connection synchronously instead.
283
354
  */
284
- consumer?: boolean;
285
- /** Workqueue event stream/consumer overrides. */
286
- events?: StreamConsumerOverrides;
287
- /** Broadcast event stream/consumer overrides. */
288
- broadcast?: StreamConsumerOverrides;
355
+ private readyForPublish;
356
+ constructor(rootOptions: JetstreamModuleOptions, targetServiceName: string, connection: ConnectionProvider, codec: Codec, eventBus: EventBus);
289
357
  /**
290
- * Ordered event consumer configuration.
358
+ * Establish connection. Called automatically by NestJS on first use.
359
+ *
360
+ * Sets up the JetStream RPC inbox (if in JetStream mode) and subscribes
361
+ * to connection status events for fail-fast disconnect handling.
362
+ *
363
+ * @returns The underlying NATS connection.
364
+ */
365
+ connect(): Promise<NatsConnection>;
366
+ /** Clean up resources: reject pending RPCs, unsubscribe from status events. */
367
+ close(): Promise<void>;
368
+ /**
369
+ * Direct access to the raw NATS connection.
370
+ *
371
+ * @throws Error if not connected.
372
+ */
373
+ unwrap<T = NatsConnection>(): T;
374
+ /**
375
+ * Publish a fire-and-forget event to JetStream.
376
+ *
377
+ * Events are published to either the workqueue stream or broadcast stream
378
+ * depending on the subject prefix. When a schedule is present the message
379
+ * is published to a `_sch` subject within the same stream, with the target
380
+ * set to the original event subject.
381
+ */
382
+ protected dispatchEvent<T = unknown>(packet: ReadPacket): Promise<T>;
383
+ /**
384
+ * Publish an RPC command and register callback for response.
385
+ *
386
+ * Core mode: uses nc.request() with timeout.
387
+ * JetStream mode: publishes to stream + waits for inbox response.
388
+ */
389
+ protected publish(packet: ReadPacket, callback: (p: WritePacket) => void): () => void;
390
+ /** Core mode: nc.request() with timeout. */
391
+ private publishCoreRpc;
392
+ /** JetStream mode: publish to stream + wait for inbox response. */
393
+ private publishJetStreamRpc;
394
+ /** Fail-fast all pending JetStream RPC callbacks on connection loss. */
395
+ private handleDisconnect;
396
+ /** Reject all pending RPC callbacks, clear timeouts, and tear down inbox. */
397
+ private rejectPendingRpcs;
398
+ /** Setup shared inbox subscription for JetStream RPC responses. */
399
+ private setupInbox;
400
+ /** Route an inbox reply to the matching pending callback. */
401
+ private routeInboxReply;
402
+ /**
403
+ * Resolve a user pattern to a fully-qualified NATS subject, dispatching
404
+ * between the event, broadcast, and ordered prefixes.
405
+ *
406
+ * The leading-char check short-circuits the `startsWith` comparisons for
407
+ * patterns that cannot possibly carry a broadcast/ordered marker, which is
408
+ * the overwhelmingly common case.
409
+ */
410
+ private buildEventSubject;
411
+ /** Build NATS headers merging custom headers with transport headers. */
412
+ private buildHeaders;
413
+ /** Extract data, headers, timeout, and schedule from raw packet data or JetstreamRecord. */
414
+ private extractRecordData;
415
+ /**
416
+ * Build a schedule-holder subject for NATS message scheduling.
417
+ *
418
+ * The schedule-holder subject resides in the same stream as the target but
419
+ * uses a separate `_sch` namespace that is NOT matched by any consumer filter.
420
+ * NATS holds the message and publishes it to the target subject after the delay.
421
+ *
422
+ * Examples:
423
+ * - `{svc}__microservice.ev.order.reminder` → `{svc}__microservice._sch.order.reminder`
424
+ * - `broadcast.config.updated` → `broadcast._sch.config.updated`
425
+ */
426
+ private buildScheduleSubject;
427
+ }
428
+
429
+ /**
430
+ * Immutable message record for JetStream transport.
431
+ *
432
+ * Compatible with NestJS record builder pattern (like RmqRecord, NatsRecord).
433
+ * Pass as the second argument to `client.send()` or `client.emit()`.
434
+ *
435
+ * @example
436
+ * ```typescript
437
+ * const record = new JetstreamRecordBuilder({ id: 1 })
438
+ * .setHeader('x-tenant', 'acme')
439
+ * .setTimeout(5000)
440
+ * .build();
441
+ *
442
+ * client.send('get.user', record);
443
+ * ```
444
+ */
445
+ declare class JetstreamRecord<TData = unknown> {
446
+ /** Message payload. */
447
+ readonly data: TData;
448
+ /** Custom headers set via {@link JetstreamRecordBuilder.setHeader}. */
449
+ readonly headers: ReadonlyMap<string, string>;
450
+ /** Per-request RPC timeout override in ms. */
451
+ readonly timeout?: number | undefined;
452
+ /** Custom message ID for JetStream deduplication. */
453
+ readonly messageId?: string | undefined;
454
+ /** Schedule options for delayed delivery. */
455
+ readonly schedule?: ScheduleRecordOptions | undefined;
456
+ /** Per-message TTL as Go duration string (e.g. "30s", "5m"). */
457
+ readonly ttl?: string | undefined;
458
+ constructor(
459
+ /** Message payload. */
460
+ data: TData,
461
+ /** Custom headers set via {@link JetstreamRecordBuilder.setHeader}. */
462
+ headers: ReadonlyMap<string, string>,
463
+ /** Per-request RPC timeout override in ms. */
464
+ timeout?: number | undefined,
465
+ /** Custom message ID for JetStream deduplication. */
466
+ messageId?: string | undefined,
467
+ /** Schedule options for delayed delivery. */
468
+ schedule?: ScheduleRecordOptions | undefined,
469
+ /** Per-message TTL as Go duration string (e.g. "30s", "5m"). */
470
+ ttl?: string | undefined);
471
+ }
472
+ /**
473
+ * Fluent builder for constructing JetstreamRecord instances.
474
+ *
475
+ * Protected headers (`correlation-id`, `reply-to`, `error`) cannot be
476
+ * set by the user — attempting to do so throws an error at build time.
477
+ */
478
+ declare class JetstreamRecordBuilder<TData = unknown> {
479
+ private data;
480
+ private readonly headers;
481
+ private timeout;
482
+ private messageId;
483
+ private scheduleOptions;
484
+ private ttlDuration;
485
+ constructor(data?: TData);
486
+ /**
487
+ * Set the message payload.
488
+ *
489
+ * @param data - Payload to serialize via the configured {@link Codec}.
490
+ */
491
+ setData(data: TData): this;
492
+ /**
493
+ * Set a single custom header.
494
+ *
495
+ * @param key - Header name (e.g. `'x-tenant'`).
496
+ * @param value - Header value.
497
+ * @throws Error if the header name is reserved by the transport.
498
+ */
499
+ setHeader(key: string, value: string): this;
500
+ /**
501
+ * Set multiple custom headers at once.
502
+ *
503
+ * @param headers - Key-value pairs to set as headers.
504
+ * @throws Error if any header name is reserved by the transport.
505
+ */
506
+ setHeaders(headers: Record<string, string>): this;
507
+ /**
508
+ * Set a custom message ID for JetStream deduplication.
509
+ *
510
+ * NATS JetStream uses this ID to detect duplicate publishes within the
511
+ * stream's `duplicate_window`. If two messages with the same ID arrive
512
+ * within the window, the second is silently dropped.
513
+ *
514
+ * When not set, a random UUID is generated automatically.
515
+ *
516
+ * @param id - Unique message identifier (e.g. order ID, idempotency key).
517
+ *
518
+ * @example
519
+ * ```typescript
520
+ * new JetstreamRecordBuilder(data)
521
+ * .setMessageId(`order-${order.id}`)
522
+ * .build();
523
+ * ```
524
+ */
525
+ setMessageId(id: string): this;
526
+ /**
527
+ * Set per-request RPC timeout.
528
+ *
529
+ * @param ms - Timeout in milliseconds. Overrides the global RPC timeout for this request only.
530
+ */
531
+ setTimeout(ms: number): this;
532
+ /**
533
+ * Schedule one-shot delayed delivery.
534
+ *
535
+ * The message is held by NATS and delivered to the event consumer
536
+ * at the specified time. Requires NATS >= 2.12 and `allow_msg_schedules: true`
537
+ * on the event stream (via `events: { stream: { allow_msg_schedules: true } }`).
538
+ *
539
+ * Only meaningful for events (`client.emit()`). If used with RPC
540
+ * (`client.send()`), a warning is logged and the schedule is ignored.
541
+ *
542
+ * @param date - Delivery time. Must be in the future.
543
+ * @throws Error if the date is not in the future.
544
+ */
545
+ scheduleAt(date: Date): this;
546
+ /**
547
+ * Set per-message TTL (time-to-live).
548
+ *
549
+ * The message expires individually after the specified duration,
550
+ * independent of the stream's `max_age`. Requires NATS >= 2.11 and
551
+ * `allow_msg_ttl: true` on the stream.
552
+ *
553
+ * Only meaningful for events (`client.emit()`). If used with RPC
554
+ * (`client.send()`), a warning is logged and the TTL is ignored.
555
+ *
556
+ * @param nanos - TTL in nanoseconds. Use {@link toNanos} for human-readable values.
557
+ *
558
+ * @example
559
+ * ```typescript
560
+ * import { toNanos } from '@horizon-republic/nestjs-jetstream';
561
+ *
562
+ * new JetstreamRecordBuilder(payload).ttl(toNanos(30, 'minutes')).build();
563
+ * new JetstreamRecordBuilder(payload).ttl(toNanos(24, 'hours')).build();
564
+ * ```
565
+ */
566
+ ttl(nanos: number): this;
567
+ /**
568
+ * Build the immutable {@link JetstreamRecord}.
569
+ *
570
+ * @returns A frozen record ready to pass to `client.send()` or `client.emit()`.
571
+ */
572
+ build(): JetstreamRecord<TData>;
573
+ /**
574
+ * Validate that a header key is not reserved. NATS treats header names
575
+ * case-insensitively, so the check is against the lowercase form to keep
576
+ * `'X-Correlation-ID'`, `'x-correlation-id'`, and any other casing in
577
+ * lockstep. `RESERVED_HEADERS` is defined as an all-lowercase set.
578
+ */
579
+ private validateHeaderKey;
580
+ }
581
+
582
+ /** Tag attached to every outgoing publish span so consumers can distinguish event / RPC / broadcast / ordered flows. */
583
+ declare enum PublishKind {
584
+ Event = "event",
585
+ RpcRequest = "rpc.request",
586
+ Broadcast = "broadcast",
587
+ Ordered = "ordered"
588
+ }
589
+ /** Tag attached to every incoming consume span. `Rpc` covers both Core and JetStream RPC handlers. */
590
+ declare enum ConsumeKind {
591
+ Event = "event",
592
+ Rpc = "rpc",
593
+ Broadcast = "broadcast",
594
+ Ordered = "ordered"
595
+ }
596
+ /**
597
+ * Handler metadata for the dispatched message. `pattern` is always set;
598
+ * `className` / `methodName` come from NestJS reflection when available.
599
+ */
600
+ interface HandlerMetadata {
601
+ readonly pattern: string;
602
+ readonly className?: string;
603
+ readonly methodName?: string;
604
+ }
605
+ /**
606
+ * Host / port pair surfaced as `server.address` / `server.port` span
607
+ * attributes. `port` is optional — OTel semconv makes it conditional on
608
+ * being different from the protocol default, and we'd rather emit nothing
609
+ * than invent a number the user never configured.
610
+ */
611
+ interface ServerEndpoint {
612
+ readonly host: string;
613
+ readonly port?: number;
614
+ }
615
+ /**
616
+ * Minimum message shape the consume span helper requires. Both `JsMsg`
617
+ * (JetStream) and Core NATS `Msg` satisfy it structurally; JetStream-only
618
+ * delivery metadata (`info`) is read separately and omitted on the Core
619
+ * RPC path.
620
+ */
621
+ interface ConsumeSourceMsg {
622
+ readonly subject: string;
623
+ readonly data: Uint8Array;
624
+ readonly headers?: MsgHdrs;
625
+ }
626
+ /** Context passed to {@link OtelOptions.publishHook} for an outgoing publish. */
627
+ interface JetstreamPublishContext {
628
+ /** Fully-resolved subject the message is being published to. */
629
+ readonly subject: string;
630
+ /** The record being published (payload, headers, builder-supplied options). */
631
+ readonly record: JetstreamRecord;
632
+ /** Classification of the outgoing operation. */
633
+ readonly kind: PublishKind;
634
+ }
635
+ /** Context passed to {@link OtelOptions.consumeHook} for an incoming dispatch. */
636
+ interface JetstreamConsumeContext {
637
+ /** Fully-resolved subject of the incoming message. */
638
+ readonly subject: string;
639
+ /**
640
+ * The incoming NATS message. JetStream messages (events, broadcast,
641
+ * ordered, JetStream RPC) satisfy `JsMsg`; Core RPC messages satisfy
642
+ * the looser {@link ConsumeSourceMsg} shape only. Narrow on `kind` to
643
+ * decide which fields are safe to access.
644
+ */
645
+ readonly msg: ConsumeSourceMsg;
646
+ /** Handler metadata; see {@link HandlerMetadata}. */
647
+ readonly handlerMetadata: HandlerMetadata;
648
+ /** Classification of the incoming operation. */
649
+ readonly kind: ConsumeKind;
650
+ }
651
+ /** Context passed to {@link OtelOptions.responseHook} once an outcome is known, just before the span ends. */
652
+ interface JetstreamResponseContext {
653
+ /** Subject the operation targeted. */
654
+ readonly subject: string;
655
+ /** Wall-clock duration of the operation in milliseconds. */
656
+ readonly durationMs: number;
657
+ /** Decoded reply payload for RPC client spans when available. */
658
+ readonly reply?: unknown;
659
+ /** Error instance if the operation failed. */
660
+ readonly error?: Error;
661
+ }
662
+ /**
663
+ * Classification outcome for a thrown error. Affects span status and
664
+ * attributes only — reply envelopes and internal logging are unchanged.
665
+ */
666
+ type ErrorClassification = 'expected' | 'unexpected';
667
+ /** Object form of {@link OtelOptions.captureBody}. */
668
+ interface CaptureBodyOptions {
669
+ /**
670
+ * Maximum number of bytes to capture. Payloads longer than this are
671
+ * truncated; a `messaging.nats.message.body.truncated` attribute is
672
+ * added to indicate truncation occurred.
673
+ *
674
+ * @default 4096
675
+ */
676
+ readonly maxBytes?: number;
677
+ /**
678
+ * Only capture body for subjects matching these glob patterns. Each
679
+ * pattern supports `*` wildcards and an optional leading `!` for
680
+ * exclusion. When omitted, body capture applies to all subjects.
681
+ */
682
+ readonly subjectAllowlist?: readonly string[];
683
+ }
684
+ /**
685
+ * OpenTelemetry configuration for `JetstreamModule.forRoot({ otel: … })`.
686
+ * All fields are optional; when the host app has not registered an OTel
687
+ * SDK, every call made by the library is a no-op regardless of config.
688
+ */
689
+ interface OtelOptions {
690
+ /**
691
+ * Global kill switch. When `false`, no spans are emitted, no propagation
692
+ * is attempted, and no hooks fire.
693
+ *
694
+ * @default true
695
+ */
696
+ readonly enabled?: boolean;
697
+ /**
698
+ * Which trace kinds to emit.
699
+ *
700
+ * - `'default'` — publish, consume, RPC client round-trip, dead letter
701
+ * - `'all'` — every trace kind defined in {@link JetstreamTrace}
702
+ * - `'none'` — emit no spans at all (useful with `enabled: true` for
703
+ * pure trace-context propagation without the span overhead)
704
+ * - `JetstreamTrace[]` — explicit selection
705
+ *
706
+ * @default 'default'
707
+ */
708
+ readonly traces?: readonly JetstreamTrace[] | 'default' | 'all' | 'none';
709
+ /**
710
+ * Header names to capture as span attributes (`messaging.header.<name>`).
711
+ * Match is case-insensitive. Glob wildcards (`*`) and negation (`!prefix-*`)
712
+ * are supported in the per-pattern form.
713
+ *
714
+ * SECURITY / GDPR: headers may contain authentication tokens, session
715
+ * identifiers, or other sensitive data. Captured values are exported to
716
+ * the configured OTel backend. Use an explicit allowlist in production.
717
+ * Setting this to `true` captures every header and is intended for
718
+ * development environments only.
719
+ *
720
+ * Transport-internal headers (`x-correlation-id`, `x-reply-to`, `x-error`,
721
+ * `x-subject`, `x-caller-name`) and propagator-owned headers
722
+ * (`traceparent`, `tracestate`, `baggage`, `sentry-trace`, `b3`, …) are
723
+ * always suppressed regardless of the allowlist.
724
+ *
725
+ * @default ['x-request-id']
726
+ */
727
+ readonly captureHeaders?: readonly string[] | boolean;
728
+ /**
729
+ * Capture the message payload as the `messaging.nats.message.body` span
730
+ * attribute. Defaults to `false` for privacy and cost reasons.
731
+ *
732
+ * Passing `true` captures up to 4 KiB per message. Use the object form
733
+ * for finer control over size and subject scope.
734
+ *
735
+ * SECURITY / GDPR: payloads commonly contain PII, credentials, financial
736
+ * data, or content regulated by HIPAA / PCI-DSS. Enabling body capture
737
+ * in production is almost always a policy violation. Prefer enabling
738
+ * only in development, or pair with a custom `SpanProcessor` that
739
+ * scrubs or drops the attribute before export.
740
+ *
741
+ * @default false
742
+ */
743
+ readonly captureBody?: boolean | CaptureBodyOptions;
744
+ /**
745
+ * Invoked after a publish span has been started and before the actual
746
+ * publish call executes. Use to enrich the span with custom attributes.
747
+ * Must be synchronous — thrown errors are caught and logged at debug
748
+ * level without affecting the publish path.
749
+ */
750
+ publishHook?(span: Span, ctx: JetstreamPublishContext): void;
751
+ /**
752
+ * Invoked after a consume span has been started and before the handler
753
+ * is dispatched. Use to enrich the span with custom attributes derived
754
+ * from the incoming message or handler metadata.
755
+ */
756
+ consumeHook?(span: Span, ctx: JetstreamConsumeContext): void;
757
+ /**
758
+ * Invoked once an operation's outcome is known (success, error, or
759
+ * timeout) and the span status has been set, just before the span ends.
760
+ * Fires for publish, consume, and RPC client spans.
761
+ */
762
+ responseHook?(span: Span, ctx: JetstreamResponseContext): void;
763
+ /**
764
+ * Predicate evaluated at the top of every outgoing publish. Returning
765
+ * `false` skips span creation for that publish; propagation of trace
766
+ * context through headers still occurs, so downstream consumers are
767
+ * unaffected. Useful for suppressing noise from health-check or
768
+ * internal-only subjects.
769
+ */
770
+ shouldTracePublish?(subject: string, record: JetstreamRecord): boolean;
771
+ /**
772
+ * Predicate evaluated at the top of every incoming message delivery.
773
+ * Returning `false` skips span creation for that delivery; the handler
774
+ * still executes normally and trace context is still extracted from
775
+ * headers for downstream calls.
776
+ */
777
+ shouldTraceConsume?(subject: string, msg: ConsumeSourceMsg): boolean;
778
+ /**
779
+ * Classify a thrown error as `'expected'` (business error, part of the
780
+ * RPC contract) or `'unexpected'` (infrastructure failure or bug). Drives
781
+ * OpenTelemetry span status and attributes only.
782
+ *
783
+ * - `'expected'` → span status `OK` with `jetstream.rpc.reply.has_error`
784
+ * and `jetstream.rpc.reply.error.code` attributes
785
+ * - `'unexpected'` → span status `ERROR` with `span.recordException(err)`
786
+ *
787
+ * Reply envelopes delivered to RPC clients are identical in both cases.
788
+ * This classification affects only the observability artifact.
789
+ *
790
+ * The default recognizes NestJS-idiomatic primitives for business errors:
791
+ * `RpcException` and `HttpException`. Teams with custom error hierarchies
792
+ * override to recognize their own types.
793
+ *
794
+ * @example
795
+ * errorClassifier: (err) => {
796
+ * if (err instanceof MyDomainError) return 'expected';
797
+ * if (err?.code?.startsWith('BIZ_')) return 'expected';
798
+ * return 'unexpected';
799
+ * }
800
+ */
801
+ errorClassifier?(err: unknown): ErrorClassification;
802
+ }
803
+
804
+ /**
805
+ * Stream config overrides exposed to users.
806
+ *
807
+ * `retention` is excluded because it is controlled by the transport layer
808
+ * (Workqueue for events/commands, Limits for broadcast/ordered).
809
+ * Any `retention` value provided at runtime is silently stripped.
810
+ */
811
+ type StreamConfigOverrides = Partial<Omit<StreamConfig, 'retention'>>;
812
+ /**
813
+ * RPC transport configuration.
814
+ *
815
+ * Discriminated union on `mode`:
816
+ * - `'core'` — NATS native request/reply. Lowest latency.
817
+ * - `'jetstream'` — Commands persisted in JetStream. Responses via Core NATS inbox.
818
+ *
819
+ * When `mode` is `'core'`, only `timeout` is available.
820
+ * When `mode` is `'jetstream'`, additional stream/consumer overrides are exposed.
821
+ */
822
+ type RpcConfig = {
823
+ mode: 'core';
824
+ /** Request timeout in ms. Default: 30_000. */
825
+ timeout?: number;
826
+ } | {
827
+ mode: 'jetstream';
828
+ /** Handler timeout in ms. Default: 180_000 (3 min). */
829
+ timeout?: number;
830
+ /** Raw NATS StreamConfig overrides for the command stream. */
831
+ stream?: StreamConfigOverrides;
832
+ /** Raw NATS ConsumerConfig overrides for the command consumer. */
833
+ consumer?: Partial<ConsumerConfig>;
834
+ /** Options passed to the nats.js `consumer.consume()` call for the command consumer. */
835
+ consume?: Partial<ConsumeOptions>;
836
+ /** Maximum number of concurrent RPC handler executions. */
837
+ concurrency?: number;
838
+ /**
839
+ * Auto-extend ack deadline via `msg.working()` during RPC handler execution.
840
+ * The RPC handler timeout (`setTimeout` + `msg.term()`) still acts as the hard cap.
841
+ */
842
+ ackExtension?: boolean | number;
843
+ };
844
+ /** Overrides for JetStream stream and consumer configuration. */
845
+ interface StreamConsumerOverrides {
846
+ stream?: StreamConfigOverrides;
847
+ consumer?: Partial<ConsumerConfig>;
848
+ /**
849
+ * Options passed to the nats.js `consumer.consume()` call.
850
+ * Controls prefetch buffer size, idle heartbeat interval, and auto-refill thresholds.
851
+ *
852
+ * nats.js supports two consumption modes (message-based and byte-based).
853
+ * Do not mix `max_bytes`/`threshold_bytes` with `threshold_messages` —
854
+ * use one mode or the other.
855
+ *
856
+ * @see https://github.com/nats-io/nats.js — ConsumeOptions
857
+ */
858
+ consume?: Partial<ConsumeOptions>;
859
+ /**
860
+ * Maximum number of concurrent handler executions (RxJS `mergeMap` limit).
861
+ *
862
+ * Default: `undefined` (unlimited — naturally bounded by `max_ack_pending`).
863
+ * Set this to protect downstream systems from overload.
864
+ *
865
+ * **Important:** if `concurrency < max_ack_pending`, messages buffer in RxJS
866
+ * while their NATS ack timer ticks. Increase `ack_wait` proportionally to
867
+ * prevent unnecessary redeliveries.
868
+ */
869
+ concurrency?: number;
870
+ /**
871
+ * Auto-extend the NATS ack deadline via `msg.working()` during handler execution.
872
+ *
873
+ * - `false` (default): disabled — NATS redelivers after `ack_wait` if not acked.
874
+ * - `true`: auto-extend at `ack_wait / 2` interval (calculated from consumer config).
875
+ * - `number`: explicit extension interval in milliseconds.
876
+ */
877
+ ackExtension?: boolean | number;
878
+ }
879
+ /**
880
+ * Configuration for ordered event consumers.
881
+ *
882
+ * Ordered consumers use Limits retention and deliver messages in strict
883
+ * sequential order with at-most-once delivery. No ack/nak/DLQ.
884
+ *
885
+ * Only a subset of consumer options applies — ordered consumers are
886
+ * ephemeral and auto-managed by nats.js.
887
+ */
888
+ interface OrderedEventOverrides {
889
+ /** Stream overrides (e.g. `max_age`, `max_bytes`). */
890
+ stream?: StreamConfigOverrides;
891
+ /**
892
+ * Where to start reading when the consumer is (re)created.
893
+ * @default DeliverPolicy.All
894
+ */
895
+ deliverPolicy?: DeliverPolicy;
896
+ /**
897
+ * Start sequence number. Only used when `deliverPolicy` is `StartSequence`.
898
+ */
899
+ optStartSeq?: number;
900
+ /**
901
+ * Start time (ISO string). Only used when `deliverPolicy` is `StartTime`.
902
+ */
903
+ optStartTime?: string;
904
+ /**
905
+ * Replay policy for historical messages.
906
+ * @default ReplayPolicy.Instant
907
+ */
908
+ replayPolicy?: ReplayPolicy;
909
+ }
910
+ /**
911
+ * Configuration for the handler metadata KV registry.
912
+ *
913
+ * When any handler has `meta` in its extras, the transport writes metadata
914
+ * entries to a NATS KV bucket at startup. External services (API gateways,
915
+ * dashboards) can watch the bucket for service discovery.
916
+ *
917
+ * All fields are optional — sensible defaults are applied.
918
+ */
919
+ interface MetadataRegistryOptions {
920
+ /**
921
+ * KV bucket name.
922
+ * @default 'handler_registry'
923
+ */
924
+ bucket?: string;
925
+ /**
926
+ * Number of KV bucket replicas. Must be an odd number (1, 3, 5, 7, ...).
927
+ * Requires a NATS cluster with at least this many nodes.
928
+ * @default 1
929
+ */
930
+ replicas?: number;
931
+ /**
932
+ * KV bucket TTL in milliseconds.
933
+ *
934
+ * Entries expire automatically unless refreshed by a heartbeat.
935
+ * The transport refreshes entries every `ttl / 2` while the pod is alive.
936
+ * When the pod stops (graceful or crash), entries expire after this duration.
937
+ *
938
+ * @default 30_000 (30 seconds)
939
+ */
940
+ ttl?: number;
941
+ }
942
+ /**
943
+ * Root module configuration for `JetstreamModule.forRoot()`.
944
+ *
945
+ * Minimal usage requires only `name` and `servers`.
946
+ * All other fields have production-ready defaults.
947
+ */
948
+ interface JetstreamModuleOptions {
949
+ /** Service name. Used for stream/consumer/subject naming. */
950
+ name: string;
951
+ /** NATS server URLs. */
952
+ servers: string[];
953
+ /**
954
+ * Global message codec.
955
+ * @default JsonCodec
956
+ */
957
+ codec?: Codec;
958
+ /**
959
+ * RPC transport mode and configuration.
960
+ * @default { mode: 'core' }
961
+ */
962
+ rpc?: RpcConfig;
963
+ /**
964
+ * Enable consumer infrastructure (streams, consumers, message routing).
965
+ * Set to `false` for publisher-only services (e.g., API gateways).
966
+ * @default true
967
+ */
968
+ consumer?: boolean;
969
+ /** Workqueue event stream/consumer overrides. */
970
+ events?: StreamConsumerOverrides;
971
+ /** Broadcast event stream/consumer overrides. */
972
+ broadcast?: StreamConsumerOverrides;
973
+ /**
974
+ * Ordered event consumer configuration.
291
975
  *
292
976
  * Ordered events use a separate stream with Limits retention and deliver
293
977
  * messages in strict sequential order. Use `ordered:` prefix when publishing.
@@ -385,6 +1069,16 @@ interface JetstreamModuleOptions {
385
1069
  * Merged with `name` and `servers` — those take precedence.
386
1070
  */
387
1071
  connectionOptions?: Partial<ConnectionOptions>;
1072
+ /**
1073
+ * OpenTelemetry integration. When omitted, sensible defaults are applied:
1074
+ * tracing is enabled, default trace kinds are emitted, only standard
1075
+ * correlation headers are captured. If no OTel SDK is registered in the
1076
+ * consuming application, all tracer calls are no-ops — there is no
1077
+ * runtime cost.
1078
+ *
1079
+ * @see OtelOptions
1080
+ */
1081
+ otel?: OtelOptions;
388
1082
  }
389
1083
  /** Options for `JetstreamModule.forFeature()`. */
390
1084
  interface JetstreamFeatureOptions {
@@ -462,138 +1156,32 @@ interface PatternsByKind {
462
1156
  /** Ordered event patterns (strict sequential delivery). */
463
1157
  ordered: string[];
464
1158
  }
465
- /** Options for configuring event/broadcast processing behavior. */
466
- interface EventProcessingConfig {
467
- events?: {
468
- concurrency?: number;
469
- ackExtension?: boolean | number;
470
- };
471
- broadcast?: {
472
- concurrency?: number;
473
- ackExtension?: boolean | number;
474
- };
475
- }
476
- /** Options for dead letter queue handling. */
477
- interface DeadLetterConfig {
478
- /**
479
- * Map of stream name -> max_deliver value.
480
- * Used to detect when a message from a given stream has exhausted all delivery attempts.
481
- */
482
- maxDeliverByStream: Map<string, number>;
483
- /** Async callback invoked when a message exhausts all deliveries. */
484
- onDeadLetter(info: DeadLetterInfo): Promise<void>;
485
- }
486
- /** Options for configuring RPC processing behavior. */
487
- interface RpcRouterOptions {
488
- timeout?: number;
489
- concurrency?: number;
490
- ackExtension?: boolean | number;
491
- }
492
-
493
- /**
494
- * Central event bus for transport lifecycle notifications.
495
- *
496
- * Dispatches events to user-provided hooks. Events without a
497
- * registered hook are silently ignored — no default logging.
498
- *
499
- * @example
500
- * ```typescript
501
- * const bus = new EventBus(logger, {
502
- * [TransportEvent.Error]: (err) => sentry.captureException(err),
503
- * });
504
- *
505
- * bus.emit(TransportEvent.Error, new Error('timeout'), 'rpc-router');
506
- * // → calls sentry
507
- *
508
- * bus.emit(TransportEvent.Connect, 'nats://localhost:4222');
509
- * // → no-op (no hook registered)
510
- * ```
511
- */
512
- declare class EventBus {
513
- private readonly hooks;
514
- private readonly logger;
515
- constructor(logger: Logger, hooks?: Partial<TransportHooks>);
516
- /**
517
- * Emit a lifecycle event. Dispatches to custom hook if registered, otherwise no-op.
518
- *
519
- * @param event - The {@link TransportEvent} to emit.
520
- * @param args - Arguments matching the hook signature for this event.
521
- */
522
- emit<K extends keyof TransportHooks>(event: K, ...args: Parameters<TransportHooks[K]>): void;
523
- /**
524
- * Hot-path optimized emit for MessageRouted events.
525
- * Avoids rest/spread overhead of the generic `emit()`.
526
- */
527
- emitMessageRouted(subject: string, kind: MessageKind): void;
528
- /**
529
- * Check whether a hook is registered for the given event.
530
- *
531
- * Used by the routing hot path to elide the emit call entirely when the
532
- * transport owner did not register a listener.
533
- */
534
- hasHook(event: keyof TransportHooks): boolean;
535
- private callHook;
536
- }
537
-
538
- /**
539
- * Manages the lifecycle of a single NATS connection shared across the application.
540
- *
541
- * Provides both Promise-based and Observable-based access to the connection:
542
- * - `connect()` / `getConnection()` — async/await for one-time setup
543
- * - `nc$` — cached observable (shareReplay) for reactive consumers
544
- * - `status$` — live connection status event stream
545
- *
546
- * One instance per application, created by `JetstreamModule.forRoot()`.
547
- */
548
- declare class ConnectionProvider {
549
- private readonly options;
550
- private readonly eventBus;
551
- /** Cached observable that replays the established connection to new subscribers. */
552
- readonly nc$: Observable<NatsConnection>;
553
- /** Live stream of connection status events (no replay). */
554
- readonly status$: Observable<Status>;
555
- private readonly logger;
556
- private connection;
557
- private connectionPromise;
558
- private jsClient;
559
- private jsmInstance;
560
- private jsmPromise;
561
- constructor(options: JetstreamModuleOptions, eventBus: EventBus);
562
- /**
563
- * Establish NATS connection. Idempotent — returns cached connection on subsequent calls.
564
- *
565
- * @throws Error if connection is refused (fail fast).
566
- */
567
- getConnection(): Promise<NatsConnection>;
568
- /**
569
- * Get the JetStream manager. Cached after first call.
570
- *
571
- * @returns The JetStreamManager for stream/consumer administration.
572
- */
573
- getJetStreamManager(): Promise<JetStreamManager>;
574
- /**
575
- * Get a cached JetStream client.
576
- *
577
- * Invalidated automatically on reconnect and shutdown so consumers always
578
- * operate against the live connection.
579
- *
580
- * @returns The cached JetStreamClient.
581
- * @throws Error if the connection has not been established yet.
582
- */
583
- getJetStreamClient(): JetStreamClient;
584
- /** Direct access to the raw NATS connection, or `null` if not yet connected. */
585
- get unwrap(): NatsConnection | null;
1159
+ /** Options for configuring event/broadcast processing behavior. */
1160
+ interface EventProcessingConfig {
1161
+ events?: {
1162
+ concurrency?: number;
1163
+ ackExtension?: boolean | number;
1164
+ };
1165
+ broadcast?: {
1166
+ concurrency?: number;
1167
+ ackExtension?: boolean | number;
1168
+ };
1169
+ }
1170
+ /** Options for dead letter queue handling. */
1171
+ interface DeadLetterConfig {
586
1172
  /**
587
- * Gracefully shut down the connection.
588
- *
589
- * Sequence: drain → wait for close. Falls back to force-close on error.
1173
+ * Map of stream name -> max_deliver value.
1174
+ * Used to detect when a message from a given stream has exhausted all delivery attempts.
590
1175
  */
591
- shutdown(): Promise<void>;
592
- private initJetStreamManager;
593
- /** Internal: establish the physical connection with reconnect monitoring. */
594
- private establish;
595
- /** Subscribe to connection status events and emit hooks. */
596
- private monitorStatus;
1176
+ maxDeliverByStream: Map<string, number>;
1177
+ /** Async callback invoked when a message exhausts all deliveries. */
1178
+ onDeadLetter(info: DeadLetterInfo): Promise<void>;
1179
+ }
1180
+ /** Options for configuring RPC processing behavior. */
1181
+ interface RpcRouterOptions {
1182
+ timeout?: number;
1183
+ concurrency?: number;
1184
+ ackExtension?: boolean | number;
597
1185
  }
598
1186
 
599
1187
  /**
@@ -658,13 +1246,15 @@ declare class PatternRegistry {
658
1246
  * This is the default RPC mode — lowest latency, no persistence overhead.
659
1247
  */
660
1248
  declare class CoreRpcServer {
661
- private readonly options;
662
1249
  private readonly connection;
663
1250
  private readonly patternRegistry;
664
1251
  private readonly codec;
665
1252
  private readonly eventBus;
666
1253
  private readonly logger;
667
1254
  private subscription;
1255
+ private readonly otel;
1256
+ private readonly serviceName;
1257
+ private readonly serverEndpoint;
668
1258
  constructor(options: JetstreamModuleOptions, connection: ConnectionProvider, patternRegistry: PatternRegistry, codec: Codec, eventBus: EventBus);
669
1259
  /** Start listening for RPC requests on the command subject. */
670
1260
  start(): Promise<void>;
@@ -691,6 +1281,9 @@ declare class StreamProvider {
691
1281
  private readonly connection;
692
1282
  private readonly logger;
693
1283
  private readonly migration;
1284
+ private readonly otel;
1285
+ private readonly otelServiceName;
1286
+ private readonly otelEndpoint;
694
1287
  constructor(options: JetstreamModuleOptions, connection: ConnectionProvider);
695
1288
  /**
696
1289
  * Ensure all required streams exist with correct configuration.
@@ -734,21 +1327,6 @@ declare class StreamProvider {
734
1327
  private stripTransportControlled;
735
1328
  }
736
1329
 
737
- /**
738
- * Routes incoming event messages (workqueue, broadcast, and ordered) to NestJS handlers.
739
- *
740
- * **Workqueue & Broadcast** — at-least-once delivery:
741
- * - Success -> ack | Error -> nak (retry) | Dead letter -> term
742
- *
743
- * **Ordered** — strict sequential delivery:
744
- * - No ack/nak/DLQ — nats.js auto-acknowledges ordered consumer messages.
745
- * - Handler errors are logged but do not affect delivery.
746
- *
747
- * **Dead-Letter Queue (DLQ) - for handling failed message deliveries**
748
- * - If `options.dlq` is configured, messages that exhaust their max delivery attempts are published to a DLQ stream.
749
- * - The DLQ stream name is derived from the service name (e.g., `orders__microservice_dlq-stream`).
750
- * - Original message data and metadata are preserved in the DLQ message, with additional headers indicating the reason for failure.
751
- */
752
1330
  declare class EventRouter {
753
1331
  private readonly messageProvider;
754
1332
  private readonly patternRegistry;
@@ -761,6 +1339,9 @@ declare class EventRouter {
761
1339
  private readonly options?;
762
1340
  private readonly logger;
763
1341
  private readonly subscriptions;
1342
+ private readonly otel;
1343
+ private readonly serviceName;
1344
+ private readonly serverEndpoint;
764
1345
  constructor(messageProvider: MessageProvider, patternRegistry: PatternRegistry, codec: Codec, eventBus: EventBus, deadLetterConfig?: DeadLetterConfig | undefined, processingConfig?: EventProcessingConfig | undefined, ackWaitMap?: Map<StreamKind, number> | undefined, connection?: ConnectionProvider | undefined, options?: JetstreamModuleOptions | undefined);
765
1346
  /**
766
1347
  * Update the max_deliver thresholds from actual NATS consumer configs.
@@ -775,14 +1356,11 @@ declare class EventRouter {
775
1356
  private subscribeToStream;
776
1357
  private getConcurrency;
777
1358
  private getAckExtensionConfig;
778
- /** Handle a dead letter: invoke callback, then term or nak based on result. */
779
1359
  /**
780
- * Fallback execution for a dead letter when DLQ is disabled, or when
781
- * publishing to the DLQ stream fails (due to network or NATS errors).
782
- *
783
- * Triggers the user-provided `onDeadLetter` hook for logging/alerting.
784
- * On success, terminates the message. On error, leaves it unacknowledged (nak)
785
- * so NATS can retry the delivery on the next cycle.
1360
+ * Last-resort path for a dead letter: invoke `onDeadLetter`, then `term` on
1361
+ * success or `nak` on hook failure so NATS retries on the next delivery
1362
+ * cycle. Used when DLQ stream isn't configured, or when publishing to it
1363
+ * failed and we still have to surface the message somewhere observable.
786
1364
  */
787
1365
  private fallbackToOnDeadLetterCallback;
788
1366
  /**
@@ -829,7 +1407,10 @@ declare class RpcRouter {
829
1407
  private resolvedAckExtensionInterval;
830
1408
  private subscription;
831
1409
  private cachedNc;
832
- constructor(messageProvider: MessageProvider, patternRegistry: PatternRegistry, connection: ConnectionProvider, codec: Codec, eventBus: EventBus, rpcOptions?: RpcRouterOptions | undefined, ackWaitMap?: Map<StreamKind, number> | undefined);
1410
+ private readonly otel;
1411
+ private readonly serviceName;
1412
+ private readonly serverEndpoint;
1413
+ constructor(messageProvider: MessageProvider, patternRegistry: PatternRegistry, connection: ConnectionProvider, codec: Codec, eventBus: EventBus, rpcOptions?: RpcRouterOptions | undefined, ackWaitMap?: Map<StreamKind, number> | undefined, options?: JetstreamModuleOptions);
833
1414
  /** Lazily resolve the ack extension interval (needs ackWaitMap populated at runtime). */
834
1415
  private get ackExtensionInterval();
835
1416
  /** Start routing command messages to handlers. */
@@ -850,6 +1431,9 @@ declare class ConsumerProvider {
850
1431
  private readonly streamProvider;
851
1432
  private readonly patternRegistry;
852
1433
  private readonly logger;
1434
+ private readonly otel;
1435
+ private readonly otelServiceName;
1436
+ private readonly otelEndpoint;
853
1437
  constructor(options: JetstreamModuleOptions, connection: ConnectionProvider, streamProvider: StreamProvider, patternRegistry: PatternRegistry);
854
1438
  /**
855
1439
  * Ensure consumers exist for the specified kinds.
@@ -1079,411 +1663,138 @@ declare class JetstreamStrategy extends Server implements CustomTransportStrateg
1079
1663
  /**
1080
1664
  * Register event listener (required by Server base class).
1081
1665
  *
1082
- * Stores callbacks for client use. Primary lifecycle events
1083
- * are routed through EventBus.
1084
- */
1085
- on(event: string, callback: Function): void;
1086
- /**
1087
- * Unwrap the underlying NATS connection.
1088
- *
1089
- * @throws Error if the transport has not started.
1090
- */
1091
- unwrap<T>(): T;
1092
- /** Access the pattern registry (for module-level introspection). */
1093
- getPatternRegistry(): PatternRegistry;
1094
- /** Determine which streams and durable consumers are needed. */
1095
- private resolveRequiredKinds;
1096
- /** Populate the shared ack_wait map from actual NATS consumer configs. */
1097
- private populateAckWaitMap;
1098
- /** Build max_deliver map from actual NATS consumer configs (not options). */
1099
- private buildMaxDeliverMap;
1100
- }
1101
-
1102
- /** Minimal interface for anything that can be stopped during shutdown. */
1103
- interface Stoppable {
1104
- close(): void;
1105
- }
1106
- /**
1107
- * Orchestrates graceful transport shutdown.
1108
- *
1109
- * Shutdown sequence:
1110
- * 1. Emit onShutdownStart hook
1111
- * 2. Stop accepting new messages (close subscriptions, stop consumers)
1112
- * 3. Drain and close NATS connection (with timeout safety net)
1113
- * 4. Emit onShutdownComplete hook
1114
- *
1115
- * Idempotent — concurrent or repeated calls return the same promise.
1116
- * This is critical because NestJS may call `onApplicationShutdown` on
1117
- * multiple module instances (forRoot + forFeature) that share this
1118
- * singleton, and the call order is not guaranteed.
1119
- */
1120
- declare class ShutdownManager {
1121
- private readonly connection;
1122
- private readonly eventBus;
1123
- private readonly timeout;
1124
- private readonly logger;
1125
- private shutdownPromise?;
1126
- constructor(connection: ConnectionProvider, eventBus: EventBus, timeout: number);
1127
- /**
1128
- * Execute the full shutdown sequence.
1129
- *
1130
- * Idempotent — concurrent or repeated calls return the same promise.
1131
- *
1132
- * @param strategy Optional stoppable to close (stops consumers and subscriptions).
1133
- */
1134
- shutdown(strategy?: Stoppable): Promise<void>;
1135
- private doShutdown;
1136
- }
1137
-
1138
- /**
1139
- * Root module for the NestJS JetStream transport.
1140
- *
1141
- * - `forRoot()` / `forRootAsync()` — registers once in AppModule.
1142
- * Creates shared NATS connection, codec, event bus, and optionally
1143
- * the consumer infrastructure.
1144
- *
1145
- * - `forFeature()` — registers in feature modules.
1146
- * Creates a lightweight client proxy targeting a specific service.
1147
- *
1148
- * @example
1149
- * ```typescript
1150
- * // AppModule — global setup
1151
- * @Module({
1152
- * imports: [
1153
- * JetstreamModule.forRoot({
1154
- * name: 'orders',
1155
- * servers: ['nats://localhost:4222'],
1156
- * }),
1157
- * ],
1158
- * })
1159
- * export class AppModule {}
1160
- *
1161
- * // Feature module — per-service clients
1162
- * @Module({
1163
- * imports: [
1164
- * JetstreamModule.forFeature({ name: 'users' }),
1165
- * JetstreamModule.forFeature({ name: 'payments' }),
1166
- * ],
1167
- * })
1168
- * export class OrdersModule {}
1169
- * ```
1170
- */
1171
- declare class JetstreamModule implements OnApplicationShutdown {
1172
- private readonly shutdownManager?;
1173
- private readonly strategy?;
1174
- constructor(shutdownManager?: ShutdownManager | undefined, strategy?: (JetstreamStrategy | null) | undefined);
1175
- /**
1176
- * Register the JetStream transport globally.
1177
- *
1178
- * Creates a shared NATS connection, codec, event bus, and optionally
1179
- * the full consumer infrastructure (streams, consumers, routers).
1180
- *
1181
- * @param options Module configuration.
1182
- * @returns Dynamic module ready to be imported.
1183
- */
1184
- static forRoot(options: JetstreamModuleOptions): DynamicModule;
1185
- /**
1186
- * Register the JetStream transport globally with async configuration.
1187
- *
1188
- * Supports `useFactory`, `useExisting`, and `useClass` patterns
1189
- * for loading configuration from ConfigService, environment, etc.
1190
- *
1191
- * @param asyncOptions Async configuration.
1192
- * @returns Dynamic module ready to be imported.
1193
- */
1194
- static forRootAsync(asyncOptions: JetstreamModuleAsyncOptions): DynamicModule;
1195
- /**
1196
- * Register a lightweight client proxy for a target service.
1197
- *
1198
- * Reuses the shared NATS connection from `forRoot()`.
1199
- * Import in each feature module that needs to communicate with a specific service.
1200
- *
1201
- * @param options Feature options with target service name.
1202
- * @returns Dynamic module with the client provider.
1203
- */
1204
- static forFeature(options: JetstreamFeatureOptions): DynamicModule;
1205
- private static createCoreProviders;
1206
- /** Create providers that depend on JETSTREAM_OPTIONS (shared by sync and async). */
1207
- private static createCoreDependentProviders;
1208
- /** Create async options provider from useFactory/useExisting/useClass. */
1209
- private static createAsyncOptionsProvider;
1210
- /**
1211
- * Gracefully shut down the transport on application termination.
1212
- */
1213
- onApplicationShutdown(): Promise<void>;
1214
- }
1215
-
1216
- /**
1217
- * NestJS ClientProxy implementation for the JetStream transport.
1218
- *
1219
- * Supports two operational modes:
1220
- * - **Core mode** (default): Uses `nc.request()` for RPC, `nc.publish()` for events.
1221
- * - **JetStream mode**: Uses `js.publish()` for RPC commands + inbox for responses.
1222
- *
1223
- * Events always go through JetStream publish for guaranteed delivery.
1224
- * The mode only affects RPC (request/reply) behavior.
1225
- *
1226
- * Clients are lightweight — they share the NATS connection from `forRoot()`.
1227
- */
1228
- declare class JetstreamClient extends ClientProxy {
1229
- private readonly rootOptions;
1230
- private readonly connection;
1231
- private readonly codec;
1232
- private readonly eventBus;
1233
- private readonly logger;
1234
- /** Target service name this client sends messages to. */
1235
- private readonly targetName;
1236
- /** Pre-cached caller name derived from rootOptions.name, computed once in constructor. */
1237
- private readonly callerName;
1238
- /**
1239
- * Subject prefixes of the form `{serviceName}__microservice.{kind}.` — one
1240
- * per stream kind this client may publish to. Built once in the constructor
1241
- * so producing a full subject is a single string concat with the user pattern.
1242
- */
1243
- private readonly eventSubjectPrefix;
1244
- private readonly commandSubjectPrefix;
1245
- private readonly orderedSubjectPrefix;
1246
- /**
1247
- * RPC configuration snapshots. The values are derived from rootOptions at
1248
- * construction time so the publish hot path never has to re-run
1249
- * isCoreRpcMode / getRpcTimeout on every call.
1250
- */
1251
- private readonly isCoreMode;
1252
- private readonly defaultRpcTimeout;
1253
- /** Shared inbox for JetStream-mode RPC responses. */
1254
- private inbox;
1255
- private inboxSubscription;
1256
- /** Pending JetStream-mode RPC callbacks, keyed by correlation ID. */
1257
- private readonly pendingMessages;
1258
- /** Pending JetStream-mode RPC timeouts, keyed by correlation ID. */
1259
- private readonly pendingTimeouts;
1260
- /** Subscription to connection status events for disconnect handling. */
1261
- private statusSubscription;
1262
- /**
1263
- * Cached readiness flag. Once `connect()` has wired the inbox and status
1264
- * subscription, subsequent publishes skip the `await connect()` microtask
1265
- * and reach for the underlying connection synchronously instead.
1266
- */
1267
- private readyForPublish;
1268
- constructor(rootOptions: JetstreamModuleOptions, targetServiceName: string, connection: ConnectionProvider, codec: Codec, eventBus: EventBus);
1269
- /**
1270
- * Establish connection. Called automatically by NestJS on first use.
1271
- *
1272
- * Sets up the JetStream RPC inbox (if in JetStream mode) and subscribes
1273
- * to connection status events for fail-fast disconnect handling.
1274
- *
1275
- * @returns The underlying NATS connection.
1276
- */
1277
- connect(): Promise<NatsConnection>;
1278
- /** Clean up resources: reject pending RPCs, unsubscribe from status events. */
1279
- close(): Promise<void>;
1280
- /**
1281
- * Direct access to the raw NATS connection.
1282
- *
1283
- * @throws Error if not connected.
1284
- */
1285
- unwrap<T = NatsConnection>(): T;
1286
- /**
1287
- * Publish a fire-and-forget event to JetStream.
1288
- *
1289
- * Events are published to either the workqueue stream or broadcast stream
1290
- * depending on the subject prefix. When a schedule is present the message
1291
- * is published to a `_sch` subject within the same stream, with the target
1292
- * set to the original event subject.
1293
- */
1294
- protected dispatchEvent<T = unknown>(packet: ReadPacket): Promise<T>;
1295
- /**
1296
- * Publish an RPC command and register callback for response.
1297
- *
1298
- * Core mode: uses nc.request() with timeout.
1299
- * JetStream mode: publishes to stream + waits for inbox response.
1666
+ * Stores callbacks for client use. Primary lifecycle events
1667
+ * are routed through EventBus.
1300
1668
  */
1301
- protected publish(packet: ReadPacket, callback: (p: WritePacket) => void): () => void;
1302
- /** Core mode: nc.request() with timeout. */
1303
- private publishCoreRpc;
1304
- /** JetStream mode: publish to stream + wait for inbox response. */
1305
- private publishJetStreamRpc;
1306
- /** Fail-fast all pending JetStream RPC callbacks on connection loss. */
1307
- private handleDisconnect;
1308
- /** Reject all pending RPC callbacks, clear timeouts, and tear down inbox. */
1309
- private rejectPendingRpcs;
1310
- /** Setup shared inbox subscription for JetStream RPC responses. */
1311
- private setupInbox;
1312
- /** Route an inbox reply to the matching pending callback. */
1313
- private routeInboxReply;
1669
+ on(event: string, callback: Function): void;
1314
1670
  /**
1315
- * Resolve a user pattern to a fully-qualified NATS subject, dispatching
1316
- * between the event, broadcast, and ordered prefixes.
1671
+ * Unwrap the underlying NATS connection.
1317
1672
  *
1318
- * The leading-char check short-circuits the `startsWith` comparisons for
1319
- * patterns that cannot possibly carry a broadcast/ordered marker, which is
1320
- * the overwhelmingly common case.
1673
+ * @throws Error if the transport has not started.
1321
1674
  */
1322
- private buildEventSubject;
1323
- /** Build NATS headers merging custom headers with transport headers. */
1324
- private buildHeaders;
1325
- /** Extract data, headers, timeout, and schedule from raw packet data or JetstreamRecord. */
1326
- private extractRecordData;
1675
+ unwrap<T>(): T;
1676
+ /** Access the pattern registry (for module-level introspection). */
1677
+ getPatternRegistry(): PatternRegistry;
1678
+ /** Determine which streams and durable consumers are needed. */
1679
+ private resolveRequiredKinds;
1680
+ /** Populate the shared ack_wait map from actual NATS consumer configs. */
1681
+ private populateAckWaitMap;
1682
+ /** Build max_deliver map from actual NATS consumer configs (not options). */
1683
+ private buildMaxDeliverMap;
1684
+ }
1685
+
1686
+ /** Minimal interface for anything that can be stopped during shutdown. */
1687
+ interface Stoppable {
1688
+ close(): void;
1689
+ }
1690
+ /**
1691
+ * Orchestrates graceful transport shutdown.
1692
+ *
1693
+ * Shutdown sequence:
1694
+ * 1. Emit onShutdownStart hook
1695
+ * 2. Stop accepting new messages (close subscriptions, stop consumers)
1696
+ * 3. Drain and close NATS connection (with timeout safety net)
1697
+ * 4. Emit onShutdownComplete hook
1698
+ *
1699
+ * Idempotent — concurrent or repeated calls return the same promise.
1700
+ * This is critical because NestJS may call `onApplicationShutdown` on
1701
+ * multiple module instances (forRoot + forFeature) that share this
1702
+ * singleton, and the call order is not guaranteed.
1703
+ */
1704
+ declare class ShutdownManager {
1705
+ private readonly connection;
1706
+ private readonly eventBus;
1707
+ private readonly timeout;
1708
+ private readonly logger;
1709
+ private shutdownPromise?;
1710
+ constructor(connection: ConnectionProvider, eventBus: EventBus, timeout: number);
1327
1711
  /**
1328
- * Build a schedule-holder subject for NATS message scheduling.
1712
+ * Execute the full shutdown sequence.
1329
1713
  *
1330
- * The schedule-holder subject resides in the same stream as the target but
1331
- * uses a separate `_sch` namespace that is NOT matched by any consumer filter.
1332
- * NATS holds the message and publishes it to the target subject after the delay.
1714
+ * Idempotent concurrent or repeated calls return the same promise.
1333
1715
  *
1334
- * Examples:
1335
- * - `{svc}__microservice.ev.order.reminder` → `{svc}__microservice._sch.order.reminder`
1336
- * - `broadcast.config.updated` → `broadcast._sch.config.updated`
1716
+ * @param strategy Optional stoppable to close (stops consumers and subscriptions).
1337
1717
  */
1338
- private buildScheduleSubject;
1718
+ shutdown(strategy?: Stoppable): Promise<void>;
1719
+ private doShutdown;
1339
1720
  }
1340
1721
 
1341
1722
  /**
1342
- * Immutable message record for JetStream transport.
1723
+ * Root module for the NestJS JetStream transport.
1343
1724
  *
1344
- * Compatible with NestJS record builder pattern (like RmqRecord, NatsRecord).
1345
- * Pass as the second argument to `client.send()` or `client.emit()`.
1725
+ * - `forRoot()` / `forRootAsync()` registers once in AppModule.
1726
+ * Creates shared NATS connection, codec, event bus, and optionally
1727
+ * the consumer infrastructure.
1728
+ *
1729
+ * - `forFeature()` — registers in feature modules.
1730
+ * Creates a lightweight client proxy targeting a specific service.
1346
1731
  *
1347
1732
  * @example
1348
1733
  * ```typescript
1349
- * const record = new JetstreamRecordBuilder({ id: 1 })
1350
- * .setHeader('x-tenant', 'acme')
1351
- * .setTimeout(5000)
1352
- * .build();
1734
+ * // AppModule global setup
1735
+ * @Module({
1736
+ * imports: [
1737
+ * JetstreamModule.forRoot({
1738
+ * name: 'orders',
1739
+ * servers: ['nats://localhost:4222'],
1740
+ * }),
1741
+ * ],
1742
+ * })
1743
+ * export class AppModule {}
1353
1744
  *
1354
- * client.send('get.user', record);
1745
+ * // Feature module — per-service clients
1746
+ * @Module({
1747
+ * imports: [
1748
+ * JetstreamModule.forFeature({ name: 'users' }),
1749
+ * JetstreamModule.forFeature({ name: 'payments' }),
1750
+ * ],
1751
+ * })
1752
+ * export class OrdersModule {}
1355
1753
  * ```
1356
1754
  */
1357
- declare class JetstreamRecord<TData = unknown> {
1358
- /** Message payload. */
1359
- readonly data: TData;
1360
- /** Custom headers set via {@link JetstreamRecordBuilder.setHeader}. */
1361
- readonly headers: ReadonlyMap<string, string>;
1362
- /** Per-request RPC timeout override in ms. */
1363
- readonly timeout?: number | undefined;
1364
- /** Custom message ID for JetStream deduplication. */
1365
- readonly messageId?: string | undefined;
1366
- /** Schedule options for delayed delivery. */
1367
- readonly schedule?: ScheduleRecordOptions | undefined;
1368
- /** Per-message TTL as Go duration string (e.g. "30s", "5m"). */
1369
- readonly ttl?: string | undefined;
1370
- constructor(
1371
- /** Message payload. */
1372
- data: TData,
1373
- /** Custom headers set via {@link JetstreamRecordBuilder.setHeader}. */
1374
- headers: ReadonlyMap<string, string>,
1375
- /** Per-request RPC timeout override in ms. */
1376
- timeout?: number | undefined,
1377
- /** Custom message ID for JetStream deduplication. */
1378
- messageId?: string | undefined,
1379
- /** Schedule options for delayed delivery. */
1380
- schedule?: ScheduleRecordOptions | undefined,
1381
- /** Per-message TTL as Go duration string (e.g. "30s", "5m"). */
1382
- ttl?: string | undefined);
1383
- }
1384
- /**
1385
- * Fluent builder for constructing JetstreamRecord instances.
1386
- *
1387
- * Protected headers (`correlation-id`, `reply-to`, `error`) cannot be
1388
- * set by the user — attempting to do so throws an error at build time.
1389
- */
1390
- declare class JetstreamRecordBuilder<TData = unknown> {
1391
- private data;
1392
- private readonly headers;
1393
- private timeout;
1394
- private messageId;
1395
- private scheduleOptions;
1396
- private ttlDuration;
1397
- constructor(data?: TData);
1398
- /**
1399
- * Set the message payload.
1400
- *
1401
- * @param data - Payload to serialize via the configured {@link Codec}.
1402
- */
1403
- setData(data: TData): this;
1404
- /**
1405
- * Set a single custom header.
1406
- *
1407
- * @param key - Header name (e.g. `'x-tenant'`).
1408
- * @param value - Header value.
1409
- * @throws Error if the header name is reserved by the transport.
1410
- */
1411
- setHeader(key: string, value: string): this;
1412
- /**
1413
- * Set multiple custom headers at once.
1414
- *
1415
- * @param headers - Key-value pairs to set as headers.
1416
- * @throws Error if any header name is reserved by the transport.
1417
- */
1418
- setHeaders(headers: Record<string, string>): this;
1755
+ declare class JetstreamModule implements OnApplicationShutdown {
1756
+ private readonly shutdownManager?;
1757
+ private readonly strategy?;
1758
+ constructor(shutdownManager?: ShutdownManager | undefined, strategy?: (JetstreamStrategy | null) | undefined);
1419
1759
  /**
1420
- * Set a custom message ID for JetStream deduplication.
1421
- *
1422
- * NATS JetStream uses this ID to detect duplicate publishes within the
1423
- * stream's `duplicate_window`. If two messages with the same ID arrive
1424
- * within the window, the second is silently dropped.
1425
- *
1426
- * When not set, a random UUID is generated automatically.
1427
- *
1428
- * @param id - Unique message identifier (e.g. order ID, idempotency key).
1760
+ * Register the JetStream transport globally.
1429
1761
  *
1430
- * @example
1431
- * ```typescript
1432
- * new JetstreamRecordBuilder(data)
1433
- * .setMessageId(`order-${order.id}`)
1434
- * .build();
1435
- * ```
1436
- */
1437
- setMessageId(id: string): this;
1438
- /**
1439
- * Set per-request RPC timeout.
1762
+ * Creates a shared NATS connection, codec, event bus, and optionally
1763
+ * the full consumer infrastructure (streams, consumers, routers).
1440
1764
  *
1441
- * @param ms - Timeout in milliseconds. Overrides the global RPC timeout for this request only.
1765
+ * @param options Module configuration.
1766
+ * @returns Dynamic module ready to be imported.
1442
1767
  */
1443
- setTimeout(ms: number): this;
1768
+ static forRoot(options: JetstreamModuleOptions): DynamicModule;
1444
1769
  /**
1445
- * Schedule one-shot delayed delivery.
1446
- *
1447
- * The message is held by NATS and delivered to the event consumer
1448
- * at the specified time. Requires NATS >= 2.12 and `allow_msg_schedules: true`
1449
- * on the event stream (via `events: { stream: { allow_msg_schedules: true } }`).
1770
+ * Register the JetStream transport globally with async configuration.
1450
1771
  *
1451
- * Only meaningful for events (`client.emit()`). If used with RPC
1452
- * (`client.send()`), a warning is logged and the schedule is ignored.
1772
+ * Supports `useFactory`, `useExisting`, and `useClass` patterns
1773
+ * for loading configuration from ConfigService, environment, etc.
1453
1774
  *
1454
- * @param date - Delivery time. Must be in the future.
1455
- * @throws Error if the date is not in the future.
1775
+ * @param asyncOptions Async configuration.
1776
+ * @returns Dynamic module ready to be imported.
1456
1777
  */
1457
- scheduleAt(date: Date): this;
1778
+ static forRootAsync(asyncOptions: JetstreamModuleAsyncOptions): DynamicModule;
1458
1779
  /**
1459
- * Set per-message TTL (time-to-live).
1460
- *
1461
- * The message expires individually after the specified duration,
1462
- * independent of the stream's `max_age`. Requires NATS >= 2.11 and
1463
- * `allow_msg_ttl: true` on the stream.
1464
- *
1465
- * Only meaningful for events (`client.emit()`). If used with RPC
1466
- * (`client.send()`), a warning is logged and the TTL is ignored.
1467
- *
1468
- * @param nanos - TTL in nanoseconds. Use {@link toNanos} for human-readable values.
1780
+ * Register a lightweight client proxy for a target service.
1469
1781
  *
1470
- * @example
1471
- * ```typescript
1472
- * import { toNanos } from '@horizon-republic/nestjs-jetstream';
1782
+ * Reuses the shared NATS connection from `forRoot()`.
1783
+ * Import in each feature module that needs to communicate with a specific service.
1473
1784
  *
1474
- * new JetstreamRecordBuilder(payload).ttl(toNanos(30, 'minutes')).build();
1475
- * new JetstreamRecordBuilder(payload).ttl(toNanos(24, 'hours')).build();
1476
- * ```
1785
+ * @param options Feature options with target service name.
1786
+ * @returns Dynamic module with the client provider.
1477
1787
  */
1478
- ttl(nanos: number): this;
1788
+ static forFeature(options: JetstreamFeatureOptions): DynamicModule;
1789
+ private static createCoreProviders;
1790
+ /** Create providers that depend on JETSTREAM_OPTIONS (shared by sync and async). */
1791
+ private static createCoreDependentProviders;
1792
+ /** Create async options provider from useFactory/useExisting/useClass. */
1793
+ private static createAsyncOptionsProvider;
1479
1794
  /**
1480
- * Build the immutable {@link JetstreamRecord}.
1481
- *
1482
- * @returns A frozen record ready to pass to `client.send()` or `client.emit()`.
1795
+ * Gracefully shut down the transport on application termination.
1483
1796
  */
1484
- build(): JetstreamRecord<TData>;
1485
- /** Validate that a header key is not reserved. */
1486
- private validateHeaderKey;
1797
+ onApplicationShutdown(): Promise<void>;
1487
1798
  }
1488
1799
 
1489
1800
  /**
@@ -1873,4 +2184,4 @@ declare const isJetStreamRpcMode: (rpc: RpcConfig | undefined) => boolean;
1873
2184
  /** Check if the RPC config specifies Core mode (default). */
1874
2185
  declare const isCoreRpcMode: (rpc: RpcConfig | undefined) => boolean;
1875
2186
 
1876
- export { type Codec, DEFAULT_BROADCAST_CONSUMER_CONFIG, DEFAULT_BROADCAST_STREAM_CONFIG, DEFAULT_COMMAND_CONSUMER_CONFIG, DEFAULT_COMMAND_STREAM_CONFIG, DEFAULT_DLQ_STREAM_CONFIG, DEFAULT_EVENT_CONSUMER_CONFIG, DEFAULT_EVENT_STREAM_CONFIG, DEFAULT_JETSTREAM_RPC_TIMEOUT, DEFAULT_METADATA_BUCKET, DEFAULT_METADATA_HISTORY, DEFAULT_METADATA_REPLICAS, DEFAULT_METADATA_TTL, DEFAULT_ORDERED_STREAM_CONFIG, DEFAULT_RPC_TIMEOUT, DEFAULT_SHUTDOWN_TIMEOUT, type DeadLetterInfo, JETSTREAM_CODEC, JETSTREAM_CONNECTION, JETSTREAM_OPTIONS, JetstreamClient, JetstreamDlqHeader, type JetstreamFeatureOptions, JetstreamHeader, JetstreamHealthIndicator, type JetstreamHealthStatus, JetstreamModule, type JetstreamModuleAsyncOptions, type JetstreamModuleOptions, JetstreamRecord, JetstreamRecordBuilder, JetstreamStrategy, JsonCodec, MIN_METADATA_TTL, MessageKind, type MetadataRegistryOptions, MsgpackCodec, NatsErrorCode, type OrderedEventOverrides, PatternPrefix, RESERVED_HEADERS, type RpcConfig, RpcContext, type ScheduleRecordOptions, type StreamConfigOverrides, type StreamConsumerOverrides, StreamKind, TransportEvent, type TransportHooks, buildBroadcastSubject, buildSubject, consumerName, dlqStreamName, getClientToken, internalName, isCoreRpcMode, isJetStreamRpcMode, metadataKey, streamName, toNanos };
2187
+ export { type CaptureBodyOptions, type Codec, ConsumeKind, type ConsumeSourceMsg, DEFAULT_BROADCAST_CONSUMER_CONFIG, DEFAULT_BROADCAST_STREAM_CONFIG, DEFAULT_COMMAND_CONSUMER_CONFIG, DEFAULT_COMMAND_STREAM_CONFIG, DEFAULT_DLQ_STREAM_CONFIG, DEFAULT_EVENT_CONSUMER_CONFIG, DEFAULT_EVENT_STREAM_CONFIG, DEFAULT_JETSTREAM_RPC_TIMEOUT, DEFAULT_METADATA_BUCKET, DEFAULT_METADATA_HISTORY, DEFAULT_METADATA_REPLICAS, DEFAULT_METADATA_TTL, DEFAULT_ORDERED_STREAM_CONFIG, DEFAULT_RPC_TIMEOUT, DEFAULT_SHUTDOWN_TIMEOUT, DEFAULT_TRACES, type DeadLetterInfo, type ErrorClassification, type HandlerMetadata, JETSTREAM_CODEC, JETSTREAM_CONNECTION, JETSTREAM_OPTIONS, JetstreamClient, type JetstreamConsumeContext, JetstreamDlqHeader, type JetstreamFeatureOptions, JetstreamHeader, JetstreamHealthIndicator, type JetstreamHealthStatus, JetstreamModule, type JetstreamModuleAsyncOptions, type JetstreamModuleOptions, type JetstreamPublishContext, JetstreamRecord, JetstreamRecordBuilder, type JetstreamResponseContext, JetstreamStrategy, JetstreamTrace, JsonCodec, MIN_METADATA_TTL, MessageKind, type MetadataRegistryOptions, MsgpackCodec, NatsErrorCode, type OrderedEventOverrides, type OtelOptions, PatternPrefix, PublishKind, RESERVED_HEADERS, type RpcConfig, RpcContext, type ScheduleRecordOptions, type ServerEndpoint, type StreamConfigOverrides, type StreamConsumerOverrides, StreamKind, TRACER_NAME, TransportEvent, type TransportHooks, buildBroadcastSubject, buildSubject, consumerName, dlqStreamName, getClientToken, internalName, isCoreRpcMode, isJetStreamRpcMode, metadataKey, streamName, toNanos };