@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.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;
|
|
866
|
+
}
|
|
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);
|
|
768
947
|
}
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
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
|
|
@@ -1022,7 +1323,7 @@ var ApiClient = class _ApiClient {
|
|
|
1022
1323
|
return;
|
|
1023
1324
|
}
|
|
1024
1325
|
throw new Error(
|
|
1025
|
-
'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
|
|
1326
|
+
'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 })'
|
|
1026
1327
|
);
|
|
1027
1328
|
}
|
|
1028
1329
|
getSendDeploymentId() {
|
|
@@ -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.
|
|
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,23 +1717,61 @@ var ApiClient = class _ApiClient {
|
|
|
1416
1717
|
|
|
1417
1718
|
// src/client.ts
|
|
1418
1719
|
var apiClients = /* @__PURE__ */ new WeakMap();
|
|
1419
|
-
|
|
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
|
+
}
|
|
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;
|
|
1423
1740
|
}
|
|
1424
|
-
|
|
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;
|
|
1748
|
+
}
|
|
1749
|
+
return input;
|
|
1750
|
+
}
|
|
1751
|
+
function getApiClient(client) {
|
|
1752
|
+
return getApi(client);
|
|
1753
|
+
}
|
|
1754
|
+
var DEFAULT_REGION = "iad1";
|
|
1755
|
+
function resolveRegion(region) {
|
|
1756
|
+
if (region) return region;
|
|
1757
|
+
const fromEnv = process.env.VERCEL_REGION;
|
|
1758
|
+
if (fromEnv) return fromEnv;
|
|
1759
|
+
console.warn(
|
|
1760
|
+
`[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" })`
|
|
1761
|
+
);
|
|
1762
|
+
return DEFAULT_REGION;
|
|
1425
1763
|
}
|
|
1426
1764
|
var QueueClient = class {
|
|
1427
|
-
constructor(options) {
|
|
1428
|
-
|
|
1765
|
+
constructor(options = {}) {
|
|
1766
|
+
const region = resolveRegion(options.region);
|
|
1767
|
+
setApi(this, new ApiClient({ ...options, region }));
|
|
1429
1768
|
}
|
|
1430
1769
|
/**
|
|
1431
1770
|
* Send a message to a topic.
|
|
1432
1771
|
*
|
|
1433
1772
|
* This is an arrow function property so it can be destructured:
|
|
1434
1773
|
* ```typescript
|
|
1435
|
-
* const { send } = new QueueClient(
|
|
1774
|
+
* const { send } = new QueueClient();
|
|
1436
1775
|
* await send("my-topic", payload);
|
|
1437
1776
|
* ```
|
|
1438
1777
|
*
|
|
@@ -1443,7 +1782,7 @@ var QueueClient = class {
|
|
|
1443
1782
|
* the message for deferred processing (no ID available yet)
|
|
1444
1783
|
*/
|
|
1445
1784
|
send = async (topicName, payload, options) => {
|
|
1446
|
-
const api =
|
|
1785
|
+
const api = getApi(this);
|
|
1447
1786
|
const result = await api.sendMessage({
|
|
1448
1787
|
queueName: topicName,
|
|
1449
1788
|
payload,
|
|
@@ -1453,7 +1792,7 @@ var QueueClient = class {
|
|
|
1453
1792
|
headers: options?.headers
|
|
1454
1793
|
});
|
|
1455
1794
|
if (result.messageId && isDevMode()) {
|
|
1456
|
-
|
|
1795
|
+
invokeDevHandlers(
|
|
1457
1796
|
topicName,
|
|
1458
1797
|
result.messageId,
|
|
1459
1798
|
api.getRegion(),
|
|
@@ -1462,75 +1801,6 @@ var QueueClient = class {
|
|
|
1462
1801
|
}
|
|
1463
1802
|
return { messageId: result.messageId };
|
|
1464
1803
|
};
|
|
1465
|
-
/**
|
|
1466
|
-
* Receive and process messages from a topic.
|
|
1467
|
-
*
|
|
1468
|
-
* Each message is automatically locked, kept alive via periodic visibility
|
|
1469
|
-
* extensions during processing, and acknowledged upon successful handler completion.
|
|
1470
|
-
* The handler is not called when the queue is empty — check `result.ok` instead.
|
|
1471
|
-
*
|
|
1472
|
-
* This is an arrow function property so it can be destructured:
|
|
1473
|
-
* ```typescript
|
|
1474
|
-
* const { receive } = new QueueClient({ region: process.env.QUEUE_REGION! });
|
|
1475
|
-
* const result = await receive("my-topic", "my-group", handler);
|
|
1476
|
-
* if (!result.ok) console.log(result.reason);
|
|
1477
|
-
* ```
|
|
1478
|
-
*
|
|
1479
|
-
* @param topicName - Name of the topic (pattern: `[A-Za-z0-9_-]+`)
|
|
1480
|
-
* @param consumerGroup - Name of the consumer group (pattern: `[A-Za-z0-9_-]+`)
|
|
1481
|
-
* @param handler - Function to process each message payload and metadata.
|
|
1482
|
-
* Not called when the queue is empty.
|
|
1483
|
-
* @param options - Optional receive options (visibilityTimeoutSeconds, limit, or messageId)
|
|
1484
|
-
* @returns Discriminated result: `{ ok: true }` on success, `{ ok: false, reason }` otherwise
|
|
1485
|
-
*/
|
|
1486
|
-
receive = async (topicName, consumerGroup, handler, options) => {
|
|
1487
|
-
const api = getApiClient(this);
|
|
1488
|
-
const topic = new Topic(api, topicName);
|
|
1489
|
-
const visibilityTimeoutSeconds = options && "visibilityTimeoutSeconds" in options ? options.visibilityTimeoutSeconds : void 0;
|
|
1490
|
-
const consumer = topic.consumerGroup(
|
|
1491
|
-
consumerGroup,
|
|
1492
|
-
visibilityTimeoutSeconds !== void 0 ? { visibilityTimeoutSeconds } : {}
|
|
1493
|
-
);
|
|
1494
|
-
try {
|
|
1495
|
-
let count;
|
|
1496
|
-
const retry = options?.retry;
|
|
1497
|
-
if (options && "messageId" in options) {
|
|
1498
|
-
count = await consumer.consume(handler, {
|
|
1499
|
-
messageId: options.messageId,
|
|
1500
|
-
retry
|
|
1501
|
-
});
|
|
1502
|
-
} else {
|
|
1503
|
-
const limit = options && "limit" in options ? options.limit : void 0;
|
|
1504
|
-
count = await consumer.consume(handler, {
|
|
1505
|
-
...limit !== void 0 ? { limit } : {},
|
|
1506
|
-
retry
|
|
1507
|
-
});
|
|
1508
|
-
}
|
|
1509
|
-
if (count === 0) {
|
|
1510
|
-
return { ok: false, reason: "empty" };
|
|
1511
|
-
}
|
|
1512
|
-
return { ok: true };
|
|
1513
|
-
} catch (error) {
|
|
1514
|
-
if (options && "messageId" in options && error instanceof MessageNotFoundError) {
|
|
1515
|
-
return { ok: false, reason: "not_found", messageId: options.messageId };
|
|
1516
|
-
}
|
|
1517
|
-
if (options && "messageId" in options && error instanceof MessageNotAvailableError) {
|
|
1518
|
-
return {
|
|
1519
|
-
ok: false,
|
|
1520
|
-
reason: "not_available",
|
|
1521
|
-
messageId: options.messageId
|
|
1522
|
-
};
|
|
1523
|
-
}
|
|
1524
|
-
if (options && "messageId" in options && error instanceof MessageAlreadyProcessedError) {
|
|
1525
|
-
return {
|
|
1526
|
-
ok: false,
|
|
1527
|
-
reason: "already_processed",
|
|
1528
|
-
messageId: options.messageId
|
|
1529
|
-
};
|
|
1530
|
-
}
|
|
1531
|
-
throw error;
|
|
1532
|
-
}
|
|
1533
|
-
};
|
|
1534
1804
|
/**
|
|
1535
1805
|
* Create a Web API route handler for processing queue callback messages.
|
|
1536
1806
|
*
|
|
@@ -1539,7 +1809,7 @@ var QueueClient = class {
|
|
|
1539
1809
|
*
|
|
1540
1810
|
* This is an arrow function property so it can be destructured:
|
|
1541
1811
|
* ```typescript
|
|
1542
|
-
* const { handleCallback } = new QueueClient(
|
|
1812
|
+
* const { handleCallback } = new QueueClient();
|
|
1543
1813
|
* export const POST = handleCallback(handler);
|
|
1544
1814
|
* ```
|
|
1545
1815
|
*
|
|
@@ -1548,10 +1818,26 @@ var QueueClient = class {
|
|
|
1548
1818
|
* @param options.visibilityTimeoutSeconds - Message lock duration (default: 300, max: 3600)
|
|
1549
1819
|
* @param options.retry - Called when the handler throws. Return `{ afterSeconds: N }` to
|
|
1550
1820
|
* reschedule the message for redelivery after N seconds.
|
|
1551
|
-
* @returns A `
|
|
1821
|
+
* @returns A route handler that accepts either `Request` or `{ request: Request }`
|
|
1552
1822
|
*/
|
|
1553
1823
|
handleCallback = (handler, options) => {
|
|
1554
|
-
|
|
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
|
+
}
|
|
1555
1841
|
try {
|
|
1556
1842
|
const parsed = await parseCallback(request);
|
|
1557
1843
|
await handleCallback(handler, parsed, {
|
|
@@ -1581,7 +1867,7 @@ var QueueClient = class {
|
|
|
1581
1867
|
*
|
|
1582
1868
|
* This is an arrow function property so it can be destructured:
|
|
1583
1869
|
* ```typescript
|
|
1584
|
-
* const { handleNodeCallback } = new QueueClient(
|
|
1870
|
+
* const { handleNodeCallback } = new QueueClient();
|
|
1585
1871
|
* app.post("/api/queue", handleNodeCallback(handler));
|
|
1586
1872
|
* ```
|
|
1587
1873
|
*
|
|
@@ -1593,11 +1879,29 @@ var QueueClient = class {
|
|
|
1593
1879
|
* @returns A `(req, res) => Promise<void>` route handler
|
|
1594
1880
|
*/
|
|
1595
1881
|
handleNodeCallback = (handler, options) => {
|
|
1882
|
+
if (isDevMode()) {
|
|
1883
|
+
registerDevHandler(handler, this, options);
|
|
1884
|
+
}
|
|
1596
1885
|
return async (req, res) => {
|
|
1597
1886
|
if (req.method !== "POST") {
|
|
1598
1887
|
res.status(200).end();
|
|
1599
1888
|
return;
|
|
1600
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
|
+
}
|
|
1601
1905
|
try {
|
|
1602
1906
|
const parsed = parseRawCallback(req.body, req.headers);
|
|
1603
1907
|
await handleCallback(handler, parsed, {
|
|
@@ -1617,6 +1921,126 @@ var QueueClient = class {
|
|
|
1617
1921
|
};
|
|
1618
1922
|
};
|
|
1619
1923
|
};
|
|
1924
|
+
var PollingQueueClient = class {
|
|
1925
|
+
constructor(options) {
|
|
1926
|
+
setApi(this, new ApiClient(options));
|
|
1927
|
+
}
|
|
1928
|
+
/**
|
|
1929
|
+
* Send a message to a topic.
|
|
1930
|
+
*
|
|
1931
|
+
* This is an arrow function property so it can be destructured:
|
|
1932
|
+
* ```typescript
|
|
1933
|
+
* const { send } = new PollingQueueClient({ region: "iad1" });
|
|
1934
|
+
* await send("my-topic", payload);
|
|
1935
|
+
* ```
|
|
1936
|
+
*
|
|
1937
|
+
* @param topicName - Name of the topic (pattern: `[A-Za-z0-9_-]+`)
|
|
1938
|
+
* @param payload - The data to send (serialized via the configured transport)
|
|
1939
|
+
* @param options - Optional send options (idempotencyKey, retentionSeconds, delaySeconds, headers)
|
|
1940
|
+
* @returns `{ messageId }` — `messageId` is `null` when the server accepted
|
|
1941
|
+
* the message for deferred processing (no ID available yet)
|
|
1942
|
+
*/
|
|
1943
|
+
send = async (topicName, payload, options) => {
|
|
1944
|
+
const api = getApi(this);
|
|
1945
|
+
const result = await api.sendMessage({
|
|
1946
|
+
queueName: topicName,
|
|
1947
|
+
payload,
|
|
1948
|
+
idempotencyKey: options?.idempotencyKey,
|
|
1949
|
+
retentionSeconds: options?.retentionSeconds,
|
|
1950
|
+
delaySeconds: options?.delaySeconds,
|
|
1951
|
+
headers: options?.headers
|
|
1952
|
+
});
|
|
1953
|
+
return { messageId: result.messageId };
|
|
1954
|
+
};
|
|
1955
|
+
/**
|
|
1956
|
+
* Receive and process messages from a topic.
|
|
1957
|
+
*
|
|
1958
|
+
* Each message is automatically locked, kept alive via periodic visibility
|
|
1959
|
+
* extensions during processing, and acknowledged upon successful handler completion.
|
|
1960
|
+
* The handler is not called when the queue is empty — check `result.ok` instead.
|
|
1961
|
+
*
|
|
1962
|
+
* This is an arrow function property so it can be destructured:
|
|
1963
|
+
* ```typescript
|
|
1964
|
+
* const { receive } = new PollingQueueClient({ region: "iad1" });
|
|
1965
|
+
* const result = await receive("my-topic", "my-group", handler);
|
|
1966
|
+
* if (!result.ok) console.log(result.reason);
|
|
1967
|
+
* ```
|
|
1968
|
+
*
|
|
1969
|
+
* @param topicName - Name of the topic (pattern: `[A-Za-z0-9_-]+`)
|
|
1970
|
+
* @param consumerGroup - Name of the consumer group (pattern: `[A-Za-z0-9_-]+`)
|
|
1971
|
+
* @param handler - Function to process each message payload and metadata.
|
|
1972
|
+
* Not called when the queue is empty.
|
|
1973
|
+
* @param options - Optional receive options (visibilityTimeoutSeconds, limit, or messageId)
|
|
1974
|
+
* @returns Discriminated result: `{ ok: true }` on success, `{ ok: false, reason }` otherwise
|
|
1975
|
+
*/
|
|
1976
|
+
receive = async (topicName, consumerGroup, handler, options) => {
|
|
1977
|
+
const api = getApi(this);
|
|
1978
|
+
const topic = new Topic(api, topicName);
|
|
1979
|
+
const visibilityTimeoutSeconds = options && "visibilityTimeoutSeconds" in options ? options.visibilityTimeoutSeconds : void 0;
|
|
1980
|
+
const consumer = topic.consumerGroup(
|
|
1981
|
+
consumerGroup,
|
|
1982
|
+
visibilityTimeoutSeconds !== void 0 ? { visibilityTimeoutSeconds } : {}
|
|
1983
|
+
);
|
|
1984
|
+
try {
|
|
1985
|
+
let count;
|
|
1986
|
+
const retry = options?.retry;
|
|
1987
|
+
if (options && "messageId" in options) {
|
|
1988
|
+
count = await consumer.consume(handler, {
|
|
1989
|
+
messageId: options.messageId,
|
|
1990
|
+
retry
|
|
1991
|
+
});
|
|
1992
|
+
} else {
|
|
1993
|
+
const limit = options && "limit" in options ? options.limit : void 0;
|
|
1994
|
+
count = await consumer.consume(handler, {
|
|
1995
|
+
...limit !== void 0 ? { limit } : {},
|
|
1996
|
+
retry
|
|
1997
|
+
});
|
|
1998
|
+
}
|
|
1999
|
+
if (count === 0) {
|
|
2000
|
+
return { ok: false, reason: "empty" };
|
|
2001
|
+
}
|
|
2002
|
+
return { ok: true };
|
|
2003
|
+
} catch (error) {
|
|
2004
|
+
if (options && "messageId" in options && error instanceof MessageNotFoundError) {
|
|
2005
|
+
return { ok: false, reason: "not_found", messageId: options.messageId };
|
|
2006
|
+
}
|
|
2007
|
+
if (options && "messageId" in options && error instanceof MessageNotAvailableError) {
|
|
2008
|
+
return {
|
|
2009
|
+
ok: false,
|
|
2010
|
+
reason: "not_available",
|
|
2011
|
+
messageId: options.messageId
|
|
2012
|
+
};
|
|
2013
|
+
}
|
|
2014
|
+
if (options && "messageId" in options && error instanceof MessageAlreadyProcessedError) {
|
|
2015
|
+
return {
|
|
2016
|
+
ok: false,
|
|
2017
|
+
reason: "already_processed",
|
|
2018
|
+
messageId: options.messageId
|
|
2019
|
+
};
|
|
2020
|
+
}
|
|
2021
|
+
throw error;
|
|
2022
|
+
}
|
|
2023
|
+
};
|
|
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
|
+
}
|
|
1620
2044
|
export {
|
|
1621
2045
|
BadRequestError,
|
|
1622
2046
|
BufferTransport,
|
|
@@ -1634,11 +2058,14 @@ export {
|
|
|
1634
2058
|
MessageLockedError,
|
|
1635
2059
|
MessageNotAvailableError,
|
|
1636
2060
|
MessageNotFoundError,
|
|
2061
|
+
PollingQueueClient,
|
|
1637
2062
|
QueueClient,
|
|
1638
2063
|
QueueEmptyError,
|
|
1639
2064
|
StreamTransport,
|
|
1640
2065
|
UnauthorizedError,
|
|
2066
|
+
handleCallback2 as handleCallback,
|
|
1641
2067
|
parseCallback,
|
|
1642
|
-
parseRawCallback
|
|
2068
|
+
parseRawCallback,
|
|
2069
|
+
send
|
|
1643
2070
|
};
|
|
1644
2071
|
//# sourceMappingURL=index.mjs.map
|