diff --git a/pkg/error.ts b/pkg/error.ts index 7e27c36d..02a0b9f9 100644 --- a/pkg/error.ts +++ b/pkg/error.ts @@ -11,7 +11,7 @@ export class UpstashError extends Error { export class UrlError extends Error { constructor(url: string) { super( - `Upstash Redis client was passed an invalid URL. You should pass the URL together with https. Received: "${url}". ` + `Upstash Redis client was passed an invalid URL. You should pass a URL starting with https. Received: "${url}". ` ); this.name = "UrlError"; } diff --git a/pkg/http.ts b/pkg/http.ts index 72c6e319..fd020f6a 100644 --- a/pkg/http.ts +++ b/pkg/http.ts @@ -127,6 +127,7 @@ export class HttpClient implements Requester { }; public readYourWrites: boolean; public upstashSyncToken = ""; + private hasCredentials: boolean; public readonly retry: { attempts: number; @@ -157,7 +158,7 @@ export class HttpClient implements Requester { * - `$` asserts the position at the end of the string. */ const urlRegex = /^https?:\/\/[^\s#$./?].\S*$/; - if (!urlRegex.test(this.baseUrl)) { + if (this.baseUrl && !urlRegex.test(this.baseUrl)) { throw new UrlError(this.baseUrl); } @@ -167,6 +168,8 @@ export class HttpClient implements Requester { ...config.headers, }; + this.hasCredentials = Boolean(this.baseUrl && this.headers.authorization.split(" ")[1]); + if (this.options.responseEncoding === "base64") { this.headers["Upstash-Encoding"] = "base64"; } @@ -206,6 +209,13 @@ export class HttpClient implements Requester { backend: this.options.backend, }; + if (!this.hasCredentials) { + throw new Error( + "[Upstash Redis] Redis client was initialized without url or token." + + " Failed to execute command." + ); + } + /** * We've recieved a new `upstash-sync-token` in the previous response. We use it in the next request to observe the effects of previous requests. */ diff --git a/platforms/cloudflare.ts b/platforms/cloudflare.ts index 9a705c14..19e25f1b 100644 --- a/platforms/cloudflare.ts +++ b/platforms/cloudflare.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import type { RequesterConfig } from "../pkg/http"; import { HttpClient } from "../pkg/http"; import * as core from "../pkg/redis"; @@ -56,27 +55,35 @@ export class Redis extends core.Redis { */ constructor(config: RedisConfigCloudflare, env?: Env) { if (!config.url) { - throw new Error( + console.warn( `[Upstash Redis] The 'url' property is missing or undefined in your Redis config.` ); } if (!config.token) { - throw new Error( + console.warn( `[Upstash Redis] The 'token' property is missing or undefined in your Redis config.` ); } - if (config.url.startsWith(" ") || config.url.endsWith(" ") || /\r|\n/.test(config.url)) { - console.warn("The redis url contains whitespace or newline, which can cause errors!"); + if (config.url!.startsWith(" ") || config.url!.endsWith(" ") || /\r|\n/.test(config.url!)) { + console.warn( + "[Upstash Redis] The redis url contains whitespace or newline, which can cause errors!" + ); } - if (config.token.startsWith(" ") || config.token.endsWith(" ") || /\r|\n/.test(config.token)) { - console.warn("The redis token contains whitespace or newline, which can cause errors!"); + if ( + config.token!.startsWith(" ") || + config.token!.endsWith(" ") || + /\r|\n/.test(config.token!) + ) { + console.warn( + "[Upstash Redis] The redis token contains whitespace or newline, which can cause errors!" + ); } const client = new HttpClient({ retry: config.retry, - baseUrl: config.url, + baseUrl: config.url!, headers: { authorization: `Bearer ${config.token}` }, responseEncoding: config.responseEncoding, signal: config.signal, @@ -127,13 +134,13 @@ export class Redis extends core.Redis { const token = env?.UPSTASH_REDIS_REST_TOKEN ?? UPSTASH_REDIS_REST_TOKEN; if (!url) { - throw new Error( - "Unable to find environment variable: `UPSTASH_REDIS_REST_URL`. Please add it via `wrangler secret put UPSTASH_REDIS_REST_URL`" + console.warn( + "[Upstash Redis] Unable to find environment variable: `UPSTASH_REDIS_REST_URL`. Please add it via `wrangler secret put UPSTASH_REDIS_REST_URL`" ); } if (!token) { - throw new Error( - "Unable to find environment variable: `UPSTASH_REDIS_REST_TOKEN`. Please add it via `wrangler secret put UPSTASH_REDIS_REST_TOKEN`" + console.warn( + "[Upstash Redis] Unable to find environment variable: `UPSTASH_REDIS_REST_TOKEN`. Please add it via `wrangler secret put UPSTASH_REDIS_REST_TOKEN`" ); } return new Redis({ ...opts, url, token }, env); diff --git a/platforms/fastly.ts b/platforms/fastly.ts index 4ca91384..279e4577 100644 --- a/platforms/fastly.ts +++ b/platforms/fastly.ts @@ -53,26 +53,34 @@ export class Redis extends core.Redis { */ constructor(config: RedisConfigFastly) { if (!config.url) { - throw new Error( + console.warn( `[Upstash Redis] The 'url' property is missing or undefined in your Redis config.` ); } if (!config.token) { - throw new Error( + console.warn( `[Upstash Redis] The 'token' property is missing or undefined in your Redis config.` ); } - if (config.url.startsWith(" ") || config.url.endsWith(" ") || /\r|\n/.test(config.url)) { - console.warn("The redis url contains whitespace or newline, which can cause errors!"); + if (config.url!.startsWith(" ") || config.url!.endsWith(" ") || /\r|\n/.test(config.url!)) { + console.warn( + "[Upstash Redis] The redis url contains whitespace or newline, which can cause errors!" + ); } - if (config.token.startsWith(" ") || config.token.endsWith(" ") || /\r|\n/.test(config.token)) { - console.warn("The redis token contains whitespace or newline, which can cause errors!"); + if ( + config.token!.startsWith(" ") || + config.token!.endsWith(" ") || + /\r|\n/.test(config.token!) + ) { + console.warn( + "[Upstash Redis] The redis token contains whitespace or newline, which can cause errors!" + ); } const client = new HttpClient({ - baseUrl: config.url, + baseUrl: config.url!, retry: config.retry, headers: { authorization: `Bearer ${config.token}` }, options: { backend: config.backend }, diff --git a/platforms/nodejs.ts b/platforms/nodejs.ts index 9bfb4879..3e3b1840 100644 --- a/platforms/nodejs.ts +++ b/platforms/nodejs.ts @@ -102,37 +102,41 @@ export class Redis extends core.Redis { } if (!configOrRequester.url) { - throw new Error( + console.warn( `[Upstash Redis] The 'url' property is missing or undefined in your Redis config.` ); } if (!configOrRequester.token) { - throw new Error( + console.warn( `[Upstash Redis] The 'token' property is missing or undefined in your Redis config.` ); } if ( - configOrRequester.url.startsWith(" ") || - configOrRequester.url.endsWith(" ") || - /\r|\n/.test(configOrRequester.url) + configOrRequester.url!.startsWith(" ") || + configOrRequester.url!.endsWith(" ") || + /\r|\n/.test(configOrRequester.url!) ) { - console.warn("The redis url contains whitespace or newline, which can cause errors!"); + console.warn( + "[Upstash Redis] The redis url contains whitespace or newline, which can cause errors!" + ); } if ( - configOrRequester.token.startsWith(" ") || - configOrRequester.token.endsWith(" ") || - /\r|\n/.test(configOrRequester.token) + configOrRequester.token!.startsWith(" ") || + configOrRequester.token!.endsWith(" ") || + /\r|\n/.test(configOrRequester.token!) ) { - console.warn("The redis token contains whitespace or newline, which can cause errors!"); + console.warn( + "[Upstash Redis] The redis token contains whitespace or newline, which can cause errors!" + ); } const client = new HttpClient({ - baseUrl: configOrRequester.url, + baseUrl: configOrRequester.url!, retry: configOrRequester.retry, headers: { authorization: `Bearer ${configOrRequester.token}` }, - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + agent: configOrRequester.agent, responseEncoding: configOrRequester.responseEncoding, cache: configOrRequester.cache ?? "no-store", @@ -172,21 +176,21 @@ export class Redis extends core.Redis { */ static fromEnv(config?: Omit): Redis { // @ts-ignore process will be defined in node - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (process.env === undefined) { throw new TypeError( - 'Unable to get environment variables, `process.env` is undefined. If you are deploying to cloudflare, please import from "@upstash/redis/cloudflare" instead' + '[Upstash Redis] Unable to get environment variables, `process.env` is undefined. If you are deploying to cloudflare, please import from "@upstash/redis/cloudflare" instead' ); } - // @ts-ignore process will be defined in node const url = process.env.UPSTASH_REDIS_REST_URL; if (!url) { - throw new Error("Unable to find environment variable: `UPSTASH_REDIS_REST_URL`"); + console.warn("[Upstash Redis] Unable to find environment variable: `UPSTASH_REDIS_REST_URL`"); } - // @ts-ignore process will be defined in node const token = process.env.UPSTASH_REDIS_REST_TOKEN; if (!token) { - throw new Error("Unable to find environment variable: `UPSTASH_REDIS_REST_TOKEN`"); + console.warn( + "[Upstash Redis] Unable to find environment variable: `UPSTASH_REDIS_REST_TOKEN`" + ); } return new Redis({ ...config, url, token }); } diff --git a/platforms/platform.test.ts b/platforms/platform.test.ts new file mode 100644 index 00000000..917d8da6 --- /dev/null +++ b/platforms/platform.test.ts @@ -0,0 +1,36 @@ +import { describe, expect, test } from "bun:test"; +import { Redis as NodeRedis } from "./nodejs"; +import { Redis as CloudflareRedis } from "./cloudflare"; +import { Redis as FastlyRedis } from "./fastly"; + +describe("should allow creating a client without credentials but fail when requesting", () => { + test("nodejs", () => { + const redis = new NodeRedis({ url: undefined, token: undefined }); + + const throws = redis.get("foo"); + expect(throws).toThrow( + "[Upstash Redis] Redis client was initialized without url or token." + + " Failed to execute command." + ); + }); + + test("cloudflare", () => { + const redis = new CloudflareRedis({ url: undefined, token: undefined }); + + const throws = redis.get("foo"); + expect(throws).toThrow( + "[Upstash Redis] Redis client was initialized without url or token." + + " Failed to execute command." + ); + }); + + test("fastly", () => { + const redis = new FastlyRedis({ url: undefined, token: undefined, backend: "upstash-db" }); + + const throws = redis.get("foo"); + expect(throws).toThrow( + "[Upstash Redis] Redis client was initialized without url or token." + + " Failed to execute command." + ); + }); +});