@vercel/queue 0.0.2 → 0.1.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/README.md +182 -99
- package/dist/index.d.mts +179 -39
- package/dist/index.d.ts +179 -39
- package/dist/index.js +640 -209
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +635 -208
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -46,12 +46,15 @@ __export(index_exports, {
|
|
|
46
46
|
MessageLockedError: () => MessageLockedError,
|
|
47
47
|
MessageNotAvailableError: () => MessageNotAvailableError,
|
|
48
48
|
MessageNotFoundError: () => MessageNotFoundError,
|
|
49
|
+
PollingQueueClient: () => PollingQueueClient,
|
|
49
50
|
QueueClient: () => QueueClient,
|
|
50
51
|
QueueEmptyError: () => QueueEmptyError,
|
|
51
52
|
StreamTransport: () => StreamTransport,
|
|
52
53
|
UnauthorizedError: () => UnauthorizedError,
|
|
54
|
+
handleCallback: () => handleCallback2,
|
|
53
55
|
parseCallback: () => parseCallback,
|
|
54
|
-
parseRawCallback: () => parseRawCallback
|
|
56
|
+
parseRawCallback: () => parseRawCallback,
|
|
57
|
+
send: () => send
|
|
55
58
|
});
|
|
56
59
|
module.exports = __toCommonJS(index_exports);
|
|
57
60
|
|
|
@@ -133,6 +136,7 @@ var import_mixpart = require("mixpart");
|
|
|
133
136
|
|
|
134
137
|
// src/dev.ts
|
|
135
138
|
var fs = __toESM(require("fs"));
|
|
139
|
+
var net = __toESM(require("net"));
|
|
136
140
|
var path = __toESM(require("path"));
|
|
137
141
|
|
|
138
142
|
// src/types.ts
|
|
@@ -318,8 +322,8 @@ var ConsumerGroup = class {
|
|
|
318
322
|
firstDelayMs = 0;
|
|
319
323
|
}
|
|
320
324
|
}
|
|
321
|
-
const lifecyclePromise = new Promise((
|
|
322
|
-
resolveLifecycle =
|
|
325
|
+
const lifecyclePromise = new Promise((resolve2) => {
|
|
326
|
+
resolveLifecycle = resolve2;
|
|
323
327
|
});
|
|
324
328
|
const safeResolve = () => {
|
|
325
329
|
if (!isResolved) {
|
|
@@ -551,7 +555,7 @@ var Topic = class {
|
|
|
551
555
|
headers: options?.headers
|
|
552
556
|
});
|
|
553
557
|
if (result.messageId && isDevMode()) {
|
|
554
|
-
|
|
558
|
+
invokeDevHandlers(
|
|
555
559
|
this.topicName,
|
|
556
560
|
result.messageId,
|
|
557
561
|
this.client.getRegion()
|
|
@@ -760,14 +764,11 @@ async function handleCallback(handler, request, options) {
|
|
|
760
764
|
}
|
|
761
765
|
|
|
762
766
|
// src/dev.ts
|
|
763
|
-
var
|
|
764
|
-
function
|
|
765
|
-
|
|
766
|
-
if (!urlPath.startsWith("/")) {
|
|
767
|
-
urlPath = "/" + urlPath;
|
|
768
|
-
}
|
|
769
|
-
return urlPath;
|
|
767
|
+
var import_meta = {};
|
|
768
|
+
function isDevMode() {
|
|
769
|
+
return process.env.NODE_ENV === "development";
|
|
770
770
|
}
|
|
771
|
+
var ROUTE_MAPPINGS_KEY = Symbol.for("@vercel/queue.devRouteMappings");
|
|
771
772
|
function filePathToConsumerGroup(filePath) {
|
|
772
773
|
return filePath.replace(/_/g, "__").replace(/\//g, "_S").replace(/\./g, "_D");
|
|
773
774
|
}
|
|
@@ -791,13 +792,18 @@ function getDevRouteMappings() {
|
|
|
791
792
|
for (const [filePath, config] of Object.entries(vercelJson.functions)) {
|
|
792
793
|
if (!config.experimentalTriggers) continue;
|
|
793
794
|
for (const trigger of config.experimentalTriggers) {
|
|
794
|
-
if (trigger.type?.startsWith("queue/")
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
topic
|
|
798
|
-
|
|
799
|
-
|
|
795
|
+
if (!trigger.type?.startsWith("queue/") || !trigger.topic) continue;
|
|
796
|
+
if (trigger.type !== "queue/v2beta") {
|
|
797
|
+
console.warn(
|
|
798
|
+
`[Dev Mode] Unsupported trigger type "${trigger.type}" for topic "${trigger.topic}" in ${filePath}. Use "queue/v2beta" instead.`
|
|
799
|
+
);
|
|
800
|
+
continue;
|
|
800
801
|
}
|
|
802
|
+
mappings.push({
|
|
803
|
+
filePath,
|
|
804
|
+
topic: trigger.topic,
|
|
805
|
+
consumer: filePathToConsumerGroup(filePath)
|
|
806
|
+
});
|
|
801
807
|
}
|
|
802
808
|
}
|
|
803
809
|
g[ROUTE_MAPPINGS_KEY] = mappings.length > 0 ? mappings : null;
|
|
@@ -810,9 +816,7 @@ function getDevRouteMappings() {
|
|
|
810
816
|
}
|
|
811
817
|
function findMatchingRoutes(topicName) {
|
|
812
818
|
const mappings = getDevRouteMappings();
|
|
813
|
-
if (!mappings)
|
|
814
|
-
return [];
|
|
815
|
-
}
|
|
819
|
+
if (!mappings) return [];
|
|
816
820
|
return mappings.filter((mapping) => {
|
|
817
821
|
if (mapping.topic.includes("*")) {
|
|
818
822
|
return matchesWildcardPattern(topicName, mapping.topic);
|
|
@@ -820,149 +824,450 @@ function findMatchingRoutes(topicName) {
|
|
|
820
824
|
return mapping.topic === topicName;
|
|
821
825
|
});
|
|
822
826
|
}
|
|
823
|
-
function
|
|
824
|
-
|
|
827
|
+
function findMappingsForFile(absolutePath) {
|
|
828
|
+
const mappings = getDevRouteMappings();
|
|
829
|
+
if (!mappings) return [];
|
|
830
|
+
const cwd = process.cwd();
|
|
831
|
+
let relative2;
|
|
832
|
+
try {
|
|
833
|
+
relative2 = path.relative(cwd, absolutePath);
|
|
834
|
+
} catch {
|
|
835
|
+
return [];
|
|
836
|
+
}
|
|
837
|
+
const normalized = relative2.replace(/\\/g, "/");
|
|
838
|
+
return mappings.filter((m) => m.filePath === normalized);
|
|
839
|
+
}
|
|
840
|
+
function parseFrameFilePath(line) {
|
|
841
|
+
let match = line.match(/\((.+?):\d+:\d+\)/);
|
|
842
|
+
if (!match) match = line.match(/at\s+(.+?):\d+:\d+/);
|
|
843
|
+
if (!match) return null;
|
|
844
|
+
let filePath = match[1].trim();
|
|
845
|
+
if (filePath === "native" || filePath.startsWith("node:") || filePath.startsWith("internal")) {
|
|
846
|
+
return null;
|
|
847
|
+
}
|
|
848
|
+
if (filePath.startsWith("file://")) {
|
|
849
|
+
try {
|
|
850
|
+
filePath = new URL(filePath).pathname;
|
|
851
|
+
} catch {
|
|
852
|
+
return null;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
if (/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(filePath)) {
|
|
856
|
+
return null;
|
|
857
|
+
}
|
|
858
|
+
if (filePath.startsWith("./")) {
|
|
859
|
+
filePath = filePath.slice(2);
|
|
860
|
+
}
|
|
861
|
+
return filePath;
|
|
862
|
+
}
|
|
863
|
+
var _sdkPackageDir;
|
|
864
|
+
function getSdkPackageDir() {
|
|
865
|
+
if (_sdkPackageDir) return _sdkPackageDir;
|
|
866
|
+
try {
|
|
867
|
+
const thisDir = typeof __dirname !== "undefined" ? __dirname : path.dirname(new URL(import_meta.url).pathname);
|
|
868
|
+
_sdkPackageDir = path.resolve(thisDir, "..");
|
|
869
|
+
} catch {
|
|
870
|
+
_sdkPackageDir = "";
|
|
871
|
+
}
|
|
872
|
+
return _sdkPackageDir;
|
|
873
|
+
}
|
|
874
|
+
function extractCallerFilePath() {
|
|
875
|
+
const stack = new Error().stack;
|
|
876
|
+
if (!stack) return null;
|
|
877
|
+
const lines = stack.split("\n").slice(1);
|
|
878
|
+
const pkgDir = getSdkPackageDir();
|
|
879
|
+
for (const line of lines) {
|
|
880
|
+
const fp = parseFrameFilePath(line);
|
|
881
|
+
if (!fp) continue;
|
|
882
|
+
const absolute = path.isAbsolute(fp) ? fp : path.resolve(process.cwd(), fp);
|
|
883
|
+
let realFp;
|
|
884
|
+
try {
|
|
885
|
+
realFp = fs.realpathSync(absolute);
|
|
886
|
+
} catch {
|
|
887
|
+
realFp = absolute;
|
|
888
|
+
}
|
|
889
|
+
if (pkgDir && realFp.startsWith(pkgDir)) continue;
|
|
890
|
+
return realFp;
|
|
891
|
+
}
|
|
892
|
+
return null;
|
|
893
|
+
}
|
|
894
|
+
var HANDLER_REGISTRY_KEY = Symbol.for("@vercel/queue.devHandlerRegistry");
|
|
895
|
+
function getHandlerRegistry() {
|
|
896
|
+
const g = globalThis;
|
|
897
|
+
if (!g[HANDLER_REGISTRY_KEY]) {
|
|
898
|
+
g[HANDLER_REGISTRY_KEY] = /* @__PURE__ */ new Map();
|
|
899
|
+
}
|
|
900
|
+
return g[HANDLER_REGISTRY_KEY];
|
|
901
|
+
}
|
|
902
|
+
function registerHandlerForFile(filePath, handler, client, options) {
|
|
903
|
+
const absolutePath = path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath);
|
|
904
|
+
const fileMappings = findMappingsForFile(absolutePath);
|
|
905
|
+
if (fileMappings.length === 0) return false;
|
|
906
|
+
const registry = getHandlerRegistry();
|
|
907
|
+
for (const mapping of fileMappings) {
|
|
908
|
+
const key = mapping.topic;
|
|
909
|
+
const existing = registry.get(key) ?? [];
|
|
910
|
+
const nextEntry = {
|
|
911
|
+
consumerGroup: mapping.consumer,
|
|
912
|
+
handler,
|
|
913
|
+
client,
|
|
914
|
+
options
|
|
915
|
+
};
|
|
916
|
+
const existingIndex = existing.findIndex(
|
|
917
|
+
(e) => e.consumerGroup === mapping.consumer
|
|
918
|
+
);
|
|
919
|
+
if (existingIndex >= 0) {
|
|
920
|
+
existing[existingIndex] = nextEntry;
|
|
921
|
+
} else {
|
|
922
|
+
existing.push(nextEntry);
|
|
923
|
+
}
|
|
924
|
+
registry.set(key, existing);
|
|
925
|
+
}
|
|
926
|
+
return true;
|
|
927
|
+
}
|
|
928
|
+
function registerDevHandler(handler, client, options, _testCallerPath) {
|
|
929
|
+
const callerPath = _testCallerPath ?? extractCallerFilePath();
|
|
930
|
+
if (!callerPath) {
|
|
931
|
+
console.warn(
|
|
932
|
+
"[Dev Mode] Could not determine caller file path for handler registration."
|
|
933
|
+
);
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
const registered = registerHandlerForFile(
|
|
937
|
+
callerPath,
|
|
938
|
+
handler,
|
|
939
|
+
client,
|
|
940
|
+
options
|
|
941
|
+
);
|
|
942
|
+
if (!registered) {
|
|
943
|
+
const allMappings = getDevRouteMappings();
|
|
944
|
+
const cwd = process.cwd();
|
|
945
|
+
let relative2;
|
|
946
|
+
try {
|
|
947
|
+
relative2 = path.relative(cwd, callerPath).replace(/\\/g, "/");
|
|
948
|
+
} catch {
|
|
949
|
+
relative2 = callerPath;
|
|
950
|
+
}
|
|
951
|
+
if (allMappings && allMappings.length > 0) {
|
|
952
|
+
const configuredFiles = Array.from(
|
|
953
|
+
new Set(allMappings.map((m) => m.filePath))
|
|
954
|
+
);
|
|
955
|
+
console.warn(
|
|
956
|
+
`[Dev Mode] handleCallback() in ${relative2} does not match any queue route in vercel.json. This handler won't receive messages.
|
|
957
|
+
Configured queue routes: [${configuredFiles.join(", ")}]
|
|
958
|
+
If this path is a bundled chunk, keep handleCallback()/handleNodeCallback() at module scope and let dev-mode route priming load the mapped file.`
|
|
959
|
+
);
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
console.warn(
|
|
963
|
+
`[Dev Mode] handleCallback() in ${relative2} has no matching experimentalTriggers in vercel.json. This handler won't receive messages.
|
|
964
|
+
|
|
965
|
+
Add a trigger to vercel.json:
|
|
966
|
+
"${relative2}": {
|
|
967
|
+
"experimentalTriggers": [{ "type": "queue/v2beta", "topic": "your-topic" }]
|
|
968
|
+
}`
|
|
969
|
+
);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
function lookupHandlers(topicName) {
|
|
973
|
+
const registry = getHandlerRegistry();
|
|
974
|
+
const result = [];
|
|
975
|
+
for (const [pattern, handlers] of registry) {
|
|
976
|
+
const matches = pattern.includes("*") ? matchesWildcardPattern(topicName, pattern) : pattern === topicName;
|
|
977
|
+
if (matches) {
|
|
978
|
+
result.push(...handlers);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
return result;
|
|
982
|
+
}
|
|
983
|
+
var DEV_RETRY_INITIAL_DELAY_MS = 50;
|
|
984
|
+
var DEV_RETRY_MAX_WAIT_MS = 5e3;
|
|
985
|
+
var DEV_RETRY_BACKOFF = 2;
|
|
986
|
+
var PORT_CHECK_TIMEOUT_MS = 250;
|
|
987
|
+
var PRIME_PORT_ENV_KEYS = [
|
|
988
|
+
"PORT",
|
|
989
|
+
"NEXT_PORT",
|
|
990
|
+
"NEXTJS_PORT",
|
|
991
|
+
"NUXT_PORT",
|
|
992
|
+
"NITRO_PORT",
|
|
993
|
+
"SVELTEKIT_PORT",
|
|
994
|
+
"VITE_PORT",
|
|
995
|
+
"DEV_PORT",
|
|
996
|
+
"npm_config_port"
|
|
997
|
+
];
|
|
998
|
+
var PRIME_URL_ENV_KEYS = [
|
|
999
|
+
"__NEXT_PRIVATE_ORIGIN",
|
|
1000
|
+
"NUXT_PUBLIC_SITE_URL",
|
|
1001
|
+
"URL"
|
|
1002
|
+
];
|
|
1003
|
+
function formatErrorReason(error) {
|
|
1004
|
+
if (error instanceof Error) {
|
|
1005
|
+
return error.message;
|
|
1006
|
+
}
|
|
1007
|
+
return String(error);
|
|
825
1008
|
}
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
1009
|
+
function isMessageNotFoundError(error) {
|
|
1010
|
+
if (error instanceof MessageNotFoundError) {
|
|
1011
|
+
return true;
|
|
1012
|
+
}
|
|
1013
|
+
if (error instanceof Error && error.name === "MessageNotFoundError") {
|
|
1014
|
+
return true;
|
|
1015
|
+
}
|
|
1016
|
+
return false;
|
|
1017
|
+
}
|
|
1018
|
+
function parsePort(value) {
|
|
1019
|
+
if (!value) return null;
|
|
1020
|
+
const parsed = Number.parseInt(value, 10);
|
|
1021
|
+
if (!Number.isFinite(parsed) || parsed < 1 || parsed > 65535) return null;
|
|
1022
|
+
return parsed;
|
|
1023
|
+
}
|
|
1024
|
+
function parsePortFromUrl(value) {
|
|
1025
|
+
if (!value) return null;
|
|
1026
|
+
try {
|
|
1027
|
+
const parsed = new URL(value).port;
|
|
1028
|
+
return parsePort(parsed);
|
|
1029
|
+
} catch {
|
|
1030
|
+
return null;
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
function collectPrimePorts() {
|
|
1034
|
+
const result = [];
|
|
1035
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1036
|
+
const add = (port) => {
|
|
1037
|
+
if (port && !seen.has(port)) {
|
|
1038
|
+
seen.add(port);
|
|
1039
|
+
result.push(port);
|
|
1040
|
+
}
|
|
1041
|
+
};
|
|
1042
|
+
for (const key of PRIME_PORT_ENV_KEYS) {
|
|
1043
|
+
add(parsePort(process.env[key]));
|
|
1044
|
+
}
|
|
1045
|
+
for (const key of PRIME_URL_ENV_KEYS) {
|
|
1046
|
+
add(parsePortFromUrl(process.env[key]));
|
|
1047
|
+
}
|
|
1048
|
+
return result;
|
|
1049
|
+
}
|
|
1050
|
+
function isPortListening(port) {
|
|
1051
|
+
return new Promise((resolve2) => {
|
|
1052
|
+
const socket = net.connect({ host: "localhost", port });
|
|
1053
|
+
let settled = false;
|
|
1054
|
+
const finish = (listening) => {
|
|
1055
|
+
if (settled) return;
|
|
1056
|
+
settled = true;
|
|
1057
|
+
socket.destroy();
|
|
1058
|
+
resolve2(listening);
|
|
1059
|
+
};
|
|
1060
|
+
socket.once("connect", () => finish(true));
|
|
1061
|
+
socket.once("error", () => finish(false));
|
|
1062
|
+
socket.setTimeout(PORT_CHECK_TIMEOUT_MS, () => finish(false));
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
async function invokeWithRetry(handler, request, options) {
|
|
831
1066
|
let elapsed = 0;
|
|
832
|
-
let
|
|
833
|
-
while (
|
|
1067
|
+
let delay = DEV_RETRY_INITIAL_DELAY_MS;
|
|
1068
|
+
while (true) {
|
|
834
1069
|
try {
|
|
835
|
-
await
|
|
836
|
-
|
|
837
|
-
consumerGroup,
|
|
838
|
-
messageId,
|
|
839
|
-
visibilityTimeoutSeconds: 0
|
|
840
|
-
});
|
|
841
|
-
return true;
|
|
1070
|
+
await handleCallback(handler, request, options);
|
|
1071
|
+
return;
|
|
842
1072
|
} catch (error) {
|
|
843
|
-
if (error
|
|
844
|
-
await new Promise((
|
|
845
|
-
elapsed +=
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
1073
|
+
if (isMessageNotFoundError(error) && elapsed < DEV_RETRY_MAX_WAIT_MS) {
|
|
1074
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
1075
|
+
elapsed += delay;
|
|
1076
|
+
delay = Math.min(
|
|
1077
|
+
delay * DEV_RETRY_BACKOFF,
|
|
1078
|
+
DEV_RETRY_MAX_WAIT_MS - elapsed
|
|
849
1079
|
);
|
|
850
1080
|
continue;
|
|
851
1081
|
}
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
1082
|
+
throw error;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
function filePathToUrlPath(filePath) {
|
|
1087
|
+
let urlPath = filePath.replace(/^app\//, "/").replace(/^pages\//, "/").replace(/^server\//, "/").replace(/^src\/routes\//, "/").replace(/\/route\.(ts|mts|js|mjs|tsx|jsx)$/, "").replace(/\/\+server\.(ts|mts|js|mjs|tsx|jsx)$/, "").replace(/\.(ts|mts|js|mjs|tsx|jsx)$/, "");
|
|
1088
|
+
if (!urlPath.startsWith("/")) {
|
|
1089
|
+
urlPath = "/" + urlPath;
|
|
1090
|
+
}
|
|
1091
|
+
return urlPath;
|
|
1092
|
+
}
|
|
1093
|
+
async function ensureHandlersLoaded(topicName, options = {}) {
|
|
1094
|
+
const diagnostics = {
|
|
1095
|
+
triedPorts: collectPrimePorts(),
|
|
1096
|
+
listeningPorts: [],
|
|
1097
|
+
unavailablePorts: [],
|
|
1098
|
+
importFailures: [],
|
|
1099
|
+
primeFailures: []
|
|
1100
|
+
};
|
|
1101
|
+
const matchingRoutes = findMatchingRoutes(topicName);
|
|
1102
|
+
if (matchingRoutes.length === 0) return diagnostics;
|
|
1103
|
+
const shouldRefreshRegistered = options.refreshRegistered === true;
|
|
1104
|
+
for (const port of diagnostics.triedPorts) {
|
|
1105
|
+
if (await isPortListening(port)) {
|
|
1106
|
+
diagnostics.listeningPorts.push(port);
|
|
1107
|
+
} else {
|
|
1108
|
+
diagnostics.unavailablePorts.push(port);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
for (const route of matchingRoutes) {
|
|
1112
|
+
const alreadyRegistered = isHandlerRegistered(topicName, route.consumer);
|
|
1113
|
+
if (alreadyRegistered && !shouldRefreshRegistered) {
|
|
1114
|
+
continue;
|
|
1115
|
+
}
|
|
1116
|
+
if (!alreadyRegistered) {
|
|
1117
|
+
const absolutePath = path.resolve(process.cwd(), route.filePath);
|
|
1118
|
+
try {
|
|
1119
|
+
await import(absolutePath);
|
|
1120
|
+
} catch (error) {
|
|
1121
|
+
diagnostics.importFailures.push({
|
|
1122
|
+
filePath: route.filePath,
|
|
1123
|
+
reason: formatErrorReason(error)
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
if (isHandlerRegistered(topicName, route.consumer)) continue;
|
|
1127
|
+
}
|
|
1128
|
+
for (const port of diagnostics.listeningPorts) {
|
|
1129
|
+
const url = `http://localhost:${port}${filePathToUrlPath(route.filePath)}`;
|
|
1130
|
+
try {
|
|
1131
|
+
const response = await fetch(url, {
|
|
1132
|
+
method: "POST",
|
|
1133
|
+
headers: {
|
|
1134
|
+
"x-vercel-queue-prime": "1",
|
|
1135
|
+
"x-vercel-queue-prime-file": route.filePath
|
|
1136
|
+
}
|
|
1137
|
+
});
|
|
1138
|
+
try {
|
|
1139
|
+
await response.text();
|
|
1140
|
+
} catch {
|
|
1141
|
+
}
|
|
1142
|
+
if (isHandlerRegistered(topicName, route.consumer)) {
|
|
1143
|
+
break;
|
|
1144
|
+
}
|
|
1145
|
+
diagnostics.primeFailures.push({
|
|
1146
|
+
filePath: route.filePath,
|
|
1147
|
+
url,
|
|
1148
|
+
reason: `HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}`.trim()
|
|
1149
|
+
});
|
|
1150
|
+
} catch (error) {
|
|
1151
|
+
diagnostics.primeFailures.push({
|
|
1152
|
+
filePath: route.filePath,
|
|
1153
|
+
url,
|
|
1154
|
+
reason: formatErrorReason(error)
|
|
1155
|
+
});
|
|
857
1156
|
}
|
|
858
|
-
console.error(
|
|
859
|
-
`[Dev Mode] Error polling for message visibility: topic="${topicName}" messageId="${messageId}"`,
|
|
860
|
-
error
|
|
861
|
-
);
|
|
862
|
-
return false;
|
|
863
1157
|
}
|
|
864
1158
|
}
|
|
865
|
-
|
|
866
|
-
|
|
1159
|
+
return diagnostics;
|
|
1160
|
+
}
|
|
1161
|
+
function buildNoHandlerWarning(topicName, routes, diagnostics) {
|
|
1162
|
+
const files = routes.map((r) => r.filePath);
|
|
1163
|
+
const suggestedPort = diagnostics.listeningPorts[0] ?? diagnostics.triedPorts[0];
|
|
1164
|
+
const suggestedUrls = suggestedPort ? routes.map(
|
|
1165
|
+
(r) => `http://localhost:${suggestedPort}${filePathToUrlPath(r.filePath)}`
|
|
1166
|
+
) : [];
|
|
1167
|
+
let portSummary;
|
|
1168
|
+
if (diagnostics.triedPorts.length === 0) {
|
|
1169
|
+
portSummary = "No local dev port detected from env. Set PORT (or NEXT_PORT/NUXT_PORT/VITE_PORT).";
|
|
1170
|
+
} else if (diagnostics.listeningPorts.length === 0) {
|
|
1171
|
+
portSummary = `Detected env ports: [${diagnostics.triedPorts.join(", ")}], but none are listening.`;
|
|
1172
|
+
} else {
|
|
1173
|
+
const unavailable = diagnostics.unavailablePorts.length > 0 ? ` Not listening: [${diagnostics.unavailablePorts.join(", ")}].` : "";
|
|
1174
|
+
portSummary = `Detected env ports: [${diagnostics.triedPorts.join(", ")}]. Listening: [${diagnostics.listeningPorts.join(", ")}].` + unavailable;
|
|
1175
|
+
}
|
|
1176
|
+
const importSummary = diagnostics.importFailures.length > 0 ? `
|
|
1177
|
+
Import failures: ` + diagnostics.importFailures.slice(0, 2).map((f) => `${f.filePath} (${f.reason})`).join("; ") : "";
|
|
1178
|
+
const primeSummary = diagnostics.primeFailures.length > 0 ? `
|
|
1179
|
+
Prime failures: ` + diagnostics.primeFailures.slice(0, 3).map((f) => `${f.url} (${f.reason})`).join("; ") : "";
|
|
1180
|
+
return `[Dev Mode] No registered handler for topic "${topicName}". vercel.json maps this topic to [${files.join(", ")}] but auto-loading failed.
|
|
1181
|
+
${portSummary}${importSummary}${primeSummary}
|
|
1182
|
+
Ensure your dev server is running, set PORT if needed, and confirm mapped route files call handleCallback()/handleNodeCallback() at module scope.
|
|
1183
|
+
` + (suggestedUrls.length > 0 ? `Try opening: ${suggestedUrls.join(" or ")}` : "Set PORT (or NEXT_PORT/NUXT_PORT/VITE_PORT) and try sending again.");
|
|
1184
|
+
}
|
|
1185
|
+
function isHandlerRegistered(topicName, consumerGroup) {
|
|
1186
|
+
return lookupHandlers(topicName).some(
|
|
1187
|
+
(h) => h.consumerGroup === consumerGroup
|
|
867
1188
|
);
|
|
868
|
-
return false;
|
|
869
1189
|
}
|
|
870
|
-
function
|
|
1190
|
+
function invokeDevHandlers(topicName, messageId, region, delaySeconds) {
|
|
871
1191
|
if (delaySeconds && delaySeconds > 0) {
|
|
872
1192
|
console.log(
|
|
873
1193
|
`[Dev Mode] Message sent with delay: topic="${topicName}" messageId="${messageId}" delay=${delaySeconds}s`
|
|
874
1194
|
);
|
|
875
1195
|
setTimeout(() => {
|
|
876
|
-
|
|
1196
|
+
invokeDevHandlers(topicName, messageId, region);
|
|
877
1197
|
}, delaySeconds * 1e3);
|
|
878
1198
|
return;
|
|
879
1199
|
}
|
|
880
1200
|
console.log(
|
|
881
1201
|
`[Dev Mode] Message sent: topic="${topicName}" messageId="${messageId}"`
|
|
882
1202
|
);
|
|
883
|
-
const matchingRoutes = findMatchingRoutes(topicName);
|
|
884
|
-
if (matchingRoutes.length === 0) {
|
|
885
|
-
console.log(
|
|
886
|
-
`[Dev Mode] No matching routes in vercel.json for topic "${topicName}"`
|
|
887
|
-
);
|
|
888
|
-
return;
|
|
889
|
-
}
|
|
890
|
-
const consumerGroups = matchingRoutes.map((r) => r.consumer);
|
|
891
|
-
console.log(
|
|
892
|
-
`[Dev Mode] Scheduling callbacks for topic="${topicName}" messageId="${messageId}" \u2192 consumers: [${consumerGroups.join(", ")}]`
|
|
893
|
-
);
|
|
894
1203
|
(async () => {
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
);
|
|
1204
|
+
let handlers = lookupHandlers(topicName);
|
|
1205
|
+
let diagnostics = null;
|
|
1206
|
+
if (handlers.length > 0) {
|
|
1207
|
+
await ensureHandlersLoaded(topicName, { refreshRegistered: true });
|
|
1208
|
+
handlers = lookupHandlers(topicName);
|
|
1209
|
+
} else {
|
|
1210
|
+
diagnostics = await ensureHandlersLoaded(topicName);
|
|
1211
|
+
handlers = lookupHandlers(topicName);
|
|
1212
|
+
}
|
|
1213
|
+
if (handlers.length === 0) {
|
|
1214
|
+
const matchingRoutes = findMatchingRoutes(topicName);
|
|
1215
|
+
if (matchingRoutes.length > 0) {
|
|
1216
|
+
const safeDiagnostics = diagnostics ?? {
|
|
1217
|
+
triedPorts: collectPrimePorts(),
|
|
1218
|
+
listeningPorts: [],
|
|
1219
|
+
unavailablePorts: [],
|
|
1220
|
+
importFailures: [],
|
|
1221
|
+
primeFailures: []
|
|
1222
|
+
};
|
|
1223
|
+
console.warn(
|
|
1224
|
+
buildNoHandlerWarning(topicName, matchingRoutes, safeDiagnostics)
|
|
1225
|
+
);
|
|
1226
|
+
} else {
|
|
1227
|
+
console.warn(
|
|
1228
|
+
`[Dev Mode] No registered handler for topic "${topicName}".
|
|
1229
|
+
Ensure vercel.json has a matching experimentalTriggers entry and the route file calls handleCallback().`
|
|
1230
|
+
);
|
|
1231
|
+
}
|
|
906
1232
|
return;
|
|
907
1233
|
}
|
|
908
|
-
const
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
1234
|
+
const consumerGroups = handlers.map((h) => h.consumerGroup);
|
|
1235
|
+
console.log(
|
|
1236
|
+
`[Dev Mode] Invoking handlers for topic="${topicName}" messageId="${messageId}" \u2192 consumers: [${consumerGroups.join(", ")}]`
|
|
1237
|
+
);
|
|
1238
|
+
for (const entry of handlers) {
|
|
1239
|
+
const request = {
|
|
1240
|
+
queueName: topicName,
|
|
1241
|
+
consumerGroup: entry.consumerGroup,
|
|
1242
|
+
messageId,
|
|
1243
|
+
region
|
|
1244
|
+
};
|
|
1245
|
+
const callbackOptions = {
|
|
1246
|
+
client: entry.client,
|
|
1247
|
+
visibilityTimeoutSeconds: entry.options?.visibilityTimeoutSeconds,
|
|
1248
|
+
retry: entry.options?.retry
|
|
1249
|
+
};
|
|
915
1250
|
try {
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
"ce-vqsqueuename": topicName,
|
|
921
|
-
"ce-vqsconsumergroup": route.consumer,
|
|
922
|
-
"ce-vqsmessageid": messageId,
|
|
923
|
-
"ce-vqsregion": region
|
|
924
|
-
}
|
|
925
|
-
});
|
|
926
|
-
if (response.ok) {
|
|
927
|
-
try {
|
|
928
|
-
const responseData = await response.json();
|
|
929
|
-
if (responseData.status === "success") {
|
|
930
|
-
console.log(
|
|
931
|
-
`[Dev Mode] \u2713 Message processed successfully: topic="${topicName}" consumer="${route.consumer}" messageId="${messageId}"`
|
|
932
|
-
);
|
|
933
|
-
}
|
|
934
|
-
} catch {
|
|
935
|
-
console.warn(
|
|
936
|
-
`[Dev Mode] Handler returned OK but response was not JSON: topic="${topicName}" consumer="${route.consumer}"`
|
|
937
|
-
);
|
|
938
|
-
}
|
|
939
|
-
} else {
|
|
940
|
-
try {
|
|
941
|
-
const errorData = await response.json();
|
|
942
|
-
console.error(
|
|
943
|
-
`[Dev Mode] \u2717 Handler failed: topic="${topicName}" consumer="${route.consumer}" messageId="${messageId}" error="${errorData.error || response.statusText}"`
|
|
944
|
-
);
|
|
945
|
-
} catch {
|
|
946
|
-
console.error(
|
|
947
|
-
`[Dev Mode] \u2717 Handler failed: topic="${topicName}" consumer="${route.consumer}" messageId="${messageId}" status=${response.status}`
|
|
948
|
-
);
|
|
949
|
-
}
|
|
950
|
-
}
|
|
1251
|
+
await invokeWithRetry(entry.handler, request, callbackOptions);
|
|
1252
|
+
console.log(
|
|
1253
|
+
`[Dev Mode] \u2713 Message processed: topic="${topicName}" consumer="${entry.consumerGroup}" messageId="${messageId}"`
|
|
1254
|
+
);
|
|
951
1255
|
} catch (error) {
|
|
952
1256
|
console.error(
|
|
953
|
-
`[Dev Mode] \u2717
|
|
1257
|
+
`[Dev Mode] \u2717 Handler failed: topic="${topicName}" consumer="${entry.consumerGroup}" messageId="${messageId}"`,
|
|
954
1258
|
error
|
|
955
1259
|
);
|
|
956
1260
|
}
|
|
957
1261
|
}
|
|
958
1262
|
})();
|
|
959
1263
|
}
|
|
960
|
-
function
|
|
1264
|
+
function clearDevState() {
|
|
961
1265
|
const g = globalThis;
|
|
962
1266
|
delete g[ROUTE_MAPPINGS_KEY];
|
|
1267
|
+
delete g[HANDLER_REGISTRY_KEY];
|
|
963
1268
|
}
|
|
964
1269
|
if (process.env.NODE_ENV === "test" || process.env.VITEST) {
|
|
965
|
-
globalThis.
|
|
1270
|
+
globalThis.__clearDevState = clearDevState;
|
|
966
1271
|
}
|
|
967
1272
|
|
|
968
1273
|
// src/oidc.ts
|
|
@@ -1079,7 +1384,7 @@ var ApiClient = class _ApiClient {
|
|
|
1079
1384
|
return;
|
|
1080
1385
|
}
|
|
1081
1386
|
throw new Error(
|
|
1082
|
-
'No deployment ID available. VERCEL_DEPLOYMENT_ID is not set.\n\nThis usually means the code is running outside a Vercel deployment (e.g. during build or in a non-Vercel environment).\n\nTo fix this, create a
|
|
1387
|
+
'No deployment ID available. VERCEL_DEPLOYMENT_ID is not set.\n\nThis usually means the code is running outside a Vercel deployment (e.g. during build or in a non-Vercel environment).\n\nTo fix this, create a client with an explicit deploymentId:\n new QueueClient({ deploymentId: "dpl_xxx" })\nOr explicitly opt out of deployment pinning:\n new QueueClient({ deploymentId: null })'
|
|
1083
1388
|
);
|
|
1084
1389
|
}
|
|
1085
1390
|
getSendDeploymentId() {
|
|
@@ -1137,7 +1442,7 @@ var ApiClient = class _ApiClient {
|
|
|
1137
1442
|
}
|
|
1138
1443
|
console.debug("[VQS Debug] Request:", JSON.stringify(logData, null, 2));
|
|
1139
1444
|
}
|
|
1140
|
-
init.headers.set("User-Agent", `@vercel/queue/${"0.
|
|
1445
|
+
init.headers.set("User-Agent", `@vercel/queue/${"0.1.1"}`);
|
|
1141
1446
|
init.headers.set("Vqs-Client-Ts", (/* @__PURE__ */ new Date()).toISOString());
|
|
1142
1447
|
const response = await fetch(url, init);
|
|
1143
1448
|
if (isDebugEnabled()) {
|
|
@@ -1473,23 +1778,61 @@ var ApiClient = class _ApiClient {
|
|
|
1473
1778
|
|
|
1474
1779
|
// src/client.ts
|
|
1475
1780
|
var apiClients = /* @__PURE__ */ new WeakMap();
|
|
1476
|
-
|
|
1781
|
+
var API_CLIENT_KEY = Symbol.for("@vercel/queue.apiClient");
|
|
1782
|
+
function setApi(client, api) {
|
|
1783
|
+
apiClients.set(client, api);
|
|
1784
|
+
Object.defineProperty(client, API_CLIENT_KEY, {
|
|
1785
|
+
value: api,
|
|
1786
|
+
writable: false,
|
|
1787
|
+
enumerable: false,
|
|
1788
|
+
configurable: false
|
|
1789
|
+
});
|
|
1790
|
+
}
|
|
1791
|
+
function getApi(client) {
|
|
1477
1792
|
const api = apiClients.get(client);
|
|
1478
|
-
if (
|
|
1479
|
-
|
|
1793
|
+
if (api) {
|
|
1794
|
+
return api;
|
|
1795
|
+
}
|
|
1796
|
+
const apiFromSymbol = client[API_CLIENT_KEY];
|
|
1797
|
+
if (typeof apiFromSymbol === "object" && apiFromSymbol !== null) {
|
|
1798
|
+
const resolvedApi = apiFromSymbol;
|
|
1799
|
+
apiClients.set(client, resolvedApi);
|
|
1800
|
+
return resolvedApi;
|
|
1480
1801
|
}
|
|
1481
|
-
|
|
1802
|
+
throw new Error(
|
|
1803
|
+
"QueueClient not initialized. This may happen when multiple bundled copies of @vercel/queue are loaded in local dev."
|
|
1804
|
+
);
|
|
1805
|
+
}
|
|
1806
|
+
function resolveCallbackRequest(input) {
|
|
1807
|
+
if ("request" in input) {
|
|
1808
|
+
return input.request;
|
|
1809
|
+
}
|
|
1810
|
+
return input;
|
|
1811
|
+
}
|
|
1812
|
+
function getApiClient(client) {
|
|
1813
|
+
return getApi(client);
|
|
1814
|
+
}
|
|
1815
|
+
var DEFAULT_REGION = "iad1";
|
|
1816
|
+
function resolveRegion(region) {
|
|
1817
|
+
if (region) return region;
|
|
1818
|
+
const fromEnv = process.env.VERCEL_REGION;
|
|
1819
|
+
if (fromEnv) return fromEnv;
|
|
1820
|
+
console.warn(
|
|
1821
|
+
`[QueueClient] Region not detected \u2014 defaulting to "${DEFAULT_REGION}". On Vercel this is set automatically via VERCEL_REGION. To silence this warning, pass region explicitly: new QueueClient({ region: "iad1" })`
|
|
1822
|
+
);
|
|
1823
|
+
return DEFAULT_REGION;
|
|
1482
1824
|
}
|
|
1483
1825
|
var QueueClient = class {
|
|
1484
|
-
constructor(options) {
|
|
1485
|
-
|
|
1826
|
+
constructor(options = {}) {
|
|
1827
|
+
const region = resolveRegion(options.region);
|
|
1828
|
+
setApi(this, new ApiClient({ ...options, region }));
|
|
1486
1829
|
}
|
|
1487
1830
|
/**
|
|
1488
1831
|
* Send a message to a topic.
|
|
1489
1832
|
*
|
|
1490
1833
|
* This is an arrow function property so it can be destructured:
|
|
1491
1834
|
* ```typescript
|
|
1492
|
-
* const { send } = new QueueClient(
|
|
1835
|
+
* const { send } = new QueueClient();
|
|
1493
1836
|
* await send("my-topic", payload);
|
|
1494
1837
|
* ```
|
|
1495
1838
|
*
|
|
@@ -1500,7 +1843,7 @@ var QueueClient = class {
|
|
|
1500
1843
|
* the message for deferred processing (no ID available yet)
|
|
1501
1844
|
*/
|
|
1502
1845
|
send = async (topicName, payload, options) => {
|
|
1503
|
-
const api =
|
|
1846
|
+
const api = getApi(this);
|
|
1504
1847
|
const result = await api.sendMessage({
|
|
1505
1848
|
queueName: topicName,
|
|
1506
1849
|
payload,
|
|
@@ -1510,7 +1853,7 @@ var QueueClient = class {
|
|
|
1510
1853
|
headers: options?.headers
|
|
1511
1854
|
});
|
|
1512
1855
|
if (result.messageId && isDevMode()) {
|
|
1513
|
-
|
|
1856
|
+
invokeDevHandlers(
|
|
1514
1857
|
topicName,
|
|
1515
1858
|
result.messageId,
|
|
1516
1859
|
api.getRegion(),
|
|
@@ -1519,75 +1862,6 @@ var QueueClient = class {
|
|
|
1519
1862
|
}
|
|
1520
1863
|
return { messageId: result.messageId };
|
|
1521
1864
|
};
|
|
1522
|
-
/**
|
|
1523
|
-
* Receive and process messages from a topic.
|
|
1524
|
-
*
|
|
1525
|
-
* Each message is automatically locked, kept alive via periodic visibility
|
|
1526
|
-
* extensions during processing, and acknowledged upon successful handler completion.
|
|
1527
|
-
* The handler is not called when the queue is empty — check `result.ok` instead.
|
|
1528
|
-
*
|
|
1529
|
-
* This is an arrow function property so it can be destructured:
|
|
1530
|
-
* ```typescript
|
|
1531
|
-
* const { receive } = new QueueClient({ region: process.env.QUEUE_REGION! });
|
|
1532
|
-
* const result = await receive("my-topic", "my-group", handler);
|
|
1533
|
-
* if (!result.ok) console.log(result.reason);
|
|
1534
|
-
* ```
|
|
1535
|
-
*
|
|
1536
|
-
* @param topicName - Name of the topic (pattern: `[A-Za-z0-9_-]+`)
|
|
1537
|
-
* @param consumerGroup - Name of the consumer group (pattern: `[A-Za-z0-9_-]+`)
|
|
1538
|
-
* @param handler - Function to process each message payload and metadata.
|
|
1539
|
-
* Not called when the queue is empty.
|
|
1540
|
-
* @param options - Optional receive options (visibilityTimeoutSeconds, limit, or messageId)
|
|
1541
|
-
* @returns Discriminated result: `{ ok: true }` on success, `{ ok: false, reason }` otherwise
|
|
1542
|
-
*/
|
|
1543
|
-
receive = async (topicName, consumerGroup, handler, options) => {
|
|
1544
|
-
const api = getApiClient(this);
|
|
1545
|
-
const topic = new Topic(api, topicName);
|
|
1546
|
-
const visibilityTimeoutSeconds = options && "visibilityTimeoutSeconds" in options ? options.visibilityTimeoutSeconds : void 0;
|
|
1547
|
-
const consumer = topic.consumerGroup(
|
|
1548
|
-
consumerGroup,
|
|
1549
|
-
visibilityTimeoutSeconds !== void 0 ? { visibilityTimeoutSeconds } : {}
|
|
1550
|
-
);
|
|
1551
|
-
try {
|
|
1552
|
-
let count;
|
|
1553
|
-
const retry = options?.retry;
|
|
1554
|
-
if (options && "messageId" in options) {
|
|
1555
|
-
count = await consumer.consume(handler, {
|
|
1556
|
-
messageId: options.messageId,
|
|
1557
|
-
retry
|
|
1558
|
-
});
|
|
1559
|
-
} else {
|
|
1560
|
-
const limit = options && "limit" in options ? options.limit : void 0;
|
|
1561
|
-
count = await consumer.consume(handler, {
|
|
1562
|
-
...limit !== void 0 ? { limit } : {},
|
|
1563
|
-
retry
|
|
1564
|
-
});
|
|
1565
|
-
}
|
|
1566
|
-
if (count === 0) {
|
|
1567
|
-
return { ok: false, reason: "empty" };
|
|
1568
|
-
}
|
|
1569
|
-
return { ok: true };
|
|
1570
|
-
} catch (error) {
|
|
1571
|
-
if (options && "messageId" in options && error instanceof MessageNotFoundError) {
|
|
1572
|
-
return { ok: false, reason: "not_found", messageId: options.messageId };
|
|
1573
|
-
}
|
|
1574
|
-
if (options && "messageId" in options && error instanceof MessageNotAvailableError) {
|
|
1575
|
-
return {
|
|
1576
|
-
ok: false,
|
|
1577
|
-
reason: "not_available",
|
|
1578
|
-
messageId: options.messageId
|
|
1579
|
-
};
|
|
1580
|
-
}
|
|
1581
|
-
if (options && "messageId" in options && error instanceof MessageAlreadyProcessedError) {
|
|
1582
|
-
return {
|
|
1583
|
-
ok: false,
|
|
1584
|
-
reason: "already_processed",
|
|
1585
|
-
messageId: options.messageId
|
|
1586
|
-
};
|
|
1587
|
-
}
|
|
1588
|
-
throw error;
|
|
1589
|
-
}
|
|
1590
|
-
};
|
|
1591
1865
|
/**
|
|
1592
1866
|
* Create a Web API route handler for processing queue callback messages.
|
|
1593
1867
|
*
|
|
@@ -1596,7 +1870,7 @@ var QueueClient = class {
|
|
|
1596
1870
|
*
|
|
1597
1871
|
* This is an arrow function property so it can be destructured:
|
|
1598
1872
|
* ```typescript
|
|
1599
|
-
* const { handleCallback } = new QueueClient(
|
|
1873
|
+
* const { handleCallback } = new QueueClient();
|
|
1600
1874
|
* export const POST = handleCallback(handler);
|
|
1601
1875
|
* ```
|
|
1602
1876
|
*
|
|
@@ -1605,10 +1879,26 @@ var QueueClient = class {
|
|
|
1605
1879
|
* @param options.visibilityTimeoutSeconds - Message lock duration (default: 300, max: 3600)
|
|
1606
1880
|
* @param options.retry - Called when the handler throws. Return `{ afterSeconds: N }` to
|
|
1607
1881
|
* reschedule the message for redelivery after N seconds.
|
|
1608
|
-
* @returns A `
|
|
1882
|
+
* @returns A route handler that accepts either `Request` or `{ request: Request }`
|
|
1609
1883
|
*/
|
|
1610
1884
|
handleCallback = (handler, options) => {
|
|
1611
|
-
|
|
1885
|
+
if (isDevMode()) {
|
|
1886
|
+
registerDevHandler(handler, this, options);
|
|
1887
|
+
}
|
|
1888
|
+
return async (requestOrEvent) => {
|
|
1889
|
+
const request = resolveCallbackRequest(requestOrEvent);
|
|
1890
|
+
if (isDevMode() && request.headers.get("x-vercel-queue-prime") === "1") {
|
|
1891
|
+
const primeFile = request.headers.get("x-vercel-queue-prime-file");
|
|
1892
|
+
if (primeFile) {
|
|
1893
|
+
registerDevHandler(
|
|
1894
|
+
handler,
|
|
1895
|
+
this,
|
|
1896
|
+
options,
|
|
1897
|
+
primeFile
|
|
1898
|
+
);
|
|
1899
|
+
}
|
|
1900
|
+
return Response.json({ status: "primed" });
|
|
1901
|
+
}
|
|
1612
1902
|
try {
|
|
1613
1903
|
const parsed = await parseCallback(request);
|
|
1614
1904
|
await handleCallback(handler, parsed, {
|
|
@@ -1638,7 +1928,7 @@ var QueueClient = class {
|
|
|
1638
1928
|
*
|
|
1639
1929
|
* This is an arrow function property so it can be destructured:
|
|
1640
1930
|
* ```typescript
|
|
1641
|
-
* const { handleNodeCallback } = new QueueClient(
|
|
1931
|
+
* const { handleNodeCallback } = new QueueClient();
|
|
1642
1932
|
* app.post("/api/queue", handleNodeCallback(handler));
|
|
1643
1933
|
* ```
|
|
1644
1934
|
*
|
|
@@ -1650,11 +1940,29 @@ var QueueClient = class {
|
|
|
1650
1940
|
* @returns A `(req, res) => Promise<void>` route handler
|
|
1651
1941
|
*/
|
|
1652
1942
|
handleNodeCallback = (handler, options) => {
|
|
1943
|
+
if (isDevMode()) {
|
|
1944
|
+
registerDevHandler(handler, this, options);
|
|
1945
|
+
}
|
|
1653
1946
|
return async (req, res) => {
|
|
1654
1947
|
if (req.method !== "POST") {
|
|
1655
1948
|
res.status(200).end();
|
|
1656
1949
|
return;
|
|
1657
1950
|
}
|
|
1951
|
+
const primeHeader = req.headers["x-vercel-queue-prime"];
|
|
1952
|
+
if (isDevMode() && primeHeader === "1") {
|
|
1953
|
+
const primeFileHeader = req.headers["x-vercel-queue-prime-file"];
|
|
1954
|
+
const primeFile = Array.isArray(primeFileHeader) ? primeFileHeader[0] : primeFileHeader;
|
|
1955
|
+
if (primeFile) {
|
|
1956
|
+
registerDevHandler(
|
|
1957
|
+
handler,
|
|
1958
|
+
this,
|
|
1959
|
+
options,
|
|
1960
|
+
primeFile
|
|
1961
|
+
);
|
|
1962
|
+
}
|
|
1963
|
+
res.status(200).json({ status: "primed" });
|
|
1964
|
+
return;
|
|
1965
|
+
}
|
|
1658
1966
|
try {
|
|
1659
1967
|
const parsed = parseRawCallback(req.body, req.headers);
|
|
1660
1968
|
await handleCallback(handler, parsed, {
|
|
@@ -1674,6 +1982,126 @@ var QueueClient = class {
|
|
|
1674
1982
|
};
|
|
1675
1983
|
};
|
|
1676
1984
|
};
|
|
1985
|
+
var PollingQueueClient = class {
|
|
1986
|
+
constructor(options) {
|
|
1987
|
+
setApi(this, new ApiClient(options));
|
|
1988
|
+
}
|
|
1989
|
+
/**
|
|
1990
|
+
* Send a message to a topic.
|
|
1991
|
+
*
|
|
1992
|
+
* This is an arrow function property so it can be destructured:
|
|
1993
|
+
* ```typescript
|
|
1994
|
+
* const { send } = new PollingQueueClient({ region: "iad1" });
|
|
1995
|
+
* await send("my-topic", payload);
|
|
1996
|
+
* ```
|
|
1997
|
+
*
|
|
1998
|
+
* @param topicName - Name of the topic (pattern: `[A-Za-z0-9_-]+`)
|
|
1999
|
+
* @param payload - The data to send (serialized via the configured transport)
|
|
2000
|
+
* @param options - Optional send options (idempotencyKey, retentionSeconds, delaySeconds, headers)
|
|
2001
|
+
* @returns `{ messageId }` — `messageId` is `null` when the server accepted
|
|
2002
|
+
* the message for deferred processing (no ID available yet)
|
|
2003
|
+
*/
|
|
2004
|
+
send = async (topicName, payload, options) => {
|
|
2005
|
+
const api = getApi(this);
|
|
2006
|
+
const result = await api.sendMessage({
|
|
2007
|
+
queueName: topicName,
|
|
2008
|
+
payload,
|
|
2009
|
+
idempotencyKey: options?.idempotencyKey,
|
|
2010
|
+
retentionSeconds: options?.retentionSeconds,
|
|
2011
|
+
delaySeconds: options?.delaySeconds,
|
|
2012
|
+
headers: options?.headers
|
|
2013
|
+
});
|
|
2014
|
+
return { messageId: result.messageId };
|
|
2015
|
+
};
|
|
2016
|
+
/**
|
|
2017
|
+
* Receive and process messages from a topic.
|
|
2018
|
+
*
|
|
2019
|
+
* Each message is automatically locked, kept alive via periodic visibility
|
|
2020
|
+
* extensions during processing, and acknowledged upon successful handler completion.
|
|
2021
|
+
* The handler is not called when the queue is empty — check `result.ok` instead.
|
|
2022
|
+
*
|
|
2023
|
+
* This is an arrow function property so it can be destructured:
|
|
2024
|
+
* ```typescript
|
|
2025
|
+
* const { receive } = new PollingQueueClient({ region: "iad1" });
|
|
2026
|
+
* const result = await receive("my-topic", "my-group", handler);
|
|
2027
|
+
* if (!result.ok) console.log(result.reason);
|
|
2028
|
+
* ```
|
|
2029
|
+
*
|
|
2030
|
+
* @param topicName - Name of the topic (pattern: `[A-Za-z0-9_-]+`)
|
|
2031
|
+
* @param consumerGroup - Name of the consumer group (pattern: `[A-Za-z0-9_-]+`)
|
|
2032
|
+
* @param handler - Function to process each message payload and metadata.
|
|
2033
|
+
* Not called when the queue is empty.
|
|
2034
|
+
* @param options - Optional receive options (visibilityTimeoutSeconds, limit, or messageId)
|
|
2035
|
+
* @returns Discriminated result: `{ ok: true }` on success, `{ ok: false, reason }` otherwise
|
|
2036
|
+
*/
|
|
2037
|
+
receive = async (topicName, consumerGroup, handler, options) => {
|
|
2038
|
+
const api = getApi(this);
|
|
2039
|
+
const topic = new Topic(api, topicName);
|
|
2040
|
+
const visibilityTimeoutSeconds = options && "visibilityTimeoutSeconds" in options ? options.visibilityTimeoutSeconds : void 0;
|
|
2041
|
+
const consumer = topic.consumerGroup(
|
|
2042
|
+
consumerGroup,
|
|
2043
|
+
visibilityTimeoutSeconds !== void 0 ? { visibilityTimeoutSeconds } : {}
|
|
2044
|
+
);
|
|
2045
|
+
try {
|
|
2046
|
+
let count;
|
|
2047
|
+
const retry = options?.retry;
|
|
2048
|
+
if (options && "messageId" in options) {
|
|
2049
|
+
count = await consumer.consume(handler, {
|
|
2050
|
+
messageId: options.messageId,
|
|
2051
|
+
retry
|
|
2052
|
+
});
|
|
2053
|
+
} else {
|
|
2054
|
+
const limit = options && "limit" in options ? options.limit : void 0;
|
|
2055
|
+
count = await consumer.consume(handler, {
|
|
2056
|
+
...limit !== void 0 ? { limit } : {},
|
|
2057
|
+
retry
|
|
2058
|
+
});
|
|
2059
|
+
}
|
|
2060
|
+
if (count === 0) {
|
|
2061
|
+
return { ok: false, reason: "empty" };
|
|
2062
|
+
}
|
|
2063
|
+
return { ok: true };
|
|
2064
|
+
} catch (error) {
|
|
2065
|
+
if (options && "messageId" in options && error instanceof MessageNotFoundError) {
|
|
2066
|
+
return { ok: false, reason: "not_found", messageId: options.messageId };
|
|
2067
|
+
}
|
|
2068
|
+
if (options && "messageId" in options && error instanceof MessageNotAvailableError) {
|
|
2069
|
+
return {
|
|
2070
|
+
ok: false,
|
|
2071
|
+
reason: "not_available",
|
|
2072
|
+
messageId: options.messageId
|
|
2073
|
+
};
|
|
2074
|
+
}
|
|
2075
|
+
if (options && "messageId" in options && error instanceof MessageAlreadyProcessedError) {
|
|
2076
|
+
return {
|
|
2077
|
+
ok: false,
|
|
2078
|
+
reason: "already_processed",
|
|
2079
|
+
messageId: options.messageId
|
|
2080
|
+
};
|
|
2081
|
+
}
|
|
2082
|
+
throw error;
|
|
2083
|
+
}
|
|
2084
|
+
};
|
|
2085
|
+
};
|
|
2086
|
+
|
|
2087
|
+
// src/default-client.ts
|
|
2088
|
+
var _defaultClient;
|
|
2089
|
+
function getDefaultClient() {
|
|
2090
|
+
if (!_defaultClient) {
|
|
2091
|
+
_defaultClient = new QueueClient();
|
|
2092
|
+
}
|
|
2093
|
+
return _defaultClient;
|
|
2094
|
+
}
|
|
2095
|
+
function resolveClient(region) {
|
|
2096
|
+
if (!region) return getDefaultClient();
|
|
2097
|
+
return new QueueClient({ region });
|
|
2098
|
+
}
|
|
2099
|
+
async function send(topicName, payload, options) {
|
|
2100
|
+
return resolveClient(options?.region).send(topicName, payload, options);
|
|
2101
|
+
}
|
|
2102
|
+
function handleCallback2(handler, options) {
|
|
2103
|
+
return getDefaultClient().handleCallback(handler, options);
|
|
2104
|
+
}
|
|
1677
2105
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1678
2106
|
0 && (module.exports = {
|
|
1679
2107
|
BadRequestError,
|
|
@@ -1692,11 +2120,14 @@ var QueueClient = class {
|
|
|
1692
2120
|
MessageLockedError,
|
|
1693
2121
|
MessageNotAvailableError,
|
|
1694
2122
|
MessageNotFoundError,
|
|
2123
|
+
PollingQueueClient,
|
|
1695
2124
|
QueueClient,
|
|
1696
2125
|
QueueEmptyError,
|
|
1697
2126
|
StreamTransport,
|
|
1698
2127
|
UnauthorizedError,
|
|
2128
|
+
handleCallback,
|
|
1699
2129
|
parseCallback,
|
|
1700
|
-
parseRawCallback
|
|
2130
|
+
parseRawCallback,
|
|
2131
|
+
send
|
|
1701
2132
|
});
|
|
1702
2133
|
//# sourceMappingURL=index.js.map
|