@horizon-republic/nestjs-jetstream 2.2.0 → 2.3.2

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.
Files changed (161) hide show
  1. package/README.md +129 -80
  2. package/dist/index.cjs +2068 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.cts +995 -0
  5. package/dist/index.d.ts +995 -13
  6. package/dist/index.js +2068 -39
  7. package/dist/index.js.map +1 -1
  8. package/package.json +29 -19
  9. package/dist/client/index.d.ts +0 -3
  10. package/dist/client/index.d.ts.map +0 -1
  11. package/dist/client/index.js +0 -9
  12. package/dist/client/index.js.map +0 -1
  13. package/dist/client/jetstream.client.d.ts +0 -76
  14. package/dist/client/jetstream.client.d.ts.map +0 -1
  15. package/dist/client/jetstream.client.js +0 -325
  16. package/dist/client/jetstream.client.js.map +0 -1
  17. package/dist/client/jetstream.record.d.ts +0 -55
  18. package/dist/client/jetstream.record.d.ts.map +0 -1
  19. package/dist/client/jetstream.record.js +0 -84
  20. package/dist/client/jetstream.record.js.map +0 -1
  21. package/dist/codec/index.d.ts +0 -2
  22. package/dist/codec/index.d.ts.map +0 -1
  23. package/dist/codec/index.js +0 -6
  24. package/dist/codec/index.js.map +0 -1
  25. package/dist/codec/json.codec.d.ts +0 -20
  26. package/dist/codec/json.codec.d.ts.map +0 -1
  27. package/dist/codec/json.codec.js +0 -30
  28. package/dist/codec/json.codec.js.map +0 -1
  29. package/dist/connection/connection.provider.d.ts +0 -50
  30. package/dist/connection/connection.provider.d.ts.map +0 -1
  31. package/dist/connection/connection.provider.js +0 -141
  32. package/dist/connection/connection.provider.js.map +0 -1
  33. package/dist/connection/index.d.ts +0 -2
  34. package/dist/connection/index.d.ts.map +0 -1
  35. package/dist/connection/index.js +0 -6
  36. package/dist/connection/index.js.map +0 -1
  37. package/dist/context/index.d.ts +0 -2
  38. package/dist/context/index.d.ts.map +0 -1
  39. package/dist/context/index.js +0 -6
  40. package/dist/context/index.js.map +0 -1
  41. package/dist/context/rpc.context.d.ts +0 -35
  42. package/dist/context/rpc.context.d.ts.map +0 -1
  43. package/dist/context/rpc.context.js +0 -44
  44. package/dist/context/rpc.context.js.map +0 -1
  45. package/dist/health/index.d.ts +0 -3
  46. package/dist/health/index.d.ts.map +0 -1
  47. package/dist/health/index.js +0 -6
  48. package/dist/health/index.js.map +0 -1
  49. package/dist/health/jetstream.health-indicator.d.ts +0 -47
  50. package/dist/health/jetstream.health-indicator.d.ts.map +0 -1
  51. package/dist/health/jetstream.health-indicator.js +0 -85
  52. package/dist/health/jetstream.health-indicator.js.map +0 -1
  53. package/dist/hooks/event-bus.d.ts +0 -31
  54. package/dist/hooks/event-bus.d.ts.map +0 -1
  55. package/dist/hooks/event-bus.js +0 -79
  56. package/dist/hooks/event-bus.js.map +0 -1
  57. package/dist/hooks/index.d.ts +0 -2
  58. package/dist/hooks/index.d.ts.map +0 -1
  59. package/dist/hooks/index.js +0 -6
  60. package/dist/hooks/index.js.map +0 -1
  61. package/dist/index.d.ts.map +0 -1
  62. package/dist/interfaces/client.interface.d.ts +0 -14
  63. package/dist/interfaces/client.interface.d.ts.map +0 -1
  64. package/dist/interfaces/client.interface.js +0 -3
  65. package/dist/interfaces/client.interface.js.map +0 -1
  66. package/dist/interfaces/codec.interface.d.ts +0 -28
  67. package/dist/interfaces/codec.interface.d.ts.map +0 -1
  68. package/dist/interfaces/codec.interface.js +0 -3
  69. package/dist/interfaces/codec.interface.js.map +0 -1
  70. package/dist/interfaces/hooks.interface.d.ts +0 -71
  71. package/dist/interfaces/hooks.interface.d.ts.map +0 -1
  72. package/dist/interfaces/hooks.interface.js +0 -16
  73. package/dist/interfaces/hooks.interface.js.map +0 -1
  74. package/dist/interfaces/index.d.ts +0 -8
  75. package/dist/interfaces/index.d.ts.map +0 -1
  76. package/dist/interfaces/index.js +0 -6
  77. package/dist/interfaces/index.js.map +0 -1
  78. package/dist/interfaces/options.interface.d.ts +0 -142
  79. package/dist/interfaces/options.interface.d.ts.map +0 -1
  80. package/dist/interfaces/options.interface.js +0 -3
  81. package/dist/interfaces/options.interface.js.map +0 -1
  82. package/dist/interfaces/routing.interface.d.ts +0 -15
  83. package/dist/interfaces/routing.interface.d.ts.map +0 -1
  84. package/dist/interfaces/routing.interface.js +0 -3
  85. package/dist/interfaces/routing.interface.js.map +0 -1
  86. package/dist/interfaces/stream.interface.d.ts +0 -5
  87. package/dist/interfaces/stream.interface.d.ts.map +0 -1
  88. package/dist/interfaces/stream.interface.js +0 -3
  89. package/dist/interfaces/stream.interface.js.map +0 -1
  90. package/dist/jetstream.constants.d.ts +0 -58
  91. package/dist/jetstream.constants.d.ts.map +0 -1
  92. package/dist/jetstream.constants.js +0 -168
  93. package/dist/jetstream.constants.js.map +0 -1
  94. package/dist/jetstream.module.d.ts +0 -89
  95. package/dist/jetstream.module.d.ts.map +0 -1
  96. package/dist/jetstream.module.js +0 -410
  97. package/dist/jetstream.module.js.map +0 -1
  98. package/dist/server/core-rpc.server.d.ts +0 -31
  99. package/dist/server/core-rpc.server.d.ts.map +0 -1
  100. package/dist/server/core-rpc.server.js +0 -95
  101. package/dist/server/core-rpc.server.js.map +0 -1
  102. package/dist/server/index.d.ts +0 -5
  103. package/dist/server/index.d.ts.map +0 -1
  104. package/dist/server/index.js +0 -16
  105. package/dist/server/index.js.map +0 -1
  106. package/dist/server/infrastructure/consumer.provider.d.ts +0 -36
  107. package/dist/server/infrastructure/consumer.provider.d.ts.map +0 -1
  108. package/dist/server/infrastructure/consumer.provider.js +0 -123
  109. package/dist/server/infrastructure/consumer.provider.js.map +0 -1
  110. package/dist/server/infrastructure/index.d.ts +0 -4
  111. package/dist/server/infrastructure/index.d.ts.map +0 -1
  112. package/dist/server/infrastructure/index.js +0 -10
  113. package/dist/server/infrastructure/index.js.map +0 -1
  114. package/dist/server/infrastructure/message.provider.d.ts +0 -46
  115. package/dist/server/infrastructure/message.provider.d.ts.map +0 -1
  116. package/dist/server/infrastructure/message.provider.js +0 -100
  117. package/dist/server/infrastructure/message.provider.js.map +0 -1
  118. package/dist/server/infrastructure/stream.provider.d.ts +0 -38
  119. package/dist/server/infrastructure/stream.provider.d.ts.map +0 -1
  120. package/dist/server/infrastructure/stream.provider.js +0 -109
  121. package/dist/server/infrastructure/stream.provider.js.map +0 -1
  122. package/dist/server/routing/event.router.d.ts +0 -56
  123. package/dist/server/routing/event.router.d.ts.map +0 -1
  124. package/dist/server/routing/event.router.js +0 -132
  125. package/dist/server/routing/event.router.js.map +0 -1
  126. package/dist/server/routing/index.d.ts +0 -5
  127. package/dist/server/routing/index.d.ts.map +0 -1
  128. package/dist/server/routing/index.js +0 -10
  129. package/dist/server/routing/index.js.map +0 -1
  130. package/dist/server/routing/pattern-registry.d.ts +0 -39
  131. package/dist/server/routing/pattern-registry.d.ts.map +0 -1
  132. package/dist/server/routing/pattern-registry.js +0 -116
  133. package/dist/server/routing/pattern-registry.js.map +0 -1
  134. package/dist/server/routing/rpc.router.d.ts +0 -37
  135. package/dist/server/routing/rpc.router.d.ts.map +0 -1
  136. package/dist/server/routing/rpc.router.js +0 -121
  137. package/dist/server/routing/rpc.router.js.map +0 -1
  138. package/dist/server/strategy.d.ts +0 -55
  139. package/dist/server/strategy.d.ts.map +0 -1
  140. package/dist/server/strategy.js +0 -113
  141. package/dist/server/strategy.js.map +0 -1
  142. package/dist/shutdown/index.d.ts +0 -2
  143. package/dist/shutdown/index.d.ts.map +0 -1
  144. package/dist/shutdown/index.js +0 -6
  145. package/dist/shutdown/index.js.map +0 -1
  146. package/dist/shutdown/shutdown.manager.d.ts +0 -27
  147. package/dist/shutdown/shutdown.manager.d.ts.map +0 -1
  148. package/dist/shutdown/shutdown.manager.js +0 -45
  149. package/dist/shutdown/shutdown.manager.js.map +0 -1
  150. package/dist/utils/index.d.ts +0 -3
  151. package/dist/utils/index.d.ts.map +0 -1
  152. package/dist/utils/index.js +0 -8
  153. package/dist/utils/index.js.map +0 -1
  154. package/dist/utils/serialize-error.d.ts +0 -10
  155. package/dist/utils/serialize-error.d.ts.map +0 -1
  156. package/dist/utils/serialize-error.js +0 -21
  157. package/dist/utils/serialize-error.js.map +0 -1
  158. package/dist/utils/unwrap-result.d.ts +0 -15
  159. package/dist/utils/unwrap-result.d.ts.map +0 -1
  160. package/dist/utils/unwrap-result.js +0 -49
  161. package/dist/utils/unwrap-result.js.map +0 -1
