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

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
@@ -1,25 +1,34 @@
1
1
  // src/transports.ts
2
+ async function streamToBuffer(stream) {
3
+ let totalLength = 0;
4
+ const reader = stream.getReader();
5
+ const chunks = [];
6
+ try {
7
+ while (true) {
8
+ const { done, value } = await reader.read();
9
+ if (done) break;
10
+ chunks.push(value);
11
+ totalLength += value.length;
12
+ }
13
+ } finally {
14
+ reader.releaseLock();
15
+ }
16
+ return Buffer.concat(chunks, totalLength);
17
+ }
2
18
  var JsonTransport = class {
3
19
  contentType = "application/json";
20
+ replacer;
21
+ reviver;
22
+ constructor(options = {}) {
23
+ this.replacer = options.replacer;
24
+ this.reviver = options.reviver;
25
+ }
4
26
  serialize(value) {
5
- return Buffer.from(JSON.stringify(value), "utf8");
27
+ return Buffer.from(JSON.stringify(value, this.replacer), "utf8");
6
28
  }
7
29
  async deserialize(stream) {
8
- const reader = stream.getReader();
9
- let totalLength = 0;
10
- const chunks = [];
11
- try {
12
- while (true) {
13
- const { done, value } = await reader.read();
14
- if (done) break;
15
- chunks.push(value);
16
- totalLength += value.length;
17
- }
18
- } finally {
19
- reader.releaseLock();
20
- }
21
- const buffer = Buffer.concat(chunks, totalLength);
22
- return JSON.parse(buffer.toString("utf8"));
30
+ const buffer = await streamToBuffer(stream);
31
+ return JSON.parse(buffer.toString("utf8"), this.reviver);
23
32
  }
24
33
  };
25
34
  var BufferTransport = class {
@@ -28,25 +37,7 @@ var BufferTransport = class {
28
37
  return value;
29
38
  }
30
39
  async deserialize(stream) {
31
- const reader = stream.getReader();
32
- const chunks = [];
33
- try {
34
- while (true) {
35
- const { done, value } = await reader.read();
36
- if (done) break;
37
- chunks.push(value);
38
- }
39
- } finally {
40
- reader.releaseLock();
41
- }
42
- const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
43
- const buffer = new Uint8Array(totalLength);
44
- let offset = 0;
45
- for (const chunk of chunks) {
46
- buffer.set(chunk, offset);
47
- offset += chunk.length;
48
- }
49
- return Buffer.from(buffer);
40
+ return await streamToBuffer(stream);
50
41
  }
51
42
  };
52
43
  var StreamTransport = class {
@@ -800,111 +791,6 @@ var ConsumerGroup = class {
800
791
  }
801
792
  };
802
793
 
803
- // src/topic.ts
804
- var Topic = class {
805
- client;
806
- topicName;
807
- transport;
808
- /**
809
- * Create a new Topic instance
810
- * @param client QueueClient instance to use for API calls
811
- * @param topicName Name of the topic to work with
812
- * @param transport Optional serializer/deserializer for the payload (defaults to JSON)
813
- */
814
- constructor(client, topicName, transport) {
815
- this.client = client;
816
- this.topicName = topicName;
817
- this.transport = transport || new JsonTransport();
818
- }
819
- /**
820
- * Publish a message to the topic
821
- * @param payload The data to publish
822
- * @param options Optional publish options
823
- * @returns An object containing the message ID
824
- * @throws {BadRequestError} When request parameters are invalid
825
- * @throws {UnauthorizedError} When authentication fails
826
- * @throws {ForbiddenError} When access is denied (environment mismatch)
827
- * @throws {InternalServerError} When server encounters an error
828
- */
829
- async publish(payload, options) {
830
- const result = await this.client.sendMessage(
831
- {
832
- queueName: this.topicName,
833
- payload,
834
- idempotencyKey: options?.idempotencyKey,
835
- retentionSeconds: options?.retentionSeconds
836
- },
837
- this.transport
838
- );
839
- return { messageId: result.messageId };
840
- }
841
- /**
842
- * Create a consumer group for this topic
843
- * @param consumerGroupName Name of the consumer group
844
- * @param options Optional configuration for the consumer group
845
- * @returns A ConsumerGroup instance
846
- */
847
- consumerGroup(consumerGroupName, options) {
848
- const consumerOptions = {
849
- ...options,
850
- transport: options?.transport || this.transport
851
- };
852
- return new ConsumerGroup(
853
- this.client,
854
- this.topicName,
855
- consumerGroupName,
856
- consumerOptions
857
- );
858
- }
859
- /**
860
- * Get the topic name
861
- */
862
- get name() {
863
- return this.topicName;
864
- }
865
- /**
866
- * Get the transport used by this topic
867
- */
868
- get serializer() {
869
- return this.transport;
870
- }
871
- };
872
-
873
- // src/factory.ts
874
- async function send(topicName, payload, options) {
875
- const transport = options?.transport || new JsonTransport();
876
- const client = new QueueClient();
877
- const result = await client.sendMessage(
878
- {
879
- queueName: topicName,
880
- payload,
881
- idempotencyKey: options?.idempotencyKey,
882
- retentionSeconds: options?.retentionSeconds
883
- },
884
- transport
885
- );
886
- return { messageId: result.messageId };
887
- }
888
- async function receive(topicName, consumerGroup, handler, options) {
889
- const transport = options?.transport || new JsonTransport();
890
- const client = new QueueClient();
891
- const topic = new Topic(client, topicName, transport);
892
- const { messageId, skipPayload, ...consumerGroupOptions } = options || {};
893
- const consumer = topic.consumerGroup(consumerGroup, consumerGroupOptions);
894
- if (messageId) {
895
- if (skipPayload) {
896
- return consumer.consume(handler, {
897
- messageId,
898
- skipPayload: true
899
- });
900
- } else {
901
- return consumer.consume(handler, { messageId });
902
- }
903
- } else {
904
- return consumer.consume(handler);
905
- }
906
- }
907
-
908
794
  // src/callback.ts
