@vercel/queue 0.0.0-alpha.15 → 0.0.0-alpha.17

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.mjs CHANGED
@@ -590,207 +590,6 @@ var QueueClient = class {
590
590
  }
591
591
  };
592
592
 
593
- // src/consumer-group.ts
594
- var ConsumerGroup = class {
595
- client;
596
- topicName;
597
- consumerGroupName;
598
- visibilityTimeout;
599
- refreshInterval;
600
- transport;
601
- /**
602
- * Create a new ConsumerGroup instance
603
- * @param client QueueClient instance to use for API calls
604
- * @param topicName Name of the topic to consume from
605
- * @param consumerGroupName Name of the consumer group
606
- * @param options Optional configuration
607
- */
608
- constructor(client, topicName, consumerGroupName, options = {}) {
609
- this.client = client;
610
- this.topicName = topicName;
611
- this.consumerGroupName = consumerGroupName;
612
- this.visibilityTimeout = options.visibilityTimeoutSeconds || 30;
613
- this.refreshInterval = options.refreshInterval || 10;
614
- this.transport = options.transport || new JsonTransport();
615
- }
616
- /**
617
- * Starts a background loop that periodically extends the visibility timeout for a message.
618
- * This prevents the message from becoming visible to other consumers while it's being processed.
619
- *
620
- * The extension loop runs every `refreshInterval` seconds and updates the message's
621
- * visibility timeout to `visibilityTimeout` seconds from the current time.
622
- *
623
- * @param messageId - The unique identifier of the message to extend visibility for
624
- * @param ticket - The receipt ticket that proves ownership of the message
625
- * @returns A function that when called will stop the extension loop
626
- *
627
- * @remarks
628
- * - The first extension attempt occurs after `refreshInterval` seconds, not immediately
629
- * - If an extension fails, the loop terminates with an error logged to console
630
- * - The returned stop function is idempotent - calling it multiple times is safe
631
- * - By default, the stop function returns immediately without waiting for in-flight
632
- * - Pass `true` to the stop function to wait for any in-flight extension to complete
633
- */
634
- startVisibilityExtension(messageId, ticket) {
635
- let isRunning = true;
636
- let resolveLifecycle;
637
- let timeoutId = null;
638
- const lifecyclePromise = new Promise((resolve) => {
639
- resolveLifecycle = resolve;
640
- });
641
- const extend = async () => {
642
- if (!isRunning) {
643
- resolveLifecycle();
644
- return;
645
- }
646
- try {
647
- await this.client.changeVisibility({
648
- queueName: this.topicName,
649
- consumerGroup: this.consumerGroupName,
650
- messageId,
651
- ticket,
652
- visibilityTimeoutSeconds: this.visibilityTimeout
653
- });
654
- if (isRunning) {
655
- timeoutId = setTimeout(() => extend(), this.refreshInterval * 1e3);
656
- } else {
657
- resolveLifecycle();
658
- }
659
- } catch (error) {
660
- console.error(
661
- `Failed to extend visibility for message ${messageId}:`,
662
- error
663
- );
664
- resolveLifecycle();
665
- }
666
- };
667
- timeoutId = setTimeout(() => extend(), this.refreshInterval * 1e3);
668
- return async (waitForCompletion = false) => {
669
- isRunning = false;
670
- if (timeoutId) {
671
- clearTimeout(timeoutId);
672
- timeoutId = null;
673
- }
674
- if (waitForCompletion) {
675
- await lifecyclePromise;
676
- } else {
677
- resolveLifecycle();
678
- }
679
- };
680
- }
681
- /**
682
- * Process a single message with the given handler
683
- * @param message The message to process
684
- * @param handler Function to process the message
685
- */
686
- async processMessage(message, handler) {
687
- const stopExtension = this.startVisibilityExtension(
688
- message.messageId,
689
- message.ticket
690
- );
691
- try {
692
- const result = await handler(message.payload, {
693
- messageId: message.messageId,
694
- deliveryCount: message.deliveryCount,
695
- createdAt: message.createdAt,
696
- topicName: this.topicName,
697
- consumerGroup: this.consumerGroupName
698
- });
699
- await stopExtension();
700
- if (result && "timeoutSeconds" in result) {
701
- await this.client.changeVisibility({
702
- queueName: this.topicName,
703
- consumerGroup: this.consumerGroupName,
704
- messageId: message.messageId,
705
- ticket: message.ticket,
706
- visibilityTimeoutSeconds: result.timeoutSeconds
707
- });
708
- } else {
709
- await this.client.deleteMessage({
710
- queueName: this.topicName,
711
- consumerGroup: this.consumerGroupName,
712
- messageId: message.messageId,
713
- ticket: message.ticket
714
- });
715
- }
716
- } catch (error) {
717
- await stopExtension();
718
- if (this.transport.finalize && message.payload !== void 0 && message.payload !== null) {
719
- try {
720
- await this.transport.finalize(message.payload);
721
- } catch (finalizeError) {
722
- console.warn("Failed to finalize message payload:", finalizeError);
723
- }
724
- }
725
- throw error;
726
- }
727
- }
728
- async consume(handler, options) {
729
- if (options?.messageId) {
730
- if (options.skipPayload) {
731
- const response = await this.client.receiveMessageById(
732
- {
733
- queueName: this.topicName,
734
- consumerGroup: this.consumerGroupName,
735
- messageId: options.messageId,
736
- visibilityTimeoutSeconds: this.visibilityTimeout,
737
- skipPayload: true
738
- },
739
- this.transport
740
- );
741
- await this.processMessage(
742
- response.message,
743
- handler
744
- );
745
- } else {
746
- const response = await this.client.receiveMessageById(
747
- {
748
- queueName: this.topicName,
749
- consumerGroup: this.consumerGroupName,
750
- messageId: options.messageId,
751
- visibilityTimeoutSeconds: this.visibilityTimeout
752
- },
753
- this.transport
754
- );
755
- await this.processMessage(
756
- response.message,
757
- handler
758
- );
759
- }
760
- } else {
761
- let messageFound = false;
762
- for await (const message of this.client.receiveMessages(
763
- {
764
- queueName: this.topicName,
765
- consumerGroup: this.consumerGroupName,
766
- visibilityTimeoutSeconds: this.visibilityTimeout,
767
- limit: 1
768
- },
769
- this.transport
770
- )) {
771
- messageFound = true;
772
- await this.processMessage(message, handler);
773
- break;
774
- }
775
- if (!messageFound) {
776
- throw new Error("No messages available");
777
- }
778
- }
779
- }
780
- /**
781
- * Get the consumer group name
782
- */
783
- get name() {
784
- return this.consumerGroupName;
785
- }
786
- /**
787
- * Get the topic name this consumer group is subscribed to
788
- */
789
- get topic() {
790
- return this.topicName;
791
- }
792
- };
793
-
794
593
  // src/callback.ts
