@horizon-republic/nestjs-jetstream 2.7.1 → 2.8.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.cjs +205 -85
- package/dist/index.d.cts +54 -12
- package/dist/index.d.ts +54 -12
- package/dist/index.js +187 -71
- package/package.json +8 -3
package/dist/index.js
CHANGED
|
@@ -24,9 +24,9 @@ import { Logger } from "@nestjs/common";
|
|
|
24
24
|
import { ClientProxy } from "@nestjs/microservices";
|
|
25
25
|
import {
|
|
26
26
|
createInbox,
|
|
27
|
-
Events,
|
|
28
27
|
headers as natsHeaders
|
|
29
|
-
} from "nats";
|
|
28
|
+
} from "@nats-io/transport-node";
|
|
29
|
+
import { nuid } from "@nats-io/nuid";
|
|
30
30
|
|
|
31
31
|
// src/interfaces/hooks.interface.ts
|
|
32
32
|
var MessageKind = /* @__PURE__ */ ((MessageKind2) => {
|
|
@@ -65,7 +65,7 @@ import {
|
|
|
65
65
|
RetentionPolicy,
|
|
66
66
|
StorageType,
|
|
67
67
|
StoreCompression
|
|
68
|
-
} from "nats";
|
|
68
|
+
} from "@nats-io/jetstream";
|
|
69
69
|
var JETSTREAM_OPTIONS = /* @__PURE__ */ Symbol("JETSTREAM_OPTIONS");
|
|
70
70
|
var JETSTREAM_CONNECTION = /* @__PURE__ */ Symbol("JETSTREAM_CONNECTION");
|
|
71
71
|
var JETSTREAM_CODEC = /* @__PURE__ */ Symbol("JETSTREAM_CODEC");
|
|
@@ -197,11 +197,12 @@ var isCoreRpcMode = (rpc) => !rpc || rpc.mode === "core";
|
|
|
197
197
|
|
|
198
198
|
// src/client/jetstream.record.ts
|
|
199
199
|
var JetstreamRecord = class {
|
|
200
|
-
constructor(data, headers2, timeout, messageId) {
|
|
200
|
+
constructor(data, headers2, timeout, messageId, schedule) {
|
|
201
201
|
this.data = data;
|
|
202
202
|
this.headers = headers2;
|
|
203
203
|
this.timeout = timeout;
|
|
204
204
|
this.messageId = messageId;
|
|
205
|
+
this.schedule = schedule;
|
|
205
206
|
}
|
|
206
207
|
};
|
|
207
208
|
var JetstreamRecordBuilder = class {
|
|
@@ -209,6 +210,7 @@ var JetstreamRecordBuilder = class {
|
|
|
209
210
|
headers = /* @__PURE__ */ new Map();
|
|
210
211
|
timeout;
|
|
211
212
|
messageId;
|
|
213
|
+
scheduleOptions;
|
|
212
214
|
constructor(data) {
|
|
213
215
|
this.data = data;
|
|
214
216
|
}
|
|
@@ -276,17 +278,43 @@ var JetstreamRecordBuilder = class {
|
|
|
276
278
|
this.timeout = ms;
|
|
277
279
|
return this;
|
|
278
280
|
}
|
|
281
|
+
/**
|
|
282
|
+
* Schedule one-shot delayed delivery.
|
|
283
|
+
*
|
|
284
|
+
* The message is held by NATS and delivered to the event consumer
|
|
285
|
+
* at the specified time. Requires NATS >= 2.12 and `allow_msg_schedules: true`
|
|
286
|
+
* on the event stream (via `events: { stream: { allow_msg_schedules: true } }`).
|
|
287
|
+
*
|
|
288
|
+
* Only meaningful for events (`client.emit()`). If used with RPC
|
|
289
|
+
* (`client.send()`), a warning is logged and the schedule is ignored.
|
|
290
|
+
*
|
|
291
|
+
* @param date - Delivery time. Must be in the future.
|
|
292
|
+
* @throws Error if the date is not in the future.
|
|
293
|
+
*/
|
|
294
|
+
scheduleAt(date) {
|
|
295
|
+
const ts = date.getTime();
|
|
296
|
+
if (Number.isNaN(ts)) {
|
|
297
|
+
throw new Error("Schedule date is invalid");
|
|
298
|
+
}
|
|
299
|
+
if (ts <= Date.now()) {
|
|
300
|
+
throw new Error("Schedule date must be in the future");
|
|
301
|
+
}
|
|
302
|
+
this.scheduleOptions = { at: new Date(ts) };
|
|
303
|
+
return this;
|
|
304
|
+
}
|
|
279
305
|
/**
|
|
280
306
|
* Build the immutable {@link JetstreamRecord}.
|
|
281
307
|
*
|
|
282
308
|
* @returns A frozen record ready to pass to `client.send()` or `client.emit()`.
|
|
283
309
|
*/
|
|
284
310
|
build() {
|
|
311
|
+
const schedule = this.scheduleOptions ? { at: new Date(this.scheduleOptions.at.getTime()) } : void 0;
|
|
285
312
|
return new JetstreamRecord(
|
|
286
313
|
this.data,
|
|
287
314
|
new Map(this.headers),
|
|
288
315
|
this.timeout,
|
|
289
|
-
this.messageId
|
|
316
|
+
this.messageId,
|
|
317
|
+
schedule
|
|
290
318
|
);
|
|
291
319
|
}
|
|
292
320
|
/** Validate that a header key is not reserved. */
|
|
@@ -338,7 +366,7 @@ var JetstreamClient = class extends ClientProxy {
|
|
|
338
366
|
this.setupInbox(nc);
|
|
339
367
|
}
|
|
340
368
|
this.statusSubscription ??= this.connection.status$.subscribe((status) => {
|
|
341
|
-
if (status.type ===
|
|
369
|
+
if (status.type === "disconnect") {
|
|
342
370
|
this.handleDisconnect();
|
|
343
371
|
}
|
|
344
372
|
});
|
|
@@ -366,19 +394,38 @@ var JetstreamClient = class extends ClientProxy {
|
|
|
366
394
|
* Publish a fire-and-forget event to JetStream.
|
|
367
395
|
*
|
|
368
396
|
* Events are published to either the workqueue stream or broadcast stream
|
|
369
|
-
* depending on the subject prefix.
|
|
397
|
+
* depending on the subject prefix. When a schedule is present the message
|
|
398
|
+
* is published to a `_sch` subject within the same stream, with the target
|
|
399
|
+
* set to the original event subject.
|
|
370
400
|
*/
|
|
371
401
|
async dispatchEvent(packet) {
|
|
372
402
|
await this.connect();
|
|
373
|
-
const { data, hdrs, messageId } = this.extractRecordData(packet.data);
|
|
374
|
-
const
|
|
375
|
-
const msgHeaders = this.buildHeaders(hdrs, { subject });
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
403
|
+
const { data, hdrs, messageId, schedule } = this.extractRecordData(packet.data);
|
|
404
|
+
const eventSubject = this.buildEventSubject(packet.pattern);
|
|
405
|
+
const msgHeaders = this.buildHeaders(hdrs, { subject: eventSubject });
|
|
406
|
+
if (schedule) {
|
|
407
|
+
const scheduleSubject = this.buildScheduleSubject(eventSubject);
|
|
408
|
+
const ack = await this.connection.getJetStreamClient().publish(scheduleSubject, this.codec.encode(data), {
|
|
409
|
+
headers: msgHeaders,
|
|
410
|
+
msgID: messageId ?? nuid.next(),
|
|
411
|
+
schedule: {
|
|
412
|
+
specification: schedule.at,
|
|
413
|
+
target: eventSubject
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
if (ack.duplicate) {
|
|
417
|
+
this.logger.warn(
|
|
418
|
+
`Duplicate scheduled publish detected: ${scheduleSubject} (seq: ${ack.seq})`
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
} else {
|
|
422
|
+
const ack = await this.connection.getJetStreamClient().publish(eventSubject, this.codec.encode(data), {
|
|
423
|
+
headers: msgHeaders,
|
|
424
|
+
msgID: messageId ?? nuid.next()
|
|
425
|
+
});
|
|
426
|
+
if (ack.duplicate) {
|
|
427
|
+
this.logger.warn(`Duplicate event publish detected: ${eventSubject} (seq: ${ack.seq})`);
|
|
428
|
+
}
|
|
382
429
|
}
|
|
383
430
|
return void 0;
|
|
384
431
|
}
|
|
@@ -390,7 +437,12 @@ var JetstreamClient = class extends ClientProxy {
|
|
|
390
437
|
*/
|
|
391
438
|
publish(packet, callback) {
|
|
392
439
|
const subject = buildSubject(this.targetName, "cmd" /* Command */, packet.pattern);
|
|
393
|
-
const { data, hdrs, timeout, messageId } = this.extractRecordData(packet.data);
|
|
440
|
+
const { data, hdrs, timeout, messageId, schedule } = this.extractRecordData(packet.data);
|
|
441
|
+
if (schedule) {
|
|
442
|
+
this.logger.warn(
|
|
443
|
+
"scheduleAt() is ignored for RPC (client.send()). Use client.emit() for scheduled events."
|
|
444
|
+
);
|
|
445
|
+
}
|
|
394
446
|
const onUnhandled = (err) => {
|
|
395
447
|
this.logger.error("Unhandled publish error:", err);
|
|
396
448
|
callback({ err: new Error("Internal transport error"), response: null, isDisposed: true });
|
|
@@ -399,7 +451,7 @@ var JetstreamClient = class extends ClientProxy {
|
|
|
399
451
|
if (isCoreRpcMode(this.rootOptions.rpc)) {
|
|
400
452
|
this.publishCoreRpc(subject, data, hdrs, timeout, callback).catch(onUnhandled);
|
|
401
453
|
} else {
|
|
402
|
-
jetStreamCorrelationId =
|
|
454
|
+
jetStreamCorrelationId = nuid.next();
|
|
403
455
|
this.publishJetStreamRpc(subject, data, callback, {
|
|
404
456
|
headers: hdrs,
|
|
405
457
|
timeout,
|
|
@@ -474,7 +526,7 @@ var JetstreamClient = class extends ClientProxy {
|
|
|
474
526
|
});
|
|
475
527
|
await this.connection.getJetStreamClient().publish(subject, this.codec.encode(data), {
|
|
476
528
|
headers: hdrs,
|
|
477
|
-
msgID: messageId ??
|
|
529
|
+
msgID: messageId ?? nuid.next()
|
|
478
530
|
});
|
|
479
531
|
} catch (err) {
|
|
480
532
|
const existingTimeout = this.pendingTimeouts.get(correlationId);
|
|
@@ -587,17 +639,51 @@ var JetstreamClient = class extends ClientProxy {
|
|
|
587
639
|
}
|
|
588
640
|
return hdrs;
|
|
589
641
|
}
|
|
590
|
-
/** Extract data, headers, and
|
|
642
|
+
/** Extract data, headers, timeout, and schedule from raw packet data or JetstreamRecord. */
|
|
591
643
|
extractRecordData(rawData) {
|
|
592
644
|
if (rawData instanceof JetstreamRecord) {
|
|
593
645
|
return {
|
|
594
646
|
data: rawData.data,
|
|
595
647
|
hdrs: rawData.headers.size > 0 ? new Map(rawData.headers) : null,
|
|
596
648
|
timeout: rawData.timeout,
|
|
597
|
-
messageId: rawData.messageId
|
|
649
|
+
messageId: rawData.messageId,
|
|
650
|
+
schedule: rawData.schedule
|
|
598
651
|
};
|
|
599
652
|
}
|
|
600
|
-
return {
|
|
653
|
+
return {
|
|
654
|
+
data: rawData,
|
|
655
|
+
hdrs: null,
|
|
656
|
+
timeout: void 0,
|
|
657
|
+
messageId: void 0,
|
|
658
|
+
schedule: void 0
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Build a schedule-holder subject for NATS message scheduling.
|
|
663
|
+
*
|
|
664
|
+
* The schedule-holder subject resides in the same stream as the target but
|
|
665
|
+
* uses a separate `_sch` namespace that is NOT matched by any consumer filter.
|
|
666
|
+
* NATS holds the message and publishes it to the target subject after the delay.
|
|
667
|
+
*
|
|
668
|
+
* Examples:
|
|
669
|
+
* - `{svc}__microservice.ev.order.reminder` → `{svc}__microservice._sch.order.reminder`
|
|
670
|
+
* - `broadcast.config.updated` → `broadcast._sch.config.updated`
|
|
671
|
+
*/
|
|
672
|
+
buildScheduleSubject(eventSubject) {
|
|
673
|
+
if (eventSubject.startsWith("broadcast.")) {
|
|
674
|
+
return eventSubject.replace("broadcast.", "broadcast._sch.");
|
|
675
|
+
}
|
|
676
|
+
const targetPrefix = `${internalName(this.targetName)}.`;
|
|
677
|
+
if (!eventSubject.startsWith(targetPrefix)) {
|
|
678
|
+
throw new Error(`Unexpected event subject format: ${eventSubject}`);
|
|
679
|
+
}
|
|
680
|
+
const withoutPrefix = eventSubject.slice(targetPrefix.length);
|
|
681
|
+
const dotIndex = withoutPrefix.indexOf(".");
|
|
682
|
+
if (dotIndex === -1) {
|
|
683
|
+
throw new Error(`Event subject missing pattern segment: ${eventSubject}`);
|
|
684
|
+
}
|
|
685
|
+
const pattern = withoutPrefix.slice(dotIndex + 1);
|
|
686
|
+
return `${targetPrefix}_sch.${pattern}`;
|
|
601
687
|
}
|
|
602
688
|
getRpcTimeout() {
|
|
603
689
|
if (!this.rootOptions.rpc) return DEFAULT_RPC_TIMEOUT;
|
|
@@ -607,25 +693,26 @@ var JetstreamClient = class extends ClientProxy {
|
|
|
607
693
|
};
|
|
608
694
|
|
|
609
695
|
// src/codec/json.codec.ts
|
|
610
|
-
|
|
696
|
+
var encoder = new TextEncoder();
|
|
697
|
+
var decoder = new TextDecoder();
|
|
611
698
|
var JsonCodec = class {
|
|
612
|
-
inner = NatsJSONCodec();
|
|
613
699
|
encode(data) {
|
|
614
|
-
return
|
|
700
|
+
return encoder.encode(JSON.stringify(data));
|
|
615
701
|
}
|
|
616
702
|
decode(data) {
|
|
617
|
-
return
|
|
703
|
+
return JSON.parse(decoder.decode(data));
|
|
618
704
|
}
|
|
619
705
|
};
|
|
620
706
|
|
|
621
707
|
// src/connection/connection.provider.ts
|
|
622
708
|
import { Logger as Logger2 } from "@nestjs/common";
|
|
623
709
|
import {
|
|
624
|
-
connect
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
710
|
+
connect
|
|
711
|
+
} from "@nats-io/transport-node";
|
|
712
|
+
import {
|
|
713
|
+
jetstream,
|
|
714
|
+
jetstreamManager
|
|
715
|
+
} from "@nats-io/jetstream";
|
|
629
716
|
import { defer, from, share, shareReplay, switchMap } from "rxjs";
|
|
630
717
|
var DEFAULT_OPTIONS = {
|
|
631
718
|
maxReconnectAttempts: -1,
|
|
@@ -679,14 +766,7 @@ var ConnectionProvider = class {
|
|
|
679
766
|
async getJetStreamManager() {
|
|
680
767
|
if (this.jsmInstance) return this.jsmInstance;
|
|
681
768
|
if (this.jsmPromise) return this.jsmPromise;
|
|
682
|
-
this.jsmPromise = (
|
|
683
|
-
const nc = await this.getConnection();
|
|
684
|
-
this.jsmInstance = await nc.jetstreamManager();
|
|
685
|
-
this.logger.log("JetStream manager initialized");
|
|
686
|
-
return this.jsmInstance;
|
|
687
|
-
})().finally(() => {
|
|
688
|
-
this.jsmPromise = null;
|
|
689
|
-
});
|
|
769
|
+
this.jsmPromise = this.initJetStreamManager();
|
|
690
770
|
return this.jsmPromise;
|
|
691
771
|
}
|
|
692
772
|
/**
|
|
@@ -702,7 +782,7 @@ var ConnectionProvider = class {
|
|
|
702
782
|
if (!this.connection || this.connection.isClosed()) {
|
|
703
783
|
throw new Error("Not connected \u2014 call getConnection() before getJetStreamClient()");
|
|
704
784
|
}
|
|
705
|
-
this.jsClient ??= this.connection
|
|
785
|
+
this.jsClient ??= jetstream(this.connection);
|
|
706
786
|
return this.jsClient;
|
|
707
787
|
}
|
|
708
788
|
/** Direct access to the raw NATS connection, or `null` if not yet connected. */
|
|
@@ -738,6 +818,16 @@ var ConnectionProvider = class {
|
|
|
738
818
|
this.jsmPromise = null;
|
|
739
819
|
}
|
|
740
820
|
}
|
|
821
|
+
async initJetStreamManager() {
|
|
822
|
+
try {
|
|
823
|
+
const nc = await this.getConnection();
|
|
824
|
+
this.jsmInstance = await jetstreamManager(nc);
|
|
825
|
+
this.logger.log("JetStream manager initialized");
|
|
826
|
+
return this.jsmInstance;
|
|
827
|
+
} finally {
|
|
828
|
+
this.jsmPromise = null;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
741
831
|
/** Internal: establish the physical connection with reconnect monitoring. */
|
|
742
832
|
async establish() {
|
|
743
833
|
const name = internalName(this.options.name);
|
|
@@ -754,7 +844,7 @@ var ConnectionProvider = class {
|
|
|
754
844
|
this.monitorStatus(nc);
|
|
755
845
|
return nc;
|
|
756
846
|
} catch (err) {
|
|
757
|
-
if (err instanceof
|
|
847
|
+
if (err instanceof Error && err.message.includes("REFUSED")) {
|
|
758
848
|
throw new Error(`NATS connection refused: ${this.options.servers.join(", ")}`);
|
|
759
849
|
}
|
|
760
850
|
throw err;
|
|
@@ -762,27 +852,33 @@ var ConnectionProvider = class {
|
|
|
762
852
|
}
|
|
763
853
|
/** Subscribe to connection status events and emit hooks. */
|
|
764
854
|
monitorStatus(nc) {
|
|
765
|
-
(async () => {
|
|
855
|
+
void (async () => {
|
|
766
856
|
for await (const status of nc.status()) {
|
|
767
857
|
switch (status.type) {
|
|
768
|
-
case
|
|
858
|
+
case "disconnect":
|
|
769
859
|
this.eventBus.emit("disconnect" /* Disconnect */);
|
|
770
860
|
break;
|
|
771
|
-
case
|
|
861
|
+
case "reconnect":
|
|
772
862
|
this.jsClient = null;
|
|
773
863
|
this.jsmInstance = null;
|
|
774
864
|
this.jsmPromise = null;
|
|
775
865
|
this.eventBus.emit("reconnect" /* Reconnect */, nc.getServer());
|
|
776
866
|
break;
|
|
777
|
-
case
|
|
778
|
-
this.eventBus.emit(
|
|
867
|
+
case "error":
|
|
868
|
+
this.eventBus.emit(
|
|
869
|
+
"error" /* Error */,
|
|
870
|
+
status.error,
|
|
871
|
+
"connection"
|
|
872
|
+
);
|
|
779
873
|
break;
|
|
780
|
-
case
|
|
781
|
-
case
|
|
782
|
-
case
|
|
783
|
-
case
|
|
784
|
-
case
|
|
785
|
-
case
|
|
874
|
+
case "update":
|
|
875
|
+
case "ldm":
|
|
876
|
+
case "reconnecting":
|
|
877
|
+
case "ping":
|
|
878
|
+
case "staleConnection":
|
|
879
|
+
case "forceReconnect":
|
|
880
|
+
case "slowConsumer":
|
|
881
|
+
case "close":
|
|
786
882
|
break;
|
|
787
883
|
}
|
|
788
884
|
}
|
|
@@ -1044,7 +1140,7 @@ var JetstreamStrategy = class extends Server {
|
|
|
1044
1140
|
|
|
1045
1141
|
// src/server/core-rpc.server.ts
|
|
1046
1142
|
import { Logger as Logger4 } from "@nestjs/common";
|
|
1047
|
-
import { headers as natsHeaders2 } from "nats";
|
|
1143
|
+
import { headers as natsHeaders2 } from "@nats-io/transport-node";
|
|
1048
1144
|
|
|
1049
1145
|
// src/context/rpc.context.ts
|
|
1050
1146
|
import { BaseRpcContext } from "@nestjs/microservices";
|
|
@@ -1341,7 +1437,7 @@ var CoreRpcServer = class {
|
|
|
1341
1437
|
|
|
1342
1438
|
// src/server/infrastructure/stream.provider.ts
|
|
1343
1439
|
import { Logger as Logger5 } from "@nestjs/common";
|
|
1344
|
-
import {
|
|
1440
|
+
import { JetStreamApiError } from "@nats-io/jetstream";
|
|
1345
1441
|
var STREAM_NOT_FOUND = 10059;
|
|
1346
1442
|
var StreamProvider = class {
|
|
1347
1443
|
constructor(options, connection) {
|
|
@@ -1367,12 +1463,22 @@ var StreamProvider = class {
|
|
|
1367
1463
|
getSubjects(kind) {
|
|
1368
1464
|
const name = internalName(this.options.name);
|
|
1369
1465
|
switch (kind) {
|
|
1370
|
-
case "ev" /* Event */:
|
|
1371
|
-
|
|
1466
|
+
case "ev" /* Event */: {
|
|
1467
|
+
const subjects = [`${name}.${"ev" /* Event */}.>`];
|
|
1468
|
+
if (this.isSchedulingEnabled(kind)) {
|
|
1469
|
+
subjects.push(`${name}._sch.>`);
|
|
1470
|
+
}
|
|
1471
|
+
return subjects;
|
|
1472
|
+
}
|
|
1372
1473
|
case "cmd" /* Command */:
|
|
1373
1474
|
return [`${name}.${"cmd" /* Command */}.>`];
|
|
1374
|
-
case "broadcast" /* Broadcast */:
|
|
1375
|
-
|
|
1475
|
+
case "broadcast" /* Broadcast */: {
|
|
1476
|
+
const subjects = ["broadcast.>"];
|
|
1477
|
+
if (this.isSchedulingEnabled(kind)) {
|
|
1478
|
+
subjects.push("broadcast._sch.>");
|
|
1479
|
+
}
|
|
1480
|
+
return subjects;
|
|
1481
|
+
}
|
|
1376
1482
|
case "ordered" /* Ordered */:
|
|
1377
1483
|
return [`${name}.${"ordered" /* Ordered */}.>`];
|
|
1378
1484
|
}
|
|
@@ -1386,7 +1492,7 @@ var StreamProvider = class {
|
|
|
1386
1492
|
this.logger.debug(`Stream exists, updating: ${config.name}`);
|
|
1387
1493
|
return await jsm.streams.update(config.name, config);
|
|
1388
1494
|
} catch (err) {
|
|
1389
|
-
if (err instanceof
|
|
1495
|
+
if (err instanceof JetStreamApiError && err.apiError().err_code === STREAM_NOT_FOUND) {
|
|
1390
1496
|
this.logger.log(`Creating stream: ${config.name}`);
|
|
1391
1497
|
return await jsm.streams.add(config);
|
|
1392
1498
|
}
|
|
@@ -1421,6 +1527,11 @@ var StreamProvider = class {
|
|
|
1421
1527
|
return DEFAULT_ORDERED_STREAM_CONFIG;
|
|
1422
1528
|
}
|
|
1423
1529
|
}
|
|
1530
|
+
/** Check if scheduling is enabled for a stream kind via `allow_msg_schedules` override. */
|
|
1531
|
+
isSchedulingEnabled(kind) {
|
|
1532
|
+
const overrides = this.getOverrides(kind);
|
|
1533
|
+
return overrides.allow_msg_schedules === true;
|
|
1534
|
+
}
|
|
1424
1535
|
/** Get user-provided overrides for a stream kind. */
|
|
1425
1536
|
getOverrides(kind) {
|
|
1426
1537
|
switch (kind) {
|
|
@@ -1438,7 +1549,7 @@ var StreamProvider = class {
|
|
|
1438
1549
|
|
|
1439
1550
|
// src/server/infrastructure/consumer.provider.ts
|
|
1440
1551
|
import { Logger as Logger6 } from "@nestjs/common";
|
|
1441
|
-
import {
|
|
1552
|
+
import { JetStreamApiError as JetStreamApiError2 } from "@nats-io/jetstream";
|
|
1442
1553
|
var CONSUMER_NOT_FOUND = 10014;
|
|
1443
1554
|
var ConsumerProvider = class {
|
|
1444
1555
|
constructor(options, connection, streamProvider, patternRegistry) {
|
|
@@ -1479,7 +1590,7 @@ var ConsumerProvider = class {
|
|
|
1479
1590
|
this.logger.debug(`Consumer exists, updating: ${name}`);
|
|
1480
1591
|
return await jsm.consumers.update(stream, name, config);
|
|
1481
1592
|
} catch (err) {
|
|
1482
|
-
if (err instanceof
|
|
1593
|
+
if (err instanceof JetStreamApiError2 && err.apiError().err_code === CONSUMER_NOT_FOUND) {
|
|
1483
1594
|
this.logger.log(`Creating consumer: ${name}`);
|
|
1484
1595
|
return await jsm.consumers.add(stream, config);
|
|
1485
1596
|
}
|
|
@@ -1538,6 +1649,10 @@ var ConsumerProvider = class {
|
|
|
1538
1649
|
return DEFAULT_BROADCAST_CONSUMER_CONFIG;
|
|
1539
1650
|
case "ordered" /* Ordered */:
|
|
1540
1651
|
throw new Error("Ordered consumers are ephemeral and should not use durable config");
|
|
1652
|
+
default: {
|
|
1653
|
+
const _exhaustive = kind;
|
|
1654
|
+
throw new Error(`Unexpected StreamKind: ${_exhaustive}`);
|
|
1655
|
+
}
|
|
1541
1656
|
}
|
|
1542
1657
|
}
|
|
1543
1658
|
/** Get user-provided overrides for a consumer kind. */
|
|
@@ -1551,16 +1666,17 @@ var ConsumerProvider = class {
|
|
|
1551
1666
|
return this.options.broadcast?.consumer ?? {};
|
|
1552
1667
|
case "ordered" /* Ordered */:
|
|
1553
1668
|
throw new Error("Ordered consumers are ephemeral and should not use durable config");
|
|
1669
|
+
default: {
|
|
1670
|
+
const _exhaustive = kind;
|
|
1671
|
+
throw new Error(`Unexpected StreamKind: ${_exhaustive}`);
|
|
1672
|
+
}
|
|
1554
1673
|
}
|
|
1555
1674
|
}
|
|
1556
1675
|
};
|
|
1557
1676
|
|
|
1558
1677
|
// src/server/infrastructure/message.provider.ts
|
|
1559
1678
|
import { Logger as Logger7 } from "@nestjs/common";
|
|
1560
|
-
import {
|
|
1561
|
-
ConsumerEvents,
|
|
1562
|
-
DeliverPolicy as DeliverPolicy2
|
|
1563
|
-
} from "nats";
|
|
1679
|
+
import { DeliverPolicy as DeliverPolicy2 } from "@nats-io/jetstream";
|
|
1564
1680
|
import {
|
|
1565
1681
|
catchError,
|
|
1566
1682
|
defer as defer2,
|
|
@@ -1629,7 +1745,7 @@ var MessageProvider = class {
|
|
|
1629
1745
|
* @param orderedConfig - Optional overrides for ordered consumer options.
|
|
1630
1746
|
*/
|
|
1631
1747
|
async startOrdered(streamName2, filterSubjects, orderedConfig) {
|
|
1632
|
-
const consumerOpts = { filterSubjects };
|
|
1748
|
+
const consumerOpts = { filter_subjects: filterSubjects };
|
|
1633
1749
|
if (orderedConfig?.deliverPolicy !== void 0 && orderedConfig.deliverPolicy !== DeliverPolicy2.All) {
|
|
1634
1750
|
consumerOpts.deliver_policy = orderedConfig.deliverPolicy;
|
|
1635
1751
|
}
|
|
@@ -1714,10 +1830,10 @@ var MessageProvider = class {
|
|
|
1714
1830
|
}
|
|
1715
1831
|
/** Monitor heartbeats and restart the consumer iterator on prolonged silence. */
|
|
1716
1832
|
monitorConsumerHealth(messages, name) {
|
|
1717
|
-
(async () => {
|
|
1718
|
-
for await (const status of
|
|
1719
|
-
if (status.type ===
|
|
1720
|
-
this.logger.warn(`Consumer ${name}: ${status.
|
|
1833
|
+
void (async () => {
|
|
1834
|
+
for await (const status of messages.status()) {
|
|
1835
|
+
if (status.type === "heartbeats_missed" && status.count >= 2) {
|
|
1836
|
+
this.logger.warn(`Consumer ${name}: ${status.count} heartbeats missed, restarting`);
|
|
1721
1837
|
messages.stop();
|
|
1722
1838
|
break;
|
|
1723
1839
|
}
|
|
@@ -2097,7 +2213,7 @@ var EventRouter = class {
|
|
|
2097
2213
|
|
|
2098
2214
|
// src/server/routing/rpc.router.ts
|
|
2099
2215
|
import { Logger as Logger10 } from "@nestjs/common";
|
|
2100
|
-
import { headers } from "nats";
|
|
2216
|
+
import { headers } from "@nats-io/transport-node";
|
|
2101
2217
|
import { from as from3, mergeMap as mergeMap2 } from "rxjs";
|
|
2102
2218
|
var RpcRouter = class {
|
|
2103
2219
|
constructor(messageProvider, patternRegistry, connection, codec, eventBus, rpcOptions, ackWaitMap) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@horizon-republic/nestjs-jetstream",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.0",
|
|
4
4
|
"description": "A NestJS transport for NATS with JetStream events, broadcast fan-out, and Core/JetStream RPC.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -57,7 +57,6 @@
|
|
|
57
57
|
"@nestjs/common": "^10.2.0 || ^11.0.0",
|
|
58
58
|
"@nestjs/core": "^10.2.0 || ^11.0.0",
|
|
59
59
|
"@nestjs/microservices": "^10.2.0 || ^11.0.0",
|
|
60
|
-
"nats": "^2.0.0",
|
|
61
60
|
"reflect-metadata": "^0.2.0",
|
|
62
61
|
"rxjs": "^7.8.0"
|
|
63
62
|
},
|
|
@@ -75,12 +74,18 @@
|
|
|
75
74
|
"eslint-plugin-sonarjs": "^4.0.2",
|
|
76
75
|
"eslint-plugin-unused-imports": "^4.4.1",
|
|
77
76
|
"prettier": "^3.8.1",
|
|
77
|
+
"testcontainers": "^11.13.0",
|
|
78
78
|
"tsup": "^8.5.1",
|
|
79
79
|
"tsx": "^4.21.0",
|
|
80
80
|
"typescript": "~5.9.3",
|
|
81
|
-
"typescript-eslint": "^8.
|
|
81
|
+
"typescript-eslint": "^8.58.0",
|
|
82
82
|
"vitest": "^4.1.2"
|
|
83
83
|
},
|
|
84
|
+
"dependencies": {
|
|
85
|
+
"@nats-io/jetstream": "^3.3.1",
|
|
86
|
+
"@nats-io/nuid": "^2.0.3",
|
|
87
|
+
"@nats-io/transport-node": "^3.3.1"
|
|
88
|
+
},
|
|
84
89
|
"scripts": {
|
|
85
90
|
"build": "tsup",
|
|
86
91
|
"build:watch": "tsup --watch",
|