@vercel/queue 0.1.0 → 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 +125 -61
- package/dist/index.d.mts +58 -3
- package/dist/index.d.ts +58 -3
- package/dist/index.js +517 -134
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +513 -133
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -51,8 +51,10 @@ __export(index_exports, {
|
|
|
51
51
|
QueueEmptyError: () => QueueEmptyError,
|
|
52
52
|
StreamTransport: () => StreamTransport,
|
|
53
53
|
UnauthorizedError: () => UnauthorizedError,
|
|
54
|
+
handleCallback: () => handleCallback2,
|
|
54
55
|
parseCallback: () => parseCallback,
|
|
55
|
-
parseRawCallback: () => parseRawCallback
|
|
56
|
+
parseRawCallback: () => parseRawCallback,
|
|
57
|
+
send: () => send
|
|
56
58
|
});
|
|
57
59
|
module.exports = __toCommonJS(index_exports);
|
|
58
60
|
|
|
@@ -134,6 +136,7 @@ var import_mixpart = require("mixpart");
|
|
|
134
136
|
|
|
135
137
|
// src/dev.ts
|
|
136
138
|
var fs = __toESM(require("fs"));
|
|
139
|
+
var net = __toESM(require("net"));
|
|
137
140
|
var path = __toESM(require("path"));
|
|
138
141
|
|
|
139
142
|
// src/types.ts
|
|
@@ -319,8 +322,8 @@ var ConsumerGroup = class {
|
|
|
319
322
|
firstDelayMs = 0;
|
|
320
323
|
}
|
|
321
324
|
}
|
|
322
|
-
const lifecyclePromise = new Promise((
|
|
323
|
-
resolveLifecycle =
|
|
325
|
+
const lifecyclePromise = new Promise((resolve2) => {
|
|
326
|
+
resolveLifecycle = resolve2;
|
|
324
327
|
});
|
|
325
328
|
const safeResolve = () => {
|
|
326
329
|
if (!isResolved) {
|
|
@@ -552,7 +555,7 @@ var Topic = class {
|
|
|
552
555
|
headers: options?.headers
|
|
553
556
|
});
|
|
554
557
|
if (result.messageId && isDevMode()) {
|
|
555
|
-
|
|
558
|
+
invokeDevHandlers(
|
|
556
559
|
this.topicName,
|
|
557
560
|
result.messageId,
|
|
558
561
|
this.client.getRegion()
|
|
@@ -761,14 +764,11 @@ async function handleCallback(handler, request, options) {
|
|
|
761
764
|
}
|
|
762
765
|
|
|
763
766
|
// src/dev.ts
|
|
764
|
-
var
|
|
765
|
-
function
|
|
766
|
-
|
|
767
|
-
if (!urlPath.startsWith("/")) {
|
|
768
|
-
urlPath = "/" + urlPath;
|
|
769
|
-
}
|
|
770
|
-
return urlPath;
|
|
767
|
+
var import_meta = {};
|
|
768
|
+
function isDevMode() {
|
|
769
|
+
return process.env.NODE_ENV === "development";
|
|
771
770
|
}
|
|
771
|
+
var ROUTE_MAPPINGS_KEY = Symbol.for("@vercel/queue.devRouteMappings");
|
|
772
772
|
function filePathToConsumerGroup(filePath) {
|
|
773
773
|
return filePath.replace(/_/g, "__").replace(/\//g, "_S").replace(/\./g, "_D");
|
|
774
774
|
}
|
|
@@ -792,13 +792,18 @@ function getDevRouteMappings() {
|
|
|
792
792
|
for (const [filePath, config] of Object.entries(vercelJson.functions)) {
|
|
793
793
|
if (!config.experimentalTriggers) continue;
|
|
794
794
|
for (const trigger of config.experimentalTriggers) {
|
|
795
|
-
if (trigger.type?.startsWith("queue/")
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
topic
|
|
799
|
-
|
|
800
|
-
|
|
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;
|
|
801
801
|
}
|
|
802
|
+
mappings.push({
|
|
803
|
+
filePath,
|
|
804
|
+
topic: trigger.topic,
|
|
805
|
+
consumer: filePathToConsumerGroup(filePath)
|
|
806
|
+
});
|
|
802
807
|
}
|
|
803
808
|
}
|
|
804
809
|
g[ROUTE_MAPPINGS_KEY] = mappings.length > 0 ? mappings : null;
|
|
@@ -811,9 +816,7 @@ function getDevRouteMappings() {
|
|
|
811
816
|
}
|
|
812
817
|
function findMatchingRoutes(topicName) {
|
|
813
818
|
const mappings = getDevRouteMappings();
|
|
814
|
-
if (!mappings)
|
|
815
|
-
return [];
|
|
816
|
-
}
|
|
819
|
+
if (!mappings) return [];
|
|
817
820
|
return mappings.filter((mapping) => {
|
|
818
821
|
if (mapping.topic.includes("*")) {
|
|
819
822
|
return matchesWildcardPattern(topicName, mapping.topic);
|
|
@@ -821,149 +824,450 @@ function findMatchingRoutes(topicName) {
|
|
|
821
824
|
return mapping.topic === topicName;
|
|
822
825
|
});
|
|
823
826
|
}
|
|
824
|
-
function
|
|
825
|
-
|
|
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;
|
|
826
927
|
}
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
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);
|
|
1008
|
+
}
|
|
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) {
|
|
832
1066
|
let elapsed = 0;
|
|
833
|
-
let
|
|
834
|
-
while (
|
|
1067
|
+
let delay = DEV_RETRY_INITIAL_DELAY_MS;
|
|
1068
|
+
while (true) {
|
|
835
1069
|
try {
|
|
836
|
-
await
|
|
837
|
-
|
|
838
|
-
consumerGroup,
|
|
839
|
-
messageId,
|
|
840
|
-
visibilityTimeoutSeconds: 0
|
|
841
|
-
});
|
|
842
|
-
return true;
|
|
1070
|
+
await handleCallback(handler, request, options);
|
|
1071
|
+
return;
|
|
843
1072
|
} catch (error) {
|
|
844
|
-
if (error
|
|
845
|
-
await new Promise((
|
|
846
|
-
elapsed +=
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
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
|
|
850
1079
|
);
|
|
851
1080
|
continue;
|
|
852
1081
|
}
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
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
|
+
});
|
|
858
1156
|
}
|
|
859
|
-
console.error(
|
|
860
|
-
`[Dev Mode] Error polling for message visibility: topic="${topicName}" messageId="${messageId}"`,
|
|
861
|
-
error
|
|
862
|
-
);
|
|
863
|
-
return false;
|
|
864
1157
|
}
|
|
865
1158
|
}
|
|
866
|
-
|
|
867
|
-
|
|
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
|
|
868
1188
|
);
|
|
869
|
-
return false;
|
|
870
1189
|
}
|
|
871
|
-
function
|
|
1190
|
+
function invokeDevHandlers(topicName, messageId, region, delaySeconds) {
|
|
872
1191
|
if (delaySeconds && delaySeconds > 0) {
|
|
873
1192
|
console.log(
|
|
874
1193
|
`[Dev Mode] Message sent with delay: topic="${topicName}" messageId="${messageId}" delay=${delaySeconds}s`
|
|
875
1194
|
);
|
|
876
1195
|
setTimeout(() => {
|
|
877
|
-
|
|
1196
|
+
invokeDevHandlers(topicName, messageId, region);
|
|
878
1197
|
}, delaySeconds * 1e3);
|
|
879
1198
|
return;
|
|
880
1199
|
}
|
|
881
1200
|
console.log(
|
|
882
1201
|
`[Dev Mode] Message sent: topic="${topicName}" messageId="${messageId}"`
|
|
883
1202
|
);
|
|
884
|
-
const matchingRoutes = findMatchingRoutes(topicName);
|
|
885
|
-
if (matchingRoutes.length === 0) {
|
|
886
|
-
console.log(
|
|
887
|
-
`[Dev Mode] No matching routes in vercel.json for topic "${topicName}"`
|
|
888
|
-
);
|
|
889
|
-
return;
|
|
890
|
-
}
|
|
891
|
-
const consumerGroups = matchingRoutes.map((r) => r.consumer);
|
|
892
|
-
console.log(
|
|
893
|
-
`[Dev Mode] Scheduling callbacks for topic="${topicName}" messageId="${messageId}" \u2192 consumers: [${consumerGroups.join(", ")}]`
|
|
894
|
-
);
|
|
895
1203
|
(async () => {
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
);
|
|
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
|
+
}
|
|
907
1232
|
return;
|
|
908
1233
|
}
|
|
909
|
-
const
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
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
|
+
};
|
|
916
1250
|
try {
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
"ce-vqsqueuename": topicName,
|
|
922
|
-
"ce-vqsconsumergroup": route.consumer,
|
|
923
|
-
"ce-vqsmessageid": messageId,
|
|
924
|
-
"ce-vqsregion": region
|
|
925
|
-
}
|
|
926
|
-
});
|
|
927
|
-
if (response.ok) {
|
|
928
|
-
try {
|
|
929
|
-
const responseData = await response.json();
|
|
930
|
-
if (responseData.status === "success") {
|
|
931
|
-
console.log(
|
|
932
|
-
`[Dev Mode] \u2713 Message processed successfully: topic="${topicName}" consumer="${route.consumer}" messageId="${messageId}"`
|
|
933
|
-
);
|
|
934
|
-
}
|
|
935
|
-
} catch {
|
|
936
|
-
console.warn(
|
|
937
|
-
`[Dev Mode] Handler returned OK but response was not JSON: topic="${topicName}" consumer="${route.consumer}"`
|
|
938
|
-
);
|
|
939
|
-
}
|
|
940
|
-
} else {
|
|
941
|
-
try {
|
|
942
|
-
const errorData = await response.json();
|
|
943
|
-
console.error(
|
|
944
|
-
`[Dev Mode] \u2717 Handler failed: topic="${topicName}" consumer="${route.consumer}" messageId="${messageId}" error="${errorData.error || response.statusText}"`
|
|
945
|
-
);
|
|
946
|
-
} catch {
|
|
947
|
-
console.error(
|
|
948
|
-
`[Dev Mode] \u2717 Handler failed: topic="${topicName}" consumer="${route.consumer}" messageId="${messageId}" status=${response.status}`
|
|
949
|
-
);
|
|
950
|
-
}
|
|
951
|
-
}
|
|
1251
|
+
await invokeWithRetry(entry.handler, request, callbackOptions);
|
|
1252
|
+
console.log(
|
|
1253
|
+
`[Dev Mode] \u2713 Message processed: topic="${topicName}" consumer="${entry.consumerGroup}" messageId="${messageId}"`
|
|
1254
|
+
);
|
|
952
1255
|
} catch (error) {
|
|
953
1256
|
console.error(
|
|
954
|
-
`[Dev Mode] \u2717
|
|
1257
|
+
`[Dev Mode] \u2717 Handler failed: topic="${topicName}" consumer="${entry.consumerGroup}" messageId="${messageId}"`,
|
|
955
1258
|
error
|
|
956
1259
|
);
|
|
957
1260
|
}
|
|
958
1261
|
}
|
|
959
1262
|
})();
|
|
960
1263
|
}
|
|
961
|
-
function
|
|
1264
|
+
function clearDevState() {
|
|
962
1265
|
const g = globalThis;
|
|
963
1266
|
delete g[ROUTE_MAPPINGS_KEY];
|
|
1267
|
+
delete g[HANDLER_REGISTRY_KEY];
|
|
964
1268
|
}
|
|
965
1269
|
if (process.env.NODE_ENV === "test" || process.env.VITEST) {
|
|
966
|
-
globalThis.
|
|
1270
|
+
globalThis.__clearDevState = clearDevState;
|
|
967
1271
|
}
|
|
968
1272
|
|
|
969
1273
|
// src/oidc.ts
|
|
@@ -1138,7 +1442,7 @@ var ApiClient = class _ApiClient {
|
|
|
1138
1442
|
}
|
|
1139
1443
|
console.debug("[VQS Debug] Request:", JSON.stringify(logData, null, 2));
|
|
1140
1444
|
}
|
|
1141
|
-
init.headers.set("User-Agent", `@vercel/queue/${"0.1.
|
|
1445
|
+
init.headers.set("User-Agent", `@vercel/queue/${"0.1.1"}`);
|
|
1142
1446
|
init.headers.set("Vqs-Client-Ts", (/* @__PURE__ */ new Date()).toISOString());
|
|
1143
1447
|
const response = await fetch(url, init);
|
|
1144
1448
|
if (isDebugEnabled()) {
|
|
@@ -1474,12 +1778,36 @@ var ApiClient = class _ApiClient {
|
|
|
1474
1778
|
|
|
1475
1779
|
// src/client.ts
|
|
1476
1780
|
var apiClients = /* @__PURE__ */ new WeakMap();
|
|
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
|
+
}
|
|
1477
1791
|
function getApi(client) {
|
|
1478
1792
|
const api = apiClients.get(client);
|
|
1479
|
-
if (
|
|
1480
|
-
|
|
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;
|
|
1801
|
+
}
|
|
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;
|
|
1481
1809
|
}
|
|
1482
|
-
return
|
|
1810
|
+
return input;
|
|
1483
1811
|
}
|
|
1484
1812
|
function getApiClient(client) {
|
|
1485
1813
|
return getApi(client);
|
|
@@ -1497,7 +1825,7 @@ function resolveRegion(region) {
|
|
|
1497
1825
|
var QueueClient = class {
|
|
1498
1826
|
constructor(options = {}) {
|
|
1499
1827
|
const region = resolveRegion(options.region);
|
|
1500
|
-
|
|
1828
|
+
setApi(this, new ApiClient({ ...options, region }));
|
|
1501
1829
|
}
|
|
1502
1830
|
/**
|
|
1503
1831
|
* Send a message to a topic.
|
|
@@ -1525,7 +1853,7 @@ var QueueClient = class {
|
|
|
1525
1853
|
headers: options?.headers
|
|
1526
1854
|
});
|
|
1527
1855
|
if (result.messageId && isDevMode()) {
|
|
1528
|
-
|
|
1856
|
+
invokeDevHandlers(
|
|
1529
1857
|
topicName,
|
|
1530
1858
|
result.messageId,
|
|
1531
1859
|
api.getRegion(),
|
|
@@ -1551,10 +1879,26 @@ var QueueClient = class {
|
|
|
1551
1879
|
* @param options.visibilityTimeoutSeconds - Message lock duration (default: 300, max: 3600)
|
|
1552
1880
|
* @param options.retry - Called when the handler throws. Return `{ afterSeconds: N }` to
|
|
1553
1881
|
* reschedule the message for redelivery after N seconds.
|
|
1554
|
-
* @returns A `
|
|
1882
|
+
* @returns A route handler that accepts either `Request` or `{ request: Request }`
|
|
1555
1883
|
*/
|
|
1556
1884
|
handleCallback = (handler, options) => {
|
|
1557
|
-
|
|
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
|
+
}
|
|
1558
1902
|
try {
|
|
1559
1903
|
const parsed = await parseCallback(request);
|
|
1560
1904
|
await handleCallback(handler, parsed, {
|
|
@@ -1596,11 +1940,29 @@ var QueueClient = class {
|
|
|
1596
1940
|
* @returns A `(req, res) => Promise<void>` route handler
|
|
1597
1941
|
*/
|
|
1598
1942
|
handleNodeCallback = (handler, options) => {
|
|
1943
|
+
if (isDevMode()) {
|
|
1944
|
+
registerDevHandler(handler, this, options);
|
|
1945
|
+
}
|
|
1599
1946
|
return async (req, res) => {
|
|
1600
1947
|
if (req.method !== "POST") {
|
|
1601
1948
|
res.status(200).end();
|
|
1602
1949
|
return;
|
|
1603
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
|
+
}
|
|
1604
1966
|
try {
|
|
1605
1967
|
const parsed = parseRawCallback(req.body, req.headers);
|
|
1606
1968
|
await handleCallback(handler, parsed, {
|
|
@@ -1622,7 +1984,7 @@ var QueueClient = class {
|
|
|
1622
1984
|
};
|
|
1623
1985
|
var PollingQueueClient = class {
|
|
1624
1986
|
constructor(options) {
|
|
1625
|
-
|
|
1987
|
+
setApi(this, new ApiClient(options));
|
|
1626
1988
|
}
|
|
1627
1989
|
/**
|
|
1628
1990
|
* Send a message to a topic.
|
|
@@ -1721,6 +2083,25 @@ var PollingQueueClient = class {
|
|
|
1721
2083
|
}
|
|
1722
2084
|
};
|
|
1723
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
|
+
}
|
|
1724
2105
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1725
2106
|
0 && (module.exports = {
|
|
1726
2107
|
BadRequestError,
|
|
@@ -1744,7 +2125,9 @@ var PollingQueueClient = class {
|
|
|
1744
2125
|
QueueEmptyError,
|
|
1745
2126
|
StreamTransport,
|
|
1746
2127
|
UnauthorizedError,
|
|
2128
|
+
handleCallback,
|
|
1747
2129
|
parseCallback,
|
|
1748
|
-
parseRawCallback
|
|
2130
|
+
parseRawCallback,
|
|
2131
|
+
send
|
|
1749
2132
|
});
|
|
1750
2133
|
//# sourceMappingURL=index.js.map
|