Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ jobs:
- name: Setup node
uses: actions/setup-node@v3
with:
node-version: 18
node-version: 20

- name: Setup Bun
uses: oven-sh/setup-bun@v1
Expand Down Expand Up @@ -169,7 +169,7 @@ jobs:
- name: Setup node
uses: actions/setup-node@v3
with:
node-version: 18
node-version: 20

- name: Setup Bun
uses: oven-sh/setup-bun@v1
Expand Down
Binary file modified bun.lockb
Binary file not shown.
2 changes: 1 addition & 1 deletion examples/cloudflare-workers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@
"wrangler": "^2.20.0"
},
"dependencies": {
"@upstash/redis": "link:../../dist"
"@upstash/redis": "latest"
}
}
56 changes: 20 additions & 36 deletions pkg/auto-pipeline.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ describe("Auto pipeline", () => {

const redis = Redis.fromEnv({
latencyLogging: false,
enableAutoPipelining: true
enableAutoPipelining: true,
});
// @ts-expect-error pipelineCounter is not in type but accessible
expect(redis.pipelineCounter).toBe(0);
Expand Down Expand Up @@ -152,10 +152,9 @@ describe("Auto pipeline", () => {
});

test("should group async requests with sync requests", async () => {

const redis = Redis.fromEnv({
latencyLogging: false,
enableAutoPipelining: true
enableAutoPipelining: true,
});
// @ts-expect-error pipelineCounter is not in type but accessible
expect(redis.pipelineCounter).toBe(0);
Expand All @@ -178,10 +177,9 @@ describe("Auto pipeline", () => {
});

test("should execute a pipeline for each consecutive awaited command", async () => {

const redis = Redis.fromEnv({
latencyLogging: false,
enableAutoPipelining: true
enableAutoPipelining: true,
});
// @ts-expect-error pipelineCounter is not in type but accessible
expect(redis.pipelineCounter).toBe(0);
Expand All @@ -204,10 +202,9 @@ describe("Auto pipeline", () => {
});

test("should execute a single pipeline for several commands inside Promise.all", async () => {

const redis = Redis.fromEnv({
latencyLogging: false,
enableAutoPipelining: true
enableAutoPipelining: true,
});
// @ts-expect-error pipelineCounter is not in type but accessible
expect(redis.pipelineCounter).toBe(0);
Expand All @@ -222,14 +219,12 @@ describe("Auto pipeline", () => {
// @ts-expect-error pipelineCounter is not in type but accessible
expect(redis.pipelineCounter).toBe(1);
expect(resArray).toEqual(["OK", 1, 2, "OK", "bar"]);

});

test("should be able to utilize only redis functions 'use' like usual", async () => {

const redis = Redis.fromEnv({
latencyLogging: false,
enableAutoPipelining: true
enableAutoPipelining: true,
});
// @ts-expect-error pipelineCounter is not in type but accessible
expect(redis.pipelineCounter).toBe(0);
Expand All @@ -239,7 +234,7 @@ describe("Auto pipeline", () => {
state = true;
return await next(req);
});

// @ts-expect-error pipelineCounter is not in type but accessible
expect(redis.pipelineCounter).toBe(0);

Expand All @@ -252,59 +247,56 @@ describe("Auto pipeline", () => {
});

test("should be able to utilize only redis functions 'multi' and 'pipeline' like usual", async () => {

const redis = Redis.fromEnv({
latencyLogging: false,
enableAutoPipelining: true
enableAutoPipelining: true,
});
// @ts-expect-error pipelineCounter is not in type but accessible
expect(redis.pipelineCounter).toBe(0);

const pipe = redis.pipeline();
pipe.incr("voila");
pipe.incr("voila");
const result = await pipe.exec()
expect(result).toEqual([1, 2])
const result = await pipe.exec();
expect(result).toEqual([1, 2]);

// @ts-expect-error pipelineCounter is not in type but accessible
expect(redis.pipelineCounter).toBe(0);

const transaction = redis.multi();
transaction.incr("et voila");
transaction.incr("et voila");
const result_2 = await transaction.exec()
expect(result_2).toEqual([1, 2])
const result_2 = await transaction.exec();
expect(result_2).toEqual([1, 2]);

// @ts-expect-error pipelineCounter is not in type but accessible
expect(redis.pipelineCounter).toBe(0);
});

test("should be able to utilize only redis functions 'createScript' like usual", async () => {

const redis = Redis.fromEnv({
latencyLogging: false,
enableAutoPipelining: true
enableAutoPipelining: true,
});
// @ts-expect-error pipelineCounter is not in type but accessible
expect(redis.pipelineCounter).toBe(0);

const script = redis.createScript("return ARGV[1];");

// @ts-expect-error pipelineCounter is not in type but accessible
expect(redis.pipelineCounter).toBe(0);

const res = await script.eval([], ["Hello World"]);
expect(res).toEqual("Hello World");

// @ts-expect-error pipelineCounter is not in type but accessible
expect(redis.pipelineCounter).toBe(1);
});

test("should handle JSON commands correctly", async () => {

const redis = Redis.fromEnv({
latencyLogging: false,
enableAutoPipelining: true
enableAutoPipelining: true,
});

// @ts-expect-error pipelineCounter is not in type but accessible
Expand All @@ -317,19 +309,11 @@ describe("Auto pipeline", () => {
redis.json.get("baz1"),
redis.json.del("baz1"),
redis.json.get("baz1"),
])
]);

// @ts-expect-error pipelineCounter is not in type but accessible
expect(redis.pipelineCounter).toBe(1);

expect(res).toEqual([
"OK",
"OK",
"bar",
{ hello: "world" },
1,
null
])
})
expect(res).toEqual(["OK", "OK", "bar", { hello: "world" }, 1, null]);
});
});

16 changes: 8 additions & 8 deletions pkg/auto-pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,21 @@ export function createAutoPipelineProxy(_redis: Redis, json?: boolean): Redis {
}

return new Proxy(redis, {
get: (redis, command: "pipelineCounter" | keyof Pipeline | redisOnly ) => {
get: (redis, command: "pipelineCounter" | keyof Pipeline | redisOnly) => {
// return pipelineCounter of autoPipelineExecutor
if (command === "pipelineCounter") {
return redis.autoPipelineExecutor.pipelineCounter;
}

if (command === "json") {
return createAutoPipelineProxy(redis, true);
};
}

const commandInRedisButNotPipeline =
command in redis && !(command in redis.autoPipelineExecutor.pipeline);

if (commandInRedisButNotPipeline) {
return redis[command as redisOnly];
return redis[command as redisOnly];
}

// If the method is a function on the pipeline, wrap it with the executor logic
Expand All @@ -39,7 +39,7 @@ export function createAutoPipelineProxy(_redis: Redis, json?: boolean): Redis {
// pass the function as a callback
return redis.autoPipelineExecutor.withAutoPipeline((pipeline) => {
if (json) {
(pipeline.json[command as keyof Pipeline["json"]] as Function)(...args)
(pipeline.json[command as keyof Pipeline["json"]] as Function)(...args);
} else {
(pipeline[command as keyof Pipeline] as Function)(...args);
}
Expand Down Expand Up @@ -94,7 +94,7 @@ class AutoPipelineExecutor {
}

private async deferExecution() {
await Promise.resolve()
return await Promise.resolve()
await Promise.resolve();
return await Promise.resolve();
}
}
}
11 changes: 4 additions & 7 deletions pkg/commands/scan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,9 @@ export class ScanCommand extends Command<[string, string[]], [string, string[]]>
if (opts?.type && opts.type.length > 0) {
command.push("type", opts.type);
}
super(
command,
{
deserialize: deserializeScanResponse,
...cmdOpts,
}
);
super(command, {
deserialize: deserializeScanResponse,
...cmdOpts,
});
}
}
11 changes: 4 additions & 7 deletions pkg/commands/sscan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,9 @@ export class SScanCommand extends Command<
command.push("count", opts.count);
}

super(
command,
{
deserialize: deserializeScanResponse,
...cmdOpts,
}
);
super(command, {
deserialize: deserializeScanResponse,
...cmdOpts,
});
}
}
11 changes: 4 additions & 7 deletions pkg/commands/zscan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,9 @@ export class ZScanCommand extends Command<
command.push("count", opts.count);
}

super(
command,
{
deserialize: deserializeScanResponse,
...cmdOpts,
}
);
super(command, {
deserialize: deserializeScanResponse,
...cmdOpts,
});
}
}
7 changes: 7 additions & 0 deletions pkg/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,10 @@ export class UpstashError extends Error {
this.name = "UpstashError";
}
}

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}". `);
this.name = "UrlError";
}
}
25 changes: 25 additions & 0 deletions pkg/http.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { describe, expect, test } from "bun:test";
import { HttpClient } from "./http";