795
594
  function validateWildcardPattern(pattern) {
796
595
  const firstIndex = pattern.indexOf("*");
@@ -1039,6 +838,17 @@ function createMockCloudEventRequest(topicName, consumerGroup, messageId) {
1039
838
  });
1040
839
  }
1041
840
  var DEV_CALLBACK_DELAY = 1e3;
841
+ function scheduleDevTimeout(topicName, messageId, timeoutSeconds) {
842
+ console.log(
843
+ `[Dev Mode] Message ${messageId} timed out for ${timeoutSeconds}s, will re-trigger`
844
+ );
845
+ setTimeout(() => {
846
+ console.log(
847
+ `[Dev Mode] Re-triggering callback for timed-out message ${messageId}`
848
+ );
849
+ triggerDevCallbacks(topicName, messageId);
850
+ }, timeoutSeconds * 1e3);
851
+ }
1042
852
  function triggerDevCallbacks(topicName, messageId) {
1043
853
  const handlersMap = findRouteHandlersForTopic(topicName);
1044
854
  if (handlersMap.size === 0) {
@@ -1108,6 +918,214 @@ if (process.env.NODE_ENV === "test" || process.env.VITEST) {
1108
918
  globalThis.__clearDevHandlers = clearDevHandlers;
1109
919
  }
1110
920
 
921
+ // src/consumer-group.ts
922
+ var ConsumerGroup = class {
923
+ client;
924
+ topicName;
925
+ consumerGroupName;
926
+ visibilityTimeout;
927
+ refreshInterval;
928
+ transport;
929
+ /**
930
+ * Create a new ConsumerGroup instance
931
+ * @param client QueueClient instance to use for API calls
932
+ * @param topicName Name of the topic to consume from
933
+ * @param consumerGroupName Name of the consumer group
934
+ * @param options Optional configuration
935
+ */
936
+ constructor(client, topicName, consumerGroupName, options = {}) {
937
+ this.client = client;
938
+ this.topicName = topicName;
939
+ this.consumerGroupName = consumerGroupName;
940
+ this.visibilityTimeout = options.visibilityTimeoutSeconds || 30;
941
+ this.refreshInterval = options.refreshInterval || 10;
942
+ this.transport = options.transport || new JsonTransport();
943
+ }
944
+ /**
945
+ * Starts a background loop that periodically extends the visibility timeout for a message.
946
+ * This prevents the message from becoming visible to other consumers while it's being processed.
947
+ *
948
+ * The extension loop runs every `refreshInterval` seconds and updates the message's
949
+ * visibility timeout to `visibilityTimeout` seconds from the current time.
950
+ *
951
+ * @param messageId - The unique identifier of the message to extend visibility for
952
+ * @param ticket - The receipt ticket that proves ownership of the message
953
+ * @returns A function that when called will stop the extension loop
954
+ *
955
+ * @remarks
956
+ * - The first extension attempt occurs after `refreshInterval` seconds, not immediately
957
+ * - If an extension fails, the loop terminates with an error logged to console
958
+ * - The returned stop function is idempotent - calling it multiple times is safe
959
+ * - By default, the stop function returns immediately without waiting for in-flight
960
+ * - Pass `true` to the stop function to wait for any in-flight extension to complete
961
+ */
962
+ startVisibilityExtension(messageId, ticket) {
963
+ let isRunning = true;
964
+ let resolveLifecycle;
965
+ let timeoutId = null;
966
+ const lifecyclePromise = new Promise((resolve) => {
967
+ resolveLifecycle = resolve;
968
+ });
969
+ const extend = async () => {
970
+ if (!isRunning) {
971
+ resolveLifecycle();
972
+ return;
973
+ }
974
+ try {
975
+ await this.client.changeVisibility({
976
+ queueName: this.topicName,
977
+ consumerGroup: this.consumerGroupName,
978
+ messageId,
979
+ ticket,
980
+ visibilityTimeoutSeconds: this.visibilityTimeout
981
+ });
982
+ if (isRunning) {
983
+ timeoutId = setTimeout(() => extend(), this.refreshInterval * 1e3);
984
+ } else {
985
+ resolveLifecycle();
986
+ }
987
+ } catch (error) {
988
+ console.error(
989
+ `Failed to extend visibility for message ${messageId}:`,
990
+ error
991
+ );
992
+ resolveLifecycle();
993
+ }
994
+ };
995
+ timeoutId = setTimeout(() => extend(), this.refreshInterval * 1e3);
996
+ return async (waitForCompletion = false) => {
997
+ isRunning = false;
998
+ if (timeoutId) {
999
+ clearTimeout(timeoutId);
1000
+ timeoutId = null;
1001
+ }
1002
+ if (waitForCompletion) {
1003
+ await lifecyclePromise;
1004
+ } else {
1005
+ resolveLifecycle();
1006
+ }
1007
+ };
1008
+ }
1009
+ /**
1010
+ * Process a single message with the given handler
1011
+ * @param message The message to process
1012
+ * @param handler Function to process the message
1013
+ */
1014
+ async processMessage(message, handler) {
1015
+ const stopExtension = this.startVisibilityExtension(
1016
+ message.messageId,
1017
+ message.ticket
1018
+ );
1019
+ try {
1020
+ const result = await handler(message.payload, {
1021
+ messageId: message.messageId,
1022
+ deliveryCount: message.deliveryCount,
1023
+ createdAt: message.createdAt,
1024
+ topicName: this.topicName,
1025
+ consumerGroup: this.consumerGroupName
1026
+ });
1027
+ await stopExtension();
1028
+ if (result && "timeoutSeconds" in result) {
1029
+ await this.client.changeVisibility({
1030
+ queueName: this.topicName,
1031
+ consumerGroup: this.consumerGroupName,
1032
+ messageId: message.messageId,
1033
+ ticket: message.ticket,
1034
+ visibilityTimeoutSeconds: result.timeoutSeconds
1035
+ });
1036
+ if (isDevMode()) {
1037
+ scheduleDevTimeout(
1038
+ this.topicName,
1039
+ message.messageId,
1040
+ result.timeoutSeconds
1041
+ );
1042
+ }
1043
+ } else {
1044
+ await this.client.deleteMessage({
1045
+ queueName: this.topicName,
1046
+ consumerGroup: this.consumerGroupName,
1047
+ messageId: message.messageId,
1048
+ ticket: message.ticket
1049
+ });
1050
+ }
1051
+ } catch (error) {
1052
+ await stopExtension();
1053
+ if (this.transport.finalize && message.payload !== void 0 && message.payload !== null) {
1054
+ try {
1055
+ await this.transport.finalize(message.payload);
1056
+ } catch (finalizeError) {
1057
+ console.warn("Failed to finalize message payload:", finalizeError);
1058
+ }
1059
+ }
1060
+ throw error;
1061
+ }
1062
+ }
1063
+ async consume(handler, options) {
1064
+ if (options?.messageId) {
1065
+ if (options.skipPayload) {
1066
+ const response = await this.client.receiveMessageById(
1067
+ {
1068
+ queueName: this.topicName,
1069
+ consumerGroup: this.consumerGroupName,
1070
+ messageId: options.messageId,
1071
+ visibilityTimeoutSeconds: this.visibilityTimeout,
1072
+ skipPayload: true
1073
+ },
1074
+ this.transport
1075
+ );
1076
+ await this.processMessage(
1077
+ response.message,
1078
+ handler
1079
+ );
1080
+ } else {
1081
+ const response = await this.client.receiveMessageById(
1082
+ {
1083
+ queueName: this.topicName,
1084
+ consumerGroup: this.consumerGroupName,
1085
+ messageId: options.messageId,
1086
+ visibilityTimeoutSeconds: this.visibilityTimeout
1087
+ },
1088
+ this.transport
1089
+ );
1090
+ await this.processMessage(
1091
+ response.message,
1092
+ handler
1093
+ );
1094
+ }
1095
+ } else {
1096
+ let messageFound = false;
1097
+ for await (const message of this.client.receiveMessages(
1098
+ {
1099
+ queueName: this.topicName,
1100
+ consumerGroup: this.consumerGroupName,
1101
+ visibilityTimeoutSeconds: this.visibilityTimeout,
1102
+ limit: 1
1103
+ },
1104
+ this.transport
1105
+ )) {
1106
+ messageFound = true;
1107
+ await this.processMessage(message, handler);
1108
+ break;
1109
+ }
1110
+ if (!messageFound) {
1111
+ throw new Error("No messages available");
1112
+ }
1113
+ }
1114
+ }
1115
+ /**
1116
+ * Get the consumer group name
1117
+ */
1118
+ get name() {
1119
+ return this.consumerGroupName;
1120
+ }
1121
+ /**
1122
+ * Get the topic name this consumer group is subscribed to
1123
+ */
1124
+ get topic() {
1125
+ return this.topicName;
1126
+ }
1127
+ };
1128
+
1111
1129
  // src/topic.ts
1112
1130
  var Topic = class {
1113
1131
  client;