@upstash/qstash 2.10.1 → 2.11.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 CHANGED
@@ -127,6 +127,26 @@ const result = await client.publishJSON({
127
127
  });
128
128
  ```
129
129
 
130
+ ## Local Development
131
+
132
+ Set `QSTASH_DEV=true` in your environment variables, and the SDK will download and connect to a local QStash dev server automatically. No tokens or signing keys required: the SDK injects deterministic dev credentials.
133
+
134
+ ```bash .env
135
+ QSTASH_DEV=true
136
+ ```
137
+
138
+ Alternatively, pass `devMode: true` to explicitly enable dev mode:
139
+
140
+ ```ts
141
+ import { Client } from "@upstash/qstash";
142
+
143
+ const client = new Client({ devMode: true });
144
+ ```
145
+
146
+ The same flag works on the receiving side: pass `devMode: true` to `Receiver` or `verifySignature*` to verify signatures with the dev server's keys. Dev mode is automatically a no-op when `NODE_ENV=production` and in browser/edge runtimes.
147
+
148
+ See [Local Development](https://docs.upstash.com/qstash/howto/local-development) for the full walkthrough, including the `registerQStashDev()` helper for Next.js edge routes.
149
+
130
150
  ## Docs
131
151
 
132
152
  See [the documentation](https://docs.upstash.com/qstash) for details.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  BaseProvider
3
- } from "./chunk-35B33QW3.mjs";
3
+ } from "./chunk-T3Z5YUS4.mjs";
4
4
 
5
5
  // src/client/api/email.ts
6
6
  var EmailProvider = class extends BaseProvider {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  Receiver,
3
3
  serve
4
- } from "./chunk-35B33QW3.mjs";
4
+ } from "./chunk-T3Z5YUS4.mjs";
5
5
 
6
6
  // node_modules/defu/dist/defu.mjs
7
7
  function isPlainObject(value) {
@@ -256,6 +256,9 @@ var formatWorkflowError = (error) => {
256
256
 
257
257
  // src/client/utils.ts
258
258
  var DEFAULT_BULK_COUNT = 100;
259
+ function serializeLabel(label) {
260
+ return Array.isArray(label) ? label.join(",") : label;
261
+ }
259
262
  var isIgnoredHeader = (header) => {
260
263
  const lowerCaseHeader = header.toLowerCase();
261
264
  return lowerCaseHeader.startsWith("content-type") || lowerCaseHeader.startsWith("upstash-");
@@ -340,7 +343,7 @@ function processHeaders(request) {
340
343
  headers.set("Upstash-Flow-Control-Value", controlValue.join(", "));
341
344
  }
342
345
  if (request.label !== void 0) {
343
- headers.set("Upstash-Label", request.label);
346
+ headers.set("Upstash-Label", serializeLabel(request.label));
344
347
  }
345
348
  if (request.redact !== void 0) {
346
349
  const redactParts = [];
@@ -437,17 +440,23 @@ function normalizeCursor(response) {
437
440
  const cursor = response.cursor;
438
441
  return { ...response, cursor: cursor || void 0 };
439
442
  }
443
+ function _processGlobal() {
444
+ const proc = globalThis["process"];
445
+ return proc;
446
+ }
440
447
  function getRuntime() {
441
- if (typeof process === "object" && typeof process.versions == "object" && process.versions.bun)
442
- return `bun@${process.versions.bun}`;
448
+ const proc = _processGlobal();
449
+ if (proc?.versions?.bun)
450
+ return `bun@${proc.versions.bun}`;
443
451
  if (typeof EdgeRuntime === "string")
444
452
  return "edge-light";
445
- else if (typeof process === "object" && typeof process.version === "string")
446
- return `node@${process.version}`;
453
+ if (typeof proc?.version === "string")
454
+ return `node@${proc.version}`;
447
455
  return "";
448
456
  }
449
457
  function getSafeEnvironment() {
450
- return typeof process === "undefined" ? {} : process.env;
458
+ const proc = _processGlobal();
459
+ return proc?.env ?? {};
451
460
  }
452
461
 
453
462
  // src/client/multi-region/utils.ts
@@ -491,12 +500,453 @@ function normalizeRegionHeader(region) {
491
500
  return void 0;
492
501
  }
493
502
 
503
+ // src/dev-server/constants.ts
504
+ var DEFAULT_DEV_PORT = 8080;
505
+ var DEV_CREDENTIALS = {
506
+ token: "eyJVc2VySUQiOiJkZWZhdWx0VXNlciIsIlBhc3N3b3JkIjoiZGVmYXVsdFBhc3N3b3JkIn0=",
507
+ currentSigningKey: "sig_7kYjw48mhY7kAjqNGcy6cr29RJ6r",
508
+ nextSigningKey: "sig_5ZB6DVzB1wjE8S6rZ7eenA8Pdnhs"
509
+ };
510
+ var GITHUB_RELEASES_URL = "https://api.github.com/repos/upstash/qstash-cli/releases/latest";
511
+ var BINARY_URL_BASE = "https://artifacts.upstash.com/qstash/versions";
512
+ var CONSOLE_URL = "https://console.upstash.com/qstash/local-mode-user";
513
+ var DEV_PREFIX = "\x1B[2m[QStash Dev]\x1B[0m";
514
+ var CLI_PREFIX = "\x1B[2m[QStash CLI]\x1B[0m";
515
+ var _n = (m) => `node:${m}`;
516
+ var importHttp = () => import(
517
+ /* webpackIgnore: true */
518
+ _n("http")
519
+ );
520
+ var importHttps = () => import(
521
+ /* webpackIgnore: true */
522
+ _n("https")
523
+ );
524
+ var importFs = () => import(
525
+ /* webpackIgnore: true */
526
+ _n("fs")
527
+ );
528
+ var importChildProcess = () => import(
529
+ /* webpackIgnore: true */
530
+ _n("child_process")
531
+ );
532
+ var importOs = () => import(
533
+ /* webpackIgnore: true */
534
+ _n("os")
535
+ );
536
+
537
+ // src/dev-server/http.ts
538
+ var HTTP_OK = 200;
539
+ var HTTP_MULTI_CHOICE = 300;
540
+ var nativeGet = async (url, headers, timeoutMs) => {
541
+ const parsedUrl = new URL(url);
542
+ const httpModule = parsedUrl.protocol === "https:" ? await importHttps() : await importHttp();
543
+ return new Promise((resolve, reject) => {
544
+ const request = httpModule.get(url, { headers }, (response) => {
545
+ const chunks = [];
546
+ response.on("data", (chunk) => chunks.push(chunk));
547
+ response.on("end", () => {
548
+ const statusCode = response.statusCode ?? 0;
549
+ resolve({
550
+ ok: statusCode >= HTTP_OK && statusCode < HTTP_MULTI_CHOICE,
551
+ statusCode,
552
+ body: Buffer.concat(chunks)
553
+ });
554
+ });
555
+ response.on("error", reject);
556
+ });
557
+ if (timeoutMs) {
558
+ request.setTimeout(timeoutMs, () => {
559
+ request.destroy(new Error("Request timed out"));
560
+ });
561
+ }
562
+ request.on("error", reject);
563
+ });
564
+ };
565
+
566
+ // src/dev-server/health.ts
567
+ var HEALTH_CHECK_TIMEOUT_MS = 2e3;
568
+ var isDevServerRunning = async (baseUrl) => {
569
+ try {
570
+ const { ok: ok4, body } = await nativeGet(
571
+ `${baseUrl}/v2/keys`,
572
+ { Authorization: `Bearer ${DEV_CREDENTIALS.token}` },
573
+ HEALTH_CHECK_TIMEOUT_MS
574
+ );
575
+ if (!ok4)
576
+ return false;
577
+ const data = JSON.parse(body.toString());
578
+ return data.current === DEV_CREDENTIALS.currentSigningKey && data.next === DEV_CREDENTIALS.nextSigningKey;
579
+ } catch {
580
+ return false;
581
+ }
582
+ };
583
+ var _didLogUnreachable = false;
584
+ var checkDevServerReachable = async (baseUrl, runtime) => {
585
+ if (await pingEdge(baseUrl))
586
+ return;
587
+ if (!_didLogUnreachable) {
588
+ console.error(unreachableMessage(baseUrl, runtime));
589
+ _didLogUnreachable = true;
590
+ }
591
+ throw new Error(`${DEV_PREFIX} dev server unreachable at ${baseUrl}`);
592
+ };
593
+ var pingEdge = async (baseUrl) => {
594
+ try {
595
+ const controller = new AbortController();
596
+ const timeout = setTimeout(() => {
597
+ controller.abort();
598
+ }, HEALTH_CHECK_TIMEOUT_MS);
599
+ const response = await fetch(`${baseUrl}/v2/keys`, {
600
+ headers: { Authorization: `Bearer ${DEV_CREDENTIALS.token}` },
601
+ signal: controller.signal
602
+ });
603
+ clearTimeout(timeout);
604
+ return response.ok;
605
+ } catch {
606
+ return false;
607
+ }
608
+ };
609
+ var unreachableMessage = (baseUrl, runtime) => {
610
+ const port = new URL(baseUrl).port;
611
+ const manualStartCmd = `npx @upstash/qstash-cli dev --port ${port}`;
612
+ const header = `
613
+ ${DEV_PREFIX} The dev server is not running at ${baseUrl}.
614
+
615
+ `;
616
+ if (runtime === "cloudflare-workers") {
617
+ return header + `Cloudflare Workers cannot start the dev server automatically.
618
+ Start it manually before running wrangler dev:
619
+
620
+ ${manualStartCmd}
621
+ `;
622
+ }
623
+ return header + `Edge runtimes cannot start the dev server automatically.
624
+ Either:
625
+ 1. Add the instrumentation hook to start it with your app:
626
+
627
+ // instrumentation.ts
628
+ import { registerQStashDev } from "@upstash/qstash/nextjs";
629
+ export async function register() { await registerQStashDev(); }
630
+
631
+ 2. Or start it manually:
632
+
633
+ ${manualStartCmd}
634
+ `;
635
+ };
636
+
637
+ // src/dev-server/binary.ts
638
+ var ensureBinary = async () => {
639
+ const fs = await importFs();
640
+ const os = await importOs();
641
+ const cacheDirectory = await findCacheDirectory();
642
+ const isWindows = os.platform() === "win32";
643
+ const binaryName = isWindows ? "qstash.exe" : "qstash";
644
+ const binaryPath = `${cacheDirectory}/${binaryName}`;
645
+ const versionFile = `${cacheDirectory}/.version`;
646
+ let version;
647
+ try {
648
+ version = await fetchLatestVersion();
649
+ } catch (error) {
650
+ if (fs.existsSync(binaryPath)) {
651
+ const cachedVersion = fs.existsSync(versionFile) ? fs.readFileSync(versionFile, "utf8").trim() : "unknown";
652
+ console.log(`${DEV_PREFIX} Offline, using local v${cachedVersion}`);
653
+ return binaryPath;
654
+ }
655
+ throw error;
656
+ }
657
+ return downloadBinary(version, cacheDirectory);
658
+ };
659
+ var fetchLatestVersion = async () => {
660
+ const { ok: ok4, statusCode, body } = await nativeGet(GITHUB_RELEASES_URL, {
661
+ Accept: "application/vnd.github.v3+json",
662
+ "User-Agent": "upstash-qstash-js"
663
+ });
664
+ if (!ok4) {
665
+ throw new Error(`[QStash Dev] Failed to fetch latest version: HTTP ${statusCode}`);
666
+ }
667
+ const data = JSON.parse(body.toString());
668
+ return data.tag_name.replace(/^v/, "");
669
+ };
670
+ var findCacheDirectory = async () => {
671
+ const fs = await importFs();
672
+ const os = await importOs();
673
+ const home = os.homedir();
674
+ const platform = os.platform();
675
+ let base;
676
+ if (platform === "darwin") {
677
+ base = `${home}/Library/Caches/upstash`;
678
+ } else if (platform === "win32") {
679
+ base = `${process.env.LOCALAPPDATA ?? `${home}/AppData/Local`}/upstash`;
680
+ } else {
681
+ base = `${home}/.cache/upstash`;
682
+ }
683
+ const cacheDirectory = `${base}/qstash-dev`;
684
+ await fs.promises.mkdir(cacheDirectory, { recursive: true });
685
+ return cacheDirectory;
686
+ };
687
+ var downloadBinary = async (version, cacheDirectory) => {
688
+ const fs = await importFs();
689
+ const childProcess = await importChildProcess();
690
+ const os = await importOs();
691
+ const osPlatform = os.platform();
692
+ const isWindows = osPlatform === "win32";
693
+ const platform = isWindows ? "windows" : osPlatform === "darwin" ? "darwin" : "linux";
694
+ const arch = os.arch() === "arm64" ? "arm64" : "amd64";
695
+ const archiveName = `qstash-server_${version}_${platform}_${arch}`;
696
+ const binaryName = isWindows ? "qstash.exe" : "qstash";
697
+ const binaryPath = `${cacheDirectory}/${binaryName}`;
698
+ const versionFile = `${cacheDirectory}/.version`;
699
+ if (fs.existsSync(binaryPath) && fs.existsSync(versionFile)) {
700
+ const cachedVersion = fs.readFileSync(versionFile, "utf8").trim();
701
+ if (cachedVersion === version) {
702
+ return binaryPath;
703
+ }
704
+ }
705
+ await fs.promises.rm(cacheDirectory, { recursive: true, force: true });
706
+ await fs.promises.mkdir(cacheDirectory, { recursive: true });
707
+ const extension = isWindows ? "zip" : "tar.gz";
708
+ const archiveUrl = `${BINARY_URL_BASE}/${version}/${archiveName}.${extension}`;
709
+ console.log(`${DEV_PREFIX} Downloading dev server v${version}...`);
710
+ const { ok: ok4, statusCode, body } = await nativeGet(archiveUrl);
711
+ if (!ok4) {
712
+ throw new Error(`[QStash Dev] Failed to download binary: HTTP ${statusCode}`);
713
+ }
714
+ const archivePath = `${cacheDirectory}/${archiveName}.${extension}`;
715
+ await fs.promises.writeFile(archivePath, new Uint8Array(body));
716
+ childProcess.execFileSync("tar", ["-xf", archivePath, "-C", cacheDirectory], {
717
+ stdio: "pipe"
718
+ });
719
+ if (!isWindows) {
720
+ const EXECUTABLE_PERMISSION = 493;
721
+ await fs.promises.chmod(binaryPath, EXECUTABLE_PERMISSION);
722
+ }
723
+ await fs.promises.writeFile(versionFile, version);
724
+ await fs.promises.unlink(archivePath).catch(() => {
725
+ });
726
+ return binaryPath;
727
+ };
728
+
729
+ // src/dev-server/process.ts
730
+ var STARTUP_TIMEOUT_MS = 3e4;
731
+ var _proc = () => {
732
+ return globalThis["process"] ?? {};
733
+ };
734
+ var spawnServer = async (binaryPath, port, onUnexpectedExit) => {
735
+ const childProcess = await importChildProcess();
736
+ const child = await new Promise((resolve, reject) => {
737
+ const child2 = childProcess.spawn(binaryPath, ["dev", "--port", String(port)], {
738
+ stdio: ["ignore", "pipe", "pipe"]
739
+ });
740
+ const timeout = setTimeout(() => {
741
+ child2.kill();
742
+ reject(new Error("[QStash Dev] Server failed to start within 30 seconds"));
743
+ }, STARTUP_TIMEOUT_MS);
744
+ let startupOutput = "";
745
+ let started = false;
746
+ const bufferLine = (line) => {
747
+ if (!started)
748
+ startupOutput += `${line}
749
+ `;
750
+ };
751
+ forwardWithPrefix(child2.stdout, _proc().stdout, (line) => {
752
+ bufferLine(line);
753
+ if (!started && /runn+ing( at|\.)/i.test(line)) {
754
+ clearTimeout(timeout);
755
+ started = true;
756
+ resolve(child2);
757
+ }
758
+ });
759
+ forwardWithPrefix(child2.stderr, _proc().stderr, bufferLine);
760
+ child2.on("error", (error) => {
761
+ clearTimeout(timeout);
762
+ reject(new Error(`[QStash Dev] Failed to start server: ${error.message}`));
763
+ });
764
+ child2.on("close", (code, _signal) => {
765
+ if (started) {
766
+ onUnexpectedExit?.();
767
+ return;
768
+ }
769
+ clearTimeout(timeout);
770
+ reject(new Error(formatStartupError(code, startupOutput)));
771
+ });
772
+ });
773
+ registerCleanup(child);
774
+ child.unref?.();
775
+ child.stdout?.unref?.();
776
+ child.stderr?.unref?.();
777
+ };
778
+ var formatStartupError = (code, startupOutput) => {
779
+ const cleaned = startupOutput.replaceAll(/\u001B\[[\d;]*m/g, "").replaceAll(/^\d{1,2}:\d{2}(AM|PM)\s+\w{3}\s+/gm, "").trim();
780
+ if (/address already in use/i.test(cleaned)) {
781
+ const match = /:(\d+)\s*$/.exec(cleaned);
782
+ const portHint = match ? ` on port ${match[1]}` : "";
783
+ return `[QStash Dev] Port already in use${portHint}. Set QSTASH_DEV_PORT to use a different port, or stop the process holding it.`;
784
+ }
785
+ const codeSuffix = code ? ` with code ${code}` : "";
786
+ const detail = cleaned ? `: ${cleaned}` : "";
787
+ return `[QStash Dev] Server exited unexpectedly${codeSuffix}${detail}`;
788
+ };
789
+ var forwardWithPrefix = (source, destination, onLine) => {
790
+ if (!source)
791
+ return;
792
+ let buffer = "";
793
+ const flushLine = (line) => {
794
+ destination?.write(`${CLI_PREFIX} ${line}
795
+ `);
796
+ onLine(line);
797
+ };
798
+ source.on("data", (data) => {
799
+ buffer += data.toString();
800
+ let newlineIndex = buffer.indexOf("\n");
801
+ while (newlineIndex !== -1) {
802
+ flushLine(buffer.slice(0, newlineIndex));
803
+ buffer = buffer.slice(newlineIndex + 1);
804
+ newlineIndex = buffer.indexOf("\n");
805
+ }
806
+ });
807
+ source.on("end", () => {
808
+ if (buffer.length > 0) {
809
+ flushLine(buffer);
810
+ buffer = "";
811
+ }
812
+ });
813
+ source.on("error", () => {
814
+ });
815
+ };
816
+ var currentChild;
817
+ var processHandlersRegistered = false;
818
+ var killCurrentChild = () => {
819
+ if (!currentChild)
820
+ return;
821
+ try {
822
+ currentChild.kill("SIGTERM");
823
+ } catch {
824
+ }
825
+ currentChild = void 0;
826
+ };
827
+ var registerCleanup = (child) => {
828
+ currentChild = child;
829
+ if (!processHandlersRegistered) {
830
+ processHandlersRegistered = true;
831
+ const proc = _proc();
832
+ proc.on?.("exit", killCurrentChild);
833
+ proc.on?.("SIGINT", () => {
834
+ killCurrentChild();
835
+ proc.exit?.(0);
836
+ });
837
+ proc.on?.("SIGTERM", () => {
838
+ killCurrentChild();
839
+ proc.exit?.(0);
840
+ });
841
+ }
842
+ };
843
+
844
+ // src/dev-server/index.ts
845
+ var _processGlobal2 = () => {
846
+ const proc = globalThis["process"];
847
+ return proc;
848
+ };
849
+ var devServerPromise;
850
+ var ensureDevelopmentServer = (env, devMode) => {
851
+ if (!shouldUseDevelopmentMode(devMode, env))
852
+ return Promise.resolve();
853
+ const procEnv = _processGlobal2()?.env;
854
+ if (procEnv?.NEXT_PHASE === "phase-production-build")
855
+ return Promise.resolve();
856
+ if (procEnv?.NODE_ENV === "production")
857
+ return Promise.resolve();
858
+ const runtime = getRuntime2();
859
+ if (runtime !== "nodejs") {
860
+ return checkDevServerReachable(getDevUrl(env), runtime);
861
+ }
862
+ if (!devServerPromise) {
863
+ devServerPromise = startPipeline(env).catch((error) => {
864
+ devServerPromise = void 0;
865
+ throw error;
866
+ });
867
+ }
868
+ return devServerPromise;
869
+ };
870
+ var startPipeline = async (env) => {
871
+ const baseUrl = getDevUrl(env);
872
+ const port = new URL(baseUrl).port;
873
+ const consoleLink = `\x1B[36m${CONSOLE_URL}?port=${port}\x1B[0m`;
874
+ if (await isDevServerRunning(baseUrl)) {
875
+ console.log(
876
+ `${DEV_PREFIX} Server already running at ${baseUrl}
877
+ ${DEV_PREFIX} Console: ${consoleLink}`
878
+ );
879
+ return;
880
+ }
881
+ const binaryPath = await ensureBinary();
882
+ await spawnServer(binaryPath, port, () => {
883
+ devServerPromise = void 0;
884
+ });
885
+ };
886
+ var shouldUseDevelopmentMode = (devMode, env) => {
887
+ if (devMode !== void 0)
888
+ return devMode;
889
+ const value = env?.QSTASH_DEV ?? getProcessEnvironment("QSTASH_DEV");
890
+ if (value === void 0 || value === "" || value === "false" || value === "0")
891
+ return false;
892
+ if (value === "true" || value === "1")
893
+ return true;
894
+ throw new Error(`[QStash Dev] Invalid value for QSTASH_DEV in environment: ${value}`);
895
+ };
896
+ var getDevelopmentCredentials = (env) => {
897
+ return {
898
+ ...DEV_CREDENTIALS,
899
+ baseUrl: getDevUrl(env)
900
+ };
901
+ };
902
+ var getDevUrl = (env) => {
903
+ const portString = env?.QSTASH_DEV_PORT ?? getProcessEnvironment("QSTASH_DEV_PORT");
904
+ let port = DEFAULT_DEV_PORT;
905
+ if (portString) {
906
+ const parsed = Number.parseInt(portString, 10);
907
+ if (!Number.isNaN(parsed) && parsed > 0) {
908
+ port = parsed;
909
+ }
910
+ }
911
+ return `http://127.0.0.1:${port}`;
912
+ };
913
+ var getRuntime2 = () => {
914
+ if (typeof navigator !== "undefined" && navigator.userAgent === "Cloudflare-Workers") {
915
+ return "cloudflare-workers";
916
+ }
917
+ const proc = _processGlobal2();
918
+ if (!proc) {
919
+ return "browser";
920
+ }
921
+ if (!proc.release?.name) {
922
+ return "edge";
923
+ }
924
+ return "nodejs";
925
+ };
926
+ var getProcessEnvironment = (key) => {
927
+ const proc = _processGlobal2();
928
+ return proc?.env ? proc.env[key] : void 0;
929
+ };
930
+
494
931
  // src/client/multi-region/incoming.ts
495
932
  var getReceiverSigningKeys = ({
496
933
  environment,
497
934
  regionFromHeader,
498
- config
935
+ config,
936
+ devMode
499
937
  }) => {
938
+ if (shouldUseDevelopmentMode(devMode, environment)) {
939
+ if (config?.currentSigningKey || config?.nextSigningKey) {
940
+ console.warn(
941
+ `${DEV_PREFIX} Dev mode is active. Ignoring signing keys from config. Set devMode: false to use your own keys.`
942
+ );
943
+ }
944
+ const developmentCreds = getDevelopmentCredentials(environment);
945
+ return {
946
+ currentSigningKey: developmentCreds.currentSigningKey,
947
+ nextSigningKey: developmentCreds.nextSigningKey
948
+ };
949
+ }
500
950
  if (config?.currentSigningKey && config.nextSigningKey) {
501
951
  return {
502
952
  currentSigningKey: config.currentSigningKey,
@@ -541,8 +991,21 @@ var getClientCredentials = (clientCredentialConfig) => {
541
991
  };
542
992
  var resolveCredentials = ({
543
993
  environment,
544
- config
994
+ config,
995
+ devMode
545
996
  }) => {
997
+ if (shouldUseDevelopmentMode(devMode, environment)) {
998
+ if (config?.baseUrl || config?.token) {
999
+ console.warn(
1000
+ `${DEV_PREFIX} Dev mode is active. Ignoring baseUrl/token from config. Set devMode: false to use your own credentials.`
1001
+ );
1002
+ }
1003
+ const developmentCreds = getDevelopmentCredentials(environment);
1004
+ return {
1005
+ baseUrl: developmentCreds.baseUrl,
1006
+ token: developmentCreds.token
1007
+ };
1008
+ }
546
1009
  if (config?.baseUrl && config.token) {
547
1010
  return {
548
1011
  baseUrl: config.baseUrl,
@@ -595,9 +1058,11 @@ var SignatureError = class extends Error {
595
1058
  var Receiver = class {
596
1059
  currentSigningKey;
597
1060
  nextSigningKey;
1061
+ devMode;
598
1062
  constructor(config) {
599
1063
  this.currentSigningKey = config?.currentSigningKey;
600
1064
  this.nextSigningKey = config?.nextSigningKey;
1065
+ this.devMode = config?.devMode;
601
1066
  }
602
1067
  /**
603
1068
  * Verify the signature of a request.
@@ -616,7 +1081,8 @@ var Receiver = class {
616
1081
  config: {
617
1082
  currentSigningKey: this.currentSigningKey,
618
1083
  nextSigningKey: this.nextSigningKey
619
- }
1084
+ },
1085
+ devMode: this.devMode
620
1086
  });
621
1087
  if (!signingKeys) {
622
1088
  throw new Error(
@@ -882,12 +1348,14 @@ var HttpClient = class {
882
1348
  baseUrl;
883
1349
  authorization;
884
1350
  options;
1351
+ devMode;
885
1352
  retry;
886
1353
  headers;
887
1354
  telemetryHeaders;
888
1355
  constructor(config) {
889
1356
  this.baseUrl = config.baseUrl.replace(/\/$/, "");
890
1357
  this.authorization = config.authorization;
1358
+ this.devMode = config.devMode;
891
1359
  this.retry = // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
892
1360
  typeof config.retry === "boolean" && !config.retry ? {
893
1361
  attempts: 1,
@@ -900,6 +1368,7 @@ var HttpClient = class {
900
1368
  this.telemetryHeaders = config.telemetryHeaders;
901
1369
  }
902
1370
  async request(request) {
1371
+ await ensureDevelopmentServer(void 0, this.devMode);
903
1372
  const { response } = await this.requestWithBackoff(request);
904
1373
  if (request.parseResponseAsJson === false) {
905
1374
  return void 0;
@@ -907,6 +1376,7 @@ var HttpClient = class {
907
1376
  return await response.json();
908
1377
  }
909
1378
  async *requestStream(request) {
1379
+ await ensureDevelopmentServer(void 0, this.devMode);
910
1380
  const { response } = await this.requestWithBackoff(request);
911
1381
  if (!response.body) {
912
1382
  throw new Error("No response body");
@@ -1629,7 +2099,7 @@ var UrlGroups = class {
1629
2099
  };
1630
2100
 
1631
2101
  // version.ts
1632
- var VERSION = "2.10.1";
2102
+ var VERSION = "2.11.1";
1633
2103
 
1634
2104
  // src/client/client.ts
1635
2105
  var Client = class {
@@ -1637,7 +2107,14 @@ var Client = class {
1637
2107
  token;
1638
2108
  constructor(config) {
1639
2109
  const environment = getSafeEnvironment();
1640
- const { baseUrl, token } = getClientCredentials({ environment, config });
2110
+ const { baseUrl, token } = getClientCredentials({
2111
+ environment,
2112
+ config,
2113
+ devMode: config?.devMode
2114
+ });
2115
+ if (shouldUseDevelopmentMode(config?.devMode, environment)) {
2116
+ void ensureDevelopmentServer(environment, config?.devMode);
2117
+ }
1641
2118
  const enableTelemetry = environment.UPSTASH_DISABLE_TELEMETRY ? false : config?.enableTelemetry ?? true;
1642
2119
  const isCloudflare = typeof caches !== "undefined" && "default" in caches;
1643
2120
  const telemetryHeaders = new Headers(
@@ -1654,7 +2131,8 @@ var Client = class {
1654
2131
  //@ts-expect-error caused by undici and bunjs type overlap
1655
2132
  headers: prefixHeaders(new Headers(config?.headers ?? {})),
1656
2133
  //@ts-expect-error caused by undici and bunjs type overlap
1657
- telemetryHeaders
2134
+ telemetryHeaders,
2135
+ devMode: config?.devMode
1658
2136
  });
1659
2137
  this.token = token;
1660
2138
  }
@@ -3272,6 +3750,8 @@ export {
3272
3750
  QStashWorkflowAbort,
3273
3751
  formatWorkflowError,
3274
3752
  decodeBase64,
3753
+ ensureDevelopmentServer,
3754
+ shouldUseDevelopmentMode,
3275
3755
  SignatureError,
3276
3756
  Receiver,
3277
3757
  FlowControlApi,