import { UrlError } from "./error";
import { newHttpClient } from "./test-utils";
test("remove trailing slash from urls", () => {
const client = new HttpClient({ baseUrl: "https://example.com/" });
Expand Down Expand Up @@ -48,3 +49,27 @@ describe("Abort", () => {
expect((await body).result).toEqual("Abort works!");
});
});

describe("should reject invalid urls", () => {
test("should reject when https is missing", () => {
try {
new HttpClient({ baseUrl: "eu1-flying-whale-434343.upstash.io" });
} catch (error) {
expect(error instanceof UrlError).toBeTrue();
return;
}
throw new Error("Test error. Should have raised when initializing client");
});

test("should allow when http is used", () => {
new HttpClient({
baseUrl: "http://eu1-flying-whale-434343.upstash.io",
});
});

test("should allow when https is used", () => {
new HttpClient({
baseUrl: "https://eu1-flying-whale-434343.upstash.io",
});
});
});
22 changes: 18 additions & 4 deletions pkg/http.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { UpstashError } from "./error";
import { UpstashError, UrlError } from "./error";
import { Telemetry } from "./types";

type CacheSetting =
Expand Down Expand Up @@ -94,7 +94,7 @@ export type HttpClientConfig = {
retry?: RetryConfig;
agent?: any;
signal?: AbortSignal;
keepAlive?: boolean
keepAlive?: boolean;
} & RequesterConfig;

