@horizon-republic/nestjs-jetstream 2.5.0 → 2.6.1
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 +441 -263
- package/dist/index.d.cts +159 -51
- package/dist/index.d.ts +159 -51
- package/dist/index.js +440 -264
- package/package.json +6 -6
package/dist/index.cjs
CHANGED
|
@@ -42,9 +42,14 @@ __export(index_exports, {
|
|
|
42
42
|
JetstreamRecordBuilder: () => JetstreamRecordBuilder,
|
|
43
43
|
JetstreamStrategy: () => JetstreamStrategy,
|
|
44
44
|
JsonCodec: () => JsonCodec,
|
|
45
|
+
MessageKind: () => MessageKind,
|
|
46
|
+
PatternPrefix: () => PatternPrefix,
|
|
45
47
|
RpcContext: () => RpcContext,
|
|
48
|
+
StreamKind: () => StreamKind,
|
|
46
49
|
TransportEvent: () => TransportEvent,
|
|
47
50
|
getClientToken: () => getClientToken,
|
|
51
|
+
isCoreRpcMode: () => isCoreRpcMode,
|
|
52
|
+
isJetStreamRpcMode: () => isJetStreamRpcMode,
|
|
48
53
|
toNanos: () => toNanos
|
|
49
54
|
});
|
|
50
55
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -58,6 +63,11 @@ var import_microservices = require("@nestjs/microservices");
|
|
|
58
63
|
var import_nats2 = require("nats");
|
|
59
64
|
|
|
60
65
|
// src/interfaces/hooks.interface.ts
|
|
66
|
+
var MessageKind = /* @__PURE__ */ ((MessageKind2) => {
|
|
67
|
+
MessageKind2["Event"] = "event";
|
|
68
|
+
MessageKind2["Rpc"] = "rpc";
|
|
69
|
+
return MessageKind2;
|
|
70
|
+
})(MessageKind || {});
|
|
61
71
|
var TransportEvent = /* @__PURE__ */ ((TransportEvent2) => {
|
|
62
72
|
TransportEvent2["Connect"] = "connect";
|
|
63
73
|
TransportEvent2["Disconnect"] = "disconnect";
|
|
@@ -71,6 +81,15 @@ var TransportEvent = /* @__PURE__ */ ((TransportEvent2) => {
|
|
|
71
81
|
return TransportEvent2;
|
|
72
82
|
})(TransportEvent || {});
|
|
73
83
|
|
|
84
|
+
// src/interfaces/stream.interface.ts
|
|
85
|
+
var StreamKind = /* @__PURE__ */ ((StreamKind2) => {
|
|
86
|
+
StreamKind2["Event"] = "ev";
|
|
87
|
+
StreamKind2["Command"] = "cmd";
|
|
88
|
+
StreamKind2["Broadcast"] = "broadcast";
|
|
89
|
+
StreamKind2["Ordered"] = "ordered";
|
|
90
|
+
return StreamKind2;
|
|
91
|
+
})(StreamKind || {});
|
|
92
|
+
|
|
74
93
|
// src/jetstream.constants.ts
|
|
75
94
|
var import_nats = require("nats");
|
|
76
95
|
var JETSTREAM_OPTIONS = /* @__PURE__ */ Symbol("JETSTREAM_OPTIONS");
|
|
@@ -95,7 +114,7 @@ var baseStreamConfig = {
|
|
|
95
114
|
num_replicas: 1,
|
|
96
115
|
discard: import_nats.DiscardPolicy.Old,
|
|
97
116
|
allow_direct: true,
|
|
98
|
-
compression: import_nats.StoreCompression.
|
|
117
|
+
compression: import_nats.StoreCompression.S2
|
|
99
118
|
};
|
|
100
119
|
var DEFAULT_EVENT_STREAM_CONFIG = {
|
|
101
120
|
...baseStreamConfig,
|
|
@@ -187,13 +206,20 @@ var internalName = (name) => `${name}__microservice`;
|
|
|
187
206
|
var buildSubject = (serviceName, kind, pattern) => `${internalName(serviceName)}.${kind}.${pattern}`;
|
|
188
207
|
var buildBroadcastSubject = (pattern) => `broadcast.${pattern}`;
|
|
189
208
|
var streamName = (serviceName, kind) => {
|
|
190
|
-
if (kind === "broadcast") return "broadcast-stream";
|
|
209
|
+
if (kind === "broadcast" /* Broadcast */) return "broadcast-stream";
|
|
191
210
|
return `${internalName(serviceName)}_${kind}-stream`;
|
|
192
211
|
};
|
|
193
212
|
var consumerName = (serviceName, kind) => {
|
|
194
|
-
if (kind === "broadcast") return `${internalName(serviceName)}_broadcast-consumer`;
|
|
213
|
+
if (kind === "broadcast" /* Broadcast */) return `${internalName(serviceName)}_broadcast-consumer`;
|
|
195
214
|
return `${internalName(serviceName)}_${kind}-consumer`;
|
|
196
215
|
};
|
|
216
|
+
var PatternPrefix = /* @__PURE__ */ ((PatternPrefix2) => {
|
|
217
|
+
PatternPrefix2["Broadcast"] = "broadcast:";
|
|
218
|
+
PatternPrefix2["Ordered"] = "ordered:";
|
|
219
|
+
return PatternPrefix2;
|
|
220
|
+
})(PatternPrefix || {});
|
|
221
|
+
var isJetStreamRpcMode = (rpc) => rpc?.mode === "jetstream";
|
|
222
|
+
var isCoreRpcMode = (rpc) => !rpc || rpc.mode === "core";
|
|
197
223
|
|
|
198
224
|
// src/client/jetstream.record.ts
|
|
199
225
|
var JetstreamRecord = class {
|
|
@@ -308,10 +334,13 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
308
334
|
this.codec = codec;
|
|
309
335
|
this.eventBus = eventBus;
|
|
310
336
|
this.targetName = targetServiceName;
|
|
337
|
+
this.callerName = internalName(this.rootOptions.name);
|
|
311
338
|
}
|
|
312
339
|
logger = new import_common.Logger("Jetstream:Client");
|
|
313
340
|
/** Target service name this client sends messages to. */
|
|
314
341
|
targetName;
|
|
342
|
+
/** Pre-cached caller name derived from rootOptions.name, computed once in constructor. */
|
|
343
|
+
callerName;
|
|
315
344
|
/** Shared inbox for JetStream-mode RPC responses. */
|
|
316
345
|
inbox = null;
|
|
317
346
|
inboxSubscription = null;
|
|
@@ -331,7 +360,7 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
331
360
|
*/
|
|
332
361
|
async connect() {
|
|
333
362
|
const nc = await this.connection.getConnection();
|
|
334
|
-
if (this.
|
|
363
|
+
if (isJetStreamRpcMode(this.rootOptions.rpc) && !this.inboxSubscription) {
|
|
335
364
|
this.setupInbox(nc);
|
|
336
365
|
}
|
|
337
366
|
this.statusSubscription ??= this.connection.status$.subscribe((status) => {
|
|
@@ -366,11 +395,11 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
366
395
|
* depending on the subject prefix.
|
|
367
396
|
*/
|
|
368
397
|
async dispatchEvent(packet) {
|
|
369
|
-
|
|
398
|
+
await this.connect();
|
|
370
399
|
const { data, hdrs, messageId } = this.extractRecordData(packet.data);
|
|
371
400
|
const subject = this.buildEventSubject(packet.pattern);
|
|
372
401
|
const msgHeaders = this.buildHeaders(hdrs, { subject });
|
|
373
|
-
const ack = await
|
|
402
|
+
const ack = await this.connection.getJetStreamClient().publish(subject, this.codec.encode(data), {
|
|
374
403
|
headers: msgHeaders,
|
|
375
404
|
msgID: messageId ?? crypto.randomUUID()
|
|
376
405
|
});
|
|
@@ -386,26 +415,23 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
386
415
|
* JetStream mode: publishes to stream + waits for inbox response.
|
|
387
416
|
*/
|
|
388
417
|
publish(packet, callback) {
|
|
389
|
-
const subject = buildSubject(this.targetName, "cmd"
|
|
418
|
+
const subject = buildSubject(this.targetName, "cmd" /* Command */, packet.pattern);
|
|
390
419
|
const { data, hdrs, timeout, messageId } = this.extractRecordData(packet.data);
|
|
391
420
|
const onUnhandled = (err) => {
|
|
392
421
|
this.logger.error("Unhandled publish error:", err);
|
|
393
422
|
callback({ err: new Error("Internal transport error"), response: null, isDisposed: true });
|
|
394
423
|
};
|
|
395
424
|
let jetStreamCorrelationId = null;
|
|
396
|
-
if (this.
|
|
425
|
+
if (isCoreRpcMode(this.rootOptions.rpc)) {
|
|
397
426
|
this.publishCoreRpc(subject, data, hdrs, timeout, callback).catch(onUnhandled);
|
|
398
427
|
} else {
|
|
399
428
|
jetStreamCorrelationId = crypto.randomUUID();
|
|
400
|
-
this.publishJetStreamRpc(
|
|
401
|
-
|
|
402
|
-
data,
|
|
403
|
-
hdrs,
|
|
429
|
+
this.publishJetStreamRpc(subject, data, callback, {
|
|
430
|
+
headers: hdrs,
|
|
404
431
|
timeout,
|
|
405
|
-
|
|
406
|
-
jetStreamCorrelationId,
|
|
432
|
+
correlationId: jetStreamCorrelationId,
|
|
407
433
|
messageId
|
|
408
|
-
).catch(onUnhandled);
|
|
434
|
+
}).catch(onUnhandled);
|
|
409
435
|
}
|
|
410
436
|
return () => {
|
|
411
437
|
if (jetStreamCorrelationId) {
|
|
@@ -442,35 +468,46 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
442
468
|
}
|
|
443
469
|
}
|
|
444
470
|
/** JetStream mode: publish to stream + wait for inbox response. */
|
|
445
|
-
async publishJetStreamRpc(subject, data,
|
|
446
|
-
const
|
|
471
|
+
async publishJetStreamRpc(subject, data, callback, options) {
|
|
472
|
+
const { headers: customHeaders, correlationId, messageId } = options;
|
|
473
|
+
const effectiveTimeout = options.timeout ?? this.getRpcTimeout();
|
|
447
474
|
this.pendingMessages.set(correlationId, callback);
|
|
448
|
-
const timeoutId = setTimeout(() => {
|
|
449
|
-
if (!this.pendingMessages.has(correlationId)) return;
|
|
450
|
-
this.pendingTimeouts.delete(correlationId);
|
|
451
|
-
this.pendingMessages.delete(correlationId);
|
|
452
|
-
this.logger.error(`JetStream RPC timeout (${effectiveTimeout}ms): ${subject}`);
|
|
453
|
-
this.eventBus.emit("rpcTimeout" /* RpcTimeout */, subject, correlationId);
|
|
454
|
-
callback({ err: new Error("RPC timeout"), response: null, isDisposed: true });
|
|
455
|
-
}, effectiveTimeout);
|
|
456
|
-
this.pendingTimeouts.set(correlationId, timeoutId);
|
|
457
475
|
try {
|
|
458
|
-
|
|
476
|
+
await this.connect();
|
|
477
|
+
if (!this.pendingMessages.has(correlationId)) return;
|
|
459
478
|
if (!this.inbox) {
|
|
460
|
-
|
|
479
|
+
this.pendingMessages.delete(correlationId);
|
|
480
|
+
callback({
|
|
481
|
+
err: new Error("Inbox not initialized \u2014 JetStream RPC mode requires a connected inbox"),
|
|
482
|
+
response: null,
|
|
483
|
+
isDisposed: true
|
|
484
|
+
});
|
|
485
|
+
return;
|
|
461
486
|
}
|
|
487
|
+
const timeoutId = setTimeout(() => {
|
|
488
|
+
if (!this.pendingMessages.has(correlationId)) return;
|
|
489
|
+
this.pendingTimeouts.delete(correlationId);
|
|
490
|
+
this.pendingMessages.delete(correlationId);
|
|
491
|
+
this.logger.error(`JetStream RPC timeout (${effectiveTimeout}ms): ${subject}`);
|
|
492
|
+
this.eventBus.emit("rpcTimeout" /* RpcTimeout */, subject, correlationId);
|
|
493
|
+
callback({ err: new Error("RPC timeout"), response: null, isDisposed: true });
|
|
494
|
+
}, effectiveTimeout);
|
|
495
|
+
this.pendingTimeouts.set(correlationId, timeoutId);
|
|
462
496
|
const hdrs = this.buildHeaders(customHeaders, {
|
|
463
497
|
subject,
|
|
464
498
|
correlationId,
|
|
465
499
|
replyTo: this.inbox
|
|
466
500
|
});
|
|
467
|
-
await
|
|
501
|
+
await this.connection.getJetStreamClient().publish(subject, this.codec.encode(data), {
|
|
468
502
|
headers: hdrs,
|
|
469
503
|
msgID: messageId ?? crypto.randomUUID()
|
|
470
504
|
});
|
|
471
505
|
} catch (err) {
|
|
472
|
-
|
|
473
|
-
|
|
506
|
+
const existingTimeout = this.pendingTimeouts.get(correlationId);
|
|
507
|
+
if (existingTimeout) {
|
|
508
|
+
clearTimeout(existingTimeout);
|
|
509
|
+
this.pendingTimeouts.delete(correlationId);
|
|
510
|
+
}
|
|
474
511
|
if (!this.pendingMessages.has(correlationId)) return;
|
|
475
512
|
this.pendingMessages.delete(correlationId);
|
|
476
513
|
const error = err instanceof Error ? err : new Error("Unknown error");
|
|
@@ -546,19 +583,23 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
546
583
|
}
|
|
547
584
|
/** Build event subject — workqueue, broadcast, or ordered. */
|
|
548
585
|
buildEventSubject(pattern) {
|
|
549
|
-
if (pattern.startsWith("broadcast:")) {
|
|
550
|
-
return buildBroadcastSubject(pattern.slice("broadcast:"
|
|
551
|
-
}
|
|
552
|
-
if (pattern.startsWith("ordered:")) {
|
|
553
|
-
return buildSubject(
|
|
586
|
+
if (pattern.startsWith("broadcast:" /* Broadcast */)) {
|
|
587
|
+
return buildBroadcastSubject(pattern.slice("broadcast:" /* Broadcast */.length));
|
|
588
|
+
}
|
|
589
|
+
if (pattern.startsWith("ordered:" /* Ordered */)) {
|
|
590
|
+
return buildSubject(
|
|
591
|
+
this.targetName,
|
|
592
|
+
"ordered" /* Ordered */,
|
|
593
|
+
pattern.slice("ordered:" /* Ordered */.length)
|
|
594
|
+
);
|
|
554
595
|
}
|
|
555
|
-
return buildSubject(this.targetName, "ev"
|
|
596
|
+
return buildSubject(this.targetName, "ev" /* Event */, pattern);
|
|
556
597
|
}
|
|
557
598
|
/** Build NATS headers merging custom headers with transport headers. */
|
|
558
599
|
buildHeaders(customHeaders, transport) {
|
|
559
600
|
const hdrs = (0, import_nats2.headers)();
|
|
560
601
|
hdrs.set("x-subject" /* Subject */, transport.subject);
|
|
561
|
-
hdrs.set("x-caller-name" /* CallerName */,
|
|
602
|
+
hdrs.set("x-caller-name" /* CallerName */, this.callerName);
|
|
562
603
|
if (transport.correlationId) {
|
|
563
604
|
hdrs.set("x-correlation-id" /* CorrelationId */, transport.correlationId);
|
|
564
605
|
}
|
|
@@ -584,15 +625,9 @@ var JetstreamClient = class extends import_microservices.ClientProxy {
|
|
|
584
625
|
}
|
|
585
626
|
return { data: rawData, hdrs: null, timeout: void 0, messageId: void 0 };
|
|
586
627
|
}
|
|
587
|
-
isCoreRpcMode() {
|
|
588
|
-
return !this.rootOptions.rpc || this.rootOptions.rpc.mode === "core";
|
|
589
|
-
}
|
|
590
|
-
isJetStreamRpcMode() {
|
|
591
|
-
return this.rootOptions.rpc?.mode === "jetstream";
|
|
592
|
-
}
|
|
593
628
|
getRpcTimeout() {
|
|
594
629
|
if (!this.rootOptions.rpc) return DEFAULT_RPC_TIMEOUT;
|
|
595
|
-
const defaultTimeout = this.
|
|
630
|
+
const defaultTimeout = isJetStreamRpcMode(this.rootOptions.rpc) ? DEFAULT_JETSTREAM_RPC_TIMEOUT : DEFAULT_RPC_TIMEOUT;
|
|
596
631
|
return this.rootOptions.rpc.timeout ?? defaultTimeout;
|
|
597
632
|
}
|
|
598
633
|
};
|
|
@@ -613,6 +648,10 @@ var JsonCodec = class {
|
|
|
613
648
|
var import_common2 = require("@nestjs/common");
|
|
614
649
|
var import_nats4 = require("nats");
|
|
615
650
|
var import_rxjs = require("rxjs");
|
|
651
|
+
var DEFAULT_OPTIONS = {
|
|
652
|
+
maxReconnectAttempts: -1,
|
|
653
|
+
reconnectTimeWait: 1e3
|
|
654
|
+
};
|
|
616
655
|
var ConnectionProvider = class {
|
|
617
656
|
constructor(options, eventBus) {
|
|
618
657
|
this.options = options;
|
|
@@ -632,6 +671,7 @@ var ConnectionProvider = class {
|
|
|
632
671
|
logger = new import_common2.Logger("Jetstream:Connection");
|
|
633
672
|
connection = null;
|
|
634
673
|
connectionPromise = null;
|
|
674
|
+
jsClient = null;
|
|
635
675
|
jsmInstance = null;
|
|
636
676
|
jsmPromise = null;
|
|
637
677
|
/**
|
|
@@ -670,6 +710,22 @@ var ConnectionProvider = class {
|
|
|
670
710
|
});
|
|
671
711
|
return this.jsmPromise;
|
|
672
712
|
}
|
|
713
|
+
/**
|
|
714
|
+
* Get a cached JetStream client.
|
|
715
|
+
*
|
|
716
|
+
* Invalidated automatically on reconnect and shutdown so consumers always
|
|
717
|
+
* operate against the live connection.
|
|
718
|
+
*
|
|
719
|
+
* @returns The cached JetStreamClient.
|
|
720
|
+
* @throws Error if the connection has not been established yet.
|
|
721
|
+
*/
|
|
722
|
+
getJetStreamClient() {
|
|
723
|
+
if (!this.connection || this.connection.isClosed()) {
|
|
724
|
+
throw new Error("Not connected \u2014 call getConnection() before getJetStreamClient()");
|
|
725
|
+
}
|
|
726
|
+
this.jsClient ??= this.connection.jetstream();
|
|
727
|
+
return this.jsClient;
|
|
728
|
+
}
|
|
673
729
|
/** Direct access to the raw NATS connection, or `null` if not yet connected. */
|
|
674
730
|
get unwrap() {
|
|
675
731
|
return this.connection;
|
|
@@ -698,6 +754,7 @@ var ConnectionProvider = class {
|
|
|
698
754
|
} finally {
|
|
699
755
|
this.connection = null;
|
|
700
756
|
this.connectionPromise = null;
|
|
757
|
+
this.jsClient = null;
|
|
701
758
|
this.jsmInstance = null;
|
|
702
759
|
this.jsmPromise = null;
|
|
703
760
|
}
|
|
@@ -707,6 +764,7 @@ var ConnectionProvider = class {
|
|
|
707
764
|
const name = internalName(this.options.name);
|
|
708
765
|
try {
|
|
709
766
|
const nc = await (0, import_nats4.connect)({
|
|
767
|
+
...DEFAULT_OPTIONS,
|
|
710
768
|
...this.options.connectionOptions,
|
|
711
769
|
servers: this.options.servers,
|
|
712
770
|
name
|
|
@@ -732,6 +790,7 @@ var ConnectionProvider = class {
|
|
|
732
790
|
this.eventBus.emit("disconnect" /* Disconnect */);
|
|
733
791
|
break;
|
|
734
792
|
case import_nats4.Events.Reconnect:
|
|
793
|
+
this.jsClient = null;
|
|
735
794
|
this.jsmInstance = null;
|
|
736
795
|
this.jsmPromise = null;
|
|
737
796
|
this.eventBus.emit("reconnect" /* Reconnect */, nc.getServer());
|
|
@@ -850,7 +909,7 @@ JetstreamHealthIndicator = __decorateClass([
|
|
|
850
909
|
// src/server/strategy.ts
|
|
851
910
|
var import_microservices2 = require("@nestjs/microservices");
|
|
852
911
|
var JetstreamStrategy = class extends import_microservices2.Server {
|
|
853
|
-
constructor(options, connection, patternRegistry, streamProvider, consumerProvider, messageProvider, eventRouter, rpcRouter, coreRpcServer) {
|
|
912
|
+
constructor(options, connection, patternRegistry, streamProvider, consumerProvider, messageProvider, eventRouter, rpcRouter, coreRpcServer, ackWaitMap = /* @__PURE__ */ new Map()) {
|
|
854
913
|
super();
|
|
855
914
|
this.options = options;
|
|
856
915
|
this.connection = connection;
|
|
@@ -861,6 +920,7 @@ var JetstreamStrategy = class extends import_microservices2.Server {
|
|
|
861
920
|
this.eventRouter = eventRouter;
|
|
862
921
|
this.rpcRouter = rpcRouter;
|
|
863
922
|
this.coreRpcServer = coreRpcServer;
|
|
923
|
+
this.ackWaitMap = ackWaitMap;
|
|
864
924
|
}
|
|
865
925
|
transportId = /* @__PURE__ */ Symbol("jetstream-transport");
|
|
866
926
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
@@ -878,17 +938,17 @@ var JetstreamStrategy = class extends import_microservices2.Server {
|
|
|
878
938
|
}
|
|
879
939
|
this.started = true;
|
|
880
940
|
this.patternRegistry.registerHandlers(this.getHandlers());
|
|
881
|
-
const streamKinds = this.
|
|
882
|
-
const durableKinds = this.resolveDurableConsumerKinds();
|
|
941
|
+
const { streams: streamKinds, durableConsumers: durableKinds } = this.resolveRequiredKinds();
|
|
883
942
|
if (streamKinds.length > 0) {
|
|
884
943
|
await this.streamProvider.ensureStreams(streamKinds);
|
|
885
944
|
if (durableKinds.length > 0) {
|
|
886
945
|
const consumers = await this.consumerProvider.ensureConsumers(durableKinds);
|
|
946
|
+
this.populateAckWaitMap(consumers);
|
|
887
947
|
this.eventRouter.updateMaxDeliverMap(this.buildMaxDeliverMap(consumers));
|
|
888
948
|
this.messageProvider.start(consumers);
|
|
889
949
|
}
|
|
890
950
|
if (this.patternRegistry.hasOrderedHandlers()) {
|
|
891
|
-
const orderedStreamName = this.streamProvider.getStreamName("ordered");
|
|
951
|
+
const orderedStreamName = this.streamProvider.getStreamName("ordered" /* Ordered */);
|
|
892
952
|
await this.messageProvider.startOrdered(
|
|
893
953
|
orderedStreamName,
|
|
894
954
|
this.patternRegistry.getOrderedSubjects(),
|
|
@@ -898,11 +958,11 @@ var JetstreamStrategy = class extends import_microservices2.Server {
|
|
|
898
958
|
if (this.patternRegistry.hasEventHandlers() || this.patternRegistry.hasBroadcastHandlers() || this.patternRegistry.hasOrderedHandlers()) {
|
|
899
959
|
this.eventRouter.start();
|
|
900
960
|
}
|
|
901
|
-
if (this.
|
|
961
|
+
if (isJetStreamRpcMode(this.options.rpc) && this.patternRegistry.hasRpcHandlers()) {
|
|
902
962
|
this.rpcRouter.start();
|
|
903
963
|
}
|
|
904
964
|
}
|
|
905
|
-
if (this.
|
|
965
|
+
if (isCoreRpcMode(this.options.rpc) && this.patternRegistry.hasRpcHandlers()) {
|
|
906
966
|
await this.coreRpcServer.start();
|
|
907
967
|
}
|
|
908
968
|
callback();
|
|
@@ -943,36 +1003,34 @@ var JetstreamStrategy = class extends import_microservices2.Server {
|
|
|
943
1003
|
getPatternRegistry() {
|
|
944
1004
|
return this.patternRegistry;
|
|
945
1005
|
}
|
|
946
|
-
/** Determine which
|
|
947
|
-
|
|
948
|
-
const
|
|
1006
|
+
/** Determine which streams and durable consumers are needed. */
|
|
1007
|
+
resolveRequiredKinds() {
|
|
1008
|
+
const streams = [];
|
|
1009
|
+
const durableConsumers = [];
|
|
949
1010
|
if (this.patternRegistry.hasEventHandlers()) {
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
if (this.patternRegistry.hasOrderedHandlers()) {
|
|
953
|
-
kinds.push("ordered");
|
|
1011
|
+
streams.push("ev" /* Event */);
|
|
1012
|
+
durableConsumers.push("ev" /* Event */);
|
|
954
1013
|
}
|
|
955
|
-
if (this.
|
|
956
|
-
|
|
1014
|
+
if (isJetStreamRpcMode(this.options.rpc) && this.patternRegistry.hasRpcHandlers()) {
|
|
1015
|
+
streams.push("cmd" /* Command */);
|
|
1016
|
+
durableConsumers.push("cmd" /* Command */);
|
|
957
1017
|
}
|
|
958
1018
|
if (this.patternRegistry.hasBroadcastHandlers()) {
|
|
959
|
-
|
|
1019
|
+
streams.push("broadcast" /* Broadcast */);
|
|
1020
|
+
durableConsumers.push("broadcast" /* Broadcast */);
|
|
960
1021
|
}
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
/** Determine which stream kinds need durable consumers (ordered consumers are ephemeral). */
|
|
964
|
-
resolveDurableConsumerKinds() {
|
|
965
|
-
const kinds = [];
|
|
966
|
-
if (this.patternRegistry.hasEventHandlers()) {
|
|
967
|
-
kinds.push("ev");
|
|
968
|
-
}
|
|
969
|
-
if (this.isJetStreamRpcMode() && this.patternRegistry.hasRpcHandlers()) {
|
|
970
|
-
kinds.push("cmd");
|
|
1022
|
+
if (this.patternRegistry.hasOrderedHandlers()) {
|
|
1023
|
+
streams.push("ordered" /* Ordered */);
|
|
971
1024
|
}
|
|
972
|
-
|
|
973
|
-
|
|
1025
|
+
return { streams, durableConsumers };
|
|
1026
|
+
}
|
|
1027
|
+
/** Populate the shared ack_wait map from actual NATS consumer configs. */
|
|
1028
|
+
populateAckWaitMap(consumers) {
|
|
1029
|
+
for (const [kind, info] of consumers) {
|
|
1030
|
+
if (info.config.ack_wait) {
|
|
1031
|
+
this.ackWaitMap.set(kind, info.config.ack_wait);
|
|
1032
|
+
}
|
|
974
1033
|
}
|
|
975
|
-
return kinds;
|
|
976
1034
|
}
|
|
977
1035
|
/** Build max_deliver map from actual NATS consumer configs (not options). */
|
|
978
1036
|
buildMaxDeliverMap(consumers) {
|
|
@@ -986,12 +1044,6 @@ var JetstreamStrategy = class extends import_microservices2.Server {
|
|
|
986
1044
|
}
|
|
987
1045
|
return map;
|
|
988
1046
|
}
|
|
989
|
-
isCoreRpcMode() {
|
|
990
|
-
return !this.options.rpc || this.options.rpc.mode === "core";
|
|
991
|
-
}
|
|
992
|
-
isJetStreamRpcMode() {
|
|
993
|
-
return this.options.rpc?.mode === "jetstream";
|
|
994
|
-
}
|
|
995
1047
|
};
|
|
996
1048
|
|
|
997
1049
|
// src/server/core-rpc.server.ts
|
|
@@ -1037,6 +1089,32 @@ var RpcContext = class extends import_microservices3.BaseRpcContext {
|
|
|
1037
1089
|
}
|
|
1038
1090
|
};
|
|
1039
1091
|
|
|
1092
|
+
// src/utils/ack-extension.ts
|
|
1093
|
+
var DEFAULT_ACK_EXTENSION_INTERVAL = 5e3;
|
|
1094
|
+
var MIN_ACK_EXTENSION_INTERVAL = 500;
|
|
1095
|
+
var resolveAckExtensionInterval = (config, ackWaitNanos) => {
|
|
1096
|
+
if (config === false || config === void 0) return null;
|
|
1097
|
+
if (typeof config === "number") {
|
|
1098
|
+
if (!Number.isFinite(config) || config <= 0) return null;
|
|
1099
|
+
return Math.floor(config);
|
|
1100
|
+
}
|
|
1101
|
+
if (!ackWaitNanos) return DEFAULT_ACK_EXTENSION_INTERVAL;
|
|
1102
|
+
const interval = Math.floor(ackWaitNanos / 1e6 / 2);
|
|
1103
|
+
return Math.max(interval, MIN_ACK_EXTENSION_INTERVAL);
|
|
1104
|
+
};
|
|
1105
|
+
var startAckExtensionTimer = (msg, interval) => {
|
|
1106
|
+
if (interval === null || interval <= 0) return null;
|
|
1107
|
+
const timer2 = setInterval(() => {
|
|
1108
|
+
try {
|
|
1109
|
+
msg.working();
|
|
1110
|
+
} catch {
|
|
1111
|
+
}
|
|
1112
|
+
}, interval);
|
|
1113
|
+
return () => {
|
|
1114
|
+
clearInterval(timer2);
|
|
1115
|
+
};
|
|
1116
|
+
};
|
|
1117
|
+
|
|
1040
1118
|
// src/utils/serialize-error.ts
|
|
1041
1119
|
var import_microservices4 = require("@nestjs/microservices");
|
|
1042
1120
|
var serializeError = (err) => {
|
|
@@ -1068,7 +1146,12 @@ var subscribeToFirst = (obs) => new Promise((resolve, reject) => {
|
|
|
1068
1146
|
subscription?.unsubscribe();
|
|
1069
1147
|
}
|
|
1070
1148
|
},
|
|
1071
|
-
error:
|
|
1149
|
+
error: (err) => {
|
|
1150
|
+
if (!done) {
|
|
1151
|
+
done = true;
|
|
1152
|
+
reject(err);
|
|
1153
|
+
}
|
|
1154
|
+
},
|
|
1072
1155
|
complete: () => {
|
|
1073
1156
|
if (!done) resolve(void 0);
|
|
1074
1157
|
}
|
|
@@ -1118,13 +1201,17 @@ var CoreRpcServer = class {
|
|
|
1118
1201
|
}
|
|
1119
1202
|
/** Handle an incoming Core NATS request. */
|
|
1120
1203
|
async handleRequest(msg) {
|
|
1204
|
+
if (!msg.reply) {
|
|
1205
|
+
this.logger.warn(`Ignoring fire-and-forget message on RPC subject: ${msg.subject}`);
|
|
1206
|
+
return;
|
|
1207
|
+
}
|
|
1121
1208
|
const handler = this.patternRegistry.getHandler(msg.subject);
|
|
1122
1209
|
if (!handler) {
|
|
1123
1210
|
this.logger.warn(`No handler for Core RPC: ${msg.subject}`);
|
|
1124
1211
|
this.respondWithError(msg, new Error(`No handler for subject: ${msg.subject}`));
|
|
1125
1212
|
return;
|
|
1126
1213
|
}
|
|
1127
|
-
this.eventBus.emit("messageRouted" /* MessageRouted */, msg.subject, "rpc");
|
|
1214
|
+
this.eventBus.emit("messageRouted" /* MessageRouted */, msg.subject, "rpc" /* Rpc */);
|
|
1128
1215
|
let data;
|
|
1129
1216
|
try {
|
|
1130
1217
|
data = this.codec.decode(msg.data);
|
|
@@ -1182,14 +1269,14 @@ var StreamProvider = class {
|
|
|
1182
1269
|
getSubjects(kind) {
|
|
1183
1270
|
const name = internalName(this.options.name);
|
|
1184
1271
|
switch (kind) {
|
|
1185
|
-
case "ev"
|
|
1186
|
-
return [`${name}
|
|
1187
|
-
case "cmd"
|
|
1188
|
-
return [`${name}
|
|
1189
|
-
case "broadcast"
|
|
1272
|
+
case "ev" /* Event */:
|
|
1273
|
+
return [`${name}.${"ev" /* Event */}.>`];
|
|
1274
|
+
case "cmd" /* Command */:
|
|
1275
|
+
return [`${name}.${"cmd" /* Command */}.>`];
|
|
1276
|
+
case "broadcast" /* Broadcast */:
|
|
1190
1277
|
return ["broadcast.>"];
|
|
1191
|
-
case "ordered"
|
|
1192
|
-
return [`${name}
|
|
1278
|
+
case "ordered" /* Ordered */:
|
|
1279
|
+
return [`${name}.${"ordered" /* Ordered */}.>`];
|
|
1193
1280
|
}
|
|
1194
1281
|
}
|
|
1195
1282
|
/** Ensure a single stream exists, creating or updating as needed. */
|
|
@@ -1226,26 +1313,26 @@ var StreamProvider = class {
|
|
|
1226
1313
|
/** Get default config for a stream kind. */
|
|
1227
1314
|
getDefaults(kind) {
|
|
1228
1315
|
switch (kind) {
|
|
1229
|
-
case "ev"
|
|
1316
|
+
case "ev" /* Event */:
|
|
1230
1317
|
return DEFAULT_EVENT_STREAM_CONFIG;
|
|
1231
|
-
case "cmd"
|
|
1318
|
+
case "cmd" /* Command */:
|
|
1232
1319
|
return DEFAULT_COMMAND_STREAM_CONFIG;
|
|
1233
|
-
case "broadcast"
|
|
1320
|
+
case "broadcast" /* Broadcast */:
|
|
1234
1321
|
return DEFAULT_BROADCAST_STREAM_CONFIG;
|
|
1235
|
-
case "ordered"
|
|
1322
|
+
case "ordered" /* Ordered */:
|
|
1236
1323
|
return DEFAULT_ORDERED_STREAM_CONFIG;
|
|
1237
1324
|
}
|
|
1238
1325
|
}
|
|
1239
1326
|
/** Get user-provided overrides for a stream kind. */
|
|
1240
1327
|
getOverrides(kind) {
|
|
1241
1328
|
switch (kind) {
|
|
1242
|
-
case "ev"
|
|
1329
|
+
case "ev" /* Event */:
|
|
1243
1330
|
return this.options.events?.stream ?? {};
|
|
1244
|
-
case "cmd"
|
|
1331
|
+
case "cmd" /* Command */:
|
|
1245
1332
|
return this.options.rpc?.mode === "jetstream" ? this.options.rpc.stream ?? {} : {};
|
|
1246
|
-
case "broadcast"
|
|
1333
|
+
case "broadcast" /* Broadcast */:
|
|
1247
1334
|
return this.options.broadcast?.stream ?? {};
|
|
1248
|
-
case "ordered"
|
|
1335
|
+
case "ordered" /* Ordered */:
|
|
1249
1336
|
return this.options.ordered?.stream ?? {};
|
|
1250
1337
|
}
|
|
1251
1338
|
}
|
|
@@ -1308,7 +1395,7 @@ var ConsumerProvider = class {
|
|
|
1308
1395
|
const serviceName = internalName(this.options.name);
|
|
1309
1396
|
const defaults = this.getDefaults(kind);
|
|
1310
1397
|
const overrides = this.getOverrides(kind);
|
|
1311
|
-
if (kind === "broadcast") {
|
|
1398
|
+
if (kind === "broadcast" /* Broadcast */) {
|
|
1312
1399
|
const broadcastPatterns = this.patternRegistry.getBroadcastPatterns();
|
|
1313
1400
|
if (broadcastPatterns.length === 0) {
|
|
1314
1401
|
throw new Error("Broadcast consumer requested but no broadcast patterns are registered");
|
|
@@ -1330,7 +1417,10 @@ var ConsumerProvider = class {
|
|
|
1330
1417
|
filter_subjects: broadcastPatterns
|
|
1331
1418
|
};
|
|
1332
1419
|
}
|
|
1333
|
-
|
|
1420
|
+
if (kind !== "ev" /* Event */ && kind !== "cmd" /* Command */) {
|
|
1421
|
+
throw new Error(`Unexpected durable consumer kind: ${kind}`);
|
|
1422
|
+
}
|
|
1423
|
+
const filter_subject = `${serviceName}.${kind}.>`;
|
|
1334
1424
|
return {
|
|
1335
1425
|
...defaults,
|
|
1336
1426
|
...overrides,
|
|
@@ -1342,26 +1432,26 @@ var ConsumerProvider = class {
|
|
|
1342
1432
|
/** Get default config for a consumer kind. */
|
|
1343
1433
|
getDefaults(kind) {
|
|
1344
1434
|
switch (kind) {
|
|
1345
|
-
case "ev"
|
|
1435
|
+
case "ev" /* Event */:
|
|
1346
1436
|
return DEFAULT_EVENT_CONSUMER_CONFIG;
|
|
1347
|
-
case "cmd"
|
|
1437
|
+
case "cmd" /* Command */:
|
|
1348
1438
|
return DEFAULT_COMMAND_CONSUMER_CONFIG;
|
|
1349
|
-
case "broadcast"
|
|
1439
|
+
case "broadcast" /* Broadcast */:
|
|
1350
1440
|
return DEFAULT_BROADCAST_CONSUMER_CONFIG;
|
|
1351
|
-
case "ordered"
|
|
1441
|
+
case "ordered" /* Ordered */:
|
|
1352
1442
|
throw new Error("Ordered consumers are ephemeral and should not use durable config");
|
|
1353
1443
|
}
|
|
1354
1444
|
}
|
|
1355
1445
|
/** Get user-provided overrides for a consumer kind. */
|
|
1356
1446
|
getOverrides(kind) {
|
|
1357
1447
|
switch (kind) {
|
|
1358
|
-
case "ev"
|
|
1448
|
+
case "ev" /* Event */:
|
|
1359
1449
|
return this.options.events?.consumer ?? {};
|
|
1360
|
-
case "cmd"
|
|
1450
|
+
case "cmd" /* Command */:
|
|
1361
1451
|
return this.options.rpc?.mode === "jetstream" ? this.options.rpc.consumer ?? {} : {};
|
|
1362
|
-
case "broadcast"
|
|
1452
|
+
case "broadcast" /* Broadcast */:
|
|
1363
1453
|
return this.options.broadcast?.consumer ?? {};
|
|
1364
|
-
case "ordered"
|
|
1454
|
+
case "ordered" /* Ordered */:
|
|
1365
1455
|
throw new Error("Ordered consumers are ephemeral and should not use durable config");
|
|
1366
1456
|
}
|
|
1367
1457
|
}
|
|
@@ -1372,9 +1462,10 @@ var import_common7 = require("@nestjs/common");
|
|
|
1372
1462
|
var import_nats8 = require("nats");
|
|
1373
1463
|
var import_rxjs3 = require("rxjs");
|
|
1374
1464
|
var MessageProvider = class {
|
|
1375
|
-
constructor(connection, eventBus) {
|
|
1465
|
+
constructor(connection, eventBus, consumeOptionsMap = /* @__PURE__ */ new Map()) {
|
|
1376
1466
|
this.connection = connection;
|
|
1377
1467
|
this.eventBus = eventBus;
|
|
1468
|
+
this.consumeOptionsMap = consumeOptionsMap;
|
|
1378
1469
|
}
|
|
1379
1470
|
logger = new import_common7.Logger("Jetstream:Message");
|
|
1380
1471
|
activeIterators = /* @__PURE__ */ new Set();
|
|
@@ -1424,6 +1515,7 @@ var MessageProvider = class {
|
|
|
1424
1515
|
*
|
|
1425
1516
|
* @param streamName - JetStream stream to consume from.
|
|
1426
1517
|
* @param filterSubjects - NATS subjects to filter on.
|
|
1518
|
+
* @param orderedConfig - Optional overrides for ordered consumer options.
|
|
1427
1519
|
*/
|
|
1428
1520
|
async startOrdered(streamName2, filterSubjects, orderedConfig) {
|
|
1429
1521
|
const consumerOpts = { filterSubjects };
|
|
@@ -1449,6 +1541,11 @@ var MessageProvider = class {
|
|
|
1449
1541
|
}
|
|
1450
1542
|
/** Stop all consumer flows and reinitialize subjects for potential restart. */
|
|
1451
1543
|
destroy() {
|
|
1544
|
+
if (this.orderedReadyReject) {
|
|
1545
|
+
this.orderedReadyReject(new Error("Destroyed before ordered consumer connected"));
|
|
1546
|
+
this.orderedReadyResolve = null;
|
|
1547
|
+
this.orderedReadyReject = null;
|
|
1548
|
+
}
|
|
1452
1549
|
this.destroy$.next();
|
|
1453
1550
|
this.destroy$.complete();
|
|
1454
1551
|
for (const messages of this.activeIterators) {
|
|
@@ -1468,42 +1565,17 @@ var MessageProvider = class {
|
|
|
1468
1565
|
/** Create a self-healing consumer flow for a specific kind. */
|
|
1469
1566
|
createFlow(kind, info) {
|
|
1470
1567
|
const target$ = this.getTargetSubject(kind);
|
|
1471
|
-
|
|
1472
|
-
let lastRunFailed = false;
|
|
1473
|
-
return (0, import_rxjs3.defer)(() => this.consumeOnce(info, target$)).pipe(
|
|
1474
|
-
(0, import_rxjs3.tap)(() => {
|
|
1475
|
-
lastRunFailed = false;
|
|
1476
|
-
}),
|
|
1477
|
-
(0, import_rxjs3.catchError)((err) => {
|
|
1478
|
-
consecutiveFailures++;
|
|
1479
|
-
lastRunFailed = true;
|
|
1480
|
-
this.logger.error(`Consumer ${info.name} error, will restart:`, err);
|
|
1481
|
-
this.eventBus.emit(
|
|
1482
|
-
"error" /* Error */,
|
|
1483
|
-
err instanceof Error ? err : new Error(String(err)),
|
|
1484
|
-
"message-provider"
|
|
1485
|
-
);
|
|
1486
|
-
return import_rxjs3.EMPTY;
|
|
1487
|
-
}),
|
|
1488
|
-
(0, import_rxjs3.repeat)({
|
|
1489
|
-
delay: () => {
|
|
1490
|
-
if (!lastRunFailed) {
|
|
1491
|
-
consecutiveFailures = 0;
|
|
1492
|
-
}
|
|
1493
|
-
const delay = Math.min(100 * Math.pow(2, consecutiveFailures), 3e4);
|
|
1494
|
-
this.logger.warn(`Consumer ${info.name} stream ended, restarting in ${delay}ms...`);
|
|
1495
|
-
return (0, import_rxjs3.timer)(delay);
|
|
1496
|
-
}
|
|
1497
|
-
}),
|
|
1498
|
-
(0, import_rxjs3.takeUntil)(this.destroy$)
|
|
1499
|
-
);
|
|
1568
|
+
return this.createSelfHealingFlow(() => this.consumeOnce(kind, info, target$), info.name);
|
|
1500
1569
|
}
|
|
1501
1570
|
/** Single iteration: get consumer -> pull messages -> emit to subject. */
|
|
1502
|
-
async consumeOnce(info, target$) {
|
|
1503
|
-
const js =
|
|
1571
|
+
async consumeOnce(kind, info, target$) {
|
|
1572
|
+
const js = this.connection.getJetStreamClient();
|
|
1504
1573
|
const consumer = await js.consumers.get(info.stream_name, info.name);
|
|
1505
|
-
const
|
|
1574
|
+
const defaults = { idle_heartbeat: 5e3 };
|
|
1575
|
+
const userOptions = this.consumeOptionsMap.get(kind) ?? {};
|
|
1576
|
+
const messages = await consumer.consume({ ...defaults, ...userOptions });
|
|
1506
1577
|
this.activeIterators.add(messages);
|
|
1578
|
+
this.monitorConsumerHealth(messages, info.name);
|
|
1507
1579
|
try {
|
|
1508
1580
|
for await (const msg of messages) {
|
|
1509
1581
|
target$.next(msg);
|
|
@@ -1515,47 +1587,73 @@ var MessageProvider = class {
|
|
|
1515
1587
|
/** Get the target subject for a consumer kind. */
|
|
1516
1588
|
getTargetSubject(kind) {
|
|
1517
1589
|
switch (kind) {
|
|
1518
|
-
case "ev"
|
|
1590
|
+
case "ev" /* Event */:
|
|
1519
1591
|
return this.eventMessages$;
|
|
1520
|
-
case "cmd"
|
|
1592
|
+
case "cmd" /* Command */:
|
|
1521
1593
|
return this.commandMessages$;
|
|
1522
|
-
case "broadcast"
|
|
1594
|
+
case "broadcast" /* Broadcast */:
|
|
1523
1595
|
return this.broadcastMessages$;
|
|
1524
|
-
case "ordered"
|
|
1596
|
+
case "ordered" /* Ordered */:
|
|
1525
1597
|
return this.orderedMessages$;
|
|
1598
|
+
default: {
|
|
1599
|
+
const _exhaustive = kind;
|
|
1600
|
+
throw new Error(`Unknown stream kind: ${_exhaustive}`);
|
|
1601
|
+
}
|
|
1526
1602
|
}
|
|
1527
1603
|
}
|
|
1604
|
+
/** Monitor heartbeats and restart the consumer iterator on prolonged silence. */
|
|
1605
|
+
monitorConsumerHealth(messages, name) {
|
|
1606
|
+
(async () => {
|
|
1607
|
+
for await (const status of await messages.status()) {
|
|
1608
|
+
if (status.type === import_nats8.ConsumerEvents.HeartbeatsMissed && status.data >= 2) {
|
|
1609
|
+
this.logger.warn(`Consumer ${name}: ${status.data} heartbeats missed, restarting`);
|
|
1610
|
+
messages.stop();
|
|
1611
|
+
break;
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
})().catch((err) => {
|
|
1615
|
+
if (err) {
|
|
1616
|
+
this.logger.debug(`Consumer ${name} health monitor ended:`, err);
|
|
1617
|
+
}
|
|
1618
|
+
});
|
|
1619
|
+
}
|
|
1528
1620
|
/** Create a self-healing ordered consumer flow. */
|
|
1529
1621
|
createOrderedFlow(streamName2, consumerOpts) {
|
|
1622
|
+
return this.createSelfHealingFlow(
|
|
1623
|
+
() => this.consumeOrderedOnce(streamName2, consumerOpts),
|
|
1624
|
+
"ordered" /* Ordered */,
|
|
1625
|
+
(err) => {
|
|
1626
|
+
if (this.orderedReadyReject) {
|
|
1627
|
+
this.orderedReadyReject(err);
|
|
1628
|
+
this.orderedReadyReject = null;
|
|
1629
|
+
this.orderedReadyResolve = null;
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
);
|
|
1633
|
+
}
|
|
1634
|
+
/** Shared self-healing flow: defer -> retry with exponential backoff on error/completion. */
|
|
1635
|
+
createSelfHealingFlow(source, label, onFirstError) {
|
|
1530
1636
|
let consecutiveFailures = 0;
|
|
1531
|
-
|
|
1532
|
-
return (0, import_rxjs3.defer)(() => this.consumeOrderedOnce(streamName2, consumerOpts)).pipe(
|
|
1637
|
+
return (0, import_rxjs3.defer)(source).pipe(
|
|
1533
1638
|
(0, import_rxjs3.tap)(() => {
|
|
1534
|
-
|
|
1639
|
+
consecutiveFailures = 0;
|
|
1535
1640
|
}),
|
|
1536
1641
|
(0, import_rxjs3.catchError)((err) => {
|
|
1537
1642
|
consecutiveFailures++;
|
|
1538
|
-
|
|
1539
|
-
this.logger.error("Ordered consumer error, will restart:", err);
|
|
1643
|
+
this.logger.error(`Consumer ${label} error, will restart:`, err);
|
|
1540
1644
|
this.eventBus.emit(
|
|
1541
1645
|
"error" /* Error */,
|
|
1542
1646
|
err instanceof Error ? err : new Error(String(err)),
|
|
1543
1647
|
"message-provider"
|
|
1544
1648
|
);
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
this.orderedReadyReject = null;
|
|
1548
|
-
this.orderedReadyResolve = null;
|
|
1549
|
-
}
|
|
1649
|
+
onFirstError?.(err);
|
|
1650
|
+
onFirstError = void 0;
|
|
1550
1651
|
return import_rxjs3.EMPTY;
|
|
1551
1652
|
}),
|
|
1552
1653
|
(0, import_rxjs3.repeat)({
|
|
1553
1654
|
delay: () => {
|
|
1554
|
-
if (!lastRunFailed) {
|
|
1555
|
-
consecutiveFailures = 0;
|
|
1556
|
-
}
|
|
1557
1655
|
const delay = Math.min(100 * Math.pow(2, consecutiveFailures), 3e4);
|
|
1558
|
-
this.logger.warn(`
|
|
1656
|
+
this.logger.warn(`Consumer ${label} stream ended, restarting in ${delay}ms...`);
|
|
1559
1657
|
return (0, import_rxjs3.timer)(delay);
|
|
1560
1658
|
}
|
|
1561
1659
|
})
|
|
@@ -1563,7 +1661,7 @@ var MessageProvider = class {
|
|
|
1563
1661
|
}
|
|
1564
1662
|
/** Single iteration: create ordered consumer -> iterate messages. */
|
|
1565
1663
|
async consumeOrderedOnce(streamName2, consumerOpts) {
|
|
1566
|
-
const js =
|
|
1664
|
+
const js = this.connection.getJetStreamClient();
|
|
1567
1665
|
const consumer = await js.consumers.get(streamName2, consumerOpts);
|
|
1568
1666
|
const messages = await consumer.consume();
|
|
1569
1667
|
if (this.orderedReadyResolve) {
|
|
@@ -1584,12 +1682,20 @@ var MessageProvider = class {
|
|
|
1584
1682
|
|
|
1585
1683
|
// src/server/routing/pattern-registry.ts
|
|
1586
1684
|
var import_common8 = require("@nestjs/common");
|
|
1685
|
+
var HANDLER_LABELS = {
|
|
1686
|
+
["broadcast" /* Broadcast */]: "broadcast" /* Broadcast */,
|
|
1687
|
+
["ordered" /* Ordered */]: "ordered" /* Ordered */,
|
|
1688
|
+
["ev" /* Event */]: "event" /* Event */,
|
|
1689
|
+
["cmd" /* Command */]: "rpc" /* Rpc */
|
|
1690
|
+
};
|
|
1587
1691
|
var PatternRegistry = class {
|
|
1588
1692
|
constructor(options) {
|
|
1589
1693
|
this.options = options;
|
|
1590
1694
|
}
|
|
1591
1695
|
logger = new import_common8.Logger("Jetstream:PatternRegistry");
|
|
1592
1696
|
registry = /* @__PURE__ */ new Map();
|
|
1697
|
+
// Cached after registerHandlers() — the registry is immutable from that point
|
|
1698
|
+
cachedPatterns = null;
|
|
1593
1699
|
/**
|
|
1594
1700
|
* Register all handlers from the NestJS strategy.
|
|
1595
1701
|
*
|
|
@@ -1607,16 +1713,12 @@ var PatternRegistry = class {
|
|
|
1607
1713
|
`Handler "${pattern}" cannot be both broadcast and ordered. Use one or the other.`
|
|
1608
1714
|
);
|
|
1609
1715
|
}
|
|
1610
|
-
let
|
|
1611
|
-
if (isBroadcast)
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
fullSubject = buildSubject(serviceName, "ev", pattern);
|
|
1617
|
-
} else {
|
|
1618
|
-
fullSubject = buildSubject(serviceName, "cmd", pattern);
|
|
1619
|
-
}
|
|
1716
|
+
let kind;
|
|
1717
|
+
if (isBroadcast) kind = "broadcast" /* Broadcast */;
|
|
1718
|
+
else if (isOrdered) kind = "ordered" /* Ordered */;
|
|
1719
|
+
else if (isEvent) kind = "ev" /* Event */;
|
|
1720
|
+
else kind = "cmd" /* Command */;
|
|
1721
|
+
const fullSubject = kind === "broadcast" /* Broadcast */ ? buildBroadcastSubject(pattern) : buildSubject(serviceName, kind, pattern);
|
|
1620
1722
|
this.registry.set(fullSubject, {
|
|
1621
1723
|
handler,
|
|
1622
1724
|
pattern,
|
|
@@ -1624,18 +1726,9 @@ var PatternRegistry = class {
|
|
|
1624
1726
|
isBroadcast,
|
|
1625
1727
|
isOrdered
|
|
1626
1728
|
});
|
|
1627
|
-
|
|
1628
|
-
if (isBroadcast) {
|
|
1629
|
-
kind = "broadcast";
|
|
1630
|
-
} else if (isOrdered) {
|
|
1631
|
-
kind = "ordered";
|
|
1632
|
-
} else if (isEvent) {
|
|
1633
|
-
kind = "event";
|
|
1634
|
-
} else {
|
|
1635
|
-
kind = "rpc";
|
|
1636
|
-
}
|
|
1637
|
-
this.logger.debug(`Registered ${kind}: ${pattern} -> ${fullSubject}`);
|
|
1729
|
+
this.logger.debug(`Registered ${HANDLER_LABELS[kind]}: ${pattern} -> ${fullSubject}`);
|
|
1638
1730
|
}
|
|
1731
|
+
this.cachedPatterns = this.buildPatternsByKind();
|
|
1639
1732
|
this.logSummary();
|
|
1640
1733
|
}
|
|
1641
1734
|
/** Find handler for a full NATS subject. */
|
|
@@ -1644,33 +1737,53 @@ var PatternRegistry = class {
|
|
|
1644
1737
|
}
|
|
1645
1738
|
/** Get all registered broadcast patterns (for consumer filter_subject setup). */
|
|
1646
1739
|
getBroadcastPatterns() {
|
|
1647
|
-
return
|
|
1740
|
+
return this.getPatternsByKind().broadcasts.map((p) => buildBroadcastSubject(p));
|
|
1648
1741
|
}
|
|
1649
|
-
/** Check if any broadcast handlers are registered. */
|
|
1650
1742
|
hasBroadcastHandlers() {
|
|
1651
|
-
return
|
|
1743
|
+
return this.getPatternsByKind().broadcasts.length > 0;
|
|
1652
1744
|
}
|
|
1653
|
-
/** Check if any RPC (command) handlers are registered. */
|
|
1654
1745
|
hasRpcHandlers() {
|
|
1655
|
-
return
|
|
1656
|
-
(r) => !r.isEvent && !r.isBroadcast && !r.isOrdered
|
|
1657
|
-
);
|
|
1746
|
+
return this.getPatternsByKind().commands.length > 0;
|
|
1658
1747
|
}
|
|
1659
|
-
/** Check if any workqueue event handlers are registered. */
|
|
1660
1748
|
hasEventHandlers() {
|
|
1661
|
-
return
|
|
1749
|
+
return this.getPatternsByKind().events.length > 0;
|
|
1662
1750
|
}
|
|
1663
|
-
/** Check if any ordered event handlers are registered. */
|
|
1664
1751
|
hasOrderedHandlers() {
|
|
1665
|
-
return
|
|
1752
|
+
return this.getPatternsByKind().ordered.length > 0;
|
|
1666
1753
|
}
|
|
1667
1754
|
/** Get fully-qualified NATS subjects for ordered handlers. */
|
|
1668
1755
|
getOrderedSubjects() {
|
|
1669
|
-
|
|
1670
|
-
|
|
1756
|
+
return this.getPatternsByKind().ordered.map(
|
|
1757
|
+
(p) => buildSubject(this.options.name, "ordered" /* Ordered */, p)
|
|
1758
|
+
);
|
|
1671
1759
|
}
|
|
1672
|
-
/** Get patterns grouped by kind. */
|
|
1760
|
+
/** Get patterns grouped by kind (cached after registration). */
|
|
1673
1761
|
getPatternsByKind() {
|
|
1762
|
+
const patterns = this.cachedPatterns ?? this.buildPatternsByKind();
|
|
1763
|
+
return {
|
|
1764
|
+
events: [...patterns.events],
|
|
1765
|
+
commands: [...patterns.commands],
|
|
1766
|
+
broadcasts: [...patterns.broadcasts],
|
|
1767
|
+
ordered: [...patterns.ordered]
|
|
1768
|
+
};
|
|
1769
|
+
}
|
|
1770
|
+
/** Normalize a full NATS subject back to the user-facing pattern. */
|
|
1771
|
+
normalizeSubject(subject) {
|
|
1772
|
+
const name = internalName(this.options.name);
|
|
1773
|
+
const prefixes = [
|
|
1774
|
+
`${name}.${"cmd" /* Command */}.`,
|
|
1775
|
+
`${name}.${"ev" /* Event */}.`,
|
|
1776
|
+
`${name}.${"ordered" /* Ordered */}.`,
|
|
1777
|
+
`${"broadcast" /* Broadcast */}.`
|
|
1778
|
+
];
|
|
1779
|
+
for (const prefix of prefixes) {
|
|
1780
|
+
if (subject.startsWith(prefix)) {
|
|
1781
|
+
return subject.slice(prefix.length);
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
return subject;
|
|
1785
|
+
}
|
|
1786
|
+
buildPatternsByKind() {
|
|
1674
1787
|
const events = [];
|
|
1675
1788
|
const commands = [];
|
|
1676
1789
|
const broadcasts = [];
|
|
@@ -1683,18 +1796,6 @@ var PatternRegistry = class {
|
|
|
1683
1796
|
}
|
|
1684
1797
|
return { events, commands, broadcasts, ordered };
|
|
1685
1798
|
}
|
|
1686
|
-
/** Normalize a full NATS subject back to the user-facing pattern. */
|
|
1687
|
-
normalizeSubject(subject) {
|
|
1688
|
-
const name = internalName(this.options.name);
|
|
1689
|
-
const prefixes = [`${name}.cmd.`, `${name}.ev.`, `${name}.ordered.`, "broadcast."];
|
|
1690
|
-
for (const prefix of prefixes) {
|
|
1691
|
-
if (subject.startsWith(prefix)) {
|
|
1692
|
-
return subject.slice(prefix.length);
|
|
1693
|
-
}
|
|
1694
|
-
}
|
|
1695
|
-
return subject;
|
|
1696
|
-
}
|
|
1697
|
-
/** Log a summary of all registered handlers. */
|
|
1698
1799
|
logSummary() {
|
|
1699
1800
|
const { events, commands, broadcasts, ordered } = this.getPatternsByKind();
|
|
1700
1801
|
const parts = [
|
|
@@ -1713,12 +1814,14 @@ var PatternRegistry = class {
|
|
|
1713
1814
|
var import_common9 = require("@nestjs/common");
|
|
1714
1815
|
var import_rxjs4 = require("rxjs");
|
|
1715
1816
|
var EventRouter = class {
|
|
1716
|
-
constructor(messageProvider, patternRegistry, codec, eventBus, deadLetterConfig) {
|
|
1817
|
+
constructor(messageProvider, patternRegistry, codec, eventBus, deadLetterConfig, processingConfig, ackWaitMap) {
|
|
1717
1818
|
this.messageProvider = messageProvider;
|
|
1718
1819
|
this.patternRegistry = patternRegistry;
|
|
1719
1820
|
this.codec = codec;
|
|
1720
1821
|
this.eventBus = eventBus;
|
|
1721
1822
|
this.deadLetterConfig = deadLetterConfig;
|
|
1823
|
+
this.processingConfig = processingConfig;
|
|
1824
|
+
this.ackWaitMap = ackWaitMap;
|
|
1722
1825
|
}
|
|
1723
1826
|
logger = new import_common9.Logger("Jetstream:EventRouter");
|
|
1724
1827
|
subscriptions = [];
|
|
@@ -1732,10 +1835,10 @@ var EventRouter = class {
|
|
|
1732
1835
|
}
|
|
1733
1836
|
/** Start routing event, broadcast, and ordered messages to handlers. */
|
|
1734
1837
|
start() {
|
|
1735
|
-
this.subscribeToStream(this.messageProvider.events$, "
|
|
1736
|
-
this.subscribeToStream(this.messageProvider.broadcasts$, "broadcast");
|
|
1838
|
+
this.subscribeToStream(this.messageProvider.events$, "ev" /* Event */);
|
|
1839
|
+
this.subscribeToStream(this.messageProvider.broadcasts$, "broadcast" /* Broadcast */);
|
|
1737
1840
|
if (this.patternRegistry.hasOrderedHandlers()) {
|
|
1738
|
-
this.subscribeToStream(this.messageProvider.ordered$, "ordered"
|
|
1841
|
+
this.subscribeToStream(this.messageProvider.ordered$, "ordered" /* Ordered */);
|
|
1739
1842
|
}
|
|
1740
1843
|
}
|
|
1741
1844
|
/** Stop routing and unsubscribe from all streams. */
|
|
@@ -1746,38 +1849,71 @@ var EventRouter = class {
|
|
|
1746
1849
|
this.subscriptions.length = 0;
|
|
1747
1850
|
}
|
|
1748
1851
|
/** Subscribe to a message stream and route each message. */
|
|
1749
|
-
subscribeToStream(stream$,
|
|
1750
|
-
const
|
|
1852
|
+
subscribeToStream(stream$, kind) {
|
|
1853
|
+
const isOrdered = kind === "ordered" /* Ordered */;
|
|
1854
|
+
const ackExtensionInterval = isOrdered ? null : resolveAckExtensionInterval(this.getAckExtensionConfig(kind), this.ackWaitMap?.get(kind));
|
|
1855
|
+
const concurrency = this.getConcurrency(kind);
|
|
1856
|
+
const route = (msg) => (0, import_rxjs4.defer)(
|
|
1857
|
+
() => isOrdered ? this.handleOrdered(msg) : this.handle(msg, ackExtensionInterval)
|
|
1858
|
+
).pipe(
|
|
1751
1859
|
(0, import_rxjs4.catchError)((err) => {
|
|
1752
|
-
this.logger.error(`Unexpected error in ${
|
|
1860
|
+
this.logger.error(`Unexpected error in ${kind} event router`, err);
|
|
1753
1861
|
return import_rxjs4.EMPTY;
|
|
1754
1862
|
})
|
|
1755
1863
|
);
|
|
1756
|
-
const subscription = stream$.pipe(isOrdered ? (0, import_rxjs4.concatMap)(route) : (0, import_rxjs4.mergeMap)(route)).subscribe();
|
|
1864
|
+
const subscription = stream$.pipe(isOrdered ? (0, import_rxjs4.concatMap)(route) : (0, import_rxjs4.mergeMap)(route, concurrency)).subscribe();
|
|
1757
1865
|
this.subscriptions.push(subscription);
|
|
1758
1866
|
}
|
|
1867
|
+
getConcurrency(kind) {
|
|
1868
|
+
if (kind === "ev" /* Event */) return this.processingConfig?.events?.concurrency;
|
|
1869
|
+
if (kind === "broadcast" /* Broadcast */) return this.processingConfig?.broadcast?.concurrency;
|
|
1870
|
+
return void 0;
|
|
1871
|
+
}
|
|
1872
|
+
getAckExtensionConfig(kind) {
|
|
1873
|
+
if (kind === "ev" /* Event */) return this.processingConfig?.events?.ackExtension;
|
|
1874
|
+
if (kind === "broadcast" /* Broadcast */) return this.processingConfig?.broadcast?.ackExtension;
|
|
1875
|
+
return void 0;
|
|
1876
|
+
}
|
|
1759
1877
|
/** Handle a single event message: decode -> execute handler -> ack/nak. */
|
|
1760
|
-
handle(msg) {
|
|
1878
|
+
handle(msg, ackExtensionInterval) {
|
|
1879
|
+
const resolved = this.decodeMessage(msg);
|
|
1880
|
+
if (!resolved) return import_rxjs4.EMPTY;
|
|
1881
|
+
return (0, import_rxjs4.from)(
|
|
1882
|
+
this.executeHandler(resolved.handler, resolved.data, resolved.ctx, msg, ackExtensionInterval)
|
|
1883
|
+
);
|
|
1884
|
+
}
|
|
1885
|
+
/** Handle an ordered message: decode -> execute handler -> no ack/nak. */
|
|
1886
|
+
handleOrdered(msg) {
|
|
1887
|
+
const resolved = this.decodeMessage(msg, true);
|
|
1888
|
+
if (!resolved) return import_rxjs4.EMPTY;
|
|
1889
|
+
return (0, import_rxjs4.from)(
|
|
1890
|
+
unwrapResult(resolved.handler(resolved.data, resolved.ctx)).catch((err) => {
|
|
1891
|
+
this.logger.error(`Ordered handler error (${msg.subject}):`, err);
|
|
1892
|
+
})
|
|
1893
|
+
);
|
|
1894
|
+
}
|
|
1895
|
+
/** Resolve handler, decode payload, and build context. Returns null on failure. */
|
|
1896
|
+
decodeMessage(msg, isOrdered = false) {
|
|
1761
1897
|
const handler = this.patternRegistry.getHandler(msg.subject);
|
|
1762
1898
|
if (!handler) {
|
|
1763
|
-
msg.term(`No handler for event: ${msg.subject}`);
|
|
1764
|
-
this.logger.error(`No handler for
|
|
1765
|
-
return
|
|
1899
|
+
if (!isOrdered) msg.term(`No handler for event: ${msg.subject}`);
|
|
1900
|
+
this.logger.error(`No handler for subject: ${msg.subject}`);
|
|
1901
|
+
return null;
|
|
1766
1902
|
}
|
|
1767
1903
|
let data;
|
|
1768
1904
|
try {
|
|
1769
1905
|
data = this.codec.decode(msg.data);
|
|
1770
1906
|
} catch (err) {
|
|
1771
|
-
msg.term("Decode error");
|
|
1907
|
+
if (!isOrdered) msg.term("Decode error");
|
|
1772
1908
|
this.logger.error(`Decode error for ${msg.subject}:`, err);
|
|
1773
|
-
return
|
|
1909
|
+
return null;
|
|
1774
1910
|
}
|
|
1775
|
-
this.eventBus.emit("messageRouted" /* MessageRouted */, msg.subject, "event");
|
|
1776
|
-
|
|
1777
|
-
return (0, import_rxjs4.from)(this.executeHandler(handler, data, ctx, msg));
|
|
1911
|
+
this.eventBus.emit("messageRouted" /* MessageRouted */, msg.subject, "event" /* Event */);
|
|
1912
|
+
return { handler, data, ctx: new RpcContext([msg]) };
|
|
1778
1913
|
}
|
|
1779
1914
|
/** Execute handler, then ack on success or nak/dead-letter on failure. */
|
|
1780
|
-
async executeHandler(handler, data, ctx, msg) {
|
|
1915
|
+
async executeHandler(handler, data, ctx, msg, ackExtensionInterval) {
|
|
1916
|
+
const stopAckExtension = startAckExtensionTimer(msg, ackExtensionInterval);
|
|
1781
1917
|
try {
|
|
1782
1918
|
await unwrapResult(handler(data, ctx));
|
|
1783
1919
|
msg.ack();
|
|
@@ -1788,30 +1924,10 @@ var EventRouter = class {
|
|
|
1788
1924
|
} else {
|
|
1789
1925
|
msg.nak();
|
|
1790
1926
|
}
|
|
1927
|
+
} finally {
|
|
1928
|
+
stopAckExtension?.();
|
|
1791
1929
|
}
|
|
1792
1930
|
}
|
|
1793
|
-
/** Handle an ordered message: decode -> execute handler -> no ack/nak. */
|
|
1794
|
-
handleOrdered(msg) {
|
|
1795
|
-
const handler = this.patternRegistry.getHandler(msg.subject);
|
|
1796
|
-
if (!handler) {
|
|
1797
|
-
this.logger.error(`No handler for ordered subject: ${msg.subject}`);
|
|
1798
|
-
return import_rxjs4.EMPTY;
|
|
1799
|
-
}
|
|
1800
|
-
let data;
|
|
1801
|
-
try {
|
|
1802
|
-
data = this.codec.decode(msg.data);
|
|
1803
|
-
} catch (err) {
|
|
1804
|
-
this.logger.error(`Decode error for ordered ${msg.subject}:`, err);
|
|
1805
|
-
return import_rxjs4.EMPTY;
|
|
1806
|
-
}
|
|
1807
|
-
this.eventBus.emit("messageRouted" /* MessageRouted */, msg.subject, "event");
|
|
1808
|
-
const ctx = new RpcContext([msg]);
|
|
1809
|
-
return (0, import_rxjs4.from)(
|
|
1810
|
-
unwrapResult(handler(data, ctx)).catch((err) => {
|
|
1811
|
-
this.logger.error(`Ordered handler error (${msg.subject}):`, err);
|
|
1812
|
-
})
|
|
1813
|
-
);
|
|
1814
|
-
}
|
|
1815
1931
|
/** Check if the message has exhausted all delivery attempts. */
|
|
1816
1932
|
isDeadLetter(msg) {
|
|
1817
1933
|
if (!this.deadLetterConfig) return false;
|
|
@@ -1832,7 +1948,10 @@ var EventRouter = class {
|
|
|
1832
1948
|
timestamp: new Date(msg.info.timestampNanos / 1e6).toISOString()
|
|
1833
1949
|
};
|
|
1834
1950
|
this.eventBus.emit("deadLetter" /* DeadLetter */, info);
|
|
1835
|
-
if (!this.deadLetterConfig)
|
|
1951
|
+
if (!this.deadLetterConfig) {
|
|
1952
|
+
msg.term("Dead letter config unavailable");
|
|
1953
|
+
return;
|
|
1954
|
+
}
|
|
1836
1955
|
try {
|
|
1837
1956
|
await this.deadLetterConfig.onDeadLetter(info);
|
|
1838
1957
|
msg.term("Dead letter processed");
|
|
@@ -1848,17 +1967,31 @@ var import_common10 = require("@nestjs/common");
|
|
|
1848
1967
|
var import_nats9 = require("nats");
|
|
1849
1968
|
var import_rxjs5 = require("rxjs");
|
|
1850
1969
|
var RpcRouter = class {
|
|
1851
|
-
constructor(messageProvider, patternRegistry, connection, codec, eventBus,
|
|
1970
|
+
constructor(messageProvider, patternRegistry, connection, codec, eventBus, rpcOptions, ackWaitMap) {
|
|
1852
1971
|
this.messageProvider = messageProvider;
|
|
1853
1972
|
this.patternRegistry = patternRegistry;
|
|
1854
1973
|
this.connection = connection;
|
|
1855
1974
|
this.codec = codec;
|
|
1856
1975
|
this.eventBus = eventBus;
|
|
1857
|
-
this.
|
|
1976
|
+
this.rpcOptions = rpcOptions;
|
|
1977
|
+
this.ackWaitMap = ackWaitMap;
|
|
1978
|
+
this.timeout = rpcOptions?.timeout ?? DEFAULT_JETSTREAM_RPC_TIMEOUT;
|
|
1979
|
+
this.concurrency = rpcOptions?.concurrency;
|
|
1858
1980
|
}
|
|
1859
1981
|
logger = new import_common10.Logger("Jetstream:RpcRouter");
|
|
1860
1982
|
timeout;
|
|
1983
|
+
concurrency;
|
|
1984
|
+
resolvedAckExtensionInterval;
|
|
1861
1985
|
subscription = null;
|
|
1986
|
+
/** Lazily resolve the ack extension interval (needs ackWaitMap populated at runtime). */
|
|
1987
|
+
get ackExtensionInterval() {
|
|
1988
|
+
if (this.resolvedAckExtensionInterval !== void 0) return this.resolvedAckExtensionInterval;
|
|
1989
|
+
this.resolvedAckExtensionInterval = resolveAckExtensionInterval(
|
|
1990
|
+
this.rpcOptions?.ackExtension,
|
|
1991
|
+
this.ackWaitMap?.get("cmd" /* Command */)
|
|
1992
|
+
);
|
|
1993
|
+
return this.resolvedAckExtensionInterval;
|
|
1994
|
+
}
|
|
1862
1995
|
/** Start routing command messages to handlers. */
|
|
1863
1996
|
start() {
|
|
1864
1997
|
this.subscription = this.messageProvider.commands$.pipe(
|
|
@@ -1868,7 +2001,8 @@ var RpcRouter = class {
|
|
|
1868
2001
|
this.logger.error("Unexpected error in RPC router", err);
|
|
1869
2002
|
return import_rxjs5.EMPTY;
|
|
1870
2003
|
})
|
|
1871
|
-
)
|
|
2004
|
+
),
|
|
2005
|
+
this.concurrency
|
|
1872
2006
|
)
|
|
1873
2007
|
).subscribe();
|
|
1874
2008
|
}
|
|
@@ -1900,7 +2034,7 @@ var RpcRouter = class {
|
|
|
1900
2034
|
this.logger.error(`Decode error for RPC ${msg.subject}:`, err);
|
|
1901
2035
|
return import_rxjs5.EMPTY;
|
|
1902
2036
|
}
|
|
1903
|
-
this.eventBus.emit("messageRouted" /* MessageRouted */, msg.subject, "rpc");
|
|
2037
|
+
this.eventBus.emit("messageRouted" /* MessageRouted */, msg.subject, "rpc" /* Rpc */);
|
|
1904
2038
|
return (0, import_rxjs5.from)(this.executeHandler(handler, data, msg, replyTo, correlationId));
|
|
1905
2039
|
}
|
|
1906
2040
|
/** Execute handler, publish response, settle message. */
|
|
@@ -1910,9 +2044,11 @@ var RpcRouter = class {
|
|
|
1910
2044
|
const hdrs = (0, import_nats9.headers)();
|
|
1911
2045
|
hdrs.set("x-correlation-id" /* CorrelationId */, correlationId);
|
|
1912
2046
|
let settled = false;
|
|
2047
|
+
const stopAckExtension = startAckExtensionTimer(msg, this.ackExtensionInterval);
|
|
1913
2048
|
const timeoutId = setTimeout(() => {
|
|
1914
2049
|
if (settled) return;
|
|
1915
2050
|
settled = true;
|
|
2051
|
+
stopAckExtension?.();
|
|
1916
2052
|
this.logger.error(`RPC timeout (${this.timeout}ms): ${msg.subject}`);
|
|
1917
2053
|
this.eventBus.emit("rpcTimeout" /* RpcTimeout */, msg.subject, correlationId);
|
|
1918
2054
|
msg.term("Handler timeout");
|
|
@@ -1922,6 +2058,7 @@ var RpcRouter = class {
|
|
|
1922
2058
|
if (settled) return;
|
|
1923
2059
|
settled = true;
|
|
1924
2060
|
clearTimeout(timeoutId);
|
|
2061
|
+
stopAckExtension?.();
|
|
1925
2062
|
msg.ack();
|
|
1926
2063
|
try {
|
|
1927
2064
|
nc.publish(replyTo, this.codec.encode(result), { headers: hdrs });
|
|
@@ -1932,6 +2069,7 @@ var RpcRouter = class {
|
|
|
1932
2069
|
if (settled) return;
|
|
1933
2070
|
settled = true;
|
|
1934
2071
|
clearTimeout(timeoutId);
|
|
2072
|
+
stopAckExtension?.();
|
|
1935
2073
|
try {
|
|
1936
2074
|
hdrs.set("x-error" /* Error */, "true");
|
|
1937
2075
|
nc.publish(replyTo, this.codec.encode(serializeError(err)), { headers: hdrs });
|
|
@@ -1955,7 +2093,7 @@ var ShutdownManager = class {
|
|
|
1955
2093
|
/**
|
|
1956
2094
|
* Execute the full shutdown sequence.
|
|
1957
2095
|
*
|
|
1958
|
-
* @param strategy Optional
|
|
2096
|
+
* @param strategy Optional stoppable to close (stops consumers and subscriptions).
|
|
1959
2097
|
*/
|
|
1960
2098
|
async shutdown(strategy) {
|
|
1961
2099
|
this.eventBus.emit("shutdownStart" /* ShutdownStart */);
|
|
@@ -1978,6 +2116,7 @@ var ShutdownManager = class {
|
|
|
1978
2116
|
};
|
|
1979
2117
|
|
|
1980
2118
|
// src/jetstream.module.ts
|
|
2119
|
+
var JETSTREAM_ACK_WAIT_MAP = /* @__PURE__ */ Symbol("JETSTREAM_ACK_WAIT_MAP");
|
|
1981
2120
|
var JetstreamModule = class {
|
|
1982
2121
|
constructor(shutdownManager, strategy) {
|
|
1983
2122
|
this.shutdownManager = shutdownManager;
|
|
@@ -2162,13 +2301,26 @@ var JetstreamModule = class {
|
|
|
2162
2301
|
return new ConsumerProvider(options, connection, streamProvider, patternRegistry);
|
|
2163
2302
|
}
|
|
2164
2303
|
},
|
|
2304
|
+
// Shared ack_wait map — populated by strategy after ensureConsumers()
|
|
2305
|
+
{
|
|
2306
|
+
provide: JETSTREAM_ACK_WAIT_MAP,
|
|
2307
|
+
useFactory: () => /* @__PURE__ */ new Map()
|
|
2308
|
+
},
|
|
2165
2309
|
// MessageProvider — pull-based message consumption
|
|
2166
2310
|
{
|
|
2167
2311
|
provide: MessageProvider,
|
|
2168
2312
|
inject: [JETSTREAM_OPTIONS, JETSTREAM_CONNECTION, JETSTREAM_EVENT_BUS],
|
|
2169
2313
|
useFactory: (options, connection, eventBus) => {
|
|
2170
2314
|
if (options.consumer === false) return null;
|
|
2171
|
-
|
|
2315
|
+
const consumeOptionsMap = /* @__PURE__ */ new Map();
|
|
2316
|
+
if (options.events?.consume)
|
|
2317
|
+
consumeOptionsMap.set("ev" /* Event */, options.events.consume);
|
|
2318
|
+
if (options.broadcast?.consume)
|
|
2319
|
+
consumeOptionsMap.set("broadcast" /* Broadcast */, options.broadcast.consume);
|
|
2320
|
+
if (options.rpc?.mode === "jetstream" && options.rpc.consume) {
|
|
2321
|
+
consumeOptionsMap.set("cmd" /* Command */, options.rpc.consume);
|
|
2322
|
+
}
|
|
2323
|
+
return new MessageProvider(connection, eventBus, consumeOptionsMap);
|
|
2172
2324
|
}
|
|
2173
2325
|
},
|
|
2174
2326
|
// EventRouter — routes event and broadcast messages to handlers
|
|
@@ -2179,20 +2331,33 @@ var JetstreamModule = class {
|
|
|
2179
2331
|
MessageProvider,
|
|
2180
2332
|
PatternRegistry,
|
|
2181
2333
|
JETSTREAM_CODEC,
|
|
2182
|
-
JETSTREAM_EVENT_BUS
|
|
2334
|
+
JETSTREAM_EVENT_BUS,
|
|
2335
|
+
JETSTREAM_ACK_WAIT_MAP
|
|
2183
2336
|
],
|
|
2184
|
-
useFactory: (options, messageProvider, patternRegistry, codec, eventBus) => {
|
|
2337
|
+
useFactory: (options, messageProvider, patternRegistry, codec, eventBus, ackWaitMap) => {
|
|
2185
2338
|
if (options.consumer === false) return null;
|
|
2186
2339
|
const deadLetterConfig = options.onDeadLetter ? {
|
|
2187
2340
|
maxDeliverByStream: /* @__PURE__ */ new Map(),
|
|
2188
2341
|
onDeadLetter: options.onDeadLetter
|
|
2189
2342
|
} : void 0;
|
|
2343
|
+
const processingConfig = {
|
|
2344
|
+
events: {
|
|
2345
|
+
concurrency: options.events?.concurrency,
|
|
2346
|
+
ackExtension: options.events?.ackExtension
|
|
2347
|
+
},
|
|
2348
|
+
broadcast: {
|
|
2349
|
+
concurrency: options.broadcast?.concurrency,
|
|
2350
|
+
ackExtension: options.broadcast?.ackExtension
|
|
2351
|
+
}
|
|
2352
|
+
};
|
|
2190
2353
|
return new EventRouter(
|
|
2191
2354
|
messageProvider,
|
|
2192
2355
|
patternRegistry,
|
|
2193
2356
|
codec,
|
|
2194
2357
|
eventBus,
|
|
2195
|
-
deadLetterConfig
|
|
2358
|
+
deadLetterConfig,
|
|
2359
|
+
processingConfig,
|
|
2360
|
+
ackWaitMap
|
|
2196
2361
|
);
|
|
2197
2362
|
}
|
|
2198
2363
|
},
|
|
@@ -2205,18 +2370,24 @@ var JetstreamModule = class {
|
|
|
2205
2370
|
PatternRegistry,
|
|
2206
2371
|
JETSTREAM_CONNECTION,
|
|
2207
2372
|
JETSTREAM_CODEC,
|
|
2208
|
-
JETSTREAM_EVENT_BUS
|
|
2373
|
+
JETSTREAM_EVENT_BUS,
|
|
2374
|
+
JETSTREAM_ACK_WAIT_MAP
|
|
2209
2375
|
],
|
|
2210
|
-
useFactory: (options, messageProvider, patternRegistry, connection, codec, eventBus) => {
|
|
2376
|
+
useFactory: (options, messageProvider, patternRegistry, connection, codec, eventBus, ackWaitMap) => {
|
|
2211
2377
|
if (options.consumer === false) return null;
|
|
2212
|
-
const
|
|
2378
|
+
const rpcOptions = options.rpc?.mode === "jetstream" ? {
|
|
2379
|
+
timeout: options.rpc.timeout,
|
|
2380
|
+
concurrency: options.rpc.concurrency,
|
|
2381
|
+
ackExtension: options.rpc.ackExtension
|
|
2382
|
+
} : void 0;
|
|
2213
2383
|
return new RpcRouter(
|
|
2214
2384
|
messageProvider,
|
|
2215
2385
|
patternRegistry,
|
|
2216
2386
|
connection,
|
|
2217
2387
|
codec,
|
|
2218
2388
|
eventBus,
|
|
2219
|
-
|
|
2389
|
+
rpcOptions,
|
|
2390
|
+
ackWaitMap
|
|
2220
2391
|
);
|
|
2221
2392
|
}
|
|
2222
2393
|
},
|
|
@@ -2247,9 +2418,10 @@ var JetstreamModule = class {
|
|
|
2247
2418
|
MessageProvider,
|
|
2248
2419
|
EventRouter,
|
|
2249
2420
|
RpcRouter,
|
|
2250
|
-
CoreRpcServer
|
|
2421
|
+
CoreRpcServer,
|
|
2422
|
+
JETSTREAM_ACK_WAIT_MAP
|
|
2251
2423
|
],
|
|
2252
|
-
useFactory: (options, connection, patternRegistry, streamProvider, consumerProvider, messageProvider, eventRouter, rpcRouter, coreRpcServer) => {
|
|
2424
|
+
useFactory: (options, connection, patternRegistry, streamProvider, consumerProvider, messageProvider, eventRouter, rpcRouter, coreRpcServer, ackWaitMap) => {
|
|
2253
2425
|
if (options.consumer === false) return null;
|
|
2254
2426
|
return new JetstreamStrategy(
|
|
2255
2427
|
options,
|
|
@@ -2260,7 +2432,8 @@ var JetstreamModule = class {
|
|
|
2260
2432
|
messageProvider,
|
|
2261
2433
|
eventRouter,
|
|
2262
2434
|
rpcRouter,
|
|
2263
|
-
coreRpcServer
|
|
2435
|
+
coreRpcServer,
|
|
2436
|
+
ackWaitMap
|
|
2264
2437
|
);
|
|
2265
2438
|
}
|
|
2266
2439
|
}
|
|
@@ -2341,8 +2514,13 @@ JetstreamModule = __decorateClass([
|
|
|
2341
2514
|
JetstreamRecordBuilder,
|
|
2342
2515
|
JetstreamStrategy,
|
|
2343
2516
|
JsonCodec,
|
|
2517
|
+
MessageKind,
|
|
2518
|
+
PatternPrefix,
|
|
2344
2519
|
RpcContext,
|
|
2520
|
+
StreamKind,
|
|
2345
2521
|
TransportEvent,
|
|
2346
2522
|
getClientToken,
|
|
2523
|
+
isCoreRpcMode,
|
|
2524
|
+
isJetStreamRpcMode,
|
|
2347
2525
|
toNanos
|
|
2348
2526
|
});
|