@upstash/qstash 2.10.1 → 2.11.0

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/index.js CHANGED
@@ -490,17 +490,23 @@ function normalizeCursor(response) {
490
490
  const cursor = response.cursor;
491
491
  return { ...response, cursor: cursor || void 0 };
492
492
  }
493
+ function _processGlobal() {
494
+ const proc = globalThis["process"];
495
+ return proc;
496
+ }
493
497
  function getRuntime() {
494
- if (typeof process === "object" && typeof process.versions == "object" && process.versions.bun)
495
- return `bun@${process.versions.bun}`;
498
+ const proc = _processGlobal();
499
+ if (proc?.versions?.bun)
500
+ return `bun@${proc.versions.bun}`;
496
501
  if (typeof EdgeRuntime === "string")
497
502
  return "edge-light";
498
- else if (typeof process === "object" && typeof process.version === "string")
499
- return `node@${process.version}`;
503
+ if (typeof proc?.version === "string")
504
+ return `node@${proc.version}`;
500
505
  return "";
501
506
  }
502
507
  function getSafeEnvironment() {
503
- return typeof process === "undefined" ? {} : process.env;
508
+ const proc = _processGlobal();
509
+ return proc?.env ?? {};
504
510
  }
505
511
 
506
512
  // src/client/multi-region/utils.ts
@@ -544,12 +550,451 @@ function normalizeRegionHeader(region) {
544
550
  return void 0;
545
551
  }
546
552
 