export class HttpClient implements Requester {
Expand All @@ -106,7 +106,7 @@ export class HttpClient implements Requester {
signal?: AbortSignal;
responseEncoding?: false | "base64";
cache?: CacheSetting;
keepAlive: boolean
keepAlive: boolean;
};

public readonly retry: {
Expand All @@ -121,11 +121,25 @@ export class HttpClient implements Requester {
responseEncoding: config.responseEncoding ?? "base64", // default to base64
cache: config.cache,
signal: config.signal,
keepAlive: config.keepAlive ?? true
keepAlive: config.keepAlive ?? true,
};

this.baseUrl = config.baseUrl.replace(/\/$/, "");

/**
* regex to check if the baseUrl starts with http:// or https://
* - `^` asserts the position at the start of the string.
* - `[^\s/$.?#]` makes sure that the domain starts correctly;
* without white space, '/', '$', '.', '?' or '#'
* - `.` matches any character except new line
* - `[^\s]*` matches anything except white space
* - `$` asserts the position at the end of the string.
*/
const urlRegex = /^https?:\/\/[^\s/$.?#].[^\s]*$/;
if (!urlRegex.test(this.baseUrl)) {
throw new UrlError(this.baseUrl);
}

this.headers = {
"Content-Type": "application/json",

Expand Down
2 changes: 1 addition & 1 deletion pkg/redis.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { createAutoPipelineProxy } from "../pkg/auto-pipeline";
import {
AppendCommand,
BitCountCommand,
Expand Down Expand Up @@ -176,7 +177,6 @@ import { Requester, UpstashRequest, UpstashResponse } from "./http";
import { Pipeline } from "./pipeline";
import { Script } from "./script";
import type { CommandArgs, RedisOptions, Telemetry } from "./types";
import { createAutoPipelineProxy } from "../pkg/auto-pipeline"

// See https://github.com/upstash/upstash-redis/issues/342
// why we need this export
Expand Down
Loading