@fluojs/microservices 1.0.5 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.ko.md +20 -6
  2. package/README.md +20 -6
  3. package/dist/module.d.ts.map +1 -1
  4. package/dist/module.js +1 -0
  5. package/dist/service.d.ts +12 -3
  6. package/dist/service.d.ts.map +1 -1
  7. package/dist/service.js +92 -36
  8. package/dist/status.d.ts +3 -0
  9. package/dist/status.d.ts.map +1 -1
  10. package/dist/status.js +10 -2
  11. package/dist/transports/grpc-transport.d.ts +4 -2
  12. package/dist/transports/grpc-transport.d.ts.map +1 -1
  13. package/dist/transports/grpc-transport.js +77 -12
  14. package/dist/transports/kafka-transport.d.ts +9 -1
  15. package/dist/transports/kafka-transport.d.ts.map +1 -1
  16. package/dist/transports/kafka-transport.js +29 -5
  17. package/dist/transports/nats-transport.d.ts +2 -0
  18. package/dist/transports/nats-transport.d.ts.map +1 -1
  19. package/dist/transports/nats-transport.js +50 -27
  20. package/dist/transports/rabbitmq-transport.d.ts +9 -1
  21. package/dist/transports/rabbitmq-transport.d.ts.map +1 -1
  22. package/dist/transports/rabbitmq-transport.js +38 -13
  23. package/dist/transports/redis-streams-transport.d.ts +18 -0
  24. package/dist/transports/redis-streams-transport.d.ts.map +1 -1
  25. package/dist/transports/redis-streams-transport.js +31 -1
  26. package/dist/transports/redis-transport.d.ts +1 -1
  27. package/dist/transports/redis-transport.d.ts.map +1 -1
  28. package/dist/transports/redis-transport.js +32 -16
  29. package/dist/transports/tcp-transport.d.ts +1 -0
  30. package/dist/transports/tcp-transport.d.ts.map +1 -1
  31. package/dist/transports/tcp-transport.js +45 -44
  32. package/dist/types.d.ts +7 -0
  33. package/dist/types.d.ts.map +1 -1
  34. package/package.json +11 -7
