@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.js
CHANGED
|
@@ -29,6 +29,11 @@ import {
|
|
|
29
29
|
} from "nats";
|
|
30
30
|
|
|
31
31
|
// src/interfaces/hooks.interface.ts
|
|
32
|
+
var MessageKind = /* @__PURE__ */ ((MessageKind2) => {
|
|
33
|
+
MessageKind2["Event"] = "event";
|
|
34
|
+
MessageKind2["Rpc"] = "rpc";
|
|
35
|
+
return MessageKind2;
|
|
36
|
+
})(MessageKind || {});
|
|
32
37
|
var TransportEvent = /* @__PURE__ */ ((TransportEvent2) => {
|
|
33
38
|
TransportEvent2["Connect"] = "connect";
|
|
34
39
|
TransportEvent2["Disconnect"] = "disconnect";
|
|
@@ -42,6 +47,15 @@ var TransportEvent = /* @__PURE__ */ ((TransportEvent2) => {
|
|
|
42
47
|
return TransportEvent2;
|
|
43
48
|
})(TransportEvent || {});
|
|
44
49
|
|
|
50
|
+
// src/interfaces/stream.interface.ts
|
|
51
|
+
var StreamKind = /* @__PURE__ */ ((StreamKind2) => {
|
|
52
|
+
StreamKind2["Event"] = "ev";
|
|
53
|
+
StreamKind2["Command"] = "cmd";
|
|
54
|
+
StreamKind2["Broadcast"] = "broadcast";
|
|
55
|
+
StreamKind2["Ordered"] = "ordered";
|
|
56
|
+
return StreamKind2;
|
|
57
|
+
})(StreamKind || {});
|
|
58
|
+
|
|
45
59
|
// src/jetstream.constants.ts
|
|
46
60
|
import {
|
|
47
61
|
AckPolicy,
|
|
@@ -74,7 +88,7 @@ var baseStreamConfig = {
|
|
|
74
88
|
num_replicas: 1,
|
|
75
89
|
discard: DiscardPolicy.Old,
|
|
76
90
|
allow_direct: true,
|
|
77
|
-
compression: StoreCompression.
|
|
91
|
+
compression: StoreCompression.S2
|
|
78
92
|
};
|
|
79
93
|
var DEFAULT_EVENT_STREAM_CONFIG = {
|
|
80
94
|
...baseStreamConfig,
|
|
@@ -166,13 +180,20 @@ var internalName = (name) => `${name}__microservice`;
|
|
|
166
180
|
var buildSubject = (serviceName, kind, pattern) => `${internalName(serviceName)}.${kind}.${pattern}`;
|
|
167
181
|
var buildBroadcastSubject = (pattern) => `broadcast.${pattern}`;
|
|
168
182
|
var streamName = (serviceName, kind) => {
|
|
169
|
-
if (kind === "broadcast") return "broadcast-stream";
|
|
183
|
+
if (kind === "broadcast" /* Broadcast */) return "broadcast-stream";
|
|
170
184
|
return `${internalName(serviceName)}_${kind}-stream`;
|
|
171
185
|
};
|
|
172
186
|
var consumerName = (serviceName, kind) => {
|
|
173
|
-
if (kind === "broadcast") return `${internalName(serviceName)}_broadcast-consumer`;
|
|
187
|
+
if (kind === "broadcast" /* Broadcast */) return `${internalName(serviceName)}_broadcast-consumer`;
|
|
174
188
|
return `${internalName(serviceName)}_${kind}-consumer`;
|
|
175
189
|
};
|
|
190
|
+
var PatternPrefix = /* @__PURE__ */ ((PatternPrefix2) => {
|
|
191
|
+
PatternPrefix2["Broadcast"] = "broadcast:";
|
|
192
|
+
PatternPrefix2["Ordered"] = "ordered:";
|
|
193
|
+
return PatternPrefix2;
|
|
194
|
+
})(PatternPrefix || {});
|
|
195
|
+
var isJetStreamRpcMode = (rpc) => rpc?.mode === "jetstream";
|
|
196
|
+
var isCoreRpcMode = (rpc) => !rpc || rpc.mode === "core";
|
|
176
197
|
|
|
177
198
|
// src/client/jetstream.record.ts
|
|
178
199
|
var JetstreamRecord = class {
|
|
@@ -287,10 +308,13 @@ var JetstreamClient = class extends ClientProxy {
|
|
|
287
308
|
this.codec = codec;
|
|
288
309
|
this.eventBus = eventBus;
|
|
289
310
|
this.targetName = targetServiceName;
|
|
311
|
+
this.callerName = internalName(this.rootOptions.name);
|
|
290
312
|
}
|
|
291
313
|
logger = new Logger("Jetstream:Client");
|
|
292
314
|
/** Target service name this client sends messages to. */
|
|
293
315
|
targetName;
|
|
316
|
+
/** Pre-cached caller name derived from rootOptions.name, computed once in constructor. */
|
|
317
|
+
callerName;
|
|
294
318
|
/** Shared inbox for JetStream-mode RPC responses. */
|
|
295
319
|
inbox = null;
|
|
296
320
|
inboxSubscription = null;
|
|
@@ -310,7 +334,7 @@ var JetstreamClient = class extends ClientProxy {
|
|
|
310
334
|
*/
|
|
311
335
|
async connect() {
|
|
312
336
|
const nc = await this.connection.getConnection();
|
|
313
|
-
if (this.
|
|
337
|
+
if (isJetStreamRpcMode(this.rootOptions.rpc) && !this.inboxSubscription) {
|
|
314
338
|
this.setupInbox(nc);
|
|
315
339
|
}
|
|
316
340
|
this.statusSubscription ??= this.connection.status$.subscribe((status) => {
|
|
@@ -345,11 +369,11 @@ var JetstreamClient = class extends ClientProxy {
|
|
|
345
369
|
* depending on the subject prefix.
|
|
346
370
|
*/
|
|
347
371
|
async dispatchEvent(packet) {
|
|
348
|
-
|
|
372
|
+
await this.connect();
|
|
349
373
|
const { data, hdrs, messageId } = this.extractRecordData(packet.data);
|
|
350
374
|
const subject = this.buildEventSubject(packet.pattern);
|
|
351
375
|
const msgHeaders = this.buildHeaders(hdrs, { subject });
|
|
352
|
-
const ack = await
|
|
376
|
+
const ack = await this.connection.getJetStreamClient().publish(subject, this.codec.encode(data), {
|
|
353
377
|
headers: msgHeaders,
|
|
354
378
|
msgID: messageId ?? crypto.randomUUID()
|
|
355
379
|
});
|
|
@@ -365,26 +389,23 @@ var JetstreamClient = class extends ClientProxy {
|
|
|
365
389
|
* JetStream mode: publishes to stream + waits for inbox response.
|
|
366
390
|
*/
|
|
367
391
|
publish(packet, callback) {
|
|
368
|
-
const subject = buildSubject(this.targetName, "cmd"
|
|
392
|
+
const subject = buildSubject(this.targetName, "cmd" /* Command */, packet.pattern);
|
|
369
393
|
const { data, hdrs, timeout, messageId } = this.extractRecordData(packet.data);
|
|
370
394
|
const onUnhandled = (err) => {
|
|
371
395
|
this.logger.error("Unhandled publish error:", err);
|
|
372
396
|
callback({ err: new Error("Internal transport error"), response: null, isDisposed: true });
|
|
373
397
|
};
|
|
374
398
|
let jetStreamCorrelationId = null;
|
|
375
|
-
if (this.
|
|
399
|
+
if (isCoreRpcMode(this.rootOptions.rpc)) {
|
|
376
400
|
this.publishCoreRpc(subject, data, hdrs, timeout, callback).catch(onUnhandled);
|
|
377
401
|
} else {
|
|
378
402
|
jetStreamCorrelationId = crypto.randomUUID();
|
|
379
|
-
this.publishJetStreamRpc(
|
|
380
|
-
|
|
381
|
-
data,
|
|
382
|
-
hdrs,
|
|
403
|
+
this.publishJetStreamRpc(subject, data, callback, {
|
|
404
|
+
headers: hdrs,
|
|
383
405
|
timeout,
|
|
384
|
-
|
|
385
|
-
jetStreamCorrelationId,
|
|
406
|
+
correlationId: jetStreamCorrelationId,
|
|
386
407
|
messageId
|
|
387
|
-
).catch(onUnhandled);
|
|
408
|
+
}).catch(onUnhandled);
|
|
388
409
|
}
|
|
389
410
|
return () => {
|
|
390
411
|
if (jetStreamCorrelationId) {
|
|
@@ -421,35 +442,46 @@ var JetstreamClient = class extends ClientProxy {
|
|
|
421
442
|
}
|
|
422
443
|
}
|
|
423
444
|
/** JetStream mode: publish to stream + wait for inbox response. */
|
|
424
|
-
async publishJetStreamRpc(subject, data,
|
|
425
|
-
const
|
|
445
|
+
async publishJetStreamRpc(subject, data, callback, options) {
|
|
446
|
+
const { headers: customHeaders, correlationId, messageId } = options;
|
|
447
|
+
const effectiveTimeout = options.timeout ?? this.getRpcTimeout();
|
|
426
448
|
this.pendingMessages.set(correlationId, callback);
|
|
427
|
-
const timeoutId = setTimeout(() => {
|
|
428
|
-
if (!this.pendingMessages.has(correlationId)) return;
|
|
429
|
-
this.pendingTimeouts.delete(correlationId);
|
|
430
|
-
this.pendingMessages.delete(correlationId);
|
|
431
|
-
this.logger.error(`JetStream RPC timeout (${effectiveTimeout}ms): ${subject}`);
|
|
432
|
-
this.eventBus.emit("rpcTimeout" /* RpcTimeout */, subject, correlationId);
|
|
433
|
-
callback({ err: new Error("RPC timeout"), response: null, isDisposed: true });
|
|
434
|
-
}, effectiveTimeout);
|
|
435
|
-
this.pendingTimeouts.set(correlationId, timeoutId);
|
|
436
449
|
try {
|
|
437
|
-
|
|
450
|
+
await this.connect();
|
|
451
|
+
if (!this.pendingMessages.has(correlationId)) return;
|
|
438
452
|
if (!this.inbox) {
|
|
439
|
-
|
|
453
|
+
this.pendingMessages.delete(correlationId);
|
|
454
|
+
callback({
|
|
455
|
+
err: new Error("Inbox not initialized \u2014 JetStream RPC mode requires a connected inbox"),
|
|
456
|
+
response: null,
|
|
457
|
+
isDisposed: true
|
|
458
|
+
});
|
|
459
|
+
return;
|
|
440
460
|
}
|
|
461
|
+
const timeoutId = setTimeout(() => {
|
|
462
|
+
if (!this.pendingMessages.has(correlationId)) return;
|
|
463
|
+
this.pendingTimeouts.delete(correlationId);
|
|
464
|
+
this.pendingMessages.delete(correlationId);
|
|
465
|
+
this.logger.error(`JetStream RPC timeout (${effectiveTimeout}ms): ${subject}`);
|
|
466
|
+
this.eventBus.emit("rpcTimeout" /* RpcTimeout */, subject, correlationId);
|
|
467
|
+
callback({ err: new Error("RPC timeout"), response: null, isDisposed: true });
|
|
468
|
+
}, effectiveTimeout);
|
|
469
|
+
this.pendingTimeouts.set(correlationId, timeoutId);
|
|
441
470
|
const hdrs = this.buildHeaders(customHeaders, {
|
|
442
471
|
subject,
|
|
443
472
|
correlationId,
|
|
444
473
|
replyTo: this.inbox
|
|
445
474
|
});
|
|
446
|
-
await
|
|
475
|
+
await this.connection.getJetStreamClient().publish(subject, this.codec.encode(data), {
|
|
447
476
|
headers: hdrs,
|
|
448
477
|
msgID: messageId ?? crypto.randomUUID()
|
|
449
478
|
});
|
|
450
479
|
} catch (err) {
|
|
451
|
-
|
|
452
|
-
|
|
480
|
+
const existingTimeout = this.pendingTimeouts.get(correlationId);
|
|
481
|
+
if (existingTimeout) {
|
|
482
|
+
clearTimeout(existingTimeout);
|
|
483
|
+
this.pendingTimeouts.delete(correlationId);
|
|
484
|
+
}
|
|
453
485
|
if (!this.pendingMessages.has(correlationId)) return;
|
|
454
486
|
this.pendingMessages.delete(correlationId);
|
|
455
487
|
const error = err instanceof Error ? err : new Error("Unknown error");
|
|
@@ -525,19 +557,23 @@ var JetstreamClient = class extends ClientProxy {
|
|
|
525
557
|
}
|
|
526
558
|
/** Build event subject — workqueue, broadcast, or ordered. */
|
|
527
559
|
buildEventSubject(pattern) {
|
|
528
|
-
if (pattern.startsWith("broadcast:")) {
|
|
529
|
-
return buildBroadcastSubject(pattern.slice("broadcast:"
|
|
530
|
-
}
|
|
531
|
-
if (pattern.startsWith("ordered:")) {
|
|
532
|
-
return buildSubject(
|
|
560
|
+
if (pattern.startsWith("broadcast:" /* Broadcast */)) {
|
|
561
|
+
return buildBroadcastSubject(pattern.slice("broadcast:" /* Broadcast */.length));
|
|
562
|
+
}
|
|
563
|
+
if (pattern.startsWith("ordered:" /* Ordered */)) {
|
|
564
|
+
return buildSubject(
|
|
565
|
+
this.targetName,
|
|
566
|
+
"ordered" /* Ordered */,
|
|
567
|
+
pattern.slice("ordered:" /* Ordered */.length)
|
|
568
|
+
);
|
|
533
569
|
}
|
|
534
|
-
return buildSubject(this.targetName, "ev"
|
|
570
|
+
return buildSubject(this.targetName, "ev" /* Event */, pattern);
|
|
535
571
|
}
|
|
536
572
|
/** Build NATS headers merging custom headers with transport headers. */
|
|
537
573
|
buildHeaders(customHeaders, transport) {
|
|
538
574
|
const hdrs = natsHeaders();
|
|
539
575
|
hdrs.set("x-subject" /* Subject */, transport.subject);
|
|
540
|
-
hdrs.set("x-caller-name" /* CallerName */,
|
|
576
|
+
hdrs.set("x-caller-name" /* CallerName */, this.callerName);
|
|
541
577
|
if (transport.correlationId) {
|
|
542
578
|
hdrs.set("x-correlation-id" /* CorrelationId */, transport.correlationId);
|
|
543
579
|
}
|
|
@@ -563,15 +599,9 @@ var JetstreamClient = class extends ClientProxy {
|
|
|
563
599
|
}
|
|
564
600
|
return { data: rawData, hdrs: null, timeout: void 0, messageId: void 0 };
|
|
565
601
|
}
|
|
566
|
-
isCoreRpcMode() {
|
|
567
|
-
return !this.rootOptions.rpc || this.rootOptions.rpc.mode === "core";
|
|
568
|
-
}
|
|
569
|
-
isJetStreamRpcMode() {
|
|
570
|
-
return this.rootOptions.rpc?.mode === "jetstream";
|
|
571
|
-
}
|
|
572
602
|
getRpcTimeout() {
|
|
573
603
|
if (!this.rootOptions.rpc) return DEFAULT_RPC_TIMEOUT;
|
|
574
|
-
const defaultTimeout = this.
|
|
604
|
+
const defaultTimeout = isJetStreamRpcMode(this.rootOptions.rpc) ? DEFAULT_JETSTREAM_RPC_TIMEOUT : DEFAULT_RPC_TIMEOUT;
|
|
575
605
|
return this.rootOptions.rpc.timeout ?? defaultTimeout;
|
|
576
606
|
}
|
|
577
607
|
};
|
|
@@ -597,6 +627,10 @@ import {
|
|
|
597
627
|
NatsError
|
|
598
628
|
} from "nats";
|
|
599
629
|
import { defer, from, share, shareReplay, switchMap } from "rxjs";
|
|
630
|
+
var DEFAULT_OPTIONS = {
|
|
631
|
+
maxReconnectAttempts: -1,
|
|
632
|
+
reconnectTimeWait: 1e3
|
|
633
|
+
};
|
|
600
634
|
var ConnectionProvider = class {
|
|
601
635
|
constructor(options, eventBus) {
|
|
602
636
|
this.options = options;
|
|
@@ -616,6 +650,7 @@ var ConnectionProvider = class {
|
|
|
616
650
|
logger = new Logger2("Jetstream:Connection");
|
|
617
651
|
connection = null;
|
|
618
652
|
connectionPromise = null;
|
|
653
|
+
jsClient = null;
|
|
619
654
|
jsmInstance = null;
|
|
620
655
|
jsmPromise = null;
|
|
621
656
|
/**
|
|
@@ -654,6 +689,22 @@ var ConnectionProvider = class {
|
|
|
654
689
|
});
|
|
655
690
|
return this.jsmPromise;
|
|
656
691
|
}
|
|
692
|
+
/**
|
|
693
|
+
* Get a cached JetStream client.
|
|
694
|
+
*
|
|
695
|
+
* Invalidated automatically on reconnect and shutdown so consumers always
|
|
696
|
+
* operate against the live connection.
|
|
697
|
+
*
|
|
698
|
+
* @returns The cached JetStreamClient.
|
|
699
|
+
* @throws Error if the connection has not been established yet.
|
|
700
|
+
*/
|
|
701
|
+
getJetStreamClient() {
|
|
702
|
+
if (!this.connection || this.connection.isClosed()) {
|
|
703
|
+
throw new Error("Not connected \u2014 call getConnection() before getJetStreamClient()");
|
|
704
|
+
}
|
|
705
|
+
this.jsClient ??= this.connection.jetstream();
|
|
706
|
+
return this.jsClient;
|
|
707
|
+
}
|
|
657
708
|
/** Direct access to the raw NATS connection, or `null` if not yet connected. */
|
|
658
709
|
get unwrap() {
|
|
659
710
|
return this.connection;
|
|
@@ -682,6 +733,7 @@ var ConnectionProvider = class {
|
|
|
682
733
|
} finally {
|
|
683
734
|
this.connection = null;
|
|
684
735
|
this.connectionPromise = null;
|
|
736
|
+
this.jsClient = null;
|
|
685
737
|
this.jsmInstance = null;
|
|
686
738
|
this.jsmPromise = null;
|
|
687
739
|
}
|
|
@@ -691,6 +743,7 @@ var ConnectionProvider = class {
|
|
|
691
743
|
const name = internalName(this.options.name);
|
|
692
744
|
try {
|
|
693
745
|
const nc = await connect({
|
|
746
|
+
...DEFAULT_OPTIONS,
|
|
694
747
|
...this.options.connectionOptions,
|
|
695
748
|
servers: this.options.servers,
|
|
696
749
|
name
|
|
@@ -716,6 +769,7 @@ var ConnectionProvider = class {
|
|
|
716
769
|
this.eventBus.emit("disconnect" /* Disconnect */);
|
|
717
770
|
break;
|
|
718
771
|
case Events2.Reconnect:
|
|
772
|
+
this.jsClient = null;
|
|
719
773
|
this.jsmInstance = null;
|
|
720
774
|
this.jsmPromise = null;
|
|
721
775
|
this.eventBus.emit("reconnect" /* Reconnect */, nc.getServer());
|
|
@@ -834,7 +888,7 @@ JetstreamHealthIndicator = __decorateClass([
|
|
|
834
888
|
// src/server/strategy.ts
|
|
835
889
|
import { Server } from "@nestjs/microservices";
|
|
836
890
|
var JetstreamStrategy = class extends Server {
|
|
837
|
-
constructor(options, connection, patternRegistry, streamProvider, consumerProvider, messageProvider, eventRouter, rpcRouter, coreRpcServer) {
|
|
891
|
+
constructor(options, connection, patternRegistry, streamProvider, consumerProvider, messageProvider, eventRouter, rpcRouter, coreRpcServer, ackWaitMap = /* @__PURE__ */ new Map()) {
|
|
838
892
|
super();
|
|
839
893
|
this.options = options;
|
|
840
894
|
this.connection = connection;
|
|
@@ -845,6 +899,7 @@ var JetstreamStrategy = class extends Server {
|
|
|
845
899
|
this.eventRouter = eventRouter;
|
|
846
900
|
this.rpcRouter = rpcRouter;
|
|
847
901
|
this.coreRpcServer = coreRpcServer;
|
|
902
|
+
this.ackWaitMap = ackWaitMap;
|
|
848
903
|
}
|
|
849
904
|
transportId = /* @__PURE__ */ Symbol("jetstream-transport");
|
|
850
905
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
@@ -862,17 +917,17 @@ var JetstreamStrategy = class extends Server {
|
|
|
862
917
|
}
|
|
863
918
|
this.started = true;
|
|
864
919
|
this.patternRegistry.registerHandlers(this.getHandlers());
|
|
865
|
-
const streamKinds = this.
|
|
866
|
-
const durableKinds = this.resolveDurableConsumerKinds();
|
|
920
|
+
const { streams: streamKinds, durableConsumers: durableKinds } = this.resolveRequiredKinds();
|
|
867
921
|
if (streamKinds.length > 0) {
|
|
868
922
|
await this.streamProvider.ensureStreams(streamKinds);
|
|
869
923
|
if (durableKinds.length > 0) {
|
|
870
924
|
const consumers = await this.consumerProvider.ensureConsumers(durableKinds);
|
|
925
|
+
this.populateAckWaitMap(consumers);
|
|
871
926
|
this.eventRouter.updateMaxDeliverMap(this.buildMaxDeliverMap(consumers));
|
|
872
927
|
this.messageProvider.start(consumers);
|
|
873
928
|
}
|
|
874
929
|
if (this.patternRegistry.hasOrderedHandlers()) {
|
|
875
|
-
const orderedStreamName = this.streamProvider.getStreamName("ordered");
|
|
930
|
+
const orderedStreamName = this.streamProvider.getStreamName("ordered" /* Ordered */);
|
|
876
931
|
await this.messageProvider.startOrdered(
|
|
877
932
|
orderedStreamName,
|
|
878
933
|
this.patternRegistry.getOrderedSubjects(),
|
|
@@ -882,11 +937,11 @@ var JetstreamStrategy = class extends Server {
|
|
|
882
937
|
if (this.patternRegistry.hasEventHandlers() || this.patternRegistry.hasBroadcastHandlers() || this.patternRegistry.hasOrderedHandlers()) {
|
|
883
938
|
this.eventRouter.start();
|
|
884
939
|
}
|
|
885
|
-
if (this.
|
|
940
|
+
if (isJetStreamRpcMode(this.options.rpc) && this.patternRegistry.hasRpcHandlers()) {
|
|
886
941
|
this.rpcRouter.start();
|
|
887
942
|
}
|
|
888
943
|
}
|
|
889
|
-
if (this.
|
|
944
|
+
if (isCoreRpcMode(this.options.rpc) && this.patternRegistry.hasRpcHandlers()) {
|
|
890
945
|
await this.coreRpcServer.start();
|
|
891
946
|
}
|
|
892
947
|
callback();
|
|
@@ -927,36 +982,34 @@ var JetstreamStrategy = class extends Server {
|
|
|
927
982
|
getPatternRegistry() {
|
|
928
983
|
return this.patternRegistry;
|
|
929
984
|
}
|
|
930
|
-
/** Determine which
|
|
931
|
-
|
|
932
|
-
const
|
|
985
|
+
/** Determine which streams and durable consumers are needed. */
|
|
986
|
+
resolveRequiredKinds() {
|
|
987
|
+
const streams = [];
|
|
988
|
+
const durableConsumers = [];
|
|
933
989
|
if (this.patternRegistry.hasEventHandlers()) {
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
if (this.patternRegistry.hasOrderedHandlers()) {
|
|
937
|
-
kinds.push("ordered");
|
|
990
|
+
streams.push("ev" /* Event */);
|
|
991
|
+
durableConsumers.push("ev" /* Event */);
|
|
938
992
|
}
|
|
939
|
-
if (this.
|
|
940
|
-
|
|
993
|
+
if (isJetStreamRpcMode(this.options.rpc) && this.patternRegistry.hasRpcHandlers()) {
|
|
994
|
+
streams.push("cmd" /* Command */);
|
|
995
|
+
durableConsumers.push("cmd" /* Command */);
|
|
941
996
|
}
|
|
942
997
|
if (this.patternRegistry.hasBroadcastHandlers()) {
|
|
943
|
-
|
|
998
|
+
streams.push("broadcast" /* Broadcast */);
|
|
999
|
+
durableConsumers.push("broadcast" /* Broadcast */);
|
|
944
1000
|
}
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
/** Determine which stream kinds need durable consumers (ordered consumers are ephemeral). */
|
|
948
|
-
resolveDurableConsumerKinds() {
|
|
949
|
-
const kinds = [];
|
|
950
|
-
if (this.patternRegistry.hasEventHandlers()) {
|
|
951
|
-
kinds.push("ev");
|
|
952
|
-
}
|
|
953
|
-
if (this.isJetStreamRpcMode() && this.patternRegistry.hasRpcHandlers()) {
|
|
954
|
-
kinds.push("cmd");
|
|
1001
|
+
if (this.patternRegistry.hasOrderedHandlers()) {
|
|
1002
|
+
streams.push("ordered" /* Ordered */);
|
|
955
1003
|
}
|
|
956
|
-
|
|
957
|
-
|
|
1004
|
+
return { streams, durableConsumers };
|
|
1005
|
+
}
|
|
1006
|
+
/** Populate the shared ack_wait map from actual NATS consumer configs. */
|
|
1007
|
+
populateAckWaitMap(consumers) {
|
|
1008
|
+
for (const [kind, info] of consumers) {
|
|
1009
|
+
if (info.config.ack_wait) {
|
|
1010
|
+
this.ackWaitMap.set(kind, info.config.ack_wait);
|
|
1011
|
+
}
|
|
958
1012
|
}
|
|
959
|
-
return kinds;
|
|
960
1013
|
}
|
|
961
1014
|
/** Build max_deliver map from actual NATS consumer configs (not options). */
|
|
962
1015
|
buildMaxDeliverMap(consumers) {
|
|
@@ -970,12 +1023,6 @@ var JetstreamStrategy = class extends Server {
|
|
|
970
1023
|
}
|
|
971
1024
|
return map;
|
|
972
1025
|
}
|
|
973
|
-
isCoreRpcMode() {
|
|
974
|
-
return !this.options.rpc || this.options.rpc.mode === "core";
|
|
975
|
-
}
|
|
976
|
-
isJetStreamRpcMode() {
|
|
977
|
-
return this.options.rpc?.mode === "jetstream";
|
|
978
|
-
}
|
|
979
1026
|
};
|
|
980
1027
|
|
|
981
1028
|
// src/server/core-rpc.server.ts
|
|
@@ -1021,6 +1068,32 @@ var RpcContext = class extends BaseRpcContext {
|
|
|
1021
1068
|
}
|
|
1022
1069
|
};
|
|
1023
1070
|
|
|
1071
|
+
// src/utils/ack-extension.ts
|
|
1072
|
+
var DEFAULT_ACK_EXTENSION_INTERVAL = 5e3;
|
|
1073
|
+
var MIN_ACK_EXTENSION_INTERVAL = 500;
|
|
1074
|
+
var resolveAckExtensionInterval = (config, ackWaitNanos) => {
|
|
1075
|
+
if (config === false || config === void 0) return null;
|
|
1076
|
+
if (typeof config === "number") {
|
|
1077
|
+
if (!Number.isFinite(config) || config <= 0) return null;
|
|
1078
|
+
return Math.floor(config);
|
|
1079
|
+
}
|
|
1080
|
+
if (!ackWaitNanos) return DEFAULT_ACK_EXTENSION_INTERVAL;
|
|
1081
|
+
const interval = Math.floor(ackWaitNanos / 1e6 / 2);
|
|
1082
|
+
return Math.max(interval, MIN_ACK_EXTENSION_INTERVAL);
|
|
1083
|
+
};
|
|
1084
|
+
var startAckExtensionTimer = (msg, interval) => {
|
|
1085
|
+
if (interval === null || interval <= 0) return null;
|
|
1086
|
+
const timer2 = setInterval(() => {
|
|
1087
|
+
try {
|
|
1088
|
+
msg.working();
|
|
1089
|
+
} catch {
|
|
1090
|
+
}
|
|
1091
|
+
}, interval);
|
|
1092
|
+
return () => {
|
|
1093
|
+
clearInterval(timer2);
|
|
1094
|
+
};
|
|
1095
|
+
};
|
|
1096
|
+
|
|
1024
1097
|
// src/utils/serialize-error.ts
|
|
1025
1098
|
import { RpcException } from "@nestjs/microservices";
|
|
1026
1099
|
var serializeError = (err) => {
|
|
@@ -1052,7 +1125,12 @@ var subscribeToFirst = (obs) => new Promise((resolve, reject) => {
|
|
|
1052
1125
|
subscription?.unsubscribe();
|
|
1053
1126
|
}
|
|
1054
1127
|
},
|
|
1055
|
-
error:
|
|
1128
|
+
error: (err) => {
|
|
1129
|
+
if (!done) {
|
|
1130
|
+
done = true;
|
|
1131
|
+
reject(err);
|
|
1132
|
+
}
|
|
1133
|
+
},
|
|
1056
1134
|
complete: () => {
|
|
1057
1135
|
if (!done) resolve(void 0);
|
|
1058
1136
|
}
|
|
@@ -1102,13 +1180,17 @@ var CoreRpcServer = class {
|
|
|
1102
1180
|
}
|
|
1103
1181
|
/** Handle an incoming Core NATS request. */
|
|
1104
1182
|
async handleRequest(msg) {
|
|
1183
|
+
if (!msg.reply) {
|
|
1184
|
+
this.logger.warn(`Ignoring fire-and-forget message on RPC subject: ${msg.subject}`);
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1105
1187
|
const handler = this.patternRegistry.getHandler(msg.subject);
|
|
1106
1188
|
if (!handler) {
|
|
1107
1189
|
this.logger.warn(`No handler for Core RPC: ${msg.subject}`);
|
|
1108
1190
|
this.respondWithError(msg, new Error(`No handler for subject: ${msg.subject}`));
|
|
1109
1191
|
return;
|
|
1110
1192
|
}
|
|
1111
|
-
this.eventBus.emit("messageRouted" /* MessageRouted */, msg.subject, "rpc");
|
|
1193
|
+
this.eventBus.emit("messageRouted" /* MessageRouted */, msg.subject, "rpc" /* Rpc */);
|
|
1112
1194
|
let data;
|
|
1113
1195
|
try {
|
|
1114
1196
|
data = this.codec.decode(msg.data);
|
|
@@ -1166,14 +1248,14 @@ var StreamProvider = class {
|
|
|
1166
1248
|
getSubjects(kind) {
|
|
1167
1249
|
const name = internalName(this.options.name);
|
|
1168
1250
|
switch (kind) {
|
|
1169
|
-
case "ev"
|
|
1170
|
-
return [`${name}
|
|
1171
|
-
case "cmd"
|
|
1172
|
-
return [`${name}
|
|
1173
|
-
case "broadcast"
|
|
1251
|
+
case "ev" /* Event */:
|
|
1252
|
+
return [`${name}.${"ev" /* Event */}.>`];
|
|
1253
|
+
case "cmd" /* Command */:
|
|
1254
|
+
return [`${name}.${"cmd" /* Command */}.>`];
|
|
1255
|
+
case "broadcast" /* Broadcast */:
|
|
1174
1256
|
return ["broadcast.>"];
|
|
1175
|
-
case "ordered"
|
|
1176
|
-
return [`${name}
|
|
1257
|
+
case "ordered" /* Ordered */:
|
|
1258
|
+
return [`${name}.${"ordered" /* Ordered */}.>`];
|
|
1177
1259
|
}
|
|
1178
1260
|
}
|
|
1179
1261
|
/** Ensure a single stream exists, creating or updating as needed. */
|
|
@@ -1210,26 +1292,26 @@ var StreamProvider = class {
|
|
|
1210
1292
|
/** Get default config for a stream kind. */
|
|
1211
1293
|
getDefaults(kind) {
|
|
1212
1294
|
switch (kind) {
|
|
1213
|
-
case "ev"
|
|
1295
|
+
case "ev" /* Event */:
|
|
1214
1296
|
return DEFAULT_EVENT_STREAM_CONFIG;
|
|
1215
|
-
case "cmd"
|
|
1297
|
+
case "cmd" /* Command */:
|
|
1216
1298
|
return DEFAULT_COMMAND_STREAM_CONFIG;
|
|
1217
|
-
case "broadcast"
|
|
1299
|
+
case "broadcast" /* Broadcast */:
|
|
1218
1300
|
return DEFAULT_BROADCAST_STREAM_CONFIG;
|
|
1219
|
-
case "ordered"
|
|
1301
|
+
case "ordered" /* Ordered */:
|
|
1220
1302
|
return DEFAULT_ORDERED_STREAM_CONFIG;
|
|
1221
1303
|
}
|
|
1222
1304
|
}
|
|
1223
1305
|
/** Get user-provided overrides for a stream kind. */
|
|
1224
1306
|
getOverrides(kind) {
|
|
1225
1307
|
switch (kind) {
|
|
1226
|
-
case "ev"
|
|
1308
|
+
case "ev" /* Event */:
|
|
1227
1309
|
return this.options.events?.stream ?? {};
|
|
1228
|
-
case "cmd"
|
|
1310
|
+
case "cmd" /* Command */:
|
|
1229
1311
|
return this.options.rpc?.mode === "jetstream" ? this.options.rpc.stream ?? {} : {};
|
|
1230
|
-
case "broadcast"
|
|
1312
|
+
case "broadcast" /* Broadcast */:
|
|
1231
1313
|
return this.options.broadcast?.stream ?? {};
|
|
1232
|
-
case "ordered"
|
|
1314
|
+
case "ordered" /* Ordered */:
|
|
1233
1315
|
return this.options.ordered?.stream ?? {};
|
|
1234
1316
|
}
|
|
1235
1317
|
}
|
|
@@ -1292,7 +1374,7 @@ var ConsumerProvider = class {
|
|
|
1292
1374
|
const serviceName = internalName(this.options.name);
|
|
1293
1375
|
const defaults = this.getDefaults(kind);
|
|
1294
1376
|
const overrides = this.getOverrides(kind);
|
|
1295
|
-
if (kind === "broadcast") {
|
|
1377
|
+
if (kind === "broadcast" /* Broadcast */) {
|
|
1296
1378
|
const broadcastPatterns = this.patternRegistry.getBroadcastPatterns();
|
|
1297
1379
|
if (broadcastPatterns.length === 0) {
|
|
1298
1380
|
throw new Error("Broadcast consumer requested but no broadcast patterns are registered");
|
|
@@ -1314,7 +1396,10 @@ var ConsumerProvider = class {
|
|
|
1314
1396
|
filter_subjects: broadcastPatterns
|
|
1315
1397
|
};
|
|
1316
1398
|
}
|
|
1317
|
-
|
|
1399
|
+
if (kind !== "ev" /* Event */ && kind !== "cmd" /* Command */) {
|
|
1400
|
+
throw new Error(`Unexpected durable consumer kind: ${kind}`);
|
|
1401
|
+
}
|
|
1402
|
+
const filter_subject = `${serviceName}.${kind}.>`;
|
|
1318
1403
|
return {
|
|
1319
1404
|
...defaults,
|
|
1320
1405
|
...overrides,
|
|
@@ -1326,26 +1411,26 @@ var ConsumerProvider = class {
|
|
|
1326
1411
|
/** Get default config for a consumer kind. */
|
|
1327
1412
|
getDefaults(kind) {
|
|
1328
1413
|
switch (kind) {
|
|
1329
|
-
case "ev"
|
|
1414
|
+
case "ev" /* Event */:
|
|
1330
1415
|
return DEFAULT_EVENT_CONSUMER_CONFIG;
|
|
1331
|
-
case "cmd"
|
|
1416
|
+
case "cmd" /* Command */:
|
|
1332
1417
|
return DEFAULT_COMMAND_CONSUMER_CONFIG;
|
|
1333
|
-
case "broadcast"
|
|
1418
|
+
case "broadcast" /* Broadcast */:
|
|
1334
1419
|
return DEFAULT_BROADCAST_CONSUMER_CONFIG;
|
|
1335
|
-
case "ordered"
|
|
1420
|
+
case "ordered" /* Ordered */:
|
|
1336
1421
|
throw new Error("Ordered consumers are ephemeral and should not use durable config");
|
|
1337
1422
|
}
|
|
1338
1423
|
}
|
|
1339
1424
|
/** Get user-provided overrides for a consumer kind. */
|
|
1340
1425
|
getOverrides(kind) {
|
|
1341
1426
|
switch (kind) {
|
|
1342
|
-
case "ev"
|
|
1427
|
+
case "ev" /* Event */:
|
|
1343
1428
|
return this.options.events?.consumer ?? {};
|
|
1344
|
-
case "cmd"
|
|
1429
|
+
case "cmd" /* Command */:
|
|
1345
1430
|
return this.options.rpc?.mode === "jetstream" ? this.options.rpc.consumer ?? {} : {};
|
|
1346
|
-
case "broadcast"
|
|
1431
|
+
case "broadcast" /* Broadcast */:
|
|
1347
1432
|
return this.options.broadcast?.consumer ?? {};
|
|
1348
|
-
case "ordered"
|
|
1433
|
+
case "ordered" /* Ordered */:
|
|
1349
1434
|
throw new Error("Ordered consumers are ephemeral and should not use durable config");
|
|
1350
1435
|
}
|
|
1351
1436
|
}
|
|
@@ -1353,7 +1438,10 @@ var ConsumerProvider = class {
|
|
|
1353
1438
|
|
|
1354
1439
|
// src/server/infrastructure/message.provider.ts
|
|
1355
1440
|
import { Logger as Logger7 } from "@nestjs/common";
|
|
1356
|
-
import {
|
|
1441
|
+
import {
|
|
1442
|
+
ConsumerEvents,
|
|
1443
|
+
DeliverPolicy as DeliverPolicy2
|
|
1444
|
+
} from "nats";
|
|
1357
1445
|
import {
|
|
1358
1446
|
catchError,
|
|
1359
1447
|
defer as defer2,
|
|
@@ -1366,9 +1454,10 @@ import {
|
|
|
1366
1454
|
timer
|
|
1367
1455
|
} from "rxjs";
|
|
1368
1456
|
var MessageProvider = class {
|
|
1369
|
-
constructor(connection, eventBus) {
|
|
1457
|
+
constructor(connection, eventBus, consumeOptionsMap = /* @__PURE__ */ new Map()) {
|
|
1370
1458
|
this.connection = connection;
|
|
1371
1459
|
this.eventBus = eventBus;
|
|
1460
|
+
this.consumeOptionsMap = consumeOptionsMap;
|
|
1372
1461
|
}
|
|
1373
1462
|
logger = new Logger7("Jetstream:Message");
|
|
1374
1463
|
activeIterators = /* @__PURE__ */ new Set();
|
|
@@ -1418,6 +1507,7 @@ var MessageProvider = class {
|
|
|
1418
1507
|
*
|
|
1419
1508
|
* @param streamName - JetStream stream to consume from.
|
|
1420
1509
|
* @param filterSubjects - NATS subjects to filter on.
|
|
1510
|
+
* @param orderedConfig - Optional overrides for ordered consumer options.
|
|
1421
1511
|
*/
|
|
1422
1512
|
async startOrdered(streamName2, filterSubjects, orderedConfig) {
|
|
1423
1513
|
const consumerOpts = { filterSubjects };
|
|
@@ -1443,6 +1533,11 @@ var MessageProvider = class {
|
|
|
1443
1533
|
}
|
|
1444
1534
|
/** Stop all consumer flows and reinitialize subjects for potential restart. */
|
|
1445
1535
|
destroy() {
|
|
1536
|
+
if (this.orderedReadyReject) {
|
|
1537
|
+
this.orderedReadyReject(new Error("Destroyed before ordered consumer connected"));
|
|
1538
|
+
this.orderedReadyResolve = null;
|
|
1539
|
+
this.orderedReadyReject = null;
|
|
1540
|
+
}
|
|
1446
1541
|
this.destroy$.next();
|
|
1447
1542
|
this.destroy$.complete();
|
|
1448
1543
|
for (const messages of this.activeIterators) {
|
|
@@ -1462,42 +1557,17 @@ var MessageProvider = class {
|
|
|
1462
1557
|
/** Create a self-healing consumer flow for a specific kind. */
|
|
1463
1558
|
createFlow(kind, info) {
|
|
1464
1559
|
const target$ = this.getTargetSubject(kind);
|
|
1465
|
-
|
|
1466
|
-
let lastRunFailed = false;
|
|
1467
|
-
return defer2(() => this.consumeOnce(info, target$)).pipe(
|
|
1468
|
-
tap(() => {
|
|
1469
|
-
lastRunFailed = false;
|
|
1470
|
-
}),
|
|
1471
|
-
catchError((err) => {
|
|
1472
|
-
consecutiveFailures++;
|
|
1473
|
-
lastRunFailed = true;
|
|
1474
|
-
this.logger.error(`Consumer ${info.name} error, will restart:`, err);
|
|
1475
|
-
this.eventBus.emit(
|
|
1476
|
-
"error" /* Error */,
|
|
1477
|
-
err instanceof Error ? err : new Error(String(err)),
|
|
1478
|
-
"message-provider"
|
|
1479
|
-
);
|
|
1480
|
-
return EMPTY;
|
|
1481
|
-
}),
|
|
1482
|
-
repeat({
|
|
1483
|
-
delay: () => {
|
|
1484
|
-
if (!lastRunFailed) {
|
|
1485
|
-
consecutiveFailures = 0;
|
|
1486
|
-
}
|
|
1487
|
-
const delay = Math.min(100 * Math.pow(2, consecutiveFailures), 3e4);
|
|
1488
|
-
this.logger.warn(`Consumer ${info.name} stream ended, restarting in ${delay}ms...`);
|
|
1489
|
-
return timer(delay);
|
|
1490
|
-
}
|
|
1491
|
-
}),
|
|
1492
|
-
takeUntil(this.destroy$)
|
|
1493
|
-
);
|
|
1560
|
+
return this.createSelfHealingFlow(() => this.consumeOnce(kind, info, target$), info.name);
|
|
1494
1561
|
}
|
|
1495
1562
|
/** Single iteration: get consumer -> pull messages -> emit to subject. */
|
|
1496
|
-
async consumeOnce(info, target$) {
|
|
1497
|
-
const js =
|
|
1563
|
+
async consumeOnce(kind, info, target$) {
|
|
1564
|
+
const js = this.connection.getJetStreamClient();
|
|
1498
1565
|
const consumer = await js.consumers.get(info.stream_name, info.name);
|
|
1499
|
-
const
|
|
1566
|
+
const defaults = { idle_heartbeat: 5e3 };
|
|
1567
|
+
const userOptions = this.consumeOptionsMap.get(kind) ?? {};
|
|
1568
|
+
const messages = await consumer.consume({ ...defaults, ...userOptions });
|
|
1500
1569
|
this.activeIterators.add(messages);
|
|
1570
|
+
this.monitorConsumerHealth(messages, info.name);
|
|
1501
1571
|
try {
|
|
1502
1572
|
for await (const msg of messages) {
|
|
1503
1573
|
target$.next(msg);
|
|
@@ -1509,47 +1579,73 @@ var MessageProvider = class {
|
|
|
1509
1579
|
/** Get the target subject for a consumer kind. */
|
|
1510
1580
|
getTargetSubject(kind) {
|
|
1511
1581
|
switch (kind) {
|
|
1512
|
-
case "ev"
|
|
1582
|
+
case "ev" /* Event */:
|
|
1513
1583
|
return this.eventMessages$;
|
|
1514
|
-
case "cmd"
|
|
1584
|
+
case "cmd" /* Command */:
|
|
1515
1585
|
return this.commandMessages$;
|
|
1516
|
-
case "broadcast"
|
|
1586
|
+
case "broadcast" /* Broadcast */:
|
|
1517
1587
|
return this.broadcastMessages$;
|
|
1518
|
-
case "ordered"
|
|
1588
|
+
case "ordered" /* Ordered */:
|
|
1519
1589
|
return this.orderedMessages$;
|
|
1590
|
+
default: {
|
|
1591
|
+
const _exhaustive = kind;
|
|
1592
|
+
throw new Error(`Unknown stream kind: ${_exhaustive}`);
|
|
1593
|
+
}
|
|
1520
1594
|
}
|
|
1521
1595
|
}
|
|
1596
|
+
/** Monitor heartbeats and restart the consumer iterator on prolonged silence. */
|
|
1597
|
+
monitorConsumerHealth(messages, name) {
|
|
1598
|
+
(async () => {
|
|
1599
|
+
for await (const status of await messages.status()) {
|
|
1600
|
+
if (status.type === ConsumerEvents.HeartbeatsMissed && status.data >= 2) {
|
|
1601
|
+
this.logger.warn(`Consumer ${name}: ${status.data} heartbeats missed, restarting`);
|
|
1602
|
+
messages.stop();
|
|
1603
|
+
break;
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
})().catch((err) => {
|
|
1607
|
+
if (err) {
|
|
1608
|
+
this.logger.debug(`Consumer ${name} health monitor ended:`, err);
|
|
1609
|
+
}
|
|
1610
|
+
});
|
|
1611
|
+
}
|
|
1522
1612
|
/** Create a self-healing ordered consumer flow. */
|
|
1523
1613
|
createOrderedFlow(streamName2, consumerOpts) {
|
|
1614
|
+
return this.createSelfHealingFlow(
|
|
1615
|
+
() => this.consumeOrderedOnce(streamName2, consumerOpts),
|
|
1616
|
+
"ordered" /* Ordered */,
|
|
1617
|
+
(err) => {
|
|
1618
|
+
if (this.orderedReadyReject) {
|
|
1619
|
+
this.orderedReadyReject(err);
|
|
1620
|
+
this.orderedReadyReject = null;
|
|
1621
|
+
this.orderedReadyResolve = null;
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
);
|
|
1625
|
+
}
|
|
1626
|
+
/** Shared self-healing flow: defer -> retry with exponential backoff on error/completion. */
|
|
1627
|
+
createSelfHealingFlow(source, label, onFirstError) {
|
|
1524
1628
|
let consecutiveFailures = 0;
|
|
1525
|
-
|
|
1526
|
-
return defer2(() => this.consumeOrderedOnce(streamName2, consumerOpts)).pipe(
|
|
1629
|
+
return defer2(source).pipe(
|
|
1527
1630
|
tap(() => {
|
|
1528
|
-
|
|
1631
|
+
consecutiveFailures = 0;
|
|
1529
1632
|
}),
|
|
1530
1633
|
catchError((err) => {
|
|
1531
1634
|
consecutiveFailures++;
|
|
1532
|
-
|
|
1533
|
-
this.logger.error("Ordered consumer error, will restart:", err);
|
|
1635
|
+
this.logger.error(`Consumer ${label} error, will restart:`, err);
|
|
1534
1636
|
this.eventBus.emit(
|
|
1535
1637
|
"error" /* Error */,
|
|
1536
1638
|
err instanceof Error ? err : new Error(String(err)),
|
|
1537
1639
|
"message-provider"
|
|
1538
1640
|
);
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
this.orderedReadyReject = null;
|
|
1542
|
-
this.orderedReadyResolve = null;
|
|
1543
|
-
}
|
|
1641
|
+
onFirstError?.(err);
|
|
1642
|
+
onFirstError = void 0;
|
|
1544
1643
|
return EMPTY;
|
|
1545
1644
|
}),
|
|
1546
1645
|
repeat({
|
|
1547
1646
|
delay: () => {
|
|
1548
|
-
if (!lastRunFailed) {
|
|
1549
|
-
consecutiveFailures = 0;
|
|
1550
|
-
}
|
|
1551
1647
|
const delay = Math.min(100 * Math.pow(2, consecutiveFailures), 3e4);
|
|
1552
|
-
this.logger.warn(`
|
|
1648
|
+
this.logger.warn(`Consumer ${label} stream ended, restarting in ${delay}ms...`);
|
|
1553
1649
|
return timer(delay);
|
|
1554
1650
|
}
|
|
1555
1651
|
})
|
|
@@ -1557,7 +1653,7 @@ var MessageProvider = class {
|
|
|
1557
1653
|
}
|
|
1558
1654
|
/** Single iteration: create ordered consumer -> iterate messages. */
|
|
1559
1655
|
async consumeOrderedOnce(streamName2, consumerOpts) {
|
|
1560
|
-
const js =
|
|
1656
|
+
const js = this.connection.getJetStreamClient();
|
|
1561
1657
|
const consumer = await js.consumers.get(streamName2, consumerOpts);
|
|
1562
1658
|
const messages = await consumer.consume();
|
|
1563
1659
|
if (this.orderedReadyResolve) {
|
|
@@ -1578,12 +1674,20 @@ var MessageProvider = class {
|
|
|
1578
1674
|
|
|
1579
1675
|
// src/server/routing/pattern-registry.ts
|
|
1580
1676
|
import { Logger as Logger8 } from "@nestjs/common";
|
|
1677
|
+
var HANDLER_LABELS = {
|
|
1678
|
+
["broadcast" /* Broadcast */]: "broadcast" /* Broadcast */,
|
|
1679
|
+
["ordered" /* Ordered */]: "ordered" /* Ordered */,
|
|
1680
|
+
["ev" /* Event */]: "event" /* Event */,
|
|
1681
|
+
["cmd" /* Command */]: "rpc" /* Rpc */
|
|
1682
|
+
};
|
|
1581
1683
|
var PatternRegistry = class {
|
|
1582
1684
|
constructor(options) {
|
|
1583
1685
|
this.options = options;
|
|
1584
1686
|
}
|
|
1585
1687
|
logger = new Logger8("Jetstream:PatternRegistry");
|
|
1586
1688
|
registry = /* @__PURE__ */ new Map();
|
|
1689
|
+
// Cached after registerHandlers() — the registry is immutable from that point
|
|
1690
|
+
cachedPatterns = null;
|
|
1587
1691
|
/**
|
|
1588
1692
|
* Register all handlers from the NestJS strategy.
|
|
1589
1693
|
*
|
|
@@ -1601,16 +1705,12 @@ var PatternRegistry = class {
|
|
|
1601
1705
|
`Handler "${pattern}" cannot be both broadcast and ordered. Use one or the other.`
|
|
1602
1706
|
);
|
|
1603
1707
|
}
|
|
1604
|
-
let
|
|
1605
|
-
if (isBroadcast)
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
fullSubject = buildSubject(serviceName, "ev", pattern);
|
|
1611
|
-
} else {
|
|
1612
|
-
fullSubject = buildSubject(serviceName, "cmd", pattern);
|
|
1613
|
-
}
|
|
1708
|
+
let kind;
|
|
1709
|
+
if (isBroadcast) kind = "broadcast" /* Broadcast */;
|
|
1710
|
+
else if (isOrdered) kind = "ordered" /* Ordered */;
|
|
1711
|
+
else if (isEvent) kind = "ev" /* Event */;
|
|
1712
|
+
else kind = "cmd" /* Command */;
|
|
1713
|
+
const fullSubject = kind === "broadcast" /* Broadcast */ ? buildBroadcastSubject(pattern) : buildSubject(serviceName, kind, pattern);
|
|
1614
1714
|
this.registry.set(fullSubject, {
|
|
1615
1715
|
handler,
|
|
1616
1716
|
pattern,
|
|
@@ -1618,18 +1718,9 @@ var PatternRegistry = class {
|
|
|
1618
1718
|
isBroadcast,
|
|
1619
1719
|
isOrdered
|
|
1620
1720
|
});
|
|
1621
|
-
|
|
1622
|
-
if (isBroadcast) {
|
|
1623
|
-
kind = "broadcast";
|
|
1624
|
-
} else if (isOrdered) {
|
|
1625
|
-
kind = "ordered";
|
|
1626
|
-
} else if (isEvent) {
|
|
1627
|
-
kind = "event";
|
|
1628
|
-
} else {
|
|
1629
|
-
kind = "rpc";
|
|
1630
|
-
}
|
|
1631
|
-
this.logger.debug(`Registered ${kind}: ${pattern} -> ${fullSubject}`);
|
|
1721
|
+
this.logger.debug(`Registered ${HANDLER_LABELS[kind]}: ${pattern} -> ${fullSubject}`);
|
|
1632
1722
|
}
|
|
1723
|
+
this.cachedPatterns = this.buildPatternsByKind();
|
|
1633
1724
|
this.logSummary();
|
|
1634
1725
|
}
|
|
1635
1726
|
/** Find handler for a full NATS subject. */
|
|
@@ -1638,33 +1729,53 @@ var PatternRegistry = class {
|
|
|
1638
1729
|
}
|
|
1639
1730
|
/** Get all registered broadcast patterns (for consumer filter_subject setup). */
|
|
1640
1731
|
getBroadcastPatterns() {
|
|
1641
|
-
return
|
|
1732
|
+
return this.getPatternsByKind().broadcasts.map((p) => buildBroadcastSubject(p));
|
|
1642
1733
|
}
|
|
1643
|
-
/** Check if any broadcast handlers are registered. */
|
|
1644
1734
|
hasBroadcastHandlers() {
|
|
1645
|
-
return
|
|
1735
|
+
return this.getPatternsByKind().broadcasts.length > 0;
|
|
1646
1736
|
}
|
|
1647
|
-
/** Check if any RPC (command) handlers are registered. */
|
|
1648
1737
|
hasRpcHandlers() {
|
|
1649
|
-
return
|
|
1650
|
-
(r) => !r.isEvent && !r.isBroadcast && !r.isOrdered
|
|
1651
|
-
);
|
|
1738
|
+
return this.getPatternsByKind().commands.length > 0;
|
|
1652
1739
|
}
|
|
1653
|
-
/** Check if any workqueue event handlers are registered. */
|
|
1654
1740
|
hasEventHandlers() {
|
|
1655
|
-
return
|
|
1741
|
+
return this.getPatternsByKind().events.length > 0;
|
|
1656
1742
|
}
|
|
1657
|
-
/** Check if any ordered event handlers are registered. */
|
|
1658
1743
|
hasOrderedHandlers() {
|
|
1659
|
-
return
|
|
1744
|
+
return this.getPatternsByKind().ordered.length > 0;
|
|
1660
1745
|
}
|
|
1661
1746
|
/** Get fully-qualified NATS subjects for ordered handlers. */
|
|
1662
1747
|
getOrderedSubjects() {
|
|
1663
|
-
|
|
1664
|
-
|
|
1748
|
+
return this.getPatternsByKind().ordered.map(
|
|
1749
|
+
(p) => buildSubject(this.options.name, "ordered" /* Ordered */, p)
|
|
1750
|
+
);
|
|
1665
1751
|
}
|
|
1666
|
-
/** Get patterns grouped by kind. */
|
|
1752
|
+
/** Get patterns grouped by kind (cached after registration). */
|
|
1667
1753
|
getPatternsByKind() {
|
|
1754
|
+
const patterns = this.cachedPatterns ?? this.buildPatternsByKind();
|
|
1755
|
+
return {
|
|
1756
|
+
events: [...patterns.events],
|
|
1757
|
+
commands: [...patterns.commands],
|
|
1758
|
+
broadcasts: [...patterns.broadcasts],
|
|
1759
|
+
ordered: [...patterns.ordered]
|
|
1760
|
+
};
|
|
1761
|
+
}
|
|
1762
|
+
/** Normalize a full NATS subject back to the user-facing pattern. */
|
|
1763
|
+
normalizeSubject(subject) {
|
|
1764
|
+
const name = internalName(this.options.name);
|
|
1765
|
+
const prefixes = [
|
|
1766
|
+
`${name}.${"cmd" /* Command */}.`,
|
|
1767
|
+
`${name}.${"ev" /* Event */}.`,
|
|
1768
|
+
`${name}.${"ordered" /* Ordered */}.`,
|
|
1769
|
+
`${"broadcast" /* Broadcast */}.`
|
|
1770
|
+
];
|
|
1771
|
+
for (const prefix of prefixes) {
|
|
1772
|
+
if (subject.startsWith(prefix)) {
|
|
1773
|
+
return subject.slice(prefix.length);
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
return subject;
|
|
1777
|
+
}
|
|
1778
|
+
buildPatternsByKind() {
|
|
1668
1779
|
const events = [];
|
|
1669
1780
|
const commands = [];
|
|
1670
1781
|
const broadcasts = [];
|
|
@@ -1677,18 +1788,6 @@ var PatternRegistry = class {
|
|
|
1677
1788
|
}
|
|
1678
1789
|
return { events, commands, broadcasts, ordered };
|
|
1679
1790
|
}
|
|
1680
|
-
/** Normalize a full NATS subject back to the user-facing pattern. */
|
|
1681
|
-
normalizeSubject(subject) {
|
|
1682
|
-
const name = internalName(this.options.name);
|
|
1683
|
-
const prefixes = [`${name}.cmd.`, `${name}.ev.`, `${name}.ordered.`, "broadcast."];
|
|
1684
|
-
for (const prefix of prefixes) {
|
|
1685
|
-
if (subject.startsWith(prefix)) {
|
|
1686
|
-
return subject.slice(prefix.length);
|
|
1687
|
-
}
|
|
1688
|
-
}
|
|
1689
|
-
return subject;
|
|
1690
|
-
}
|
|
1691
|
-
/** Log a summary of all registered handlers. */
|
|
1692
1791
|
logSummary() {
|
|
1693
1792
|
const { events, commands, broadcasts, ordered } = this.getPatternsByKind();
|
|
1694
1793
|
const parts = [
|
|
@@ -1714,12 +1813,14 @@ import {
|
|
|
1714
1813
|
mergeMap
|
|
1715
1814
|
} from "rxjs";
|
|
1716
1815
|
var EventRouter = class {
|
|
1717
|
-
constructor(messageProvider, patternRegistry, codec, eventBus, deadLetterConfig) {
|
|
1816
|
+
constructor(messageProvider, patternRegistry, codec, eventBus, deadLetterConfig, processingConfig, ackWaitMap) {
|
|
1718
1817
|
this.messageProvider = messageProvider;
|
|
1719
1818
|
this.patternRegistry = patternRegistry;
|
|
1720
1819
|
this.codec = codec;
|
|
1721
1820
|
this.eventBus = eventBus;
|
|
1722
1821
|
this.deadLetterConfig = deadLetterConfig;
|
|
1822
|
+
this.processingConfig = processingConfig;
|
|
1823
|
+
this.ackWaitMap = ackWaitMap;
|
|
1723
1824
|
}
|
|
1724
1825
|
logger = new Logger9("Jetstream:EventRouter");
|
|
1725
1826
|
subscriptions = [];
|
|
@@ -1733,10 +1834,10 @@ var EventRouter = class {
|
|
|
1733
1834
|
}
|
|
1734
1835
|
/** Start routing event, broadcast, and ordered messages to handlers. */
|
|
1735
1836
|
start() {
|
|
1736
|
-
this.subscribeToStream(this.messageProvider.events$, "
|
|
1737
|
-
this.subscribeToStream(this.messageProvider.broadcasts$, "broadcast");
|
|
1837
|
+
this.subscribeToStream(this.messageProvider.events$, "ev" /* Event */);
|
|
1838
|
+
this.subscribeToStream(this.messageProvider.broadcasts$, "broadcast" /* Broadcast */);
|
|
1738
1839
|
if (this.patternRegistry.hasOrderedHandlers()) {
|
|
1739
|
-
this.subscribeToStream(this.messageProvider.ordered$, "ordered"
|
|
1840
|
+
this.subscribeToStream(this.messageProvider.ordered$, "ordered" /* Ordered */);
|
|
1740
1841
|
}
|
|
1741
1842
|
}
|
|
1742
1843
|
/** Stop routing and unsubscribe from all streams. */
|
|
@@ -1747,38 +1848,71 @@ var EventRouter = class {
|
|
|
1747
1848
|
this.subscriptions.length = 0;
|
|
1748
1849
|
}
|
|
1749
1850
|
/** Subscribe to a message stream and route each message. */
|
|
1750
|
-
subscribeToStream(stream$,
|
|
1751
|
-
const
|
|
1851
|
+
subscribeToStream(stream$, kind) {
|
|
1852
|
+
const isOrdered = kind === "ordered" /* Ordered */;
|
|
1853
|
+
const ackExtensionInterval = isOrdered ? null : resolveAckExtensionInterval(this.getAckExtensionConfig(kind), this.ackWaitMap?.get(kind));
|
|
1854
|
+
const concurrency = this.getConcurrency(kind);
|
|
1855
|
+
const route = (msg) => defer3(
|
|
1856
|
+
() => isOrdered ? this.handleOrdered(msg) : this.handle(msg, ackExtensionInterval)
|
|
1857
|
+
).pipe(
|
|
1752
1858
|
catchError2((err) => {
|
|
1753
|
-
this.logger.error(`Unexpected error in ${
|
|
1859
|
+
this.logger.error(`Unexpected error in ${kind} event router`, err);
|
|
1754
1860
|
return EMPTY2;
|
|
1755
1861
|
})
|
|
1756
1862
|
);
|
|
1757
|
-
const subscription = stream$.pipe(isOrdered ? concatMap(route) : mergeMap(route)).subscribe();
|
|
1863
|
+
const subscription = stream$.pipe(isOrdered ? concatMap(route) : mergeMap(route, concurrency)).subscribe();
|
|
1758
1864
|
this.subscriptions.push(subscription);
|
|
1759
1865
|
}
|
|
1866
|
+
getConcurrency(kind) {
|
|
1867
|
+
if (kind === "ev" /* Event */) return this.processingConfig?.events?.concurrency;
|
|
1868
|
+
if (kind === "broadcast" /* Broadcast */) return this.processingConfig?.broadcast?.concurrency;
|
|
1869
|
+
return void 0;
|
|
1870
|
+
}
|
|
1871
|
+
getAckExtensionConfig(kind) {
|
|
1872
|
+
if (kind === "ev" /* Event */) return this.processingConfig?.events?.ackExtension;
|
|
1873
|
+
if (kind === "broadcast" /* Broadcast */) return this.processingConfig?.broadcast?.ackExtension;
|
|
1874
|
+
return void 0;
|
|
1875
|
+
}
|
|
1760
1876
|
/** Handle a single event message: decode -> execute handler -> ack/nak. */
|
|
1761
|
-
handle(msg) {
|
|
1877
|
+
handle(msg, ackExtensionInterval) {
|
|
1878
|
+
const resolved = this.decodeMessage(msg);
|
|
1879
|
+
if (!resolved) return EMPTY2;
|
|
1880
|
+
return from2(
|
|
1881
|
+
this.executeHandler(resolved.handler, resolved.data, resolved.ctx, msg, ackExtensionInterval)
|
|
1882
|
+
);
|
|
1883
|
+
}
|
|
1884
|
+
/** Handle an ordered message: decode -> execute handler -> no ack/nak. */
|
|
1885
|
+
handleOrdered(msg) {
|
|
1886
|
+
const resolved = this.decodeMessage(msg, true);
|
|
1887
|
+
if (!resolved) return EMPTY2;
|
|
1888
|
+
return from2(
|
|
1889
|
+
unwrapResult(resolved.handler(resolved.data, resolved.ctx)).catch((err) => {
|
|
1890
|
+
this.logger.error(`Ordered handler error (${msg.subject}):`, err);
|
|
1891
|
+
})
|
|
1892
|
+
);
|
|
1893
|
+
}
|
|
1894
|
+
/** Resolve handler, decode payload, and build context. Returns null on failure. */
|
|
1895
|
+
decodeMessage(msg, isOrdered = false) {
|
|
1762
1896
|
const handler = this.patternRegistry.getHandler(msg.subject);
|
|
1763
1897
|
if (!handler) {
|
|
1764
|
-
msg.term(`No handler for event: ${msg.subject}`);
|
|
1765
|
-
this.logger.error(`No handler for
|
|
1766
|
-
return
|
|
1898
|
+
if (!isOrdered) msg.term(`No handler for event: ${msg.subject}`);
|
|
1899
|
+
this.logger.error(`No handler for subject: ${msg.subject}`);
|
|
1900
|
+
return null;
|
|
1767
1901
|
}
|
|
1768
1902
|
let data;
|
|
1769
1903
|
try {
|
|
1770
1904
|
data = this.codec.decode(msg.data);
|
|
1771
1905
|
} catch (err) {
|
|
1772
|
-
msg.term("Decode error");
|
|
1906
|
+
if (!isOrdered) msg.term("Decode error");
|
|
1773
1907
|
this.logger.error(`Decode error for ${msg.subject}:`, err);
|
|
1774
|
-
return
|
|
1908
|
+
return null;
|
|
1775
1909
|
}
|
|
1776
|
-
this.eventBus.emit("messageRouted" /* MessageRouted */, msg.subject, "event");
|
|
1777
|
-
|
|
1778
|
-
return from2(this.executeHandler(handler, data, ctx, msg));
|
|
1910
|
+
this.eventBus.emit("messageRouted" /* MessageRouted */, msg.subject, "event" /* Event */);
|
|
1911
|
+
return { handler, data, ctx: new RpcContext([msg]) };
|
|
1779
1912
|
}
|
|
1780
1913
|
/** Execute handler, then ack on success or nak/dead-letter on failure. */
|
|
1781
|
-
async executeHandler(handler, data, ctx, msg) {
|
|
1914
|
+
async executeHandler(handler, data, ctx, msg, ackExtensionInterval) {
|
|
1915
|
+
const stopAckExtension = startAckExtensionTimer(msg, ackExtensionInterval);
|
|
1782
1916
|
try {
|
|
1783
1917
|
await unwrapResult(handler(data, ctx));
|
|
1784
1918
|
msg.ack();
|
|
@@ -1789,30 +1923,10 @@ var EventRouter = class {
|
|
|
1789
1923
|
} else {
|
|
1790
1924
|
msg.nak();
|
|
1791
1925
|
}
|
|
1926
|
+
} finally {
|
|
1927
|
+
stopAckExtension?.();
|
|
1792
1928
|
}
|
|
1793
1929
|
}
|
|
1794
|
-
/** Handle an ordered message: decode -> execute handler -> no ack/nak. */
|
|
1795
|
-
handleOrdered(msg) {
|
|
1796
|
-
const handler = this.patternRegistry.getHandler(msg.subject);
|
|
1797
|
-
if (!handler) {
|
|
1798
|
-
this.logger.error(`No handler for ordered subject: ${msg.subject}`);
|
|
1799
|
-
return EMPTY2;
|
|
1800
|
-
}
|
|
1801
|
-
let data;
|
|
1802
|
-
try {
|
|
1803
|
-
data = this.codec.decode(msg.data);
|
|
1804
|
-
} catch (err) {
|
|
1805
|
-
this.logger.error(`Decode error for ordered ${msg.subject}:`, err);
|
|
1806
|
-
return EMPTY2;
|
|
1807
|
-
}
|
|
1808
|
-
this.eventBus.emit("messageRouted" /* MessageRouted */, msg.subject, "event");
|
|
1809
|
-
const ctx = new RpcContext([msg]);
|
|
1810
|
-
return from2(
|
|
1811
|
-
unwrapResult(handler(data, ctx)).catch((err) => {
|
|
1812
|
-
this.logger.error(`Ordered handler error (${msg.subject}):`, err);
|
|
1813
|
-
})
|
|
1814
|
-
);
|
|
1815
|
-
}
|
|
1816
1930
|
/** Check if the message has exhausted all delivery attempts. */
|
|
1817
1931
|
isDeadLetter(msg) {
|
|
1818
1932
|
if (!this.deadLetterConfig) return false;
|
|
@@ -1833,7 +1947,10 @@ var EventRouter = class {
|
|
|
1833
1947
|
timestamp: new Date(msg.info.timestampNanos / 1e6).toISOString()
|
|
1834
1948
|
};
|
|
1835
1949
|
this.eventBus.emit("deadLetter" /* DeadLetter */, info);
|
|
1836
|
-
if (!this.deadLetterConfig)
|
|
1950
|
+
if (!this.deadLetterConfig) {
|
|
1951
|
+
msg.term("Dead letter config unavailable");
|
|
1952
|
+
return;
|
|
1953
|
+
}
|
|
1837
1954
|
try {
|
|
1838
1955
|
await this.deadLetterConfig.onDeadLetter(info);
|
|
1839
1956
|
msg.term("Dead letter processed");
|
|
@@ -1849,17 +1966,31 @@ import { Logger as Logger10 } from "@nestjs/common";
|
|
|
1849
1966
|
import { headers } from "nats";
|
|
1850
1967
|
import { catchError as catchError3, defer as defer4, EMPTY as EMPTY3, from as from3, mergeMap as mergeMap2 } from "rxjs";
|
|
1851
1968
|
var RpcRouter = class {
|
|
1852
|
-
constructor(messageProvider, patternRegistry, connection, codec, eventBus,
|
|
1969
|
+
constructor(messageProvider, patternRegistry, connection, codec, eventBus, rpcOptions, ackWaitMap) {
|
|
1853
1970
|
this.messageProvider = messageProvider;
|
|
1854
1971
|
this.patternRegistry = patternRegistry;
|
|
1855
1972
|
this.connection = connection;
|
|
1856
1973
|
this.codec = codec;
|
|
1857
1974
|
this.eventBus = eventBus;
|
|
1858
|
-
this.
|
|
1975
|
+
this.rpcOptions = rpcOptions;
|
|
1976
|
+
this.ackWaitMap = ackWaitMap;
|
|
1977
|
+
this.timeout = rpcOptions?.timeout ?? DEFAULT_JETSTREAM_RPC_TIMEOUT;
|
|
1978
|
+
this.concurrency = rpcOptions?.concurrency;
|
|
1859
1979
|
}
|
|
1860
1980
|
logger = new Logger10("Jetstream:RpcRouter");
|
|
1861
1981
|
timeout;
|
|
1982
|
+
concurrency;
|
|
1983
|
+
resolvedAckExtensionInterval;
|
|
1862
1984
|
subscription = null;
|
|
1985
|
+
/** Lazily resolve the ack extension interval (needs ackWaitMap populated at runtime). */
|
|
1986
|
+
get ackExtensionInterval() {
|
|
1987
|
+
if (this.resolvedAckExtensionInterval !== void 0) return this.resolvedAckExtensionInterval;
|
|
1988
|
+
this.resolvedAckExtensionInterval = resolveAckExtensionInterval(
|
|
1989
|
+
this.rpcOptions?.ackExtension,
|
|
1990
|
+
this.ackWaitMap?.get("cmd" /* Command */)
|
|
1991
|
+
);
|
|
1992
|
+
return this.resolvedAckExtensionInterval;
|
|
1993
|
+
}
|
|
1863
1994
|
/** Start routing command messages to handlers. */
|
|
1864
1995
|
start() {
|
|
1865
1996
|
this.subscription = this.messageProvider.commands$.pipe(
|
|
@@ -1869,7 +2000,8 @@ var RpcRouter = class {
|
|
|
1869
2000
|
this.logger.error("Unexpected error in RPC router", err);
|
|
1870
2001
|
return EMPTY3;
|
|
1871
2002
|
})
|
|
1872
|
-
)
|
|
2003
|
+
),
|
|
2004
|
+
this.concurrency
|
|
1873
2005
|
)
|
|
1874
2006
|
).subscribe();
|
|
1875
2007
|
}
|
|
@@ -1901,7 +2033,7 @@ var RpcRouter = class {
|
|
|
1901
2033
|
this.logger.error(`Decode error for RPC ${msg.subject}:`, err);
|
|
1902
2034
|
return EMPTY3;
|
|
1903
2035
|
}
|
|
1904
|
-
this.eventBus.emit("messageRouted" /* MessageRouted */, msg.subject, "rpc");
|
|
2036
|
+
this.eventBus.emit("messageRouted" /* MessageRouted */, msg.subject, "rpc" /* Rpc */);
|
|
1905
2037
|
return from3(this.executeHandler(handler, data, msg, replyTo, correlationId));
|
|
1906
2038
|
}
|
|
1907
2039
|
/** Execute handler, publish response, settle message. */
|
|
@@ -1911,9 +2043,11 @@ var RpcRouter = class {
|
|
|
1911
2043
|
const hdrs = headers();
|
|
1912
2044
|
hdrs.set("x-correlation-id" /* CorrelationId */, correlationId);
|
|
1913
2045
|
let settled = false;
|
|
2046
|
+
const stopAckExtension = startAckExtensionTimer(msg, this.ackExtensionInterval);
|
|
1914
2047
|
const timeoutId = setTimeout(() => {
|
|
1915
2048
|
if (settled) return;
|
|
1916
2049
|
settled = true;
|
|
2050
|
+
stopAckExtension?.();
|
|
1917
2051
|
this.logger.error(`RPC timeout (${this.timeout}ms): ${msg.subject}`);
|
|
1918
2052
|
this.eventBus.emit("rpcTimeout" /* RpcTimeout */, msg.subject, correlationId);
|
|
1919
2053
|
msg.term("Handler timeout");
|
|
@@ -1923,6 +2057,7 @@ var RpcRouter = class {
|
|
|
1923
2057
|
if (settled) return;
|
|
1924
2058
|
settled = true;
|
|
1925
2059
|
clearTimeout(timeoutId);
|
|
2060
|
+
stopAckExtension?.();
|
|
1926
2061
|
msg.ack();
|
|
1927
2062
|
try {
|
|
1928
2063
|
nc.publish(replyTo, this.codec.encode(result), { headers: hdrs });
|
|
@@ -1933,6 +2068,7 @@ var RpcRouter = class {
|
|
|
1933
2068
|
if (settled) return;
|
|
1934
2069
|
settled = true;
|
|
1935
2070
|
clearTimeout(timeoutId);
|
|
2071
|
+
stopAckExtension?.();
|
|
1936
2072
|
try {
|
|
1937
2073
|
hdrs.set("x-error" /* Error */, "true");
|
|
1938
2074
|
nc.publish(replyTo, this.codec.encode(serializeError(err)), { headers: hdrs });
|
|
@@ -1956,7 +2092,7 @@ var ShutdownManager = class {
|
|
|
1956
2092
|
/**
|
|
1957
2093
|
* Execute the full shutdown sequence.
|
|
1958
2094
|
*
|
|
1959
|
-
* @param strategy Optional
|
|
2095
|
+
* @param strategy Optional stoppable to close (stops consumers and subscriptions).
|
|
1960
2096
|
*/
|
|
1961
2097
|
async shutdown(strategy) {
|
|
1962
2098
|
this.eventBus.emit("shutdownStart" /* ShutdownStart */);
|
|
@@ -1979,6 +2115,7 @@ var ShutdownManager = class {
|
|
|
1979
2115
|
};
|
|
1980
2116
|
|
|
1981
2117
|
// src/jetstream.module.ts
|
|
2118
|
+
var JETSTREAM_ACK_WAIT_MAP = /* @__PURE__ */ Symbol("JETSTREAM_ACK_WAIT_MAP");
|
|
1982
2119
|
var JetstreamModule = class {
|
|
1983
2120
|
constructor(shutdownManager, strategy) {
|
|
1984
2121
|
this.shutdownManager = shutdownManager;
|
|
@@ -2163,13 +2300,26 @@ var JetstreamModule = class {
|
|
|
2163
2300
|
return new ConsumerProvider(options, connection, streamProvider, patternRegistry);
|
|
2164
2301
|
}
|
|
2165
2302
|
},
|
|
2303
|
+
// Shared ack_wait map — populated by strategy after ensureConsumers()
|
|
2304
|
+
{
|
|
2305
|
+
provide: JETSTREAM_ACK_WAIT_MAP,
|
|
2306
|
+
useFactory: () => /* @__PURE__ */ new Map()
|
|
2307
|
+
},
|
|
2166
2308
|
// MessageProvider — pull-based message consumption
|
|
2167
2309
|
{
|
|
2168
2310
|
provide: MessageProvider,
|
|
2169
2311
|
inject: [JETSTREAM_OPTIONS, JETSTREAM_CONNECTION, JETSTREAM_EVENT_BUS],
|
|
2170
2312
|
useFactory: (options, connection, eventBus) => {
|
|
2171
2313
|
if (options.consumer === false) return null;
|
|
2172
|
-
|
|
2314
|
+
const consumeOptionsMap = /* @__PURE__ */ new Map();
|
|
2315
|
+
if (options.events?.consume)
|
|
2316
|
+
consumeOptionsMap.set("ev" /* Event */, options.events.consume);
|
|
2317
|
+
if (options.broadcast?.consume)
|
|
2318
|
+
consumeOptionsMap.set("broadcast" /* Broadcast */, options.broadcast.consume);
|
|
2319
|
+
if (options.rpc?.mode === "jetstream" && options.rpc.consume) {
|
|
2320
|
+
consumeOptionsMap.set("cmd" /* Command */, options.rpc.consume);
|
|
2321
|
+
}
|
|
2322
|
+
return new MessageProvider(connection, eventBus, consumeOptionsMap);
|
|
2173
2323
|
}
|
|
2174
2324
|
},
|
|
2175
2325
|
// EventRouter — routes event and broadcast messages to handlers
|
|
@@ -2180,20 +2330,33 @@ var JetstreamModule = class {
|
|
|
2180
2330
|
MessageProvider,
|
|
2181
2331
|
PatternRegistry,
|
|
2182
2332
|
JETSTREAM_CODEC,
|
|
2183
|
-
JETSTREAM_EVENT_BUS
|
|
2333
|
+
JETSTREAM_EVENT_BUS,
|
|
2334
|
+
JETSTREAM_ACK_WAIT_MAP
|
|
2184
2335
|
],
|
|
2185
|
-
useFactory: (options, messageProvider, patternRegistry, codec, eventBus) => {
|
|
2336
|
+
useFactory: (options, messageProvider, patternRegistry, codec, eventBus, ackWaitMap) => {
|
|
2186
2337
|
if (options.consumer === false) return null;
|
|
2187
2338
|
const deadLetterConfig = options.onDeadLetter ? {
|
|
2188
2339
|
maxDeliverByStream: /* @__PURE__ */ new Map(),
|
|
2189
2340
|
onDeadLetter: options.onDeadLetter
|
|
2190
2341
|
} : void 0;
|
|
2342
|
+
const processingConfig = {
|
|
2343
|
+
events: {
|
|
2344
|
+
concurrency: options.events?.concurrency,
|
|
2345
|
+
ackExtension: options.events?.ackExtension
|
|
2346
|
+
},
|
|
2347
|
+
broadcast: {
|
|
2348
|
+
concurrency: options.broadcast?.concurrency,
|
|
2349
|
+
ackExtension: options.broadcast?.ackExtension
|
|
2350
|
+
}
|
|
2351
|
+
};
|
|
2191
2352
|
return new EventRouter(
|
|
2192
2353
|
messageProvider,
|
|
2193
2354
|
patternRegistry,
|
|
2194
2355
|
codec,
|
|
2195
2356
|
eventBus,
|
|
2196
|
-
deadLetterConfig
|
|
2357
|
+
deadLetterConfig,
|
|
2358
|
+
processingConfig,
|
|
2359
|
+
ackWaitMap
|
|
2197
2360
|
);
|
|
2198
2361
|
}
|
|
2199
2362
|
},
|
|
@@ -2206,18 +2369,24 @@ var JetstreamModule = class {
|
|
|
2206
2369
|
PatternRegistry,
|
|
2207
2370
|
JETSTREAM_CONNECTION,
|
|
2208
2371
|
JETSTREAM_CODEC,
|
|
2209
|
-
JETSTREAM_EVENT_BUS
|
|
2372
|
+
JETSTREAM_EVENT_BUS,
|
|
2373
|
+
JETSTREAM_ACK_WAIT_MAP
|
|
2210
2374
|
],
|
|
2211
|
-
useFactory: (options, messageProvider, patternRegistry, connection, codec, eventBus) => {
|
|
2375
|
+
useFactory: (options, messageProvider, patternRegistry, connection, codec, eventBus, ackWaitMap) => {
|
|
2212
2376
|
if (options.consumer === false) return null;
|
|
2213
|
-
const
|
|
2377
|
+
const rpcOptions = options.rpc?.mode === "jetstream" ? {
|
|
2378
|
+
timeout: options.rpc.timeout,
|
|
2379
|
+
concurrency: options.rpc.concurrency,
|
|
2380
|
+
ackExtension: options.rpc.ackExtension
|
|
2381
|
+
} : void 0;
|
|
2214
2382
|
return new RpcRouter(
|
|
2215
2383
|
messageProvider,
|
|
2216
2384
|
patternRegistry,
|
|
2217
2385
|
connection,
|
|
2218
2386
|
codec,
|
|
2219
2387
|
eventBus,
|
|
2220
|
-
|
|
2388
|
+
rpcOptions,
|
|
2389
|
+
ackWaitMap
|
|
2221
2390
|
);
|
|
2222
2391
|
}
|
|
2223
2392
|
},
|
|
@@ -2248,9 +2417,10 @@ var JetstreamModule = class {
|
|
|
2248
2417
|
MessageProvider,
|
|
2249
2418
|
EventRouter,
|
|
2250
2419
|
RpcRouter,
|
|
2251
|
-
CoreRpcServer
|
|
2420
|
+
CoreRpcServer,
|
|
2421
|
+
JETSTREAM_ACK_WAIT_MAP
|
|
2252
2422
|
],
|
|
2253
|
-
useFactory: (options, connection, patternRegistry, streamProvider, consumerProvider, messageProvider, eventRouter, rpcRouter, coreRpcServer) => {
|
|
2423
|
+
useFactory: (options, connection, patternRegistry, streamProvider, consumerProvider, messageProvider, eventRouter, rpcRouter, coreRpcServer, ackWaitMap) => {
|
|
2254
2424
|
if (options.consumer === false) return null;
|
|
2255
2425
|
return new JetstreamStrategy(
|
|
2256
2426
|
options,
|
|
@@ -2261,7 +2431,8 @@ var JetstreamModule = class {
|
|
|
2261
2431
|
messageProvider,
|
|
2262
2432
|
eventRouter,
|
|
2263
2433
|
rpcRouter,
|
|
2264
|
-
coreRpcServer
|
|
2434
|
+
coreRpcServer,
|
|
2435
|
+
ackWaitMap
|
|
2265
2436
|
);
|
|
2266
2437
|
}
|
|
2267
2438
|
}
|
|
@@ -2341,8 +2512,13 @@ export {
|
|
|
2341
2512
|
JetstreamRecordBuilder,
|
|
2342
2513
|
JetstreamStrategy,
|
|
2343
2514
|
JsonCodec,
|
|
2515
|
+
MessageKind,
|
|
2516
|
+
PatternPrefix,
|
|
2344
2517
|
RpcContext,
|
|
2518
|
+
StreamKind,
|
|
2345
2519
|
TransportEvent,
|
|
2346
2520
|
getClientToken,
|
|
2521
|
+
isCoreRpcMode,
|
|
2522
|
+
isJetStreamRpcMode,
|
|
2347
2523
|
toNanos
|
|
2348
2524
|
};
|