909
795
  function validateWildcardPattern(pattern) {
910
796
  const firstIndex = pattern.indexOf("*");
@@ -984,7 +870,7 @@ function handleCallback(handlers) {
984
870
  }
985
871
  }
986
872
  }
987
- return async (request) => {
873
+ const routeHandler = async (request) => {
988
874
  try {
989
875
  const { queueName, consumerGroup, messageId } = await parseCallback(request);
990
876
  const topicHandler = findTopicHandler(queueName, handlers);
@@ -1025,6 +911,309 @@ function handleCallback(handlers) {
1025
911
  );
1026
912
  }
1027
913
  };
914
+ if (isDevMode()) {
915
+ registerDevRouteHandler(routeHandler, handlers);
916
+ }
917
+ return routeHandler;
918
+ }
919
+
920
+ // src/dev.ts
921
+ var devRouteHandlers = /* @__PURE__ */ new Map();
922
+ var wildcardRouteHandlers = /* @__PURE__ */ new Map();
923
+ var routeHandlerKeys = /* @__PURE__ */ new WeakMap();
924
+ function cleanupDeadRefs(key, refs) {
925
+ const aliveRefs = refs.filter((ref) => ref.deref() !== void 0);
926
+ if (aliveRefs.length === 0) {
927
+ wildcardRouteHandlers.delete(key);
928
+ } else if (aliveRefs.length < refs.length) {
929
+ wildcardRouteHandlers.set(key, aliveRefs);
930
+ }
931
+ }
932
+ function isDevMode() {
933
+ return process.env.NODE_ENV === "development";
934
+ }
935
+ function registerDevRouteHandler(routeHandler, handlers) {
936
+ const existingKeys = routeHandlerKeys.get(routeHandler);
937
+ if (existingKeys) {
938
+ const newKeys = /* @__PURE__ */ new Set();
939
+ for (const topicName in handlers) {
940
+ for (const consumerGroup in handlers[topicName]) {
941
+ newKeys.add(`${topicName}:${consumerGroup}`);
942
+ }
943
+ }
944
+ for (const key of existingKeys) {
945
+ if (!newKeys.has(key)) {
946
+ const [topicPattern] = key.split(":");
947
+ if (topicPattern.includes("*")) {
948
+ const refs = wildcardRouteHandlers.get(key);
949
+ if (refs) {
950
+ const filteredRefs = refs.filter(
951
+ (ref) => ref.deref() !== routeHandler
952
+ );
953
+ if (filteredRefs.length === 0) {
954
+ wildcardRouteHandlers.delete(key);
955
+ } else {
956
+ wildcardRouteHandlers.set(key, filteredRefs);
957
+ }
958
+ }
959
+ } else {
960
+ devRouteHandlers.delete(key);
961
+ }
962
+ }
963
+ }
964
+ }
965
+ const keys = /* @__PURE__ */ new Set();
966
+ for (const topicName in handlers) {
967
+ for (const consumerGroup in handlers[topicName]) {
968
+ const key = `${topicName}:${consumerGroup}`;
969
+ keys.add(key);
970
+ if (topicName.includes("*")) {
971
+ const weakRef = new WeakRef(routeHandler);
972
+ const existing = wildcardRouteHandlers.get(key) || [];
973
+ cleanupDeadRefs(key, existing);
974
+ const cleanedRefs = wildcardRouteHandlers.get(key) || [];
975
+ cleanedRefs.push(weakRef);
976
+ wildcardRouteHandlers.set(key, cleanedRefs);
977
+ } else {
978
+ devRouteHandlers.set(key, {
979
+ routeHandler,
980
+ topicPattern: topicName
981
+ });
982
+ }
983
+ }
984
+ }
985
+ routeHandlerKeys.set(routeHandler, keys);
986
+ }
987
+ function findRouteHandlersForTopic(topicName) {
988
+ const handlersMap = /* @__PURE__ */ new Map();
989
+ for (const [
990
+ key,
991
+ { routeHandler, topicPattern }
992
+ ] of devRouteHandlers.entries()) {
993
+ const [_, consumerGroup] = key.split(":");
994
+ if (topicPattern === topicName) {
995
+ if (!handlersMap.has(routeHandler)) {
996
+ handlersMap.set(routeHandler, /* @__PURE__ */ new Set());
997
+ }
998
+ handlersMap.get(routeHandler).add(consumerGroup);
999
+ }
1000
+ }
1001
+ for (const [key, refs] of wildcardRouteHandlers.entries()) {
1002
+ const [pattern, consumerGroup] = key.split(":");
1003
+ if (matchesWildcardPattern(topicName, pattern)) {
1004
+ cleanupDeadRefs(key, refs);
1005
+ const cleanedRefs = wildcardRouteHandlers.get(key) || [];
1006
+ for (const ref of cleanedRefs) {
1007
+ const routeHandler = ref.deref();
1008
+ if (routeHandler) {
1009
+ if (!handlersMap.has(routeHandler)) {
1010
+ handlersMap.set(routeHandler, /* @__PURE__ */ new Set());
1011
+ }
1012
+ handlersMap.get(routeHandler).add(consumerGroup);
1013
+ }
1014
+ }
1015
+ }
1016
+ }
1017
+ return handlersMap;
1018
+ }
1019
+ function createMockCloudEventRequest(topicName, consumerGroup, messageId) {
1020
+ const cloudEvent = {
1021
+ type: "com.vercel.queue.v1beta",
1022
+ source: `/topic/${topicName}/consumer/${consumerGroup}`,
1023
+ id: messageId,
1024
+ datacontenttype: "application/json",
1025
+ data: {
1026
+ messageId,
1027
+ queueName: topicName,
1028
+ consumerGroup
1029
+ },
1030
+ time: (/* @__PURE__ */ new Date()).toISOString(),
1031
+ specversion: "1.0"
1032
+ };
1033
+ return new Request("https://localhost/api/queue/callback", {
1034
+ method: "POST",
1035
+ headers: {
1036
+ "Content-Type": "application/cloudevents+json"
1037
+ },
1038
+ body: JSON.stringify(cloudEvent)
1039
+ });
1040
+ }
1041
+ var DEV_CALLBACK_DELAY = 1e3;
1042
+ function triggerDevCallbacks(topicName, messageId) {
1043
+ const handlersMap = findRouteHandlersForTopic(topicName);
1044
+ if (handlersMap.size === 0) {
1045
+ return;
1046
+ }
1047
+ const consumerGroups = Array.from(
1048
+ new Set(
1049
+ Array.from(handlersMap.values()).flatMap((groups) => Array.from(groups))
1050
+ )
1051
+ );
1052
+ console.log(
1053
+ `[Dev Mode] Triggering local callbacks for topic "${topicName}" \u2192 consumers: ${consumerGroups.join(", ")}`
1054
+ );
1055
+ setTimeout(async () => {
1056
+ for (const [routeHandler, consumerGroups2] of handlersMap.entries()) {
1057
+ for (const consumerGroup of consumerGroups2) {
1058
+ try {
1059
+ const request = createMockCloudEventRequest(
1060
+ topicName,
1061
+ consumerGroup,
1062
+ messageId
1063
+ );
1064
+ const response = await routeHandler(request);
1065
+ if (response.ok) {
1066
+ try {
1067
+ const responseData = await response.json();
1068
+ if (responseData.status === "success") {
1069
+ console.log(
1070
+ `[Dev Mode] Message processed for ${topicName}/${consumerGroup}`
1071
+ );
1072
+ }
1073
+ } catch (jsonError) {
1074
+ console.error(
1075
+ `[Dev Mode] Failed to parse success response for ${topicName}/${consumerGroup}:`,
1076
+ jsonError
1077
+ );
1078
+ }
1079
+ } else {
1080
+ try {
1081
+ const errorData = await response.json();
1082
+ console.error(
1083
+ `[Dev Mode] Failed to process message for ${topicName}/${consumerGroup}:`,
1084
+ errorData.error || response.statusText
1085
+ );
1086
+ } catch (jsonError) {
1087
+ console.error(
1088
+ `[Dev Mode] Failed to process message for ${topicName}/${consumerGroup}:`,
1089
+ response.statusText
1090
+ );
1091
+ }
1092
+ }
1093
+ } catch (error) {
1094
+ console.error(
1095
+ `[Dev Mode] Error triggering callback for ${topicName}/${consumerGroup}:`,
1096
+ error
1097
+ );
1098
+ }
1099
+ }
1100
+ }
1101
+ }, DEV_CALLBACK_DELAY);
1102
+ }
1103
+ function clearDevHandlers() {
1104
+ devRouteHandlers.clear();
1105
+ wildcardRouteHandlers.clear();
1106
+ }
1107
+ if (process.env.NODE_ENV === "test" || process.env.VITEST) {
1108
+ globalThis.__clearDevHandlers = clearDevHandlers;
1109
+ }
1110
+
1111
+ // src/topic.ts
1112
+ var Topic = class {
1113
+ client;
1114
+ topicName;
1115
+ transport;
1116
+ /**
1117
+ * Create a new Topic instance
1118
+ * @param client QueueClient instance to use for API calls
1119
+ * @param topicName Name of the topic to work with
1120
+ * @param transport Optional serializer/deserializer for the payload (defaults to JSON)
1121
+ */
1122
+ constructor(client, topicName, transport) {
1123
+ this.client = client;
1124
+ this.topicName = topicName;
1125
+ this.transport = transport || new JsonTransport();
1126
+ }
1127
+ /**
1128
+ * Publish a message to the topic
1129
+ * @param payload The data to publish
1130
+ * @param options Optional publish options
1131
+ * @returns An object containing the message ID
1132
+ * @throws {BadRequestError} When request parameters are invalid
1133
+ * @throws {UnauthorizedError} When authentication fails
1134
+ * @throws {ForbiddenError} When access is denied (environment mismatch)
1135
+ * @throws {InternalServerError} When server encounters an error
1136
+ */
1137
+ async publish(payload, options) {
1138
+ const result = await this.client.sendMessage(
1139
+ {
1140
+ queueName: this.topicName,
1141
+ payload,
1142
+ idempotencyKey: options?.idempotencyKey,
1143
+ retentionSeconds: options?.retentionSeconds
1144
+ },
1145
+ this.transport
1146
+ );
1147
+ if (isDevMode()) {
1148
+ triggerDevCallbacks(this.topicName, result.messageId);
1149
+ }
1150
+ return { messageId: result.messageId };
1151
+ }
1152
+ /**
1153
+ * Create a consumer group for this topic
1154
+ * @param consumerGroupName Name of the consumer group
1155
+ * @param options Optional configuration for the consumer group
1156
+ * @returns A ConsumerGroup instance
1157
+ */
1158
+ consumerGroup(consumerGroupName, options) {
1159
+ const consumerOptions = {
1160
+ ...options,
1161
+ transport: options?.transport || this.transport
1162
+ };
1163
+ return new ConsumerGroup(
1164
+ this.client,
1165
+ this.topicName,
1166
+ consumerGroupName,
1167
+ consumerOptions
1168
+ );
1169
+ }
1170
+ /**
1171
+ * Get the topic name
1172
+ */
1173
+ get name() {
1174
+ return this.topicName;
1175
+ }
1176
+ /**
1177
+ * Get the transport used by this topic
1178
+ */
1179
+ get serializer() {
1180
+ return this.transport;
1181
+ }
1182
+ };
1183
+
1184
+ // src/factory.ts
1185
+ async function send(topicName, payload, options) {
1186
+ const transport = options?.transport || new JsonTransport();
1187
+ const client = new QueueClient();
1188
+ const result = await client.sendMessage(
1189
+ {
1190
+ queueName: topicName,
1191
+ payload,
1192
+ idempotencyKey: options?.idempotencyKey,
1193
+ retentionSeconds: options?.retentionSeconds
1194
+ },
1195
+ transport
1196
+ );
1197
+ return { messageId: result.messageId };
1198
+ }
1199
+ async function receive(topicName, consumerGroup, handler, options) {
1200
+ const transport = options?.transport || new JsonTransport();
1201
+ const client = new QueueClient();
1202
+ const topic = new Topic(client, topicName, transport);
1203
+ const { messageId, skipPayload, ...consumerGroupOptions } = options || {};
1204
+ const consumer = topic.consumerGroup(consumerGroup, consumerGroupOptions);
1205
+ if (messageId) {
1206
+ if (skipPayload) {
1207
+ return consumer.consume(handler, {
1208
+ messageId,
1209
+ skipPayload: true
1210
+ });
1211
+ } else {
1212
+ return consumer.consume(handler, { messageId });
1213
+ }
1214
+ } else {
1215
+ return consumer.consume(handler);
1216
+ }
1028
1217
  }
1029
1218
  export {
1030
1219
  BadRequestError,