@@ -16,8 +16,10 @@ const grpcKinds = {
16
16
  * and exposes matching unary/server-stream/client-stream/bidi-stream client calls through one transport surface.
17
17
  */
18
18
  export class GrpcMicroserviceTransport {
19
- /** Indicates whether gRPC owns server shutdown in addition to cached client cleanup. */
20
- ownsResources;
19
+ /** Indicates that gRPC closes the cached outbound clients it creates. */
20
+ ownsResources = true;
21
+ /** Reports server and cached outbound client ownership independently. */
22
+ resourceOwnership;
21
23
  bidiStreamHandler;
22
24
  clientStreamHandler;
23
25
  closePromise;
@@ -46,7 +48,10 @@ export class GrpcMicroserviceTransport {
46
48
  this.requestTimeoutMs = options.requestTimeoutMs ?? 3_000;
47
49
  this.server = options.server;
48
50
  this.ownsServer = !options.server;
49
- this.ownsResources = this.ownsServer;
51
+ this.resourceOwnership = {
52
+ outboundClients: 'framework',
53
+ server: this.ownsServer ? 'framework' : 'caller'
54
+ };
50
55
  }
51
56
  setLogger(logger) {
52
57
  this.logger = logger;
@@ -553,6 +558,8 @@ export class GrpcMicroserviceTransport {
553
558
  let ended = false;
554
559
  let cleanupAbortListener;
555
560
  let settled = false;
561
+ let writerError;
562
+ let rejectResult;
556
563
  const cleanup = () => {
557
564
  cleanupAbortListener?.();
558
565
  cleanupAbortListener = undefined;
@@ -563,6 +570,7 @@ export class GrpcMicroserviceTransport {
563
570
  reject(new Error('gRPC client stream aborted before dispatch.'));
564
571
  return;
565
572
  }
573
+ rejectResult = reject;
566
574
  const settle = complete => {
567
575
  if (settled) {
568
576
  return;
@@ -572,6 +580,12 @@ export class GrpcMicroserviceTransport {
572
580
  complete();
573
581
  };
574
582
  callStream = method.call(runtime.client, metadata, (error, response) => {
583
+ // A local writer.error() failure is the authoritative cause: the transport-level
584
+ // status that follows an aborted call would otherwise mask it.
585
+ if (writerError) {
586
+ settle(() => reject(writerError));
587
+ return;
588
+ }
575
589
  if (error) {
576
590
  if (signal?.aborted) {
577
591
  settle(() => reject(new Error('gRPC client stream aborted.')));
@@ -615,10 +629,19 @@ export class GrpcMicroserviceTransport {
615
629
  }
616
630
  },
617
631
  error(err) {
618
- void err;
619
- if (!ended) {
620
- ended = true;
621
- callStream?.end();
632
+ if (ended) {
633
+ return;
634
+ }
635
+ ended = true;
636
+ writerError = err;
637
+ abortClientStreamCall(callStream, err);
638
+
639
+ // Runtimes without cancel()/destroy() cannot abort the call, so settle locally
640
+ // instead of leaving the caller with a silently successful stream.
641
+ if (!settled) {
642
+ settled = true;
643
+ cleanup();
644
+ rejectResult?.(err);
622
645
  }
623
646
  }
624
647
  };
@@ -637,6 +660,9 @@ export class GrpcMicroserviceTransport {
637
660
  const duplexStream = method.call(runtime.client, metadata);
638
661
  let writerEnded = false;
639
662
  let cleanupAbortListener;
663
+ const localFailure = {
664
+ error: undefined
665
+ };
640
666
  if (signal) {
641
667
  if (signal.aborted) {
642
668
  duplexStream.cancel?.();
@@ -656,6 +682,7 @@ export class GrpcMicroserviceTransport {
656
682
  }
657
683
  const reader = grpcReadableToAsyncIterable(duplexStream, {
658
684
  externalAbortCleanup: cleanupAbortListener,
685
+ localFailure,
659
686
  signal
660
687
  });
661
688
  const writer = {
@@ -671,11 +698,14 @@ export class GrpcMicroserviceTransport {
671
698
  }
672
699
  },
673
700
  error(err) {
674
- void err;
675
- if (!writerEnded) {
676
- writerEnded = true;
677
- duplexStream.end();
701
+ if (writerEnded) {
702
+ return;
678
703
  }
704
+ writerEnded = true;
705
+ // Recorded before cancelation so the reader reports the caller's cause rather than
706
+ // the CANCELLED status the aborted call raises.
707
+ localFailure.error = err;
708
+ abortDuplexStreamCall(duplexStream, err);
679
709
  }
680
710
  };
681
711
  return {
@@ -948,7 +978,7 @@ function grpcReadableToAsyncIterable(stream, options = {}) {
948
978
  }
949
979
  });
950
980
  stream.on('error', err => {
951
- const streamError = signal?.aborted ? new Error(abortErrorMessage) : err;
981
+ const streamError = options.localFailure?.error ?? (signal?.aborted ? new Error(abortErrorMessage) : err);
952
982
  if (!finish()) {
953
983
  return;
954
984
  }
@@ -1009,6 +1039,41 @@ function grpcReadableToAsyncIterable(stream, options = {}) {
1009
1039
  }
1010
1040
  };
1011
1041
  }
1042
+
1043
+ /**
1044
+ * Aborts an outbound client-streaming call so the peer observes a failure instead of a clean EOF.
1045
+ *
1046
+ * `destroy(err)` is preferred because it carries the cause; `cancel()` is the grpc-js fallback.
1047
+ */
1048
+ function abortClientStreamCall(stream, err) {
1049
+ if (!stream) {
1050
+ return;
1051
+ }
1052
+ if (stream.destroy) {
1053
+ stream.destroy(err);
1054
+ return;
1055
+ }
1056
+ if (stream.cancel) {
1057
+ stream.cancel();
1058
+ return;
1059
+ }
1060
+ stream.end();
1061
+ }
1062
+
1063
+ /**
1064
+ * Aborts an outbound bidirectional-streaming call so the peer observes a failure instead of a clean EOF.
1065
+ */
1066
+ function abortDuplexStreamCall(stream, err) {
1067
+ if (stream.destroy) {
1068
+ stream.destroy(err);
1069
+ return;
1070
+ }
1071
+ if (stream.cancel) {
1072
+ stream.cancel();
1073
+ return;
1074
+ }
1075
+ stream.end();
1076
+ }
1012
1077
  function parseGrpcPattern(pattern) {
1013
1078
  const segments = pattern.split('.');
1014
1079
  if (segments.length !== 2 || segments[0]?.length === 0 || segments[1]?.length === 0) {
@@ -1,4 +1,4 @@
1
- import type { MicroserviceTransport, TransportHandler } from '../types.js';
1
+ import type { MicroserviceTransport, MicroserviceTransportLogger, TransportHandler } from '../types.js';
2
2
  interface KafkaConsumerLike {
3
3
  subscribe(topic: string, handler: (message: string) => Promise<void> | void): Promise<void>;
4
4
  unsubscribe(topic: string): Promise<void>;
@@ -28,11 +28,19 @@ export declare class KafkaMicroserviceTransport implements MicroserviceTransport
28
28
  private handler;
29
29
  private listening;
30
30
  private listenPromise;
31
+ private logger;
31
32
  private readonly eventTopic;
32
33
  private readonly messageTopic;
33
34
  private readonly pending;
34
35
  private readonly requestTimeoutMs;
35
36
  private readonly responseTopic;
37
+ private logEventHandlerFailure;
38
+ /**
39
+ * Registers the runtime logger that receives inbound event handler failures.
40
+ *
41
+ * @param logger Logger used to report event handler failures.
42
+ */
43
+ setLogger(logger: MicroserviceTransportLogger): void;
36
44
  /**
37
45
  * Creates a Kafka transport with explicit producer and consumer collaborators.
38
46
  *
@@ -1 +1 @@
1
- {"version":3,"file":"kafka-transport.d.ts","sourceRoot":"","sources":["../../src/transports/kafka-transport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE3E,UAAU,iBAAiB;IACzB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5F,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3C;AAED,UAAU,iBAAiB;IACzB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxD;AAgBD,gEAAgE;AAChE,MAAM,WAAW,iCAAiC;IAChD,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;GAKG;AACH,qBAAa,0BAA2B,YAAW,qBAAqB;IAiB1D,OAAO,CAAC,QAAQ,CAAC,OAAO;IAhBpC,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,OAAO,CAA+B;IAC9C,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,aAAa,CAA4B;IACjD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqC;IAC7D,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IAEvC;;;;OAIG;gBAC0B,OAAO,EAAE,iCAAiC;IAOvE;;;;;OAKG;IACG,MAAM,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAkDtD;;;;;;;OAOG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;IAsFrF;;;;;;OAMG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAc5D;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YA6Cd,kBAAkB;YAkBlB,oBAAoB;YA2CpB,qBAAqB;IAqBnC,OAAO,CAAC,YAAY;CAqCrB"}
1
+ {"version":3,"file":"kafka-transport.d.ts","sourceRoot":"","sources":["../../src/transports/kafka-transport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,2BAA2B,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAGxG,UAAU,iBAAiB;IACzB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5F,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3C;AAED,UAAU,iBAAiB;IACzB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxD;AAgBD,gEAAgE;AAChE,MAAM,WAAW,iCAAiC;IAChD,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;GAKG;AACH,qBAAa,0BAA2B,YAAW,qBAAqB;IAmC1D,OAAO,CAAC,QAAQ,CAAC,OAAO;IAlCpC,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,OAAO,CAA+B;IAC9C,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,aAAa,CAA4B;IACjD,OAAO,CAAC,MAAM,CAA0C;IACxD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqC;IAC7D,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IAEvC,OAAO,CAAC,sBAAsB;IAQ9B;;;;OAIG;IACH,SAAS,CAAC,MAAM,EAAE,2BAA2B,GAAG,IAAI;IAIpD;;;;OAIG;gBAC0B,OAAO,EAAE,iCAAiC;IAOvE;;;;;OAKG;IACG,MAAM,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAkDtD;;;;;;;OAOG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;IAsFrF;;;;;;OAMG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAc5D;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YA6Cd,kBAAkB;YAuBlB,oBAAoB;YA2CpB,qBAAqB;IAqBnC,OAAO,CAAC,YAAY;CAqCrB"}
@@ -1,3 +1,5 @@
1
+ import { logTransportEventHandlerFailure } from './event-handler-logger.js';
2
+
1
3
  /** Options for configuring the Kafka microservice transport. */
2
4
 
3
5
  /**
@@ -12,11 +14,28 @@ export class KafkaMicroserviceTransport {
12
14
  handler;
13
15
  listening = false;
14
16
  listenPromise;
17
+ logger;
15
18
  eventTopic;
16
19
  messageTopic;
17
20
  pending = new Map();
18
21
  requestTimeoutMs;
19
22
  responseTopic;
23
+ logEventHandlerFailure(error) {
24
+ try {
25
+ logTransportEventHandlerFailure(this.logger, 'KafkaMicroserviceTransport', error);
26
+ } catch {
27
+ // A failing logger must not mask the event failure the broker still has to observe.
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Registers the runtime logger that receives inbound event handler failures.
33
+ *
34
+ * @param logger Logger used to report event handler failures.
35
+ */
36
+ setLogger(logger) {
37
+ this.logger = logger;
38
+ }
20
39
 
21
40
  /**
22
41
  * Creates a Kafka transport with explicit producer and consumer collaborators.
@@ -226,11 +245,16 @@ export class KafkaMicroserviceTransport {
226
245
  if (!message || message.kind !== 'event') {
227
246
  return;
228
247
  }
229
- await this.handler({
230
- kind: 'event',
231
- pattern: message.pattern,
232
- payload: message.payload
233
- });
248
+ try {
249
+ await this.handler({
250
+ kind: 'event',
251
+ pattern: message.pattern,
252
+ payload: message.payload
253
+ });
254
+ } catch (error) {
255
+ this.logEventHandlerFailure(error);
256
+ throw error;
257
+ }
234
258
  }
235
259
  async handleInboundRequest(rawMessage) {
236
260
  if (!this.handler) {
@@ -47,6 +47,8 @@ export declare class NatsMicroserviceTransport implements MicroserviceTransport
47
47
  private subscriptions;
48
48
  private logEventHandlerFailure;
49
49
  private handleEventMessageSafely;
50
+ private handleRequestMessageSafely;
51
+ private logRequestCallbackFailure;
50
52
  /**
51
53
  * Creates a NATS transport using a client and codec supplied by the application.
52
54
  *
@@ -1 +1 @@
1
- {"version":3,"file":"nats-transport.d.ts","sourceRoot":"","sources":["../../src/transports/nats-transport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,2BAA2B,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAGxG,UAAU,eAAe;IACvB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,OAAO,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;CACjC;AAED,UAAU,aAAa;IACrB,MAAM,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAAC;IACjC,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAAC;CACnC;AAED,UAAU,oBAAoB;IAC5B,WAAW,IAAI,IAAI,CAAC;CACrB;AAED,UAAU,QAAQ;IAChB,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,IAAI,CAAC;IACpD,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,UAAU,CAAA;KAAE,CAAC,CAAC;IAC7G,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,GAAG,oBAAoB,CAAC;CAC/F;AAED,+DAA+D;AAC/D,MAAM,WAAW,gCAAgC;IAC/C,MAAM,EAAE,QAAQ,CAAC;IACjB,KAAK,EAAE,aAAa,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAkBD;;;;;GAKG;AACH,qBAAa,yBAA0B,YAAW,qBAAqB;IA2BzD,OAAO,CAAC,QAAQ,CAAC,OAAO;IA1BpC,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,OAAO,CAA+B;IAC9C,OAAO,CAAC,MAAM,CAA0C;IACxD,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqC;IAC7D,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,aAAa,CAA8B;IAEnD,OAAO,CAAC,sBAAsB;IAI9B,OAAO,CAAC,wBAAwB;IAMhC;;;;OAIG;gBAC0B,OAAO,EAAE,gCAAgC;IAMtE,SAAS,CAAC,MAAM,EAAE,2BAA2B,GAAG,IAAI;IAIpD;;;;;OAKG;IACG,MAAM,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAwCtD;;;;;;;OAOG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;IA+FrF;;;;;;OAMG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAc5D;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAuCd,kBAAkB;YAsBlB,oBAAoB;IAwBlC,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,MAAM;CAGf"}
1
+ {"version":3,"file":"nats-transport.d.ts","sourceRoot":"","sources":["../../src/transports/nats-transport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,2BAA2B,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAGxG,UAAU,eAAe;IACvB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,OAAO,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;CACjC;AAED,UAAU,aAAa;IACrB,MAAM,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAAC;IACjC,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAAC;CACnC;AAED,UAAU,oBAAoB;IAC5B,WAAW,IAAI,IAAI,CAAC;CACrB;AAED,UAAU,QAAQ;IAChB,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,IAAI,CAAC;IACpD,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,UAAU,CAAA;KAAE,CAAC,CAAC;IAC7G,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,GAAG,oBAAoB,CAAC;CAC/F;AAED,+DAA+D;AAC/D,MAAM,WAAW,gCAAgC;IAC/C,MAAM,EAAE,QAAQ,CAAC;IACjB,KAAK,EAAE,aAAa,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAkBD;;;;;GAKG;AACH,qBAAa,yBAA0B,YAAW,qBAAqB;IA+CzD,OAAO,CAAC,QAAQ,CAAC,OAAO;IA9CpC,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,OAAO,CAA+B;IAC9C,OAAO,CAAC,MAAM,CAA0C;IACxD,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqC;IAC7D,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,aAAa,CAA8B;IAEnD,OAAO,CAAC,sBAAsB;IAI9B,OAAO,CAAC,wBAAwB;IAMhC,OAAO,CAAC,0BAA0B;IAMlC,OAAO,CAAC,yBAAyB;IAcjC;;;;OAIG;gBAC0B,OAAO,EAAE,gCAAgC;IAMtE,SAAS,CAAC,MAAM,EAAE,2BAA2B,GAAG,IAAI;IAIpD;;;;;OAKG;IACG,MAAM,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IA4CtD;;;;;;;OAOG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;IA+FrF;;;;;;OAMG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAc5D;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YA+Cd,kBAAkB;YAsBlB,oBAAoB;IA8BlC,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,MAAM;CAGf"}
@@ -27,6 +27,22 @@ export class NatsMicroserviceTransport {
27
27
  this.logEventHandlerFailure(error);
28
28
  });
29
29
  }
30
+ handleRequestMessageSafely(message) {
31
+ void this.handleRequestMessage(message).catch(error => {
32
+ this.logRequestCallbackFailure(error);
33
+ });
34
+ }
35
+ logRequestCallbackFailure(error) {
36
+ const logger = this.logger;
37
+ if (!logger) {
38
+ return;
39
+ }
40
+ try {
41
+ logger.error('Request callback failed.', error, 'NatsMicroserviceTransport');
42
+ } catch (loggerError) {
43
+ void loggerError;
44
+ }
45
+ }
30
46
 
31
47
  /**
32
48
  * Creates a NATS transport using a client and codec supplied by the application.
@@ -54,6 +70,9 @@ export class NatsMicroserviceTransport {
54
70
  if (this.closePromise) {
55
71
  throw new Error('NATS microservice transport is closing. Wait for close() to complete before listen().');
56
72
  }
73
+ if (this.subscriptions.length > 0) {
74
+ throw new Error('NATS subscription cleanup is incomplete. Call close() again before listen().');
75
+ }
57
76
  this.closing = false;
58
77
  }
59
78
  this.handler = handler;
@@ -65,7 +84,7 @@ export class NatsMicroserviceTransport {
65
84
  this.handleEventMessageSafely(message);
66
85
  }));
67
86
  this.subscriptions.push(this.options.client.subscribe(this.messageSubject, message => {
68
- void this.handleRequestMessage(message);
87
+ this.handleRequestMessageSafely(message);
69
88
  }));
70
89
  } catch (error) {
71
90
  for (const subscription of this.subscriptions.reverse()) {
@@ -198,23 +217,27 @@ export class NatsMicroserviceTransport {
198
217
  }
199
218
  this.closing = true;
200
219
  this.closePromise = (async () => {
201
- let closeError;
202
- try {
203
- for (const subscription of this.subscriptions) {
220
+ const cleanupErrors = [];
221
+ const retainedSubscriptions = [];
222
+ for (const subscription of this.subscriptions) {
223
+ try {
204
224
  subscription.unsubscribe();
205
- }
206
- } catch (error) {
207
- closeError = error;
208
- } finally {
209
- this.subscriptions = [];
210
- this.listening = false;
211
- this.handler = undefined;
212
- for (const pending of [...this.pending.values()]) {
213
- pending.reject(new Error('NATS microservice transport closed before response.'));
225
+ } catch (error) {
226
+ cleanupErrors.push(error);
227
+ retainedSubscriptions.push(subscription);
214
228
  }
215
229
  }
216
- if (closeError) {
217
- throw closeError;
230
+ this.subscriptions = retainedSubscriptions;
231
+ this.listening = false;
232
+ this.handler = undefined;
233
+ for (const pending of [...this.pending.values()]) {
234
+ pending.reject(new Error('NATS microservice transport closed before response.'));
235
+ }
236
+ if (cleanupErrors.length === 1) {
237
+ throw cleanupErrors[0];
238
+ }
239
+ if (cleanupErrors.length > 1) {
240
+ throw new AggregateError(cleanupErrors, 'NATS subscription cleanup failed for multiple subscriptions.');
218
241
  }
219
242
  })();
220
243
  try {
@@ -249,21 +272,21 @@ export class NatsMicroserviceTransport {
249
272
  if (packet.kind !== 'message') {
250
273
  return;
251
274
  }
275
+ let response;
252
276
  try {
253
- const payload = await this.handler({
254
- kind: 'message',
255
- pattern: packet.pattern,
256
- payload: packet.payload
257
- });
258
- message.respond(this.encode({
259
- payload
260
- }));
277
+ response = {
278
+ payload: await this.handler({
279
+ kind: 'message',
280
+ pattern: packet.pattern,
281
+ payload: packet.payload
282
+ })
283
+ };
261
284
  } catch (error) {
262
- const errorMessage = error instanceof Error ? error.message : 'Unhandled microservice error';
263
- message.respond(this.encode({
264
- error: errorMessage
265
- }));
285
+ response = {
286
+ error: error instanceof Error ? error.message : 'Unhandled microservice error'
287
+ };
266
288
  }
289
+ message.respond(this.encode(response));
267
290
  }
268
291
  decode(data) {
269
292
  return JSON.parse(this.options.codec.decode(data));
@@ -1,4 +1,4 @@
1
- import type { MicroserviceTransport, TransportHandler } from '../types.js';
1
+ import type { MicroserviceTransport, MicroserviceTransportLogger, TransportHandler } from '../types.js';
2
2
  interface RabbitMqConsumerLike {
3
3
  consume(queue: string, handler: (message: string) => Promise<void> | void): Promise<void>;
4
4
  cancel(queue: string): Promise<void>;
@@ -28,12 +28,20 @@ export declare class RabbitMqMicroserviceTransport implements MicroserviceTransp
28
28
  private handler;
29
29
  private listening;
30
30
  private listenPromise;
31
+ private logger;
31
32
  private readonly eventQueue;
32
33
  private readonly messageQueue;
33
34
  private readonly pending;
34
35
  private readonly requestTimeoutMs;
35
36
  private readonly responseQueue;
36
37
  private readonly subscribedQueues;
38
+ private logEventHandlerFailure;
39
+ /**
40
+ * Registers the runtime logger that receives inbound event handler failures.
41
+ *
42
+ * @param logger Logger used to report event handler failures.
43
+ */
44
+ setLogger(logger: MicroserviceTransportLogger): void;
37
45
  /**
38
46
  * Creates a RabbitMQ transport with explicit consumer and publisher collaborators.
39
47
  *
@@ -1 +1 @@
1
- {"version":3,"file":"rabbitmq-transport.d.ts","sourceRoot":"","sources":["../../src/transports/rabbitmq-transport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE3E,UAAU,oBAAoB;IAC5B,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1F,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACtC;AAED,UAAU,qBAAqB;IAC7B,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxD;AAWD,mEAAmE;AACnE,MAAM,WAAW,oCAAoC;IACnD,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,qBAAqB,CAAC;IACjC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;GAKG;AACH,qBAAa,6BAA8B,YAAW,qBAAqB;IAsB7D,OAAO,CAAC,QAAQ,CAAC,OAAO;IArBpC,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,OAAO,CAA+B;IAC9C,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,aAAa,CAA4B;IACjD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAInB;IACL,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAqB;IAEtD;;;;OAIG;gBAC0B,OAAO,EAAE,oCAAoC;IAO1E;;;;;OAKG;IACG,MAAM,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAiDtD;;;;;;;OAOG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;IA6GrF;;;;;;OAMG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAc5D;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAoDd,oBAAoB;YAiCpB,aAAa;IAsC3B,OAAO,CAAC,cAAc;IAqBtB,OAAO,CAAC,qBAAqB;CAO9B"}
1
+ {"version":3,"file":"rabbitmq-transport.d.ts","sourceRoot":"","sources":["../../src/transports/rabbitmq-transport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,2BAA2B,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAGxG,UAAU,oBAAoB;IAC5B,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1F,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACtC;AAED,UAAU,qBAAqB;IAC7B,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxD;AAWD,mEAAmE;AACnE,MAAM,WAAW,oCAAoC;IACnD,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,qBAAqB,CAAC;IACjC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;GAKG;AACH,qBAAa,6BAA8B,YAAW,qBAAqB;IAwC7D,OAAO,CAAC,QAAQ,CAAC,OAAO;IAvCpC,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,OAAO,CAA+B;IAC9C,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,aAAa,CAA4B;IACjD,OAAO,CAAC,MAAM,CAA0C;IACxD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAInB;IACL,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAqB;IAEtD,OAAO,CAAC,sBAAsB;IAQ9B;;;;OAIG;IACH,SAAS,CAAC,MAAM,EAAE,2BAA2B,GAAG,IAAI;IAIpD;;;;OAIG;gBAC0B,OAAO,EAAE,oCAAoC;IAO1E;;;;;OAKG;IACG,MAAM,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAqDtD;;;;;;;OAOG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;IA6GrF;;;;;;OAMG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAc5D;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAkDd,oBAAoB;YAuCpB,aAAa;IAsC3B,OAAO,CAAC,cAAc;IAqBtB,OAAO,CAAC,qBAAqB;CAO9B"}
@@ -1,3 +1,5 @@
1
+ import { logTransportEventHandlerFailure } from './event-handler-logger.js';
2
+
1
3
  /** Options for configuring the RabbitMQ microservice transport. */
2
4
 
3
5
  /**
@@ -12,12 +14,29 @@ export class RabbitMqMicroserviceTransport {
12
14
  handler;
13
15
  listening = false;
14
16
  listenPromise;
17
+ logger;
15
18
  eventQueue;
16
19
  messageQueue;
17
20
  pending = new Map();
18
21
  requestTimeoutMs;
19
22
  responseQueue;
20
23
  subscribedQueues = new Set();
24
+ logEventHandlerFailure(error) {
25
+ try {
26
+ logTransportEventHandlerFailure(this.logger, 'RabbitMqMicroserviceTransport', error);
27
+ } catch {
28
+ // A failing logger must not mask the event failure the broker still has to observe.
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Registers the runtime logger that receives inbound event handler failures.
34
+ *
35
+ * @param logger Logger used to report event handler failures.
36
+ */
37
+ setLogger(logger) {
38
+ this.logger = logger;
39
+ }
21
40
 
22
41
  /**
23
42
  * Creates a RabbitMQ transport with explicit consumer and publisher collaborators.
@@ -43,6 +62,9 @@ export class RabbitMqMicroserviceTransport {
43
62
  if (this.closePromise) {
44
63
  throw new Error('RabbitMqMicroserviceTransport is closing. Wait for close() to complete before listen().');
45
64
  }
65
+ if (this.subscribedQueues.size > 0) {
66
+ throw new Error('RabbitMQ consumer cleanup is incomplete. Call close() again before listen().');
67
+ }
46
68
  this.closing = false;
47
69
  }
48
70
  this.handler = handler;
@@ -220,17 +242,15 @@ export class RabbitMqMicroserviceTransport {
220
242
  }
221
243
  }
222
244
  try {
223
- if (this.listening) {
224
- for (const queue of this.subscribedQueues) {
225
- try {
226
- await this.options.consumer.cancel(queue);
227
- } catch (error) {
228
- closeError ??= error;
229
- }
245
+ for (const queue of [...this.subscribedQueues]) {
246
+ try {
247
+ await this.options.consumer.cancel(queue);
248
+ this.subscribedQueues.delete(queue);
249
+ } catch (error) {
250
+ closeError ??= error;
230
251
  }
231
252
  }
232
253
  } finally {
233
- this.subscribedQueues.clear();
234
254
  this.rejectPendingRequests(new Error('RabbitMQ microservice transport closed before response.'));
235
255
  this.listening = false;
236
256
  this.handler = undefined;
@@ -263,11 +283,16 @@ export class RabbitMqMicroserviceTransport {
263
283
  return;
264
284
  }
265
285
  if (message.kind === 'event') {
266
- await this.handler({
267
- kind: 'event',
268
- pattern: message.pattern,
269
- payload: message.payload
270
- });
286
+ try {
287
+ await this.handler({
288
+ kind: 'event',
289
+ pattern: message.pattern,
290
+ payload: message.payload
291
+ });
292
+ } catch (error) {
293
+ this.logEventHandlerFailure(error);
294
+ throw error;
295
+ }
271
296
  return;
272
297
  }
273
298
  if (message.kind === 'message' && message.requestId) {
@@ -15,6 +15,12 @@ export interface RedisStreamClientLike {
15
15
  blockMs?: number;
16
16
  count?: number;
17
17
  }): Promise<readonly StreamReadGroupResult[] | null>;
18
+ xautoclaim?(stream: string, group: string, consumer: string, minIdleTimeMs: number, startId: string, options?: {
19
+ count?: number;
20
+ }): Promise<{
21
+ readonly entries: readonly StreamReadGroupResult[];
22
+ readonly nextStartId: string;
23
+ }>;
18
24
  xack(stream: string, group: string, id: string): Promise<void>;
19
25
  get?(key: string): Promise<string | null>;
20
26
  incr?(key: string): Promise<number>;
@@ -45,6 +51,14 @@ export interface RedisStreamsMicroserviceTransportOptions {
45
51
  responseRetentionMaxLen?: number;
46
52
  consumerGroup?: string;
47
53
  namespace?: string;
54
+ /**
55
+ * Minimum pending-entry idle time before `XAUTOCLAIM` recovery in the shared request group
56
+ * or this listener's instance-scoped event group.
57
+ *
58
+ * Defaults to `60_000`; set a non-positive value to disable recovery. The reader client must
59
+ * implement {@link RedisStreamClientLike.xautoclaim} for recovery to run.
60
+ */
61
+ pendingReclaimIdleMs?: number;
48
62
  requestTimeoutMs?: number;
49
63
  pollBlockMs?: number;
50
64
  }
@@ -66,9 +80,12 @@ export declare class RedisStreamsMicroserviceTransport implements MicroserviceTr
66
80
  private messageGroupLeaseRegistered;
67
81
  private ownsMessageGroup;
68
82
  private readonly pending;
83
+ private readonly pendingReclaimFailureGroups;
84
+ private readonly pendingReclaimStartIds;
69
85
  private pollPromises;
70
86
  private readonly namespace;
71
87
  private readonly consumerGroup;
88
+ private readonly pendingReclaimIdleMs;
72
89
  private readonly requestTimeoutMs;
73
90
  private readonly pollBlockMs;
74
91
  private readonly messageRetentionMaxLen;
@@ -115,6 +132,7 @@ export declare class RedisStreamsMicroserviceTransport implements MicroserviceTr
115
132
  private registerMessageGroupLease;
116
133
  private releaseMessageGroupLease;
117
134
  private pollStream;
135
+ private reclaimPendingEntries;
118
136
  private handleStreamEntry;
119
137
  private handleInboundRequest;
120
138
  private publishFrame;
@@ -1 +1 @@
1
- {"version":3,"file":"redis-streams-transport.d.ts","sourceRoot":"","sources":["../../src/transports/redis-streams-transport.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"redis-streams-transport.d.ts","sourceRoot":"","sources":["../../src/transports/redis-streams-transport.ts"],"names":[],"mappings":"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"}
@@ -29,9 +29,12 @@ export class RedisStreamsMicroserviceTransport {
29
29
  messageGroupLeaseRegistered = false;
30
30
  ownsMessageGroup = false;
31
31
  pending = new Map();
32
+ pendingReclaimFailureGroups = new Set();
33
+ pendingReclaimStartIds = new Map();
32
34
  pollPromises = [];
33
35
  namespace;
34
36
  consumerGroup;
37
+ pendingReclaimIdleMs;
35
38
  requestTimeoutMs;
36
39
  pollBlockMs;
37
40
  messageRetentionMaxLen;
@@ -48,6 +51,7 @@ export class RedisStreamsMicroserviceTransport {
48
51
  this.consumerId = crypto.randomUUID();
49
52
  this.namespace = options.namespace ?? 'fluo:streams';
50
53
  this.consumerGroup = options.consumerGroup ?? 'fluo-handlers';
54
+ this.pendingReclaimIdleMs = options.pendingReclaimIdleMs ?? 60_000;
51
55
  this.requestTimeoutMs = options.requestTimeoutMs ?? 3_000;
52
56
  this.pollBlockMs = options.pollBlockMs ?? 500;
53
57
  this.messageRetentionMaxLen = options.messageRetentionMaxLen;
@@ -264,6 +268,8 @@ export class RedisStreamsMicroserviceTransport {
264
268
  this.handler = undefined;
265
269
  this.messageGroupLeaseRegistered = false;
266
270
  this.ownsMessageGroup = false;
271
+ this.pendingReclaimFailureGroups.clear();
272
+ this.pendingReclaimStartIds.clear();
267
273
  this.pollPromises = [];
268
274
  for (const pending of [...this.pending.values()]) {
269
275
  pending.reject(new Error('Redis Streams microservice transport closed before response.'));
@@ -342,7 +348,8 @@ export class RedisStreamsMicroserviceTransport {
342
348
  async pollStream(stream, group) {
343
349
  while (!this.closing) {
344
350
  try {
345
- const entries = await this.options.readerClient.xreadgroup(group, this.consumerId, [stream], {
351
+ const reclaimedEntries = await this.reclaimPendingEntries(stream, group);
352
+ const entries = reclaimedEntries.length > 0 ? reclaimedEntries : await this.options.readerClient.xreadgroup(group, this.consumerId, [stream], {
346
353
  blockMs: this.pollBlockMs,
347
354
  count: 10
348
355
  });
@@ -369,6 +376,29 @@ export class RedisStreamsMicroserviceTransport {
369
376
  }
370
377
  }
371
378
  }
379
+ async reclaimPendingEntries(stream, group) {
380
+ const {
381
+ readerClient
382
+ } = this.options;
383
+ if (stream === this.responseStream || this.pendingReclaimIdleMs <= 0 || !readerClient.xautoclaim) {
384
+ return [];
385
+ }
386
+ const groupKey = `${stream}::${group}`;
387
+ try {
388
+ const result = await readerClient.xautoclaim(stream, group, this.consumerId, this.pendingReclaimIdleMs, this.pendingReclaimStartIds.get(groupKey) ?? '0-0', {
389
+ count: 10
390
+ });
391
+ this.pendingReclaimFailureGroups.delete(groupKey);
392
+ this.pendingReclaimStartIds.set(groupKey, result.nextStartId);
393
+ return result.entries;
394
+ } catch (error) {
395
+ if (!this.pendingReclaimFailureGroups.has(groupKey)) {
396
+ this.logger?.error('Redis Streams pending reclaim failed; continuing normal consumption.', error, 'RedisStreamsMicroserviceTransport');
397
+ this.pendingReclaimFailureGroups.add(groupKey);
398
+ }
399
+ return [];
400
+ }
401
+ }
372
402
  async handleStreamEntry(stream, message) {
373
403
  if (stream === this.messageStream) {
374
404
  await this.handleInboundRequest(message);