@vercel/queue 0.0.0-alpha.14 → 0.0.0-alpha.16

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.js CHANGED
@@ -632,207 +632,6 @@ var QueueClient = class {
632
632
  }
633
633
  };
634
634
 
635
- // src/consumer-group.ts
636
- var ConsumerGroup = class {
637
- client;
638
- topicName;
639
- consumerGroupName;
640
- visibilityTimeout;
641
- refreshInterval;
642
- transport;
643
- /**
644
- * Create a new ConsumerGroup instance
645
- * @param client QueueClient instance to use for API calls
646
- * @param topicName Name of the topic to consume from
647
- * @param consumerGroupName Name of the consumer group
648
- * @param options Optional configuration
649
- */
650
- constructor(client, topicName, consumerGroupName, options = {}) {
651
- this.client = client;
652
- this.topicName = topicName;
653
- this.consumerGroupName = consumerGroupName;
654
- this.visibilityTimeout = options.visibilityTimeoutSeconds || 30;
655
- this.refreshInterval = options.refreshInterval || 10;
656
- this.transport = options.transport || new JsonTransport();
657
- }
658
- /**
659
- * Starts a background loop that periodically extends the visibility timeout for a message.
660
- * This prevents the message from becoming visible to other consumers while it's being processed.
661
- *
662
- * The extension loop runs every `refreshInterval` seconds and updates the message's
663
- * visibility timeout to `visibilityTimeout` seconds from the current time.
664
- *
665
- * @param messageId - The unique identifier of the message to extend visibility for
666
- * @param ticket - The receipt ticket that proves ownership of the message
667
- * @returns A function that when called will stop the extension loop
668
- *
669
- * @remarks
670
- * - The first extension attempt occurs after `refreshInterval` seconds, not immediately
671
- * - If an extension fails, the loop terminates with an error logged to console
672
- * - The returned stop function is idempotent - calling it multiple times is safe
673
- * - By default, the stop function returns immediately without waiting for in-flight
674
- * - Pass `true` to the stop function to wait for any in-flight extension to complete
675
- */
676
- startVisibilityExtension(messageId, ticket) {
677
- let isRunning = true;
678
- let resolveLifecycle;
679
- let timeoutId = null;
680
- const lifecyclePromise = new Promise((resolve) => {
681
- resolveLifecycle = resolve;
682
- });
683
- const extend = async () => {
684
- if (!isRunning) {
685
- resolveLifecycle();
686
- return;
687
- }
688
- try {
689
- await this.client.changeVisibility({
690
- queueName: this.topicName,
691
- consumerGroup: this.consumerGroupName,
692
- messageId,
693
- ticket,
694
- visibilityTimeoutSeconds: this.visibilityTimeout
695
- });
696
- if (isRunning) {
697
- timeoutId = setTimeout(() => extend(), this.refreshInterval * 1e3);
698
- } else {
699
- resolveLifecycle();
700
- }
701
- } catch (error) {
702
- console.error(
703
- `Failed to extend visibility for message ${messageId}:`,
704
- error
705
- );
706
- resolveLifecycle();
707
- }
708
- };
709
- timeoutId = setTimeout(() => extend(), this.refreshInterval * 1e3);
710
- return async (waitForCompletion = false) => {
711
- isRunning = false;
712
- if (timeoutId) {
713
- clearTimeout(timeoutId);
714
- timeoutId = null;
715
- }
716
- if (waitForCompletion) {
717
- await lifecyclePromise;
718
- } else {
719
- resolveLifecycle();
720
- }
721
- };
722
- }
723
- /**
724
- * Process a single message with the given handler
725
- * @param message The message to process
726
- * @param handler Function to process the message
727
- */
728
- async processMessage(message, handler) {
729
- const stopExtension = this.startVisibilityExtension(
730
- message.messageId,
731
- message.ticket
732
- );
733
- try {
734
- const result = await handler(message.payload, {
735
- messageId: message.messageId,
736
- deliveryCount: message.deliveryCount,
737
- createdAt: message.createdAt,
738
- topicName: this.topicName,
739
- consumerGroup: this.consumerGroupName
740
- });
741
- await stopExtension();
742
- if (result && "timeoutSeconds" in result) {
743
- await this.client.changeVisibility({
744
- queueName: this.topicName,
745
- consumerGroup: this.consumerGroupName,
746
- messageId: message.messageId,
747
- ticket: message.ticket,
748
- visibilityTimeoutSeconds: result.timeoutSeconds
749
- });
750
- } else {
751
- await this.client.deleteMessage({
752
- queueName: this.topicName,
753
- consumerGroup: this.consumerGroupName,
754
- messageId: message.messageId,
755
- ticket: message.ticket
756
- });
757
- }
758
- } catch (error) {
759
- await stopExtension();
760
- if (this.transport.finalize && message.payload !== void 0 && message.payload !== null) {
761
- try {
762
- await this.transport.finalize(message.payload);
763
- } catch (finalizeError) {
764
- console.warn("Failed to finalize message payload:", finalizeError);
765
- }
766
- }
767
- throw error;
768
- }
769
- }
770
- async consume(handler, options) {
771
- if (options?.messageId) {
772
- if (options.skipPayload) {
773
- const response = await this.client.receiveMessageById(
774
- {
775
- queueName: this.topicName,
776
- consumerGroup: this.consumerGroupName,
777
- messageId: options.messageId,
778
- visibilityTimeoutSeconds: this.visibilityTimeout,
779
- skipPayload: true
780
- },
781
- this.transport
782
- );
783
- await this.processMessage(
784
- response.message,
785
- handler
786
- );
787
- } else {
788
- const response = await this.client.receiveMessageById(
789
- {
790
- queueName: this.topicName,
791
- consumerGroup: this.consumerGroupName,
792
- messageId: options.messageId,
793
- visibilityTimeoutSeconds: this.visibilityTimeout
794
- },
795
- this.transport
796
- );
797
- await this.processMessage(
798
- response.message,
799
- handler
800
- );
801
- }
802
- } else {
803
- let messageFound = false;
804
- for await (const message of this.client.receiveMessages(
805
- {
806
- queueName: this.topicName,
807
- consumerGroup: this.consumerGroupName,
808
- visibilityTimeoutSeconds: this.visibilityTimeout,
809
- limit: 1
810
- },
811
- this.transport
812
- )) {
813
- messageFound = true;
814
- await this.processMessage(message, handler);
815
- break;
816
- }
817
- if (!messageFound) {
818
- throw new Error("No messages available");
819
- }
820
- }
821
- }
822
- /**
823
- * Get the consumer group name
824
- */
825
- get name() {
826
- return this.consumerGroupName;
827
- }
828
- /**
829
- * Get the topic name this consumer group is subscribed to
830
- */
831
- get topic() {
832
- return this.topicName;
833
- }
834
- };
835
-
836
635
  // src/callback.ts
