@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.mjs
CHANGED
|
@@ -76,6 +76,7 @@ import { parseMultipartStream } from "mixpart";
|
|
|
76
76
|
|
|
77
77
|
// src/dev.ts
|
|
78
78
|
import * as fs from "fs";
|
|
79
|
+
import * as net from "net";
|
|
79
80
|
import * as path from "path";
|
|
80
81
|
|
|
81
82
|
// src/types.ts
|
|
@@ -261,8 +262,8 @@ var ConsumerGroup = class {
|
|
|
261
262
|
firstDelayMs = 0;
|
|
262
263
|
}
|
|
263
264
|
}
|
|
264
|
-
const lifecyclePromise = new Promise((
|
|
265
|
-
resolveLifecycle =
|
|
265
|
+
const lifecyclePromise = new Promise((resolve2) => {
|
|
266
|
+
resolveLifecycle = resolve2;
|
|
266
267
|
});
|
|
267
268
|
const safeResolve = () => {
|
|
268
269
|
if (!isResolved) {
|
|
@@ -494,7 +495,7 @@ var Topic = class {
|
|
|
494
495
|
headers: options?.headers
|
|
495
496
|
});
|
|
496
497
|
if (result.messageId && isDevMode()) {
|
|
497
|
-
|
|
498
|
+
invokeDevHandlers(
|
|
498
499
|
this.topicName,
|
|
499
500
|
result.messageId,
|
|
500
501
|
this.client.getRegion()
|
|
@@ -703,14 +704,10 @@ async function handleCallback(handler, request, options) {
|
|
|
703
704
|
}
|
|
704
705
|
|
|
705
706
|
// src/dev.ts
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
let urlPath = filePath.replace(/^app\//, "/").replace(/^pages\//, "/").replace(/\/route\.(ts|mts|js|mjs|tsx|jsx)$/, "").replace(/\.(ts|mts|js|mjs|tsx|jsx)$/, "");
|
|
709
|
-
if (!urlPath.startsWith("/")) {
|
|
710
|
-
urlPath = "/" + urlPath;
|
|
711
|
-
}
|
|
712
|
-
return urlPath;
|
|
707
|
+
function isDevMode() {
|
|
708
|
+
return process.env.NODE_ENV === "development";
|
|
713
709
|
}
|
|
710
|
+
var ROUTE_MAPPINGS_KEY = Symbol.for("@vercel/queue.devRouteMappings");
|
|
714
711
|
function filePathToConsumerGroup(filePath) {
|
|
715
712
|
return filePath.replace(/_/g, "__").replace(/\//g, "_S").replace(/\./g, "_D");
|
|
716
713
|
}
|
|
@@ -734,13 +731,18 @@ function getDevRouteMappings() {
|
|
|
734
731
|
for (const [filePath, config] of Object.entries(vercelJson.functions)) {
|
|
735
732
|
if (!config.experimentalTriggers) continue;
|
|
736
733
|
for (const trigger of config.experimentalTriggers) {
|
|
737
|
-
if (trigger.type?.startsWith("queue/")
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
topic
|
|
741
|
-
|
|
742
|
-
|
|
734
|
+
if (!trigger.type?.startsWith("queue/") || !trigger.topic) continue;
|
|
735
|
+
if (trigger.type !== "queue/v2beta") {
|
|
736
|
+
console.warn(
|
|
737
|
+
`[Dev Mode] Unsupported trigger type "${trigger.type}" for topic "${trigger.topic}" in ${filePath}. Use "queue/v2beta" instead.`
|
|
738
|
+
);
|
|
739
|
+
continue;
|
|
743
740
|
}
|
|
741
|
+
mappings.push({
|
|
742
|
+
filePath,
|
|
743
|
+
topic: trigger.topic,
|
|
744
|
+
consumer: filePathToConsumerGroup(filePath)
|
|
745
|
+
});
|
|
744
746
|
}
|
|
745
747
|
}
|
|
746
748
|
g[ROUTE_MAPPINGS_KEY] = mappings.length > 0 ? mappings : null;
|
|
@@ -753,9 +755,7 @@ function getDevRouteMappings() {
|
|
|
753
755
|
}
|
|
754
756
|
function findMatchingRoutes(topicName) {
|
|
755
757
|
const mappings = getDevRouteMappings();
|
|
756
|
-
if (!mappings)
|
|
757
|
-
return [];
|
|
758
|
-
}
|
|
758
|
+
if (!mappings) return [];
|
|
759
759
|
return mappings.filter((mapping) => {
|
|
760
760
|
if (mapping.topic.includes("*")) {
|
|
761
761
|
return matchesWildcardPattern(topicName, mapping.topic);
|
|
@@ -763,149 +763,450 @@ function findMatchingRoutes(topicName) {
|
|
|
763
763
|
return mapping.topic === topicName;
|
|
764
764
|
});
|
|
765
765
|
}
|
|
766
|
-
function
|
|
767
|
-
|
|
766
|
+
function findMappingsForFile(absolutePath) {
|
|
767
|
+
const mappings = getDevRouteMappings();
|
|
768
|
+
if (!mappings) return [];
|
|
769
|
+
const cwd = process.cwd();
|
|
770
|
+
let relative2;
|
|
771
|
+
try {
|
|
772
|
+
relative2 = path.relative(cwd, absolutePath);
|
|
773
|
+
} catch {
|
|
774
|
+
return [];
|
|
775
|
+
}
|
|
776
|
+
const normalized = relative2.replace(/\\/g, "/");
|
|
777
|
+
return mappings.filter((m) => m.filePath === normalized);
|
|
778
|
+
}
|
|
779
|
+
function parseFrameFilePath(line) {
|
|
780
|
+
let match = line.match(/\((.+?):\d+:\d+\)/);
|
|
781
|
+
if (!match) match = line.match(/at\s+(.+?):\d+:\d+/);
|
|
782
|
+
if (!match) return null;
|
|
783
|
+
let filePath = match[1].trim();
|
|
784
|
+
if (filePath === "native" || filePath.startsWith("node:") || filePath.startsWith("internal")) {
|
|
785
|
+
return null;
|
|
786
|
+
}
|
|
787
|
+
if (filePath.startsWith("file://")) {
|
|
788
|
+
try {
|
|
789
|
+
filePath = new URL(filePath).pathname;
|
|
790
|
+
} catch {
|
|
791
|
+
return null;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
if (/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(filePath)) {
|
|
795
|
+
return null;
|
|
796
|
+
}
|
|
797
|
+
if (filePath.startsWith("./")) {
|
|
798
|
+
filePath = filePath.slice(2);
|
|
799
|
+
}
|
|
800
|
+
return filePath;
|
|
801
|
+
}
|
|
802
|
+
var _sdkPackageDir;
|
|
803
|
+
function getSdkPackageDir() {
|
|
804
|
+
if (_sdkPackageDir) return _sdkPackageDir;
|
|
805
|
+
try {
|
|
806
|
+
const thisDir = typeof __dirname !== "undefined" ? __dirname : path.dirname(new URL(import.meta.url).pathname);
|
|
807
|
+
_sdkPackageDir = path.resolve(thisDir, "..");
|
|
808
|
+
} catch {
|
|
809
|
+
_sdkPackageDir = "";
|
|
810
|
+
}
|
|
811
|
+
return _sdkPackageDir;
|
|
812
|
+
}
|
|
813
|
+
function extractCallerFilePath() {
|
|
814
|
+
const stack = new Error().stack;
|
|
815
|
+
if (!stack) return null;
|
|
816
|
+
const lines = stack.split("\n").slice(1);
|
|
817
|
+
const pkgDir = getSdkPackageDir();
|
|
818
|
+
for (const line of lines) {
|
|
819
|
+
const fp = parseFrameFilePath(line);
|
|
820
|
+
if (!fp) continue;
|
|
821
|
+
const absolute = path.isAbsolute(fp) ? fp : path.resolve(process.cwd(), fp);
|
|
822
|
+
let realFp;
|
|
823
|
+
try {
|
|
824
|
+
realFp = fs.realpathSync(absolute);
|
|
825
|
+
} catch {
|
|
826
|
+
realFp = absolute;
|
|
827
|
+
}
|
|
828
|
+
if (pkgDir && realFp.startsWith(pkgDir)) continue;
|
|
829
|
+
return realFp;
|
|
830
|
+
}
|
|
831
|
+
return null;
|
|
832
|
+
}
|
|
833
|
+
var HANDLER_REGISTRY_KEY = Symbol.for("@vercel/queue.devHandlerRegistry");
|
|
834
|
+
function getHandlerRegistry() {
|
|
835
|
+
const g = globalThis;
|
|
836
|
+
if (!g[HANDLER_REGISTRY_KEY]) {
|
|
837
|
+
g[HANDLER_REGISTRY_KEY] = /* @__PURE__ */ new Map();
|
|
838
|
+
}
|
|
839
|
+
return g[HANDLER_REGISTRY_KEY];
|
|
840
|
+
}
|
|
841
|
+
function registerHandlerForFile(filePath, handler, client, options) {
|
|
842
|
+
const absolutePath = path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath);
|
|
843
|
+
const fileMappings = findMappingsForFile(absolutePath);
|
|
844
|
+
if (fileMappings.length === 0) return false;
|
|
845
|
+
const registry = getHandlerRegistry();
|
|
846
|
+
for (const mapping of fileMappings) {
|
|
847
|
+
const key = mapping.topic;
|
|
848
|
+
const existing = registry.get(key) ?? [];
|
|
849
|
+
const nextEntry = {
|
|
850
|
+
consumerGroup: mapping.consumer,
|
|
851
|
+
handler,
|
|
852
|
+
client,
|
|
853
|
+
options
|
|
854
|
+
};
|
|
855
|
+
const existingIndex = existing.findIndex(
|
|
856
|
+
(e) => e.consumerGroup === mapping.consumer
|
|
857
|
+
);
|
|
858
|
+
if (existingIndex >= 0) {
|
|
859
|
+
existing[existingIndex] = nextEntry;
|
|
860
|
+
} else {
|
|
861
|
+
existing.push(nextEntry);
|
|
862
|
+
}
|
|
863
|
+
registry.set(key, existing);
|
|
864
|
+
}
|
|
865
|
+
return true;
|
|
768
866
|
}
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
867
|
+
function registerDevHandler(handler, client, options, _testCallerPath) {
|
|
868
|
+
const callerPath = _testCallerPath ?? extractCallerFilePath();
|
|
869
|
+
if (!callerPath) {
|
|
870
|
+
console.warn(
|
|
871
|
+
"[Dev Mode] Could not determine caller file path for handler registration."
|
|
872
|
+
);
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
const registered = registerHandlerForFile(
|
|
876
|
+
callerPath,
|
|
877
|
+
handler,
|
|
878
|
+
client,
|
|
879
|
+
options
|
|
880
|
+
);
|
|
881
|
+
if (!registered) {
|
|
882
|
+
const allMappings = getDevRouteMappings();
|
|
883
|
+
const cwd = process.cwd();
|
|
884
|
+
let relative2;
|
|
885
|
+
try {
|
|
886
|
+
relative2 = path.relative(cwd, callerPath).replace(/\\/g, "/");
|
|
887
|
+
} catch {
|
|
888
|
+
relative2 = callerPath;
|
|
889
|
+
}
|
|
890
|
+
if (allMappings && allMappings.length > 0) {
|
|
891
|
+
const configuredFiles = Array.from(
|
|
892
|
+
new Set(allMappings.map((m) => m.filePath))
|
|
893
|
+
);
|
|
894
|
+
console.warn(
|
|
895
|
+
`[Dev Mode] handleCallback() in ${relative2} does not match any queue route in vercel.json. This handler won't receive messages.
|
|
896
|
+
Configured queue routes: [${configuredFiles.join(", ")}]
|
|
897
|
+
If this path is a bundled chunk, keep handleCallback()/handleNodeCallback() at module scope and let dev-mode route priming load the mapped file.`
|
|
898
|
+
);
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
console.warn(
|
|
902
|
+
`[Dev Mode] handleCallback() in ${relative2} has no matching experimentalTriggers in vercel.json. This handler won't receive messages.
|
|
903
|
+
|
|
904
|
+
Add a trigger to vercel.json:
|
|
905
|
+
"${relative2}": {
|
|
906
|
+
"experimentalTriggers": [{ "type": "queue/v2beta", "topic": "your-topic" }]
|
|
907
|
+
}`
|
|
908
|
+
);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
function lookupHandlers(topicName) {
|
|
912
|
+
const registry = getHandlerRegistry();
|
|
913
|
+
const result = [];
|
|
914
|
+
for (const [pattern, handlers] of registry) {
|
|
915
|
+
const matches = pattern.includes("*") ? matchesWildcardPattern(topicName, pattern) : pattern === topicName;
|
|
916
|
+
if (matches) {
|
|
917
|
+
result.push(...handlers);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
return result;
|
|
921
|
+
}
|
|
922
|
+
var DEV_RETRY_INITIAL_DELAY_MS = 50;
|
|
923
|
+
var DEV_RETRY_MAX_WAIT_MS = 5e3;
|
|
924
|
+
var DEV_RETRY_BACKOFF = 2;
|
|
925
|
+
var PORT_CHECK_TIMEOUT_MS = 250;
|
|
926
|
+
var PRIME_PORT_ENV_KEYS = [
|
|
927
|
+
"PORT",
|
|
928
|
+
"NEXT_PORT",
|
|
929
|
+
"NEXTJS_PORT",
|
|
930
|
+
"NUXT_PORT",
|
|
931
|
+
"NITRO_PORT",
|
|
932
|
+
"SVELTEKIT_PORT",
|
|
933
|
+
"VITE_PORT",
|
|
934
|
+
"DEV_PORT",
|
|
935
|
+
"npm_config_port"
|
|
936
|
+
];
|
|
937
|
+
var PRIME_URL_ENV_KEYS = [
|
|
938
|
+
"__NEXT_PRIVATE_ORIGIN",
|
|
939
|
+
"NUXT_PUBLIC_SITE_URL",
|
|
940
|
+
"URL"
|
|
941
|
+
];
|
|
942
|
+
function formatErrorReason(error) {
|
|
943
|
+
if (error instanceof Error) {
|
|
944
|
+
return error.message;
|
|
945
|
+
}
|
|
946
|
+
return String(error);
|
|
947
|
+
}
|
|
948
|
+
function isMessageNotFoundError(error) {
|
|
949
|
+
if (error instanceof MessageNotFoundError) {
|
|
950
|
+
return true;
|
|
951
|
+
}
|
|
952
|
+
if (error instanceof Error && error.name === "MessageNotFoundError") {
|
|
953
|
+
return true;
|
|
954
|
+
}
|
|
955
|
+
return false;
|
|
956
|
+
}
|
|
957
|
+
function parsePort(value) {
|
|
958
|
+
if (!value) return null;
|
|
959
|
+
const parsed = Number.parseInt(value, 10);
|
|
960
|
+
if (!Number.isFinite(parsed) || parsed < 1 || parsed > 65535) return null;
|
|
961
|
+
return parsed;
|
|
962
|
+
}
|
|
963
|
+
function parsePortFromUrl(value) {
|
|
964
|
+
if (!value) return null;
|
|
965
|
+
try {
|
|
966
|
+
const parsed = new URL(value).port;
|
|
967
|
+
return parsePort(parsed);
|
|
968
|
+
} catch {
|
|
969
|
+
return null;
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
function collectPrimePorts() {
|
|
973
|
+
const result = [];
|
|
974
|
+
const seen = /* @__PURE__ */ new Set();
|
|
975
|
+
const add = (port) => {
|
|
976
|
+
if (port && !seen.has(port)) {
|
|
977
|
+
seen.add(port);
|
|
978
|
+
result.push(port);
|
|
979
|
+
}
|
|
980
|
+
};
|
|
981
|
+
for (const key of PRIME_PORT_ENV_KEYS) {
|
|
982
|
+
add(parsePort(process.env[key]));
|
|
983
|
+
}
|
|
984
|
+
for (const key of PRIME_URL_ENV_KEYS) {
|
|
985
|
+
add(parsePortFromUrl(process.env[key]));
|
|
986
|
+
}
|
|
987
|
+
return result;
|
|
988
|
+
}
|
|
989
|
+
function isPortListening(port) {
|
|
990
|
+
return new Promise((resolve2) => {
|
|
991
|
+
const socket = net.connect({ host: "localhost", port });
|
|
992
|
+
let settled = false;
|
|
993
|
+
const finish = (listening) => {
|
|
994
|
+
if (settled) return;
|
|
995
|
+
settled = true;
|
|
996
|
+
socket.destroy();
|
|
997
|
+
resolve2(listening);
|
|
998
|
+
};
|
|
999
|
+
socket.once("connect", () => finish(true));
|
|
1000
|
+
socket.once("error", () => finish(false));
|
|
1001
|
+
socket.setTimeout(PORT_CHECK_TIMEOUT_MS, () => finish(false));
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
async function invokeWithRetry(handler, request, options) {
|
|
774
1005
|
let elapsed = 0;
|
|
775
|
-
let
|
|
776
|
-
while (
|
|
1006
|
+
let delay = DEV_RETRY_INITIAL_DELAY_MS;
|
|
1007
|
+
while (true) {
|
|
777
1008
|
try {
|
|
778
|
-
await
|
|
779
|
-
|
|
780
|
-
consumerGroup,
|
|
781
|
-
messageId,
|
|
782
|
-
visibilityTimeoutSeconds: 0
|
|
783
|
-
});
|
|
784
|
-
return true;
|
|
1009
|
+
await handleCallback(handler, request, options);
|
|
1010
|
+
return;
|
|
785
1011
|
} catch (error) {
|
|
786
|
-
if (error
|
|
787
|
-
await new Promise((
|
|
788
|
-
elapsed +=
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
1012
|
+
if (isMessageNotFoundError(error) && elapsed < DEV_RETRY_MAX_WAIT_MS) {
|
|
1013
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
1014
|
+
elapsed += delay;
|
|
1015
|
+
delay = Math.min(
|
|
1016
|
+
delay * DEV_RETRY_BACKOFF,
|
|
1017
|
+
DEV_RETRY_MAX_WAIT_MS - elapsed
|
|
792
1018
|
);
|
|
793
1019
|
continue;
|
|
794
1020
|
}
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
1021
|
+
throw error;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
function filePathToUrlPath(filePath) {
|
|
1026
|
+
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)$/, "");
|
|
1027
|
+
if (!urlPath.startsWith("/")) {
|
|
1028
|
+
urlPath = "/" + urlPath;
|
|
1029
|
+
}
|
|
1030
|
+
return urlPath;
|
|
1031
|
+
}
|
|
1032
|
+
async function ensureHandlersLoaded(topicName, options = {}) {
|
|
1033
|
+
const diagnostics = {
|
|
1034
|
+
triedPorts: collectPrimePorts(),
|
|
1035
|
+
listeningPorts: [],
|
|
1036
|
+
unavailablePorts: [],
|
|
1037
|
+
importFailures: [],
|
|
1038
|
+
primeFailures: []
|
|
1039
|
+
};
|
|
1040
|
+
const matchingRoutes = findMatchingRoutes(topicName);
|
|
1041
|
+
if (matchingRoutes.length === 0) return diagnostics;
|
|
1042
|
+
const shouldRefreshRegistered = options.refreshRegistered === true;
|
|
1043
|
+
for (const port of diagnostics.triedPorts) {
|
|
1044
|
+
if (await isPortListening(port)) {
|
|
1045
|
+
diagnostics.listeningPorts.push(port);
|
|
1046
|
+
} else {
|
|
1047
|
+
diagnostics.unavailablePorts.push(port);
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
for (const route of matchingRoutes) {
|
|
1051
|
+
const alreadyRegistered = isHandlerRegistered(topicName, route.consumer);
|
|
1052
|
+
if (alreadyRegistered && !shouldRefreshRegistered) {
|
|
1053
|
+
continue;
|
|
1054
|
+
}
|
|
1055
|
+
if (!alreadyRegistered) {
|
|
1056
|
+
const absolutePath = path.resolve(process.cwd(), route.filePath);
|
|
1057
|
+
try {
|
|
1058
|
+
await import(absolutePath);
|
|
1059
|
+
} catch (error) {
|
|
1060
|
+
diagnostics.importFailures.push({
|
|
1061
|
+
filePath: route.filePath,
|
|
1062
|
+
reason: formatErrorReason(error)
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
if (isHandlerRegistered(topicName, route.consumer)) continue;
|
|
1066
|
+
}
|
|
1067
|
+
for (const port of diagnostics.listeningPorts) {
|
|
1068
|
+
const url = `http://localhost:${port}${filePathToUrlPath(route.filePath)}`;
|
|
1069
|
+
try {
|
|
1070
|
+
const response = await fetch(url, {
|
|
1071
|
+
method: "POST",
|
|
1072
|
+
headers: {
|
|
1073
|
+
"x-vercel-queue-prime": "1",
|
|
1074
|
+
"x-vercel-queue-prime-file": route.filePath
|
|
1075
|
+
}
|
|
1076
|
+
});
|
|
1077
|
+
try {
|
|
1078
|
+
await response.text();
|
|
1079
|
+
} catch {
|
|
1080
|
+
}
|
|
1081
|
+
if (isHandlerRegistered(topicName, route.consumer)) {
|
|
1082
|
+
break;
|
|
1083
|
+
}
|
|
1084
|
+
diagnostics.primeFailures.push({
|
|
1085
|
+
filePath: route.filePath,
|
|
1086
|
+
url,
|
|
1087
|
+
reason: `HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}`.trim()
|
|
1088
|
+
});
|
|
1089
|
+
} catch (error) {
|
|
1090
|
+
diagnostics.primeFailures.push({
|
|
1091
|
+
filePath: route.filePath,
|
|
1092
|
+
url,
|
|
1093
|
+
reason: formatErrorReason(error)
|
|
1094
|
+
});
|
|
800
1095
|
}
|
|
801
|
-
console.error(
|
|
802
|
-
`[Dev Mode] Error polling for message visibility: topic="${topicName}" messageId="${messageId}"`,
|
|
803
|
-
error
|
|
804
|
-
);
|
|
805
|
-
return false;
|
|
806
1096
|
}
|
|
807
1097
|
}
|
|
808
|
-
|
|
809
|
-
|
|
1098
|
+
return diagnostics;
|
|
1099
|
+
}
|
|
1100
|
+
function buildNoHandlerWarning(topicName, routes, diagnostics) {
|
|
1101
|
+
const files = routes.map((r) => r.filePath);
|
|
1102
|
+
const suggestedPort = diagnostics.listeningPorts[0] ?? diagnostics.triedPorts[0];
|
|
1103
|
+
const suggestedUrls = suggestedPort ? routes.map(
|
|
1104
|
+
(r) => `http://localhost:${suggestedPort}${filePathToUrlPath(r.filePath)}`
|
|
1105
|
+
) : [];
|
|
1106
|
+
let portSummary;
|
|
1107
|
+
if (diagnostics.triedPorts.length === 0) {
|
|
1108
|
+
portSummary = "No local dev port detected from env. Set PORT (or NEXT_PORT/NUXT_PORT/VITE_PORT).";
|
|
1109
|
+
} else if (diagnostics.listeningPorts.length === 0) {
|
|
1110
|
+
portSummary = `Detected env ports: [${diagnostics.triedPorts.join(", ")}], but none are listening.`;
|
|
1111
|
+
} else {
|
|
1112
|
+
const unavailable = diagnostics.unavailablePorts.length > 0 ? ` Not listening: [${diagnostics.unavailablePorts.join(", ")}].` : "";
|
|
1113
|
+
portSummary = `Detected env ports: [${diagnostics.triedPorts.join(", ")}]. Listening: [${diagnostics.listeningPorts.join(", ")}].` + unavailable;
|
|
1114
|
+
}
|
|
1115
|
+
const importSummary = diagnostics.importFailures.length > 0 ? `
|
|
1116
|
+
Import failures: ` + diagnostics.importFailures.slice(0, 2).map((f) => `${f.filePath} (${f.reason})`).join("; ") : "";
|
|
1117
|
+
const primeSummary = diagnostics.primeFailures.length > 0 ? `
|
|
1118
|
+
Prime failures: ` + diagnostics.primeFailures.slice(0, 3).map((f) => `${f.url} (${f.reason})`).join("; ") : "";
|
|
1119
|
+
return `[Dev Mode] No registered handler for topic "${topicName}". vercel.json maps this topic to [${files.join(", ")}] but auto-loading failed.
|
|
1120
|
+
${portSummary}${importSummary}${primeSummary}
|
|
1121
|
+
Ensure your dev server is running, set PORT if needed, and confirm mapped route files call handleCallback()/handleNodeCallback() at module scope.
|
|
1122
|
+
` + (suggestedUrls.length > 0 ? `Try opening: ${suggestedUrls.join(" or ")}` : "Set PORT (or NEXT_PORT/NUXT_PORT/VITE_PORT) and try sending again.");
|
|
1123
|
+
}
|
|
1124
|
+
function isHandlerRegistered(topicName, consumerGroup) {
|
|
1125
|
+
return lookupHandlers(topicName).some(
|
|
1126
|
+
(h) => h.consumerGroup === consumerGroup
|
|
810
1127
|
);
|
|
811
|
-
return false;
|
|
812
1128
|
}
|
|
813
|
-
function
|
|
1129
|
+
function invokeDevHandlers(topicName, messageId, region, delaySeconds) {
|
|
814
1130
|
if (delaySeconds && delaySeconds > 0) {
|
|
815
1131
|
console.log(
|
|
816
1132
|
`[Dev Mode] Message sent with delay: topic="${topicName}" messageId="${messageId}" delay=${delaySeconds}s`
|
|
817
1133
|
);
|
|
818
1134
|
setTimeout(() => {
|
|
819
|
-
|
|
1135
|
+
invokeDevHandlers(topicName, messageId, region);
|
|
820
1136
|
}, delaySeconds * 1e3);
|
|
821
1137
|
return;
|
|
822
1138
|
}
|
|
823
1139
|
console.log(
|
|
824
1140
|
`[Dev Mode] Message sent: topic="${topicName}" messageId="${messageId}"`
|
|
825
1141
|
);
|
|
826
|
-
const matchingRoutes = findMatchingRoutes(topicName);
|
|
827
|
-
if (matchingRoutes.length === 0) {
|
|
828
|
-
console.log(
|
|
829
|
-
`[Dev Mode] No matching routes in vercel.json for topic "${topicName}"`
|
|
830
|
-
);
|
|
831
|
-
return;
|
|
832
|
-
}
|
|
833
|
-
const consumerGroups = matchingRoutes.map((r) => r.consumer);
|
|
834
|
-
console.log(
|
|
835
|
-
`[Dev Mode] Scheduling callbacks for topic="${topicName}" messageId="${messageId}" \u2192 consumers: [${consumerGroups.join(", ")}]`
|
|
836
|
-
);
|
|
837
1142
|
(async () => {
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
);
|
|
1143
|
+
let handlers = lookupHandlers(topicName);
|
|
1144
|
+
let diagnostics = null;
|
|
1145
|
+
if (handlers.length > 0) {
|
|
1146
|
+
await ensureHandlersLoaded(topicName, { refreshRegistered: true });
|
|
1147
|
+
handlers = lookupHandlers(topicName);
|
|
1148
|
+
} else {
|
|
1149
|
+
diagnostics = await ensureHandlersLoaded(topicName);
|
|
1150
|
+
handlers = lookupHandlers(topicName);
|
|
1151
|
+
}
|
|
1152
|
+
if (handlers.length === 0) {
|
|
1153
|
+
const matchingRoutes = findMatchingRoutes(topicName);
|
|
1154
|
+
if (matchingRoutes.length > 0) {
|
|
1155
|
+
const safeDiagnostics = diagnostics ?? {
|
|
1156
|
+
triedPorts: collectPrimePorts(),
|
|
1157
|
+
listeningPorts: [],
|
|
1158
|
+
unavailablePorts: [],
|
|
1159
|
+
importFailures: [],
|
|
1160
|
+
primeFailures: []
|
|
1161
|
+
};
|
|
1162
|
+
console.warn(
|
|
1163
|
+
buildNoHandlerWarning(topicName, matchingRoutes, safeDiagnostics)
|
|
1164
|
+
);
|
|
1165
|
+
} else {
|
|
1166
|
+
console.warn(
|
|
1167
|
+
`[Dev Mode] No registered handler for topic "${topicName}".
|
|
1168
|
+
Ensure vercel.json has a matching experimentalTriggers entry and the route file calls handleCallback().`
|
|
1169
|
+
);
|
|
1170
|
+
}
|
|
849
1171
|
return;
|
|
850
1172
|
}
|
|
851
|
-
const
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
1173
|
+
const consumerGroups = handlers.map((h) => h.consumerGroup);
|
|
1174
|
+
console.log(
|
|
1175
|
+
`[Dev Mode] Invoking handlers for topic="${topicName}" messageId="${messageId}" \u2192 consumers: [${consumerGroups.join(", ")}]`
|
|
1176
|
+
);
|
|
1177
|
+
for (const entry of handlers) {
|
|
1178
|
+
const request = {
|
|
1179
|
+
queueName: topicName,
|
|
1180
|
+
consumerGroup: entry.consumerGroup,
|
|
1181
|
+
messageId,
|
|
1182
|
+
region
|
|
1183
|
+
};
|
|
1184
|
+
const callbackOptions = {
|
|
1185
|
+
client: entry.client,
|
|
1186
|
+
visibilityTimeoutSeconds: entry.options?.visibilityTimeoutSeconds,
|
|
1187
|
+
retry: entry.options?.retry
|
|
1188
|
+
};
|
|
858
1189
|
try {
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
"ce-vqsqueuename": topicName,
|
|
864
|
-
"ce-vqsconsumergroup": route.consumer,
|
|
865
|
-
"ce-vqsmessageid": messageId,
|
|
866
|
-
"ce-vqsregion": region
|
|
867
|
-
}
|
|
868
|
-
});
|
|
869
|
-
if (response.ok) {
|
|
870
|
-
try {
|
|
871
|
-
const responseData = await response.json();
|
|
872
|
-
if (responseData.status === "success") {
|
|
873
|
-
console.log(
|
|
874
|
-
`[Dev Mode] \u2713 Message processed successfully: topic="${topicName}" consumer="${route.consumer}" messageId="${messageId}"`
|
|
875
|
-
);
|
|
876
|
-
}
|
|
877
|
-
} catch {
|
|
878
|
-
console.warn(
|
|
879
|
-
`[Dev Mode] Handler returned OK but response was not JSON: topic="${topicName}" consumer="${route.consumer}"`
|
|
880
|
-
);
|
|
881
|
-
}
|
|
882
|
-
} else {
|
|
883
|
-
try {
|
|
884
|
-
const errorData = await response.json();
|
|
885
|
-
console.error(
|
|
886
|
-
`[Dev Mode] \u2717 Handler failed: topic="${topicName}" consumer="${route.consumer}" messageId="${messageId}" error="${errorData.error || response.statusText}"`
|
|
887
|
-
);
|
|
888
|
-
} catch {
|
|
889
|
-
console.error(
|
|
890
|
-
`[Dev Mode] \u2717 Handler failed: topic="${topicName}" consumer="${route.consumer}" messageId="${messageId}" status=${response.status}`
|
|
891
|
-
);
|
|
892
|
-
}
|
|
893
|
-
}
|
|
1190
|
+
await invokeWithRetry(entry.handler, request, callbackOptions);
|
|
1191
|
+
console.log(
|
|
1192
|
+
`[Dev Mode] \u2713 Message processed: topic="${topicName}" consumer="${entry.consumerGroup}" messageId="${messageId}"`
|
|
1193
|
+
);
|
|
894
1194
|
} catch (error) {
|
|
895
1195
|
console.error(
|
|
896
|
-
`[Dev Mode] \u2717
|
|
1196
|
+
`[Dev Mode] \u2717 Handler failed: topic="${topicName}" consumer="${entry.consumerGroup}" messageId="${messageId}"`,
|
|
897
1197
|
error
|
|
898
1198
|
);
|
|
899
1199
|
}
|
|
900
1200
|
}
|
|
901
1201
|
})();
|
|
902
1202
|
}
|
|
903
|
-
function
|
|
1203
|
+
function clearDevState() {
|
|
904
1204
|
const g = globalThis;
|
|
905
1205
|
delete g[ROUTE_MAPPINGS_KEY];
|
|
1206
|
+
delete g[HANDLER_REGISTRY_KEY];
|
|
906
1207
|
}
|
|
907
1208
|
if (process.env.NODE_ENV === "test" || process.env.VITEST) {
|
|
908
|
-
globalThis.
|
|
1209
|
+
globalThis.__clearDevState = clearDevState;
|
|
909
1210
|
}
|
|
910
1211
|
|
|
911
1212
|
// src/oidc.ts
|
|
@@ -1080,7 +1381,7 @@ var ApiClient = class _ApiClient {
|
|
|
1080
1381
|
}
|
|
1081
1382
|
console.debug("[VQS Debug] Request:", JSON.stringify(logData, null, 2));
|
|
1082
1383
|
}
|
|
1083
|
-
init.headers.set("User-Agent", `@vercel/queue/${"0.1.
|
|
1384
|
+
init.headers.set("User-Agent", `@vercel/queue/${"0.1.1"}`);
|
|
1084
1385
|
init.headers.set("Vqs-Client-Ts", (/* @__PURE__ */ new Date()).toISOString());
|
|
1085
1386
|
const response = await fetch(url, init);
|
|
1086
1387
|
if (isDebugEnabled()) {
|
|
@@ -1416,12 +1717,36 @@ var ApiClient = class _ApiClient {
|
|
|
1416
1717
|
|
|
1417
1718
|
// src/client.ts
|
|
1418
1719
|
var apiClients = /* @__PURE__ */ new WeakMap();
|
|
1720
|
+
var API_CLIENT_KEY = Symbol.for("@vercel/queue.apiClient");
|
|
1721
|
+
function setApi(client, api) {
|
|
1722
|
+
apiClients.set(client, api);
|
|
1723
|
+
Object.defineProperty(client, API_CLIENT_KEY, {
|
|
1724
|
+
value: api,
|
|
1725
|
+
writable: false,
|
|
1726
|
+
enumerable: false,
|
|
1727
|
+
configurable: false
|
|
1728
|
+
});
|
|
1729
|
+
}
|
|
1419
1730
|
function getApi(client) {
|
|
1420
1731
|
const api = apiClients.get(client);
|
|
1421
|
-
if (
|
|
1422
|
-
|
|
1732
|
+
if (api) {
|
|
1733
|
+
return api;
|
|
1734
|
+
}
|
|
1735
|
+
const apiFromSymbol = client[API_CLIENT_KEY];
|
|
1736
|
+
if (typeof apiFromSymbol === "object" && apiFromSymbol !== null) {
|
|
1737
|
+
const resolvedApi = apiFromSymbol;
|
|
1738
|
+
apiClients.set(client, resolvedApi);
|
|
1739
|
+
return resolvedApi;
|
|
1740
|
+
}
|
|
1741
|
+
throw new Error(
|
|
1742
|
+
"QueueClient not initialized. This may happen when multiple bundled copies of @vercel/queue are loaded in local dev."
|
|
1743
|
+
);
|
|
1744
|
+
}
|
|
1745
|
+
function resolveCallbackRequest(input) {
|
|
1746
|
+
if ("request" in input) {
|
|
1747
|
+
return input.request;
|
|
1423
1748
|
}
|
|
1424
|
-
return
|
|
1749
|
+
return input;
|
|
1425
1750
|
}
|
|
1426
1751
|
function getApiClient(client) {
|
|
1427
1752
|
return getApi(client);
|
|
@@ -1439,7 +1764,7 @@ function resolveRegion(region) {
|
|
|
1439
1764
|
var QueueClient = class {
|
|
1440
1765
|
constructor(options = {}) {
|
|
1441
1766
|
const region = resolveRegion(options.region);
|
|
1442
|
-
|
|
1767
|
+
setApi(this, new ApiClient({ ...options, region }));
|
|
1443
1768
|
}
|
|
1444
1769
|
/**
|
|
1445
1770
|
* Send a message to a topic.
|
|
@@ -1467,7 +1792,7 @@ var QueueClient = class {
|
|
|
1467
1792
|
headers: options?.headers
|
|
1468
1793
|
});
|
|
1469
1794
|
if (result.messageId && isDevMode()) {
|
|
1470
|
-
|
|
1795
|
+
invokeDevHandlers(
|
|
1471
1796
|
topicName,
|
|
1472
1797
|
result.messageId,
|
|
1473
1798
|
api.getRegion(),
|
|
@@ -1493,10 +1818,26 @@ var QueueClient = class {
|
|
|
1493
1818
|
* @param options.visibilityTimeoutSeconds - Message lock duration (default: 300, max: 3600)
|
|
1494
1819
|
* @param options.retry - Called when the handler throws. Return `{ afterSeconds: N }` to
|
|
1495
1820
|
* reschedule the message for redelivery after N seconds.
|
|
1496
|
-
* @returns A `
|
|
1821
|
+
* @returns A route handler that accepts either `Request` or `{ request: Request }`
|
|
1497
1822
|
*/
|
|
1498
1823
|
handleCallback = (handler, options) => {
|
|
1499
|
-
|
|
1824
|
+
if (isDevMode()) {
|
|
1825
|
+
registerDevHandler(handler, this, options);
|
|
1826
|
+
}
|
|
1827
|
+
return async (requestOrEvent) => {
|
|
1828
|
+
const request = resolveCallbackRequest(requestOrEvent);
|
|
1829
|
+
if (isDevMode() && request.headers.get("x-vercel-queue-prime") === "1") {
|
|
1830
|
+
const primeFile = request.headers.get("x-vercel-queue-prime-file");
|
|
1831
|
+
if (primeFile) {
|
|
1832
|
+
registerDevHandler(
|
|
1833
|
+
handler,
|
|
1834
|
+
this,
|
|
1835
|
+
options,
|
|
1836
|
+
primeFile
|
|
1837
|
+
);
|
|
1838
|
+
}
|
|
1839
|
+
return Response.json({ status: "primed" });
|
|
1840
|
+
}
|
|
1500
1841
|
try {
|
|
1501
1842
|
const parsed = await parseCallback(request);
|
|
1502
1843
|
await handleCallback(handler, parsed, {
|
|
@@ -1538,11 +1879,29 @@ var QueueClient = class {
|
|
|
1538
1879
|
* @returns A `(req, res) => Promise<void>` route handler
|
|
1539
1880
|
*/
|
|
1540
1881
|
handleNodeCallback = (handler, options) => {
|
|
1882
|
+
if (isDevMode()) {
|
|
1883
|
+
registerDevHandler(handler, this, options);
|
|
1884
|
+
}
|
|
1541
1885
|
return async (req, res) => {
|
|
1542
1886
|
if (req.method !== "POST") {
|
|
1543
1887
|
res.status(200).end();
|
|
1544
1888
|
return;
|
|
1545
1889
|
}
|
|
1890
|
+
const primeHeader = req.headers["x-vercel-queue-prime"];
|
|
1891
|
+
if (isDevMode() && primeHeader === "1") {
|
|
1892
|
+
const primeFileHeader = req.headers["x-vercel-queue-prime-file"];
|
|
1893
|
+
const primeFile = Array.isArray(primeFileHeader) ? primeFileHeader[0] : primeFileHeader;
|
|
1894
|
+
if (primeFile) {
|
|
1895
|
+
registerDevHandler(
|
|
1896
|
+
handler,
|
|
1897
|
+
this,
|
|
1898
|
+
options,
|
|
1899
|
+
primeFile
|
|
1900
|
+
);
|
|
1901
|
+
}
|
|
1902
|
+
res.status(200).json({ status: "primed" });
|
|
1903
|
+
return;
|
|
1904
|
+
}
|
|
1546
1905
|
try {
|
|
1547
1906
|
const parsed = parseRawCallback(req.body, req.headers);
|
|
1548
1907
|
await handleCallback(handler, parsed, {
|
|
@@ -1564,7 +1923,7 @@ var QueueClient = class {
|
|
|
1564
1923
|
};
|
|
1565
1924
|
var PollingQueueClient = class {
|
|
1566
1925
|
constructor(options) {
|
|
1567
|
-
|
|
1926
|
+
setApi(this, new ApiClient(options));
|
|
1568
1927
|
}
|
|
1569
1928
|
/**
|
|
1570
1929
|
* Send a message to a topic.
|
|
@@ -1663,6 +2022,25 @@ var PollingQueueClient = class {
|
|
|
1663
2022
|
}
|
|
1664
2023
|
};
|
|
1665
2024
|
};
|
|
2025
|
+
|
|
2026
|
+
// src/default-client.ts
|
|
2027
|
+
var _defaultClient;
|
|
2028
|
+
function getDefaultClient() {
|
|
2029
|
+
if (!_defaultClient) {
|
|
2030
|
+
_defaultClient = new QueueClient();
|
|
2031
|
+
}
|
|
2032
|
+
return _defaultClient;
|
|
2033
|
+
}
|
|
2034
|
+
function resolveClient(region) {
|
|
2035
|
+
if (!region) return getDefaultClient();
|
|
2036
|
+
return new QueueClient({ region });
|
|
2037
|
+
}
|
|
2038
|
+
async function send(topicName, payload, options) {
|
|
2039
|
+
return resolveClient(options?.region).send(topicName, payload, options);
|
|
2040
|
+
}
|
|
2041
|
+
function handleCallback2(handler, options) {
|
|
2042
|
+
return getDefaultClient().handleCallback(handler, options);
|
|
2043
|
+
}
|
|
1666
2044
|
export {
|
|
1667
2045
|
BadRequestError,
|
|
1668
2046
|
BufferTransport,
|
|
@@ -1685,7 +2063,9 @@ export {
|
|
|
1685
2063
|
QueueEmptyError,
|
|
1686
2064
|
StreamTransport,
|
|
1687
2065
|
UnauthorizedError,
|
|
2066
|
+
handleCallback2 as handleCallback,
|
|
1688
2067
|
parseCallback,
|
|
1689
|
-
parseRawCallback
|
|
2068
|
+
parseRawCallback,
|
|
2069
|
+
send
|
|
1690
2070
|
};
|
|
1691
2071
|
//# sourceMappingURL=index.mjs.map
|