@horizon-republic/nestjs-jetstream 2.3.0 → 2.3.4
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/README.md +110 -99
- package/dist/index.cjs +164 -93
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +148 -39
- package/dist/index.d.ts +148 -39
- package/dist/index.js +167 -93
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -16,24 +16,25 @@ A production-grade NestJS transport for NATS JetStream with built-in support for
|
|
|
16
16
|
- [forRoot / forRootAsync](#forroot--forrootasync)
|
|
17
17
|
- [forFeature](#forfeature)
|
|
18
18
|
- [Full Options Reference](#full-options-reference)
|
|
19
|
-
- [
|
|
20
|
-
- [
|
|
21
|
-
- [
|
|
22
|
-
- [
|
|
23
|
-
|
|
24
|
-
- [
|
|
25
|
-
- [
|
|
26
|
-
- [
|
|
27
|
-
- [
|
|
28
|
-
- [
|
|
29
|
-
- [
|
|
30
|
-
- [
|
|
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
|
-
- [
|
|
36
|
-
- [
|
|
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)
|
|
37
38
|
- [License](#license)
|
|
38
39
|
- [Links](#links)
|
|
39
40
|
|
|
@@ -251,7 +252,7 @@ interface JetstreamModuleOptions {
|
|
|
251
252
|
/** Broadcast event stream/consumer overrides. */
|
|
252
253
|
broadcast?: { stream?: Partial<StreamConfig>; consumer?: Partial<ConsumerConfig> };
|
|
253
254
|
|
|
254
|
-
/** Transport lifecycle hook handlers. Unset hooks
|
|
255
|
+
/** Transport lifecycle hook handlers. Unset hooks are silently ignored. */
|
|
255
256
|
hooks?: Partial<TransportHooks>;
|
|
256
257
|
|
|
257
258
|
/** Async callback for dead letter handling. See Dead Letter Queue section below. */
|
|
@@ -317,9 +318,11 @@ rpc: {
|
|
|
317
318
|
}
|
|
318
319
|
```
|
|
319
320
|
|
|
320
|
-
##
|
|
321
|
+
## Messaging Patterns
|
|
321
322
|
|
|
322
|
-
###
|
|
323
|
+
### RPC (Request/Reply)
|
|
324
|
+
|
|
325
|
+
#### Core Mode (Default)
|
|
323
326
|
|
|
324
327
|
Uses NATS native `request/reply` for the lowest possible latency.
|
|
325
328
|
|
|
@@ -348,7 +351,7 @@ JetstreamModule.forRoot({
|
|
|
348
351
|
| No handler running | Client times out |
|
|
349
352
|
| Decode error | Error response returned to caller |
|
|
350
353
|
|
|
351
|
-
|
|
354
|
+
#### JetStream Mode
|
|
352
355
|
|
|
353
356
|
Commands are persisted in a JetStream stream. Responses flow back via NATS Core inbox.
|
|
354
357
|
|
|
@@ -379,9 +382,9 @@ JetstreamModule.forRoot({
|
|
|
379
382
|
|
|
380
383
|
> **Why `term` instead of `nak` for RPC errors?** Redelivering a failed command could cause duplicate side effects. The caller is responsible for retrying.
|
|
381
384
|
|
|
382
|
-
|
|
385
|
+
### Events
|
|
383
386
|
|
|
384
|
-
|
|
387
|
+
#### Workqueue Events
|
|
385
388
|
|
|
386
389
|
Each event is delivered to **one** handler instance (load-balanced). Messages are acked **after** the handler completes successfully.
|
|
387
390
|
|
|
@@ -429,7 +432,7 @@ JetstreamModule.forRoot({
|
|
|
429
432
|
})
|
|
430
433
|
```
|
|
431
434
|
|
|
432
|
-
|
|
435
|
+
#### Broadcast Events
|
|
433
436
|
|
|
434
437
|
Broadcast events are delivered to **all** subscribing services. Each service gets its own durable consumer on a shared `broadcast-stream`.
|
|
435
438
|
|
|
@@ -469,7 +472,7 @@ JetstreamModule.forRoot({
|
|
|
469
472
|
|
|
470
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.
|
|
471
474
|
|
|
472
|
-
|
|
475
|
+
### JetstreamRecord Builder
|
|
473
476
|
|
|
474
477
|
Attach custom headers and per-request timeouts using the builder pattern:
|
|
475
478
|
|
|
@@ -507,7 +510,41 @@ Attempting to set a reserved header throws an error at build time.
|
|
|
507
510
|
| `x-trace-id` | Available for distributed tracing |
|
|
508
511
|
| `x-span-id` | Available for distributed tracing |
|
|
509
512
|
|
|
510
|
-
##
|
|
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
|
|
511
548
|
|
|
512
549
|
The library uses JSON by default. Implement the `Codec` interface for any serialization format:
|
|
513
550
|
|
|
@@ -543,41 +580,11 @@ JetstreamModule.forFeature({
|
|
|
543
580
|
|
|
544
581
|
> All services communicating with each other **must use the same codec**. A codec mismatch results in decode errors (`term`, no redelivery).
|
|
545
582
|
|
|
546
|
-
##
|
|
583
|
+
## Operations
|
|
547
584
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
```typescript
|
|
551
|
-
import { Ctx, Payload, MessagePattern } from '@nestjs/microservices';
|
|
552
|
-
import { RpcContext } from '@horizon-republic/nestjs-jetstream';
|
|
553
|
-
|
|
554
|
-
@MessagePattern('user.get')
|
|
555
|
-
getUser(@Payload() data: GetUserDto, @Ctx() ctx: RpcContext) {
|
|
556
|
-
const subject = ctx.getSubject(); // Full NATS subject
|
|
557
|
-
const traceId = ctx.getHeader('x-trace-id'); // Single header value
|
|
558
|
-
const headers = ctx.getHeaders(); // All headers (MsgHdrs)
|
|
559
|
-
const isJs = ctx.isJetStream(); // true for JetStream messages
|
|
560
|
-
const msg = ctx.getMessage(); // Raw JsMsg | Msg (escape hatch)
|
|
561
|
-
|
|
562
|
-
return this.userService.findOne(data.id);
|
|
563
|
-
}
|
|
564
|
-
```
|
|
565
|
-
|
|
566
|
-
**Available methods:**
|
|
567
|
-
|
|
568
|
-
| Method | Returns | Description |
|
|
569
|
-
|------------------|------------------------|-------------------------------------------|
|
|
570
|
-
| `getSubject()` | `string` | NATS subject the message was published to |
|
|
571
|
-
| `getHeader(key)` | `string \| undefined` | Single header value by key |
|
|
572
|
-
| `getHeaders()` | `MsgHdrs \| undefined` | All NATS message headers |
|
|
573
|
-
| `isJetStream()` | `boolean` | Whether the message supports ack/nak/term |
|
|
574
|
-
| `getMessage()` | `JsMsg \| Msg` | Raw NATS message (escape hatch) |
|
|
575
|
-
|
|
576
|
-
Available on both `@EventPattern` and `@MessagePattern` handlers.
|
|
577
|
-
|
|
578
|
-
## Lifecycle Hooks
|
|
585
|
+
### Lifecycle Hooks
|
|
579
586
|
|
|
580
|
-
Subscribe to transport events for monitoring, alerting, or custom logic:
|
|
587
|
+
Subscribe to transport events for monitoring, alerting, or custom logic. Events without a registered hook are silently ignored — no default logging:
|
|
581
588
|
|
|
582
589
|
```typescript
|
|
583
590
|
import { JetstreamModule, TransportEvent } from '@horizon-republic/nestjs-jetstream';
|
|
@@ -604,19 +611,19 @@ JetstreamModule.forRoot({
|
|
|
604
611
|
|
|
605
612
|
**Available events:**
|
|
606
613
|
|
|
607
|
-
| Event | Arguments |
|
|
608
|
-
|
|
609
|
-
| `connect` | `(server: string)` |
|
|
610
|
-
| `disconnect` | `()` |
|
|
611
|
-
| `reconnect` | `(server: string)` |
|
|
612
|
-
| `error` | `(error: Error, context?: string)` |
|
|
613
|
-
| `rpcTimeout` | `(subject: string, correlationId: string)` |
|
|
614
|
-
| `messageRouted` | `(subject: string, kind: 'rpc' \| 'event')` |
|
|
615
|
-
| `shutdownStart` | `()` |
|
|
616
|
-
| `shutdownComplete` | `()` |
|
|
617
|
-
| `deadLetter` | `(info: DeadLetterInfo)` |
|
|
614
|
+
| Event | Arguments |
|
|
615
|
+
|--------------------|---------------------------------------------|
|
|
616
|
+
| `connect` | `(server: string)` |
|
|
617
|
+
| `disconnect` | `()` |
|
|
618
|
+
| `reconnect` | `(server: string)` |
|
|
619
|
+
| `error` | `(error: Error, context?: string)` |
|
|
620
|
+
| `rpcTimeout` | `(subject: string, correlationId: string)` |
|
|
621
|
+
| `messageRouted` | `(subject: string, kind: 'rpc' \| 'event')` |
|
|
622
|
+
| `shutdownStart` | `()` |
|
|
623
|
+
| `shutdownComplete` | `()` |
|
|
624
|
+
| `deadLetter` | `(info: DeadLetterInfo)` |
|
|
618
625
|
|
|
619
|
-
|
|
626
|
+
#### Dead Letter Queue (DLQ)
|
|
620
627
|
|
|
621
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:
|
|
622
629
|
|
|
@@ -662,7 +669,7 @@ JetstreamModule.forRootAsync({
|
|
|
662
669
|
});
|
|
663
670
|
```
|
|
664
671
|
|
|
665
|
-
|
|
672
|
+
### Health Checks
|
|
666
673
|
|
|
667
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.
|
|
668
675
|
|
|
@@ -702,7 +709,7 @@ const status = await this.jetstream.check();
|
|
|
702
709
|
| `check()` | `JetstreamHealthStatus` | Never |
|
|
703
710
|
| `isHealthy(key?)` | `{ [key]: { status: 'up', ... } }` | On unhealthy (Terminus convention) |
|
|
704
711
|
|
|
705
|
-
|
|
712
|
+
### Graceful Shutdown
|
|
706
713
|
|
|
707
714
|
The transport shuts down automatically via NestJS `onApplicationShutdown()`:
|
|
708
715
|
|
|
@@ -720,13 +727,15 @@ JetstreamModule.forRoot({
|
|
|
720
727
|
|
|
721
728
|
No manual shutdown code needed.
|
|
722
729
|
|
|
723
|
-
##
|
|
730
|
+
## Reference
|
|
724
731
|
|
|
725
|
-
###
|
|
732
|
+
### Edge Cases & Important Notes
|
|
733
|
+
|
|
734
|
+
#### Event handlers must be idempotent
|
|
726
735
|
|
|
727
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.
|
|
728
737
|
|
|
729
|
-
|
|
738
|
+
#### RPC error handling
|
|
730
739
|
|
|
731
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:
|
|
732
741
|
|
|
@@ -751,11 +760,11 @@ this.client.send('user.update', data).subscribe({
|
|
|
751
760
|
|
|
752
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.
|
|
753
762
|
|
|
754
|
-
|
|
763
|
+
#### Fire-and-forget events
|
|
755
764
|
|
|
756
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.
|
|
757
766
|
|
|
758
|
-
|
|
767
|
+
#### Publisher-only mode
|
|
759
768
|
|
|
760
769
|
For services that only send messages (e.g., API gateways), disable consumer infrastructure:
|
|
761
770
|
|
|
@@ -767,17 +776,17 @@ JetstreamModule.forRoot({
|
|
|
767
776
|
})
|
|
768
777
|
```
|
|
769
778
|
|
|
770
|
-
|
|
779
|
+
#### Broadcast stream is shared
|
|
771
780
|
|
|
772
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.
|
|
773
782
|
|
|
774
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.
|
|
775
784
|
|
|
776
|
-
|
|
785
|
+
#### Connection failure behavior
|
|
777
786
|
|
|
778
|
-
If the initial NATS connection is refused, the module throws
|
|
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.
|
|
779
788
|
|
|
780
|
-
|
|
789
|
+
#### Observable return values
|
|
781
790
|
|
|
782
791
|
Handlers can return Observables. The transport takes the **first emitted value** for RPC responses and awaits completion for events:
|
|
783
792
|
|
|
@@ -793,15 +802,15 @@ handleOrder(@Payload() data: OrderDto): Observable<void> {
|
|
|
793
802
|
}
|
|
794
803
|
```
|
|
795
804
|
|
|
796
|
-
|
|
805
|
+
#### Consumer self-healing
|
|
797
806
|
|
|
798
|
-
If a JetStream consumer's message iterator ends unexpectedly (e.g., NATS restart), the transport automatically re-establishes consumption
|
|
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.
|
|
799
808
|
|
|
800
|
-
|
|
809
|
+
#### NATS header size
|
|
801
810
|
|
|
802
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.
|
|
803
812
|
|
|
804
|
-
|
|
813
|
+
### NATS Naming Conventions
|
|
805
814
|
|
|
806
815
|
The transport generates NATS subjects, streams, and consumers based on the service `name`:
|
|
807
816
|
|
|
@@ -818,7 +827,7 @@ The transport generates NATS subjects, streams, and consumers based on the servi
|
|
|
818
827
|
| Command consumer | `{internal}_cmd-consumer` | `orders__microservice_cmd-consumer` |
|
|
819
828
|
| Broadcast consumer | `{internal}_broadcast-consumer` | `orders__microservice_broadcast-consumer` |
|
|
820
829
|
|
|
821
|
-
|
|
830
|
+
### Default Stream & Consumer Configs
|
|
822
831
|
|
|
823
832
|
All defaults can be overridden via `events`, `broadcast`, or `rpc` options.
|
|
824
833
|
|
|
@@ -905,9 +914,9 @@ All defaults can be overridden via `events`, `broadcast`, or `rpc` options.
|
|
|
905
914
|
|
|
906
915
|
</details>
|
|
907
916
|
|
|
908
|
-
|
|
917
|
+
### API Reference
|
|
909
918
|
|
|
910
|
-
|
|
919
|
+
#### Exports
|
|
911
920
|
|
|
912
921
|
```typescript
|
|
913
922
|
// Module
|
|
@@ -951,7 +960,7 @@ StreamConsumerOverrides
|
|
|
951
960
|
TransportHooks
|
|
952
961
|
```
|
|
953
962
|
|
|
954
|
-
|
|
963
|
+
#### Helper: `nanos(ms)`
|
|
955
964
|
|
|
956
965
|
Convert milliseconds to nanoseconds (required by NATS JetStream config):
|
|
957
966
|
|
|
@@ -965,9 +974,11 @@ events: {
|
|
|
965
974
|
}
|
|
966
975
|
```
|
|
967
976
|
|
|
968
|
-
##
|
|
977
|
+
## Development
|
|
978
|
+
|
|
979
|
+
### Testing
|
|
969
980
|
|
|
970
|
-
The project uses [
|
|
981
|
+
The project uses [Vitest](https://vitest.dev/) with two test suites configured as [projects](https://vitest.dev/guide/workspace):
|
|
971
982
|
|
|
972
983
|
```bash
|
|
973
984
|
# Unit tests (no external dependencies)
|
|
@@ -986,7 +997,7 @@ pnpm test:watch
|
|
|
986
997
|
pnpm test:cov
|
|
987
998
|
```
|
|
988
999
|
|
|
989
|
-
|
|
1000
|
+
#### Running NATS locally
|
|
990
1001
|
|
|
991
1002
|
Integration tests require a NATS server with JetStream enabled:
|
|
992
1003
|
|
|
@@ -994,15 +1005,15 @@ Integration tests require a NATS server with JetStream enabled:
|
|
|
994
1005
|
docker run -d --name nats -p 4222:4222 nats:latest -js
|
|
995
1006
|
```
|
|
996
1007
|
|
|
997
|
-
|
|
1008
|
+
#### Writing tests
|
|
998
1009
|
|
|
999
1010
|
- Use `sut` (system under test) for the main instance
|
|
1000
|
-
- Use `createMock<T>()` from `@golevelup/ts-
|
|
1011
|
+
- Use `createMock<T>()` from `@golevelup/ts-vitest` for mocking
|
|
1001
1012
|
- Follow Given-When-Then structure with comments
|
|
1002
1013
|
- Order: happy path → edge cases → error cases
|
|
1003
|
-
- Always include `afterEach(
|
|
1014
|
+
- Always include `afterEach(vi.resetAllMocks)`
|
|
1004
1015
|
|
|
1005
|
-
|
|
1016
|
+
### Contributing
|
|
1006
1017
|
|
|
1007
1018
|
Contributions are welcome! Please read [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines.
|
|
1008
1019
|
|
|
@@ -1017,4 +1028,4 @@ Contributions are welcome! Please read [CONTRIBUTING.md](./CONTRIBUTING.md) for
|
|
|
1017
1028
|
- [GitHub Repository](https://github.com/HorizonRepublic/nestjs-jetstream)
|
|
1018
1029
|
- [npm Package](https://www.npmjs.com/package/@horizon-republic/nestjs-jetstream)
|
|
1019
1030
|
- [Report bugs](https://github.com/HorizonRepublic/nestjs-jetstream/issues)
|
|
1020
|
-
- [Discussions](https://github.com/HorizonRepublic/nestjs-jetstream/discussions)
|
|
1031
|
+
- [Discussions](https://github.com/HorizonRepublic/nestjs-jetstream/discussions)
|