837
636
  function validateWildcardPattern(pattern) {
838
637
  const firstIndex = pattern.indexOf("*");
@@ -1081,6 +880,17 @@ function createMockCloudEventRequest(topicName, consumerGroup, messageId) {
1081
880
  });
1082
881
  }
1083
882
  var DEV_CALLBACK_DELAY = 1e3;
883
+ function scheduleDevTimeout(topicName, messageId, timeoutSeconds) {
884
+ console.log(
885
+ `[Dev Mode] Message ${messageId} timed out for ${timeoutSeconds}s, will re-trigger`
886
+ );
887
+ setTimeout(() => {
888
+ console.log(
889
+ `[Dev Mode] Re-triggering callback for timed-out message ${messageId}`
890
+ );
891
+ triggerDevCallbacks(topicName, messageId);
892
+ }, timeoutSeconds * 1e3);
893
+ }
1084
894
  function triggerDevCallbacks(topicName, messageId) {
1085
895
  const handlersMap = findRouteHandlersForTopic(topicName);
1086
896
  if (handlersMap.size === 0) {
@@ -1150,6 +960,214 @@ if (process.env.NODE_ENV === "test" || process.env.VITEST) {
1150
960
  globalThis.__clearDevHandlers = clearDevHandlers;
1151
961
  }
1152
962
 
963
+ // src/consumer-group.ts
964
+ var ConsumerGroup = class {
965
+ client;
966
+ topicName;
967
+ consumerGroupName;
968
+ visibilityTimeout;
969
+ refreshInterval;
970
+ transport;
971
+ /**
972
+ * Create a new ConsumerGroup instance
973
+ * @param client QueueClient instance to use for API calls
974
+ * @param topicName Name of the topic to consume from
975
+ * @param consumerGroupName Name of the consumer group
976
+ * @param options Optional configuration
977
+ */
978
+ constructor(client, topicName, consumerGroupName, options = {}) {
979
+ this.client = client;
980
+ this.topicName = topicName;
981
+ this.consumerGroupName = consumerGroupName;
982
+ this.visibilityTimeout = options.visibilityTimeoutSeconds || 30;
983
+ this.refreshInterval = options.refreshInterval || 10;
984
+ this.transport = options.transport || new JsonTransport();
985
+ }
986
+ /**
987
+ * Starts a background loop that periodically extends the visibility timeout for a message.
988
+ * This prevents the message from becoming visible to other consumers while it's being processed.
989
+ *
990
+ * The extension loop runs every `refreshInterval` seconds and updates the message's
991
+ * visibility timeout to `visibilityTimeout` seconds from the current time.
992
+ *
993
+ * @param messageId - The unique identifier of the message to extend visibility for
994
+ * @param ticket - The receipt ticket that proves ownership of the message
995
+ * @returns A function that when called will stop the extension loop
996
+ *
997
+ * @remarks
998
+ * - The first extension attempt occurs after `refreshInterval` seconds, not immediately
999
+ * - If an extension fails, the loop terminates with an error logged to console
1000
+ * - The returned stop function is idempotent - calling it multiple times is safe
1001
+ * - By default, the stop function returns immediately without waiting for in-flight
1002
+ * - Pass `true` to the stop function to wait for any in-flight extension to complete
1003
+ */
1004
+ startVisibilityExtension(messageId, ticket) {
1005
+ let isRunning = true;
1006
+ let resolveLifecycle;
1007
+ let timeoutId = null;
1008
+ const lifecyclePromise = new Promise((resolve) => {
1009
+ resolveLifecycle = resolve;
1010
+ });
1011
+ const extend = async () => {
1012
+ if (!isRunning) {
1013
+ resolveLifecycle();
1014
+ return;
1015
+ }
1016
+ try {
1017
+ await this.client.changeVisibility({
1018
+ queueName: this.topicName,
1019
+ consumerGroup: this.consumerGroupName,
1020
+ messageId,
1021
+ ticket,
1022
+ visibilityTimeoutSeconds: this.visibilityTimeout
1023
+ });
1024
+ if (isRunning) {
1025
+ timeoutId = setTimeout(() => extend(), this.refreshInterval * 1e3);
1026
+ } else {
1027
+ resolveLifecycle();
1028
+ }
1029
+ } catch (error) {
1030
+ console.error(
1031
+ `Failed to extend visibility for message ${messageId}:`,
1032
+ error
1033
+ );
1034
+ resolveLifecycle();
1035
+ }
1036
+ };
1037
+ timeoutId = setTimeout(() => extend(), this.refreshInterval * 1e3);
1038
+ return async (waitForCompletion = false) => {
1039
+ isRunning = false;
1040
+ if (timeoutId) {
1041
+ clearTimeout(timeoutId);
1042
+ timeoutId = null;
1043
+ }
1044
+ if (waitForCompletion) {
1045
+ await lifecyclePromise;
1046
+ } else {
1047
+ resolveLifecycle();
1048
+ }
1049
+ };
1050
+ }
1051
+ /**
1052
+ * Process a single message with the given handler
1053
+ * @param message The message to process
1054
+ * @param handler Function to process the message
1055
+ */
1056
+ async processMessage(message, handler) {
1057
+ const stopExtension = this.startVisibilityExtension(
1058
+ message.messageId,
1059
+ message.ticket
1060
+ );
1061
+ try {
1062
+ const result = await handler(message.payload, {
1063
+ messageId: message.messageId,
1064
+ deliveryCount: message.deliveryCount,
1065
+ createdAt: message.createdAt,
1066
+ topicName: this.topicName,
1067
+ consumerGroup: this.consumerGroupName
1068
+ });
1069
+ await stopExtension();
1070
+ if (result && "timeoutSeconds" in result) {
1071
+ await this.client.changeVisibility({
1072
+ queueName: this.topicName,
1073
+ consumerGroup: this.consumerGroupName,
1074
+ messageId: message.messageId,
1075
+ ticket: message.ticket,
1076
+ visibilityTimeoutSeconds: result.timeoutSeconds
1077
+ });
1078
+ if (isDevMode()) {
1079
+ scheduleDevTimeout(
1080
+ this.topicName,
1081
+ message.messageId,
1082
+ result.timeoutSeconds
1083
+ );
1084
+ }
1085
+ } else {
1086
+ await this.client.deleteMessage({
1087
+ queueName: this.topicName,
1088
+ consumerGroup: this.consumerGroupName,
1089
+ messageId: message.messageId,
1090
+ ticket: message.ticket
1091
+ });
1092
+ }
1093
+ } catch (error) {
1094
+ await stopExtension();
1095
+ if (this.transport.finalize && message.payload !== void 0 && message.payload !== null) {
1096
+ try {
1097
+ await this.transport.finalize(message.payload);
1098
+ } catch (finalizeError) {
1099
+ console.warn("Failed to finalize message payload:", finalizeError);
1100
+ }
1101
+ }
1102
+ throw error;
1103
+ }
1104
+ }
1105
+ async consume(handler, options) {
1106
+ if (options?.messageId) {
1107
+ if (options.skipPayload) {
1108
+ const response = await this.client.receiveMessageById(
1109
+ {
1110
+ queueName: this.topicName,
1111
+ consumerGroup: this.consumerGroupName,
1112
+ messageId: options.messageId,
1113
+ visibilityTimeoutSeconds: this.visibilityTimeout,
1114
+ skipPayload: true
1115
+ },
1116
+ this.transport
1117
+ );
1118
+ await this.processMessage(
1119
+ response.message,
1120
+ handler
1121
+ );
1122
+ } else {
1123
+ const response = await this.client.receiveMessageById(
1124
+ {
1125
+ queueName: this.topicName,
1126
+ consumerGroup: this.consumerGroupName,
1127
+ messageId: options.messageId,
1128
+ visibilityTimeoutSeconds: this.visibilityTimeout
1129
+ },
1130
+ this.transport
1131
+ );
1132
+ await this.processMessage(
1133
+ response.message,
1134
+ handler
1135
+ );
1136
+ }
1137
+ } else {
1138
+ let messageFound = false;
1139
+ for await (const message of this.client.receiveMessages(
1140
+ {
1141
+ queueName: this.topicName,
1142
+ consumerGroup: this.consumerGroupName,
1143
+ visibilityTimeoutSeconds: this.visibilityTimeout,
1144
+ limit: 1
1145
+ },
1146
+ this.transport
1147
+ )) {
1148
+ messageFound = true;
1149
+ await this.processMessage(message, handler);
1150
+ break;
1151
+ }
1152
+ if (!messageFound) {
1153
+ throw new Error("No messages available");
1154
+ }
1155
+ }
1156
+ }
1157
+ /**
1158
+ * Get the consumer group name
1159
+ */
1160
+ get name() {
1161
+ return this.consumerGroupName;
1162
+ }
1163
+ /**
1164
+ * Get the topic name this consumer group is subscribed to
1165
+ */
1166
+ get topic() {
1167
+ return this.topicName;
1168
+ }
1169
+ };
1170
+
1153
1171
  // src/topic.ts
1154
1172
  var Topic = class {
1155
1173
  client;
@@ -1236,6 +1254,9 @@ async function send(topicName, payload, options) {
1236
1254
  },
1237
1255
  transport
1238
1256
  );
1257
+ if (isDevMode()) {
1258
+ triggerDevCallbacks(topicName, result.messageId);
1259
+ }
1239
1260
  return { messageId: result.messageId };
1240
1261
  }
1241
1262
  async function receive(topicName, consumerGroup, handler, options) {