From 4c437ddc1fc05e0da28ab2952445fe323017431a Mon Sep 17 00:00:00 2001 From: Andreas Thomas Date: Mon, 23 May 2022 12:38:35 +0200 Subject: [PATCH 1/2] feat: add import path for fetch polyfilled redis --- .github/workflows/tests.yaml | 47 +++++++++ README.md | 16 +--- cmd/build.ts | 12 +++ examples/aws-lambda/index.js | 2 +- examples/nodejs/index.js | 9 +- platforms/node_with_fetch.ts | 179 +++++++++++++++++++++++++++++++++++ 6 files changed, 251 insertions(+), 14 deletions(-) create mode 100644 platforms/node_with_fetch.ts diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index b0fb03fb..8bb2965b 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -304,3 +304,50 @@ jobs: echo "assertEqualsed response to contain 'Counter: 2', got $(cat response.html)" exit 1 fi + + + + example-nodejs: + needs: + - test + env: + UPSTASH_REDIS_REST_URL: http://127.0.0.1:6379 + UPSTASH_REDIS_REST_TOKEN: ${{ secrets.UPSTASH_AUTH_TOKEN }} + runs-on: ubuntu-latest + steps: + - name: Setup repo + uses: actions/checkout@v2 + - uses: denoland/setup-deno@v1 + with: + deno-version: v1.x + - name: Cache pnpm modules + uses: actions/cache@v2 + with: + path: ~/.pnpm-store + key: ${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}- + + - uses: pnpm/action-setup@v2 + with: + version: 6 + + - name: Build + run: deno run -A ./cmd/build.ts + + - name: Start redis server + uses: ./.github/actions/redis + with: + UPSTASH_REDIS_REST_URL: http://127.0.0.1:6379 + UPSTASH_REDIS_REST_TOKEN: ${{ secrets.UPSTASH_AUTH_TOKEN }} + UPSTASH_REPO_ACCESS_TOKEN: ${{ secrets.UPSTASH_REPO_ACCESS_TOKEN }} + REDIS_SERVER_CONFIG: ${{ secrets.REDIS_SERVER_CONFIG }} + + - name: Install example + run: | + pnpm install + working-directory: examples/nodejs + + - name: Run example + run: node ./index.js + working-directory: examples/nodejs diff --git a/README.md b/README.md index 045f2b0c..90ae2f3e 100644 --- a/README.md +++ b/README.md @@ -25,21 +25,15 @@ supported. ## Upgrading to v1.4.0 **(ReferenceError: fetch is not defined)** -If you are running on nodejs v17 and earlier, you need to manually provide a -`fetch` polyfill. The simplest way is using `isomorphic-fetch` - -```bash -npm install isomorphic-fetch -``` +If you are running on nodejs v17 and earlier, `fetch` will not be natively +supported. Platforms like Vercel, Netlify, Deno, Fastly etc. provide a polyfill +for you. But if you are running on bare node, you need to either specify a +polyfill yourself or change the import path to: ```typescript -import "isomorphic-fetch"; -import { Redis } from "@upstash/redis"; +import { Redis } from "@upstash/redis/with-fetch"; ``` -`fetch` is natively supported in node18 as well as all major platforms: Vercel, -Netlify, Deno, Fastly etc. and you do not need to do anything. - ## Upgrading from v0.2.0? Please read the diff --git a/cmd/build.ts b/cmd/build.ts index b5cdee41..9ac7fc04 100644 --- a/cmd/build.ts +++ b/cmd/build.ts @@ -22,6 +22,10 @@ await build({ name: "./fastly", path: "./platforms/fastly.ts", }, + { + name: "./with-fetch", + path: "./platforms/node_with_fetch.ts", + }, ], outDir, shims: { @@ -36,6 +40,11 @@ await build({ typesPackage: { name: "@types/node", version: "latest" }, globalNames: [], }, + { + package: { name: "@types/node", version: "latest" }, + typesPackage: { name: "@types/node", version: "latest" }, + globalNames: [], + }, ], }, typeCheck: true, @@ -56,6 +65,9 @@ await build({ bugs: { url: "https://github.com/upstash/upstash-redis/issues", }, + dependencies: { + "isomorphic-fetch": "^3.0.0", + }, homepage: "https://github.com/upstash/upstash-redis#readme", browser: { "isomorphic-fetch": false, diff --git a/examples/aws-lambda/index.js b/examples/aws-lambda/index.js index fd6aa58d..a6d6e4a9 100644 --- a/examples/aws-lambda/index.js +++ b/examples/aws-lambda/index.js @@ -1,4 +1,4 @@ -const { Redis } = require("@upstash/redis"); +const { Redis } = require("@upstash/redis/with-fetch"); exports.handler = async (_event, _context) => { let response; diff --git a/examples/nodejs/index.js b/examples/nodejs/index.js index 78df366d..2d5c92f1 100644 --- a/examples/nodejs/index.js +++ b/examples/nodejs/index.js @@ -1,14 +1,19 @@ -const { Redis } = require("@upstash/redis"); +import { Redis } from "@upstash/redis/with-fetch"; const redis = Redis.fromEnv(); async function run() { const key = "key"; + const value = { hello: "world" }; - const res1 = await redis.set(key, '{"hello":"world"}'); + const res1 = await redis.set(key, value); console.log(res1); const res2 = await redis.get(key); console.log(typeof res2, res2); + + if (JSON.stringify(value) != JSON.stringify(res2)) { + throw new Error("value not equal"); + } } run(); diff --git a/platforms/node_with_fetch.ts b/platforms/node_with_fetch.ts new file mode 100644 index 00000000..620c1565 --- /dev/null +++ b/platforms/node_with_fetch.ts @@ -0,0 +1,179 @@ +// deno-lint-ignore-file + +import * as core from "../pkg/redis.ts"; +import { Requester, UpstashRequest, UpstashResponse } from "../pkg/http.ts"; +import { UpstashError } from "../pkg/error.ts"; +import "isomorphic-fetch"; +// @ts-ignore Deno can't compile +// import https from "https"; +// @ts-ignore Deno can't compile +// import http from "http"; +// import "isomorphic-fetch"; + +export type { Requester, UpstashRequest, UpstashResponse }; + +/** + * Connection credentials for upstash redis. + * Get them from https://console.upstash.com/redis/ + */ +export type RedisConfigNodejs = { + /** + * UPSTASH_REDIS_REST_URL + */ + url: string; + /** + * UPSTASH_REDIS_REST_TOKEN + */ + token: string; + /** + * An agent allows you to reuse connections to reduce latency for multiple sequential requests. + * + * This is a node specific implementation and is not supported in various runtimes like Vercel + * edge functions. + * + * @example + * ```ts + * import https from "https" + * + * const options: RedisConfigNodejs = { + * agent: new https.Agent({ keepAlive: true }) + * } + * ``` + */ + // agent?: http.Agent | https.Agent; +} & core.RedisOptions; + +/** + * Serverless redis client for upstash. + */ +export class Redis extends core.Redis { + /** + * Create a new redis client by providing the url and token + * + * @example + * ```typescript + * const redis = new Redis({ + * url: "", + * token: "", + * }); + * ``` + */ + constructor(config: RedisConfigNodejs); + + /** + * Create a new redis client by providing a custom `Requester` implementation + * + * @example + * ```ts + * + * import { UpstashRequest, Requester, UpstashResponse, Redis } from "@upstash/redis" + * + * const requester: Requester = { + * request: (req: UpstashRequest): Promise> => { + * // ... + * } + * } + * + * const redis = new Redis(requester) + * ``` + */ + constructor(requesters: Requester); + constructor(configOrRequester: RedisConfigNodejs | Requester) { + if ("request" in configOrRequester) { + super(configOrRequester); + return; + } + if ( + configOrRequester.url.startsWith(" ") || + configOrRequester.url.endsWith(" ") || + /\r|\n/.test(configOrRequester.url) + ) { + console.warn( + "The redis url contains whitespace or newline, which can cause errors!", + ); + } + if ( + configOrRequester.token.startsWith(" ") || + configOrRequester.token.endsWith(" ") || + /\r|\n/.test(configOrRequester.token) + ) { + console.warn( + "The redis token contains whitespace or newline, which can cause errors!", + ); + } + + const client = defaultRequester({ + baseUrl: configOrRequester.url, + headers: { authorization: `Bearer ${configOrRequester.token}` }, + // agent: configOrRequester.agent, + }); + + super(client, { + automaticDeserialization: configOrRequester.automaticDeserialization, + }); + } + + /** + * Create a new Upstash Redis instance from environment variables. + * + * Use this to automatically load connection secrets from your environment + * variables. For instance when using the Vercel integration. + * + * This tries to load `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` from + * your environment using `process.env`. + */ + static fromEnv(config?: Omit): Redis { + // @ts-ignore process will be defined in node + if (typeof process?.env === "undefined") { + throw new Error( + '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`", + ); + } + // @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`", + ); + } + return new Redis({ url, token, ...config }); + } +} + +function defaultRequester(config: { + headers?: Record; + baseUrl: string; + // agent?: http.Agent | https.Agent; +}): Requester { + return { + request: async function ( + req: UpstashRequest, + ): Promise> { + if (!req.path) { + req.path = []; + } + + const res = await fetch([config.baseUrl, ...req.path].join("/"), { + method: "POST", + headers: { "Content-Type": "application/json", ...config.headers }, + body: JSON.stringify(req.body), + keepalive: true, + // @ts-ignore + agent: config.agent, + }); + const body = (await res.json()) as UpstashResponse; + if (!res.ok) { + throw new UpstashError(body.error!); + } + + return body; + }, + }; +} From ff8761fdd8e34366229cb77e82ffeedd1a59067f Mon Sep 17 00:00:00 2001 From: Andreas Thomas Date: Mon, 23 May 2022 12:41:07 +0200 Subject: [PATCH 2/2] docs: update nodejs section --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 90ae2f3e..7d68af68 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,15 @@ const redis = new Redis({ const redis = Redis.fromEnv() ``` +If you are running on nodejs v17 and earlier, `fetch` will not be natively +supported. Platforms like Vercel, Netlify, Deno, Fastly etc. provide a polyfill +for you. But if you are running on bare node, you need to either specify a +polyfill yourself or change the import path to: + +```typescript +import { Redis } from "@upstash/redis/with-fetch"; +``` + - [Code example](https://github.com/upstash/upstash-redis/blob/main/examples/nodejs) #### Cloudflare Workers