553
+ // src/dev-server/constants.ts
554
+ var DEFAULT_DEV_PORT = 8080;
555
+ var DEV_CREDENTIALS = {
556
+ token: "eyJVc2VySUQiOiJkZWZhdWx0VXNlciIsIlBhc3N3b3JkIjoiZGVmYXVsdFBhc3N3b3JkIn0=",
557
+ currentSigningKey: "sig_7kYjw48mhY7kAjqNGcy6cr29RJ6r",
558
+ nextSigningKey: "sig_5ZB6DVzB1wjE8S6rZ7eenA8Pdnhs"
559
+ };
560
+ var GITHUB_RELEASES_URL = "https://api.github.com/repos/upstash/qstash-cli/releases/latest";
561
+ var BINARY_URL_BASE = "https://artifacts.upstash.com/qstash/versions";
562
+ var CONSOLE_URL = "https://console.upstash.com/qstash/local-mode-user";
563
+ var DEV_PREFIX = "\x1B[2m[QStash Dev]\x1B[0m";
564
+ var CLI_PREFIX = "\x1B[2m[QStash CLI]\x1B[0m";
565
+ var _n = (m) => `node:${m}`;
566
+ var importHttp = () => import(
567
+ /* webpackIgnore: true */
568
+ _n("http")
569
+ );
570
+ var importHttps = () => import(
571
+ /* webpackIgnore: true */
572
+ _n("https")
573
+ );
574
+ var importFs = () => import(
575
+ /* webpackIgnore: true */
576
+ _n("fs")
577
+ );
578
+ var importChildProcess = () => import(
579
+ /* webpackIgnore: true */
580
+ _n("child_process")
581
+ );
582
+ var importOs = () => import(
583
+ /* webpackIgnore: true */
584
+ _n("os")
585
+ );
586
+
587
+ // src/dev-server/http.ts
588
+ var HTTP_OK = 200;
589
+ var HTTP_MULTI_CHOICE = 300;
590
+ var nativeGet = async (url, headers, timeoutMs) => {
591
+ const parsedUrl = new URL(url);
592
+ const httpModule = parsedUrl.protocol === "https:" ? await importHttps() : await importHttp();
593
+ return new Promise((resolve, reject) => {
594
+ const request = httpModule.get(url, { headers }, (response) => {
595
+ const chunks = [];
596
+ response.on("data", (chunk) => chunks.push(chunk));
597
+ response.on("end", () => {
598
+ const statusCode = response.statusCode ?? 0;
599
+ resolve({
600
+ ok: statusCode >= HTTP_OK && statusCode < HTTP_MULTI_CHOICE,
601
+ statusCode,
602
+ body: Buffer.concat(chunks)
603
+ });
604
+ });
605
+ response.on("error", reject);
606
+ });
607
+ if (timeoutMs) {
608
+ request.setTimeout(timeoutMs, () => {
609
+ request.destroy(new Error("Request timed out"));
610
+ });
611
+ }
612
+ request.on("error", reject);
613
+ });
614
+ };
615
+
616
+ // src/dev-server/health.ts
617
+ var HEALTH_CHECK_TIMEOUT_MS = 2e3;
618
+ var isDevServerRunning = async (baseUrl) => {
619
+ try {
620
+ const { ok: ok4, body } = await nativeGet(
621
+ `${baseUrl}/v2/keys`,
622
+ { Authorization: `Bearer ${DEV_CREDENTIALS.token}` },
623
+ HEALTH_CHECK_TIMEOUT_MS
624
+ );
625
+ if (!ok4)
626
+ return false;
627
+ const data = JSON.parse(body.toString());
628
+ return data.current === DEV_CREDENTIALS.currentSigningKey && data.next === DEV_CREDENTIALS.nextSigningKey;
629
+ } catch {
630
+ return false;
631
+ }
632
+ };
633
+ var _didLogUnreachable = false;
634
+ var checkDevServerReachable = async (baseUrl, runtime) => {
635
+ if (await pingEdge(baseUrl))
636
+ return;
637
+ if (!_didLogUnreachable) {
638
+ console.error(unreachableMessage(baseUrl, runtime));
639
+ _didLogUnreachable = true;
640
+ }
641
+ throw new Error(`${DEV_PREFIX} dev server unreachable at ${baseUrl}`);
642
+ };
643
+ var pingEdge = async (baseUrl) => {
644
+ try {
645
+ const controller = new AbortController();
646
+ const timeout = setTimeout(() => controller.abort(), HEALTH_CHECK_TIMEOUT_MS);
647
+ const response = await fetch(`${baseUrl}/v2/keys`, {
648
+ headers: { Authorization: `Bearer ${DEV_CREDENTIALS.token}` },
649
+ signal: controller.signal
650
+ });
651
+ clearTimeout(timeout);
652
+ return response.ok;
653
+ } catch {
654
+ return false;
655
+ }
656
+ };
657
+ var unreachableMessage = (baseUrl, runtime) => {
658
+ const port = new URL(baseUrl).port;
659
+ const manualStartCmd = `npx @upstash/qstash-cli dev --port ${port}`;
660
+ const header = `
661
+ ${DEV_PREFIX} The dev server is not running at ${baseUrl}.
662
+
663
+ `;
664
+ if (runtime === "cloudflare-workers") {
665
+ return header + `Cloudflare Workers cannot start the dev server automatically.
666
+ Start it manually before running wrangler dev:
667
+
668
+ ${manualStartCmd}
669
+ `;
670
+ }
671
+ return header + `Edge runtimes cannot start the dev server automatically.
672
+ Either:
673
+ 1. Add the instrumentation hook to start it with your app:
674
+
675
+ // instrumentation.ts
676
+ import { registerQStashDev } from "@upstash/qstash/nextjs";
677
+ export async function register() { await registerQStashDev(); }
678
+
679
+ 2. Or start it manually:
680
+
681
+ ${manualStartCmd}
682
+ `;
683
+ };
684
+
685
+ // src/dev-server/binary.ts
686
+ var ensureBinary = async () => {
687
+ const fs = await importFs();
688
+ const os = await importOs();
689
+ const cacheDirectory = await findCacheDirectory();
690
+ const isWindows = os.platform() === "win32";
691
+ const binaryName = isWindows ? "qstash.exe" : "qstash";
692
+ const binaryPath = `${cacheDirectory}/${binaryName}`;
693
+ const versionFile = `${cacheDirectory}/.version`;
694
+ let version;
695
+ try {
696
+ version = await fetchLatestVersion();
697
+ } catch (error) {
698
+ if (fs.existsSync(binaryPath)) {
699
+ const cachedVersion = fs.existsSync(versionFile) ? fs.readFileSync(versionFile, "utf8").trim() : "unknown";
700
+ console.log(`${DEV_PREFIX} Offline, using local v${cachedVersion}`);
701
+ return binaryPath;
702
+ }
703
+ throw error;
704
+ }
705
+ return downloadBinary(version, cacheDirectory);
706
+ };
707
+ var fetchLatestVersion = async () => {
708
+ const { ok: ok4, statusCode, body } = await nativeGet(GITHUB_RELEASES_URL, {
709
+ Accept: "application/vnd.github.v3+json",
710
+ "User-Agent": "upstash-qstash-js"
711
+ });
712
+ if (!ok4) {
713
+ throw new Error(`[QStash Dev] Failed to fetch latest version: HTTP ${statusCode}`);
714
+ }
715
+ const data = JSON.parse(body.toString());
716
+ return data.tag_name.replace(/^v/, "");
717
+ };
718
+ var findCacheDirectory = async () => {
719
+ const fs = await importFs();
720
+ const os = await importOs();
721
+ const home = os.homedir();
722
+ const platform = os.platform();
723
+ let base;
724
+ if (platform === "darwin") {
725
+ base = `${home}/Library/Caches/upstash`;
726
+ } else if (platform === "win32") {
727
+ base = `${process.env.LOCALAPPDATA ?? `${home}/AppData/Local`}/upstash`;
728
+ } else {
729
+ base = `${home}/.cache/upstash`;
730
+ }
731
+ const cacheDirectory = `${base}/qstash-dev`;
732
+ await fs.promises.mkdir(cacheDirectory, { recursive: true });
733
+ return cacheDirectory;
734
+ };
735
+ var downloadBinary = async (version, cacheDirectory) => {
736
+ const fs = await importFs();
737
+ const childProcess = await importChildProcess();
738
+ const os = await importOs();
739
+ const osPlatform = os.platform();
740
+ const isWindows = osPlatform === "win32";
741
+ const platform = isWindows ? "windows" : osPlatform === "darwin" ? "darwin" : "linux";
742
+ const arch = os.arch() === "arm64" ? "arm64" : "amd64";
743
+ const archiveName = `qstash-server_${version}_${platform}_${arch}`;
744
+ const binaryName = isWindows ? "qstash.exe" : "qstash";
745
+ const binaryPath = `${cacheDirectory}/${binaryName}`;
746
+ const versionFile = `${cacheDirectory}/.version`;
747
+ if (fs.existsSync(binaryPath) && fs.existsSync(versionFile)) {
748
+ const cachedVersion = fs.readFileSync(versionFile, "utf8").trim();
749
+ if (cachedVersion === version) {
750
+ return binaryPath;
751
+ }
752
+ }
753
+ await fs.promises.rm(cacheDirectory, { recursive: true, force: true });
754
+ await fs.promises.mkdir(cacheDirectory, { recursive: true });
755
+ const extension = isWindows ? "zip" : "tar.gz";
756
+ const archiveUrl = `${BINARY_URL_BASE}/${version}/${archiveName}.${extension}`;
757
+ console.log(`${DEV_PREFIX} Downloading dev server v${version}...`);
758
+ const { ok: ok4, statusCode, body } = await nativeGet(archiveUrl);
759
+ if (!ok4) {
760
+ throw new Error(`[QStash Dev] Failed to download binary: HTTP ${statusCode}`);
761
+ }
762
+ const archivePath = `${cacheDirectory}/${archiveName}.${extension}`;
763
+ await fs.promises.writeFile(archivePath, new Uint8Array(body));
764
+ childProcess.execFileSync("tar", ["-xf", archivePath, "-C", cacheDirectory], {
765
+ stdio: "pipe"
766
+ });
767
+ if (!isWindows) {
768
+ const EXECUTABLE_PERMISSION = 493;
769
+ await fs.promises.chmod(binaryPath, EXECUTABLE_PERMISSION);
770
+ }
771
+ await fs.promises.writeFile(versionFile, version);
772
+ await fs.promises.unlink(archivePath).catch(() => {
773
+ });
774
+ return binaryPath;
775
+ };
776
+
777
+ // src/dev-server/process.ts
778
+ var STARTUP_TIMEOUT_MS = 3e4;
779
+ var _proc = () => {
780
+ return globalThis["process"] ?? {};
781
+ };
782
+ var spawnServer = async (binaryPath, port, onUnexpectedExit) => {
783
+ const childProcess = await importChildProcess();
784
+ const child = await new Promise((resolve, reject) => {
785
+ const child2 = childProcess.spawn(binaryPath, ["dev", "--port", String(port)], {
786
+ stdio: ["ignore", "pipe", "pipe"]
787
+ });
788
+ const timeout = setTimeout(() => {
789
+ child2.kill();
790
+ reject(new Error("[QStash Dev] Server failed to start within 30 seconds"));
791
+ }, STARTUP_TIMEOUT_MS);
792
+ let startupOutput = "";
793
+ let started = false;
794
+ const bufferLine = (line) => {
795
+ if (!started)
796
+ startupOutput += `${line}
797
+ `;
798
+ };
799
+ forwardWithPrefix(child2.stdout, _proc().stdout, (line) => {
800
+ bufferLine(line);
801
+ if (!started && /runn+ing( at|\.)/i.test(line)) {
802
+ clearTimeout(timeout);
803
+ started = true;
804
+ resolve(child2);
805
+ }
806
+ });
807
+ forwardWithPrefix(child2.stderr, _proc().stderr, bufferLine);
808
+ child2.on("error", (error) => {
809
+ clearTimeout(timeout);
810
+ reject(new Error(`[QStash Dev] Failed to start server: ${error.message}`));
811
+ });
812
+ child2.on("close", (code, _signal) => {
813
+ if (started) {
814
+ onUnexpectedExit?.();
815
+ return;
816
+ }
817
+ clearTimeout(timeout);
818
+ reject(new Error(formatStartupError(code, startupOutput)));
819
+ });
820
+ });
821
+ registerCleanup(child);
822
+ child.unref?.();
823
+ child.stdout?.unref?.();
824
+ child.stderr?.unref?.();
825
+ };
826
+ var formatStartupError = (code, startupOutput) => {
827
+ const cleaned = startupOutput.replaceAll(/\u001B\[[\d;]*m/g, "").replaceAll(/^\d{1,2}:\d{2}(AM|PM)\s+\w{3}\s+/gm, "").trim();
828
+ if (/address already in use/i.test(cleaned)) {
829
+ const match = /:(\d+)\s*$/.exec(cleaned);
830
+ const portHint = match ? ` on port ${match[1]}` : "";
831
+ return `[QStash Dev] Port already in use${portHint}. Set QSTASH_DEV_PORT to use a different port, or stop the process holding it.`;
832
+ }
833
+ const codeSuffix = code ? ` with code ${code}` : "";
834
+ const detail = cleaned ? `: ${cleaned}` : "";
835
+ return `[QStash Dev] Server exited unexpectedly${codeSuffix}${detail}`;
836
+ };
837
+ var forwardWithPrefix = (source, destination, onLine) => {
838
+ if (!source)
839
+ return;
840
+ let buffer = "";
841
+ const flushLine = (line) => {
842
+ destination?.write(`${CLI_PREFIX} ${line}
843
+ `);
844
+ onLine(line);
845
+ };
846
+ source.on("data", (data) => {
847
+ buffer += data.toString();
848
+ let newlineIndex = buffer.indexOf("\n");
849
+ while (newlineIndex !== -1) {
850
+ flushLine(buffer.slice(0, newlineIndex));
851
+ buffer = buffer.slice(newlineIndex + 1);
852
+ newlineIndex = buffer.indexOf("\n");
853
+ }
854
+ });
855
+ source.on("end", () => {
856
+ if (buffer.length > 0) {
857
+ flushLine(buffer);
858
+ buffer = "";
859
+ }
860
+ });
861
+ source.on("error", () => {
862
+ });
863
+ };
864
+ var currentChild;
865
+ var processHandlersRegistered = false;
866
+ var killCurrentChild = () => {
867
+ if (!currentChild)
868
+ return;
869
+ try {
870
+ currentChild.kill("SIGTERM");
871
+ } catch {
872
+ }
873
+ currentChild = void 0;
874
+ };
875
+ var registerCleanup = (child) => {
876
+ currentChild = child;
877
+ if (!processHandlersRegistered) {
878
+ processHandlersRegistered = true;
879
+ const proc = _proc();
880
+ proc.on?.("exit", killCurrentChild);
881
+ proc.on?.("SIGINT", () => {
882
+ killCurrentChild();
883
+ proc.exit?.(0);
884
+ });
885
+ proc.on?.("SIGTERM", () => {
886
+ killCurrentChild();
887
+ proc.exit?.(0);
888
+ });
889
+ }
890
+ };
891
+
892
+ // src/dev-server/index.ts
893
+ var _processGlobal2 = () => {
894
+ const proc = globalThis["process"];
895
+ return proc;
896
+ };
897
+ var devServerPromise;
898
+ var ensureDevelopmentServer = (env, devMode) => {
899
+ if (!shouldUseDevelopmentMode(devMode, env))
900
+ return Promise.resolve();
901
+ const procEnv = _processGlobal2()?.env;
902
+ if (procEnv?.NEXT_PHASE === "phase-production-build")
903
+ return Promise.resolve();
904
+ if (procEnv?.NODE_ENV === "production")
905
+ return Promise.resolve();
906
+ const runtime = getRuntime2();
907
+ if (runtime !== "nodejs") {
908
+ return checkDevServerReachable(getDevUrl(env), runtime);
909
+ }
910
+ if (!devServerPromise) {
911
+ devServerPromise = startPipeline(env).catch((error) => {
912
+ devServerPromise = void 0;
913
+ throw error;
914
+ });
915
+ }
916
+ return devServerPromise;
917
+ };
918
+ var startPipeline = async (env) => {
919
+ const baseUrl = getDevUrl(env);
920
+ const port = new URL(baseUrl).port;
921
+ const consoleLink = `\x1B[36m${CONSOLE_URL}?port=${port}\x1B[0m`;
922
+ if (await isDevServerRunning(baseUrl)) {
923
+ console.log(
924
+ `${DEV_PREFIX} Server already running at ${baseUrl}
925
+ ${DEV_PREFIX} Console: ${consoleLink}`
926
+ );
927
+ return;
928
+ }
929
+ const binaryPath = await ensureBinary();
930
+ await spawnServer(binaryPath, port, () => {
931
+ devServerPromise = void 0;
932
+ });
933
+ };
934
+ var shouldUseDevelopmentMode = (devMode, env) => {
935
+ if (devMode !== void 0)
936
+ return devMode;
937
+ const value = env?.QSTASH_DEV ?? getProcessEnvironment("QSTASH_DEV");
938
+ if (value === void 0 || value === "" || value === "false" || value === "0")
939
+ return false;
940
+ if (value === "true" || value === "1")
941
+ return true;
942
+ throw new Error(`[QStash Dev] Invalid value for QSTASH_DEV in environment: ${value}`);
943
+ };
944
+ var getDevelopmentCredentials = (env) => {
945
+ return {
946
+ ...DEV_CREDENTIALS,
947
+ baseUrl: getDevUrl(env)
948
+ };
949
+ };
950
+ var getDevUrl = (env) => {
951
+ const portString = env?.QSTASH_DEV_PORT ?? getProcessEnvironment("QSTASH_DEV_PORT");
952
+ let port = DEFAULT_DEV_PORT;
953
+ if (portString) {
954
+ const parsed = Number.parseInt(portString, 10);
955
+ if (!Number.isNaN(parsed) && parsed > 0) {
956
+ port = parsed;
957
+ }
958
+ }
959
+ return `http://127.0.0.1:${port}`;
960
+ };
961
+ var getRuntime2 = () => {
962
+ if (typeof navigator !== "undefined" && navigator.userAgent === "Cloudflare-Workers") {
963
+ return "cloudflare-workers";
964
+ }
965
+ const proc = _processGlobal2();
966
+ if (!proc) {
967
+ return "browser";
968
+ }
969
+ if (!proc.release?.name) {
970
+ return "edge";
971
+ }
972
+ return "nodejs";
973
+ };
974
+ var getProcessEnvironment = (key) => {
975
+ const proc = _processGlobal2();
976
+ return proc?.env ? proc.env[key] : void 0;
977
+ };
978
+
547
979
  // src/client/multi-region/incoming.ts
548
980
  var getReceiverSigningKeys = ({
549
981
  environment,
550
982
  regionFromHeader,
551
- config
983
+ config,
984
+ devMode
552
985
  }) => {
986
+ if (shouldUseDevelopmentMode(devMode, environment)) {
987
+ if (config?.currentSigningKey || config?.nextSigningKey) {
988
+ console.warn(
989
+ `${DEV_PREFIX} Dev mode is active. Ignoring signing keys from config. Set devMode: false to use your own keys.`
990
+ );
991
+ }
992
+ const developmentCreds = getDevelopmentCredentials(environment);
993
+ return {
994
+ currentSigningKey: developmentCreds.currentSigningKey,
995
+ nextSigningKey: developmentCreds.nextSigningKey
996
+ };
997
+ }
553
998
  if (config?.currentSigningKey && config.nextSigningKey) {
554
999
  return {
555
1000
  currentSigningKey: config.currentSigningKey,
@@ -594,8 +1039,21 @@ var getClientCredentials = (clientCredentialConfig) => {
594
1039
  };
595
1040
  var resolveCredentials = ({
596
1041
  environment,
597
- config
1042
+ config,
1043
+ devMode
598
1044
  }) => {
1045
+ if (shouldUseDevelopmentMode(devMode, environment)) {
1046
+ if (config?.baseUrl || config?.token) {
1047
+ console.warn(
1048
+ `${DEV_PREFIX} Dev mode is active. Ignoring baseUrl/token from config. Set devMode: false to use your own credentials.`
1049
+ );
1050
+ }
1051
+ const developmentCreds = getDevelopmentCredentials(environment);
1052
+ return {
1053
+ baseUrl: developmentCreds.baseUrl,
1054
+ token: developmentCreds.token
1055
+ };
1056
+ }
599
1057
  if (config?.baseUrl && config.token) {
600
1058
  return {
601
1059
  baseUrl: config.baseUrl,
@@ -648,9 +1106,11 @@ var SignatureError = class extends Error {
648
1106
  var Receiver = class {
649
1107
  currentSigningKey;
650
1108
  nextSigningKey;
1109
+ devMode;
651
1110
  constructor(config) {
652
1111
  this.currentSigningKey = config?.currentSigningKey;
653
1112
  this.nextSigningKey = config?.nextSigningKey;
1113
+ this.devMode = config?.devMode;
654
1114
  }
655
1115
  /**
656
1116
  * Verify the signature of a request.
@@ -669,7 +1129,8 @@ var Receiver = class {
669
1129
  config: {
670
1130
  currentSigningKey: this.currentSigningKey,
671
1131
  nextSigningKey: this.nextSigningKey
672
- }
1132
+ },
1133
+ devMode: this.devMode
673
1134
  });
674
1135
  if (!signingKeys) {
675
1136
  throw new Error(
@@ -935,12 +1396,14 @@ var HttpClient = class {
935
1396
  baseUrl;
936
1397
  authorization;
937
1398
  options;
1399
+ devMode;
938
1400
  retry;
939
1401
  headers;
940
1402
  telemetryHeaders;
941
1403
  constructor(config) {
942
1404
  this.baseUrl = config.baseUrl.replace(/\/$/, "");
943
1405
  this.authorization = config.authorization;
1406
+ this.devMode = config.devMode;
944
1407
  this.retry = // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
945
1408
  typeof config.retry === "boolean" && !config.retry ? {
946
1409
  attempts: 1,
@@ -953,6 +1416,7 @@ var HttpClient = class {
953
1416
  this.telemetryHeaders = config.telemetryHeaders;
954
1417
  }
955
1418
  async request(request) {
1419
+ await ensureDevelopmentServer(void 0, this.devMode);
956
1420
  const { response } = await this.requestWithBackoff(request);
957
1421
  if (request.parseResponseAsJson === false) {
958
1422
  return void 0;
@@ -960,6 +1424,7 @@ var HttpClient = class {
960
1424
  return await response.json();
961
1425
  }
962
1426
  async *requestStream(request) {
1427
+ await ensureDevelopmentServer(void 0, this.devMode);
963
1428
  const { response } = await this.requestWithBackoff(request);
964
1429
  if (!response.body) {
965
1430
  throw new Error("No response body");
@@ -1713,7 +2178,7 @@ var Workflow = class {
1713
2178
  };
1714
2179
 
1715
2180
  // version.ts
1716
- var VERSION = "2.10.1";
2181
+ var VERSION = "2.11.0";
1717
2182
 
1718
2183
  // src/client/client.ts
1719
2184
  var Client = class {
@@ -1721,7 +2186,14 @@ var Client = class {
1721
2186
  token;
1722
2187
  constructor(config) {
1723
2188
  const environment = getSafeEnvironment();
1724
- const { baseUrl, token } = getClientCredentials({ environment, config });
2189
+ const { baseUrl, token } = getClientCredentials({
2190
+ environment,
2191
+ config,
2192
+ devMode: config?.devMode
2193
+ });
2194
+ if (shouldUseDevelopmentMode(config?.devMode, environment)) {
2195
+ void ensureDevelopmentServer(environment, config?.devMode);
2196
+ }
1725
2197
  const enableTelemetry = environment.UPSTASH_DISABLE_TELEMETRY ? false : config?.enableTelemetry ?? true;
1726
2198
  const isCloudflare = typeof caches !== "undefined" && "default" in caches;
1727
2199
  const telemetryHeaders = new Headers(
@@ -1738,7 +2210,8 @@ var Client = class {
1738
2210
  //@ts-expect-error caused by undici and bunjs type overlap
1739
2211
  headers: prefixHeaders(new Headers(config?.headers ?? {})),
1740
2212
  //@ts-expect-error caused by undici and bunjs type overlap
1741
- telemetryHeaders
2213
+ telemetryHeaders,
2214
+ devMode: config?.devMode
1742
2215
  });
1743
2216
  this.token = token;
1744
2217
  }
package/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  resend
3
- } from "./chunk-Z37KJCW7.mjs";
3
+ } from "./chunk-7DSF3QVE.mjs";
4
4
  import {
5
5
  Chat,
6
6
  Client,
@@ -24,7 +24,7 @@ import {
24
24
  openai,
25
25
  setupAnalytics,
26
26
  upstash
27
- } from "./chunk-35B33QW3.mjs";
27
+ } from "./chunk-LB3C5PJP.mjs";
28
28
  export {
29
29
  Chat,
30
30
  Client,
package/nextjs.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { NextApiHandler } from 'next';
2
2
  import { NextRequest, NextFetchEvent } from 'next/server';
3
- import { ae as RouteFunction, af as WorkflowServeOptions } from './client-CsM1dTnz.mjs';
3
+ import { ae as RouteFunction, af as WorkflowServeOptions } from './client-CUioGZfg.mjs';
4
4
  import 'neverthrow';
5
5
 
6
6
  type VerifySignatureConfig = {
@@ -18,6 +18,17 @@ type VerifySignatureConfig = {
18
18
  * @default 0
19
19
  */
20
20
  clockTolerance?: number;
21
+ /**
22
+ * Use the local dev server signing keys. Pair this with
23
+ * `new Client({ devMode: true })` for end-to-end local development.
24
+ *
25
+ * - `true`: use dev server signing keys
26
+ * - `false`: never use dev server signing keys (ignores QSTASH_DEV env var)
27
+ * - `undefined`: check QSTASH_DEV env var
28
+ *
29
+ * @default undefined
30
+ */
31
+ devMode?: boolean;
21
32
  };
22
33
  declare function verifySignature(handler: NextApiHandler, config?: VerifySignatureConfig): NextApiHandler;
23
34
  declare function verifySignatureEdge(handler: (request: NextRequest, nfe?: NextFetchEvent) => Response | Promise<Response>, config?: VerifySignatureConfig): (request: NextRequest, nfe: NextFetchEvent) => Promise<Response>;
@@ -43,5 +54,25 @@ declare const serve: <TInitialPayload = unknown>(routeFunction: RouteFunction<TI
43
54
  * Migration Guide: https://upstash.com/docs/workflow/migration
44
55
  */
45
56
  declare const servePagesRouter: <TInitialPayload = unknown>(routeFunction: RouteFunction<TInitialPayload>, options?: Omit<WorkflowServeOptions<Response, TInitialPayload>, "onStepFinish">) => NextApiHandler;
57
+ /**
58
+ * Start the QStash dev server early during Next.js initialization.
59
+ *
60
+ * Call this inside your `instrumentation.ts` `register()` function so
61
+ * the dev server is ready before any request — including edge routes
62
+ * that cannot start it themselves.
63
+ *
64
+ * No-op in production, during `next build`, or in edge runtime.
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * // instrumentation.ts
69
+ * import { registerQStashDev } from "@upstash/qstash/nextjs";
70
+ *
71
+ * export function register() {
72
+ * registerQStashDev();
73
+ * }
74
+ * ```
75
+ */
76
+ declare function registerQStashDev(): Promise<void>;
46
77
 
47
- export { type VerifySignatureConfig, serve, servePagesRouter, verifySignature, verifySignatureAppRouter, verifySignatureEdge };
78
+ export { type VerifySignatureConfig, registerQStashDev, serve, servePagesRouter, verifySignature, verifySignatureAppRouter, verifySignatureEdge };