package/README.md CHANGED
@@ -5,6 +5,7 @@ A production-grade NestJS transport for NATS JetStream with built-in support for
5
5
  [![npm version](https://img.shields.io/npm/v/@horizon-republic/nestjs-jetstream.svg)](https://www.npmjs.com/package/@horizon-republic/nestjs-jetstream)
6
6
  [![codecov](https://codecov.io/github/HorizonRepublic/nestjs-jetstream/graph/badge.svg?token=40IPSWFMT4)](https://codecov.io/github/HorizonRepublic/nestjs-jetstream)
7
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
+ [![Socket Badge](https://badge.socket.dev/npm/package/@horizon-republic/nestjs-jetstream)](https://badge.socket.dev/npm/package/@horizon-republic/nestjs-jetstream)
8
9
 
9
10
  ## Table of Contents
10
11
 
@@ -15,23 +16,25 @@ A production-grade NestJS transport for NATS JetStream with built-in support for
15
16
  - [forRoot / forRootAsync](#forroot--forrootasync)
16
17
  - [forFeature](#forfeature)
17
18
  - [Full Options Reference](#full-options-reference)
18
- - [RPC (Request/Reply)](#rpc-requestreply)
19
- - [Core Mode (Default)](#core-mode-default)
20
- - [JetStream Mode](#jetstream-mode)
21
- - [Events](#events)
22
- - [Workqueue Events](#workqueue-events)
23
- - [Broadcast Events](#broadcast-events)
24
- - [JetstreamRecord Builder](#jetstreamrecord-builder)
25
- - [Custom Codec](#custom-codec)
26
- - [RpcContext](#rpccontext)
27
- - [Lifecycle Hooks](#lifecycle-hooks)
28
- - [Health Checks](#health-checks)
29
- - [Graceful Shutdown](#graceful-shutdown)
30
- - [Edge Cases & Important Notes](#edge-cases--important-notes)
31
- - [NATS Naming Conventions](#nats-naming-conventions)
32
- - [Default Stream & Consumer Configs](#default-stream--consumer-configs)
33
- - [API Reference](#api-reference)
34
- - [Contributing](#contributing)
19
+ - [Messaging Patterns](#messaging-patterns)
20
+ - [RPC (Request/Reply)](#rpc-requestreply)
21
+ - [Events](#events)
22
+ - [JetstreamRecord Builder](#jetstreamrecord-builder)
23
+ - [Handler Context & Serialization](#handler-context--serialization)
24
+ - [RpcContext](#rpccontext)
25
+ - [Custom Codec](#custom-codec)
26
+ - [Operations](#operations)
27
+ - [Lifecycle Hooks](#lifecycle-hooks)
28
+ - [Health Checks](#health-checks)
29
+ - [Graceful Shutdown](#graceful-shutdown)
30
+ - [Reference](#reference)
31
+ - [Edge Cases & Important Notes](#edge-cases--important-notes)
32
+ - [NATS Naming Conventions](#nats-naming-conventions)
33
+ - [Default Stream & Consumer Configs](#default-stream--consumer-configs)
34
+ - [API Reference](#api-reference)
35
+ - [Development](#development)
36
+ - [Testing](#testing)
37
+ - [Contributing](#contributing)
35
38
  - [License](#license)
36
39
  - [Links](#links)
37
40
 
@@ -315,9 +318,11 @@ rpc: {
315
318
  }
316
319
  ```
317
320
 
318
- ## RPC (Request/Reply)
321
+ ## Messaging Patterns
319
322
 
320
- ### Core Mode (Default)
323
+ ### RPC (Request/Reply)
324
+
325
+ #### Core Mode (Default)
321
326
 
322
327
  Uses NATS native `request/reply` for the lowest possible latency.
323
328
 
@@ -346,7 +351,7 @@ JetstreamModule.forRoot({
346
351
  | No handler running | Client times out |
347
352
  | Decode error | Error response returned to caller |
348
353
 
349
- ### JetStream Mode
354
+ #### JetStream Mode
350
355
 
351
356
  Commands are persisted in a JetStream stream. Responses flow back via NATS Core inbox.
352
357
 
@@ -377,9 +382,9 @@ JetstreamModule.forRoot({
377
382
 
378
383
  > **Why `term` instead of `nak` for RPC errors?** Redelivering a failed command could cause duplicate side effects. The caller is responsible for retrying.
379
384
 
380
- ## Events
385
+ ### Events
381
386
 
382
- ### Workqueue Events
387
+ #### Workqueue Events
383
388
 
384
389
  Each event is delivered to **one** handler instance (load-balanced). Messages are acked **after** the handler completes successfully.
385
390
 
@@ -427,7 +432,7 @@ JetstreamModule.forRoot({
427
432
  })
428
433
  ```
429
434
 
430
- ### Broadcast Events
435
+ #### Broadcast Events
431
436
 
432
437
  Broadcast events are delivered to **all** subscribing services. Each service gets its own durable consumer on a shared `broadcast-stream`.
433
438
 
@@ -467,7 +472,7 @@ JetstreamModule.forRoot({
467
472
 
468
473
  > **Note:** The broadcast stream is shared across all services — stream-level settings (e.g., `max_age`, `max_bytes`) affect everyone. Consumer-level settings are per-service.
469
474
 
470
- ## JetstreamRecord Builder
475
+ ### JetstreamRecord Builder
471
476
 
472
477
  Attach custom headers and per-request timeouts using the builder pattern:
473
478
 
@@ -491,7 +496,6 @@ this.client.emit('user.created', record);
491
496
  |--------------------|-------------------------------|
492
497
  | `x-correlation-id` | RPC request/response matching |
493
498
  | `x-reply-to` | JetStream RPC response inbox |
494
- | `x-message-id` | Deduplication |
495
499
  | `x-error` | RPC error response flag |
496
500
 
497
501
  Attempting to set a reserved header throws an error at build time.
@@ -506,7 +510,41 @@ Attempting to set a reserved header throws an error at build time.
506
510
  | `x-trace-id` | Available for distributed tracing |
507
511
  | `x-span-id` | Available for distributed tracing |
508
512
 
509
- ## Custom Codec
513
+ ## Handler Context & Serialization
514
+
515
+ ### RpcContext
516
+
517
+ Execution context available in all handlers via `@Ctx()`:
518
+
519
+ ```typescript
520
+ import { Ctx, Payload, MessagePattern } from '@nestjs/microservices';
521
+ import { RpcContext } from '@horizon-republic/nestjs-jetstream';
522
+
523
+ @MessagePattern('user.get')
524
+ getUser(@Payload() data: GetUserDto, @Ctx() ctx: RpcContext) {
525
+ const subject = ctx.getSubject(); // Full NATS subject
526
+ const traceId = ctx.getHeader('x-trace-id'); // Single header value
527
+ const headers = ctx.getHeaders(); // All headers (MsgHdrs)
528
+ const isJs = ctx.isJetStream(); // true for JetStream messages
529
+ const msg = ctx.getMessage(); // Raw JsMsg | Msg (escape hatch)
530
+
531
+ return this.userService.findOne(data.id);
532
+ }
533
+ ```
534
+
535
+ **Available methods:**
536
+
537
+ | Method | Returns | Description |
538
+ |------------------|------------------------|-------------------------------------------|
539
+ | `getSubject()` | `string` | NATS subject the message was published to |
540
+ | `getHeader(key)` | `string \| undefined` | Single header value by key |
541
+ | `getHeaders()` | `MsgHdrs \| undefined` | All NATS message headers |
542
+ | `isJetStream()` | `boolean` | Whether the message supports ack/nak/term |
543
+ | `getMessage()` | `JsMsg \| Msg` | Raw NATS message (escape hatch) |
544
+
545
+ Available on both `@EventPattern` and `@MessagePattern` handlers.
546
+
547
+ ### Custom Codec
510
548
 
511
549
  The library uses JSON by default. Implement the `Codec` interface for any serialization format:
512
550
 
@@ -542,39 +580,9 @@ JetstreamModule.forFeature({
542
580
 
543
581
  > All services communicating with each other **must use the same codec**. A codec mismatch results in decode errors (`term`, no redelivery).
544
582
 
545
- ## RpcContext
583
+ ## Operations
546
584
 
547
- Execution context available in all handlers via `@Ctx()`:
548
-
549
- ```typescript
550
- import { Ctx, Payload, MessagePattern } from '@nestjs/microservices';
551
- import { RpcContext } from '@horizon-republic/nestjs-jetstream';
552
-
553
- @MessagePattern('user.get')
554
- getUser(@Payload() data: GetUserDto, @Ctx() ctx: RpcContext) {
555
- const subject = ctx.getSubject(); // Full NATS subject
556
- const traceId = ctx.getHeader('x-trace-id'); // Single header value
557
- const headers = ctx.getHeaders(); // All headers (MsgHdrs)
558
- const isJs = ctx.isJetStream(); // true for JetStream messages
559
- const msg = ctx.getMessage(); // Raw JsMsg | Msg (escape hatch)
560
-
561
- return this.userService.findOne(data.id);
562
- }
563
- ```
564
-
565
- **Available methods:**
566
-
567
- | Method | Returns | Description |
568
- |------------------|------------------------|-------------------------------------------|
569
- | `getSubject()` | `string` | NATS subject the message was published to |
570
- | `getHeader(key)` | `string \| undefined` | Single header value by key |
571
- | `getHeaders()` | `MsgHdrs \| undefined` | All NATS message headers |
572
- | `isJetStream()` | `boolean` | Whether the message supports ack/nak/term |
573
- | `getMessage()` | `JsMsg \| Msg` | Raw NATS message (escape hatch) |
574
-
575
- Available on both `@EventPattern` and `@MessagePattern` handlers.
576
-
577
- ## Lifecycle Hooks
585
+ ### Lifecycle Hooks
578
586
 
579
587
  Subscribe to transport events for monitoring, alerting, or custom logic:
580
588
 
@@ -615,7 +623,7 @@ JetstreamModule.forRoot({
615
623
  | `shutdownComplete` | `()` | `Logger.log` |
616
624
  | `deadLetter` | `(info: DeadLetterInfo)` | `Logger.warn` |
617
625
 
618
- ### Dead Letter Queue (DLQ)
626
+ #### Dead Letter Queue (DLQ)
619
627
 
620
628
  When an event handler fails on every delivery attempt (`max_deliver`), the message becomes a "dead letter." By default, NATS terminates it silently. Configure `onDeadLetter` to intercept these messages:
621
629
 
@@ -661,7 +669,7 @@ JetstreamModule.forRootAsync({
661
669
  });
662
670
  ```
663
671
 
664
- ## Health Checks
672
+ ### Health Checks
665
673
 
666
674
  `JetstreamHealthIndicator` is automatically registered and exported by `forRoot()`. It checks NATS connection status and measures round-trip latency. `@nestjs/terminus` is **not required** — the indicator follows the Terminus API convention so it works seamlessly when Terminus is present, but can also be used standalone.
667
675
 
@@ -701,7 +709,7 @@ const status = await this.jetstream.check();
701
709
  | `check()` | `JetstreamHealthStatus` | Never |
702
710
  | `isHealthy(key?)` | `{ [key]: { status: 'up', ... } }` | On unhealthy (Terminus convention) |
703
711
 
704
- ## Graceful Shutdown
712
+ ### Graceful Shutdown
705
713
 
706
714
  The transport shuts down automatically via NestJS `onApplicationShutdown()`:
707
715
 
@@ -719,13 +727,15 @@ JetstreamModule.forRoot({
719
727
 
720
728
  No manual shutdown code needed.
721
729
 
722
- ## Edge Cases & Important Notes
730
+ ## Reference
731
+
732
+ ### Edge Cases & Important Notes
723
733
 
724
- ### Event handlers must be idempotent
734
+ #### Event handlers must be idempotent
725
735
 
726
736
  Events use at-least-once delivery. If your handler throws, the message is `nak`'d and NATS redelivers it (up to `max_deliver` times, default 3). Design handlers to be safe for repeated execution.
727
737
 
728
- ### RPC error handling
738
+ #### RPC error handling
729
739
 
730
740
  The transport fully supports NestJS `RpcException` and custom exception filters. Throw `RpcException` with any payload — it will be delivered to the caller as-is:
731
741
 
@@ -750,11 +760,11 @@ this.client.send('user.update', data).subscribe({
750
760
 
751
761
  In JetStream mode, failed RPC messages are `term`'d (not `nak`'d) to prevent duplicate side effects. The caller is responsible for implementing retry logic.
752
762
 
753
- ### Fire-and-forget events
763
+ #### Fire-and-forget events
754
764
 
755
765
  This library focuses on **reliable, persistent** event delivery via JetStream. If you need fire-and-forget (no persistence, no ack) for high-throughput scenarios, use the standard [NestJS NATS transport](https://docs.nestjs.com/microservices/nats) — it works perfectly alongside this library on the same NATS server.
756
766
 
757
- ### Publisher-only mode
767
+ #### Publisher-only mode
758
768
 
759
769
  For services that only send messages (e.g., API gateways), disable consumer infrastructure:
760
770
 
@@ -766,17 +776,17 @@ JetstreamModule.forRoot({
766
776
  })
767
777
  ```
768
778
 
769
- ### Broadcast stream is shared
779
+ #### Broadcast stream is shared
770
780
 
771
781
  All services share a single `broadcast-stream`. Each service creates its own durable consumer with `filter_subjects` matching only its registered broadcast patterns. Stream-level configuration (`broadcast.stream`) affects all services.
772
782
 
773
783
  Broadcast consumers use the same ack/nak semantics as workqueue consumers. Because each service has an **isolated durable consumer**, a `nak` (retry) from one service only causes redelivery to that specific service — other consumers are unaffected. This gives broadcast **at-least-once delivery per consumer** with independent retry.
774
784
 
775
- ### Connection failure behavior
785
+ #### Connection failure behavior
776
786
 
777
- If the initial NATS connection is refused, the module throws a `RuntimeException` immediately (fail fast). For transient disconnects after startup, NATS handles reconnection automatically and the `reconnect` hook fires.
787
+ If the initial NATS connection is refused, the module throws an `Error` immediately (fail fast). For transient disconnects after startup, NATS handles reconnection automatically and the `reconnect` hook fires.
778
788
 
779
- ### Observable return values
789
+ #### Observable return values
780
790
 
781
791
  Handlers can return Observables. The transport takes the **first emitted value** for RPC responses and awaits completion for events:
782
792
 
@@ -792,15 +802,15 @@ handleOrder(@Payload() data: OrderDto): Observable<void> {
792
802
  }
793
803
  ```
794
804
 
795
- ### Consumer self-healing
805
+ #### Consumer self-healing
796
806
 
797
- If a JetStream consumer's message iterator ends unexpectedly (e.g., NATS restart), the transport automatically re-establishes consumption after a 100ms delay. This is logged as a warning.
807
+ If a JetStream consumer's message iterator ends unexpectedly (e.g., NATS restart), the transport automatically re-establishes consumption with exponential backoff (100ms up to 30s). This is logged as a warning.
798
808
 
799
- ### NATS header size
809
+ #### NATS header size
800
810
 
801
811
  Custom headers are transmitted as NATS message headers. NATS has a default header size limit. If you're attaching large metadata, consider putting it in the message body instead.
802
812
 
803
- ## NATS Naming Conventions
813
+ ### NATS Naming Conventions
804
814
 
805
815
  The transport generates NATS subjects, streams, and consumers based on the service `name`:
806
816
 
@@ -817,7 +827,7 @@ The transport generates NATS subjects, streams, and consumers based on the servi
817
827
  | Command consumer | `{internal}_cmd-consumer` | `orders__microservice_cmd-consumer` |
818
828
  | Broadcast consumer | `{internal}_broadcast-consumer` | `orders__microservice_broadcast-consumer` |
819
829
 
820
- ## Default Stream & Consumer Configs
830
+ ### Default Stream & Consumer Configs
821
831
 
822
832
  All defaults can be overridden via `events`, `broadcast`, or `rpc` options.
823
833
 
@@ -904,9 +914,9 @@ All defaults can be overridden via `events`, `broadcast`, or `rpc` options.
904
914
 
905
915
  </details>
906
916
 
907
- ## API Reference
917
+ ### API Reference
908
918
 
909
- ### Exports
919
+ #### Exports
910
920
 
911
921
  ```typescript
912
922
  // Module
@@ -950,7 +960,7 @@ StreamConsumerOverrides
950
960
  TransportHooks
951
961
  ```
952
962
 
953
- ### Helper: `nanos(ms)`
963
+ #### Helper: `nanos(ms)`
954
964
 
955
965
  Convert milliseconds to nanoseconds (required by NATS JetStream config):
956
966
 
@@ -964,7 +974,46 @@ events: {
964
974
  }
965
975
  ```
966
976
 
967
- ## Contributing
977
+ ## Development
978
+
979
+ ### Testing
980
+
981
+ The project uses [Vitest](https://vitest.dev/) with two test suites configured as [projects](https://vitest.dev/guide/workspace):
982
+
983
+ ```bash
984
+ # Unit tests (no external dependencies)
985
+ pnpm test
986
+
987
+ # Integration tests (requires a running NATS server with JetStream)
988
+ pnpm test:integration
989
+
990
+ # Both suites sequentially
991
+ pnpm test:all
992
+
993
+ # Unit tests in watch mode
994
+ pnpm test:watch
995
+
996
+ # Unit tests with coverage report
997
+ pnpm test:cov
998
+ ```
999
+
1000
+ #### Running NATS locally
1001
+
1002
+ Integration tests require a NATS server with JetStream enabled:
1003
+
1004
+ ```bash
1005
+ docker run -d --name nats -p 4222:4222 nats:latest -js
1006
+ ```
1007
+
1008
+ #### Writing tests
1009
+
1010
+ - Use `sut` (system under test) for the main instance
1011
+ - Use `createMock<T>()` from `@golevelup/ts-vitest` for mocking
1012
+ - Follow Given-When-Then structure with comments
1013
+ - Order: happy path → edge cases → error cases
1014
+ - Always include `afterEach(vi.resetAllMocks)`
1015
+
1016
+ ### Contributing
968
1017
 
969
1018
  Contributions are welcome! Please read [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines.
970
1019
 
@@ -979,4 +1028,4 @@ Contributions are welcome! Please read [CONTRIBUTING.md](./CONTRIBUTING.md) for
979
1028
  - [GitHub Repository](https://github.com/HorizonRepublic/nestjs-jetstream)
980
1029
  - [npm Package](https://www.npmjs.com/package/@horizon-republic/nestjs-jetstream)
981
1030
  - [Report bugs](https://github.com/HorizonRepublic/nestjs-jetstream/issues)
982
- - [Discussions](https://github.com/HorizonRepublic/nestjs-jetstream/discussions)
1031
+ - [Discussions](https://github.com/HorizonRepublic/nestjs-jetstream/discussions)