Skip to content
Open
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
136 changes: 134 additions & 2 deletions packages/bun-types/redis.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2676,6 +2676,29 @@ declare module "bun" {
*/
ping(message: RedisClient.KeyLike): Promise<string>;

/**
* Switch this connection to another logical database
*
* A connection starts on the database named by the path of the connection
* URL (`redis://localhost:6379/2` selects database 2), or on database 0
* when the URL has no path. That URL database is selected again after an
* automatic reconnect and is what {@link duplicate `.duplicate()`} starts
* on; a database chosen with this method only lasts until the connection
* drops. Not available while the client is in subscriber mode.
*
* @param index The database index. Servers have 16 databases (0 to 15)
* unless configured otherwise.
* @returns Promise that resolves with "OK", or rejects when the index is
* out of range for the server
*
* @example
* ```ts
* await redis.select(1);
* await redis.set("key", "value"); // written to database 1
* ```
*/
select(index: number | string): Promise<"OK">;

/**
* Publish a message to a Redis channel.
*
Expand All @@ -2695,8 +2718,9 @@ declare module "bun" {
*
* Subscribing moves the channel to a dedicated subscription state which
* prevents most other commands from being executed until unsubscribed. Only
* {@link ping `.ping()`}, {@link subscribe `.subscribe()`}, and
* {@link unsubscribe `.unsubscribe()`} can be called while subscribed.
* {@link ping `.ping()`}, {@link subscribe `.subscribe()`},
* {@link unsubscribe `.unsubscribe()`}, and {@link pubsub `.pubsub()`} can
* be called while subscribed.
*
* @param channel The channel to subscribe to.
* @param listener The listener to call when a message is received on the
Expand Down Expand Up @@ -2793,6 +2817,114 @@ declare module "bun" {
*/
duplicate(): Promise<RedisClient>;

/**
* List the channels that currently have at least one subscriber
*
* The result covers every client connected to the server, not only this
* one, and only counts subscriptions made with
* {@link subscribe `.subscribe()`}: pattern subscriptions are not included.
* Unlike most commands, PUBSUB can also be sent while this client is in
* subscriber mode.
*
* @param subcommand "CHANNELS"
* @returns Promise that resolves with the channel names
*
* @example
* ```ts
* await subscriber.subscribe("news", () => {});
* console.log(await redis.pubsub("CHANNELS")); // ["news"]
* ```
*/
pubsub(subcommand: "CHANNELS"): Promise<string[]>;

/**
* List the channels that currently have at least one subscriber and whose
* name matches a pattern
*
* See the single-argument overload for what is counted.
*
* @param subcommand "CHANNELS"
* @param pattern Glob-style pattern, as used by `KEYS` (`"news.*"`)
* @returns Promise that resolves with the matching channel names
*/
pubsub(subcommand: "CHANNELS", pattern: string): Promise<string[]>;

/**
* Count the subscribers of each given channel
*
* Counts the subscriptions every client connected to the server made with
* {@link subscribe `.subscribe()`}; pattern subscriptions are not included.
* A channel nobody is subscribed to is reported with a count of 0.
*
* @param subcommand "NUMSUB"
* @param channels The channels to count subscribers for
* @returns Promise that resolves with a flat array alternating channel name
* and subscriber count, in the order the channels were given (empty when no
* channels were given)
*
* @example
* ```ts
* await subscriber.subscribe("news", () => {});
* console.log(await redis.pubsub("NUMSUB", "news", "sports")); // ["news", 1, "sports", 0]
* ```
*/
pubsub(subcommand: "NUMSUB", ...channels: string[]): Promise<(string | number)[]>;

/**
* Count the patterns clients are subscribed to
*
* @param subcommand "NUMPAT"
* @returns Promise that resolves with the number of distinct patterns any
* client connected to the server is subscribed to. Two clients subscribed
* to the same pattern count once.
*/
pubsub(subcommand: "NUMPAT"): Promise<number>;

/**
* List the shard channels that currently have at least one subscriber
*
* Shard channels are the ones used by `SSUBSCRIBE` and
* {@link spublish `.spublish()`}; regular channels are not included.
*
* @param subcommand "SHARDCHANNELS"
* @returns Promise that resolves with the shard channel names
*/
pubsub(subcommand: "SHARDCHANNELS"): Promise<string[]>;

/**
* List the shard channels that currently have at least one subscriber and
* whose name matches a pattern
*
* @param subcommand "SHARDCHANNELS"
* @param pattern Glob-style pattern, as used by `KEYS`
* @returns Promise that resolves with the matching shard channel names
*/
pubsub(subcommand: "SHARDCHANNELS", pattern: string): Promise<string[]>;

/**
* Count the subscribers of each given shard channel
*
* @param subcommand "SHARDNUMSUB"
* @param shardchannels The shard channels to count subscribers for
* @returns Promise that resolves with a flat array alternating shard channel
* name and subscriber count, in the order the channels were given (empty
* when no channels were given)
*/
pubsub(subcommand: "SHARDNUMSUB", ...shardchannels: string[]): Promise<(string | number)[]>;

/**
* Send any other PUBSUB subcommand
*
* The subcommand and its arguments are sent to the server as given. The
* server also accepts lowercase spellings of the subcommands above; they
* go through this overload, so their replies are untyped.
*
* @param subcommand The subcommand, for example `"HELP"`
* @param args The subcommand's arguments
* @returns Promise that resolves with the server's reply
*/
pubsub(subcommand: string, ...args: string[]): Promise<any>;

/**
* Copy the value stored at the source key to the destination key
*
Expand Down
57 changes: 36 additions & 21 deletions test/integration/bun-types/bun-types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,36 +367,51 @@ describe("@types/bun integration test", () => {
});
});

// Runs on debug builds too: spawning tsc over a single file is cheap,
// unlike the in-process LanguageService runs above.
// The checks below run on debug builds too: spawning tsc over a few files is
// cheap, unlike the in-process LanguageService runs above.
async function expectTscToAccept(checkDirName: string, files: Record<string, string>) {
const checkDir = join(TEMP_DIR, checkDirName);
const tsconfig = structuredClone(sourceTsconfig);
tsconfig.include = Object.keys(files);
tsconfig.compilerOptions.typeRoots = [join(BASE_FIXTURE_DIR, "node_modules", "@types")];
await mkdir(checkDir, { recursive: true });
await makeTree(checkDir, {
"tsconfig.json": JSON.stringify(tsconfig, null, 2),
...files,
});

await using proc = Bun.spawn({
cmd: [bunExe(), join(BASE_FIXTURE_DIR, "node_modules", "typescript", "bin", "tsc"), "-p", "."],
env: bunEnv,
cwd: checkDir,
stdout: "pipe",
stderr: "pipe",
});

const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);

expect(stderr.trim()).toBe("");
expect(stdout.trim()).toBe("");
expect(exitCode).toBe(0);
}

describe("Bun.mmap", () => {
test("MMapOptions accepts offset and size", async () => {
const checkDir = join(TEMP_DIR, "mmap-options-check");
const tsconfig = structuredClone(sourceTsconfig);
tsconfig.include = ["mmap-options.ts"];
tsconfig.compilerOptions.typeRoots = [join(BASE_FIXTURE_DIR, "node_modules", "@types")];
await mkdir(checkDir, { recursive: true });
await makeTree(checkDir, {
"tsconfig.json": JSON.stringify(tsconfig, null, 2),
await expectTscToAccept("mmap-options-check", {
"mmap-options.ts": `const view = Bun.mmap("./data.bin", { shared: true, sync: false, offset: 4096, size: 1024 });
view satisfies Uint8Array<ArrayBuffer>;
Bun.mmap("./data.bin", { offset: 4096 }) satisfies Uint8Array<ArrayBuffer>;
Bun.mmap("./data.bin", { size: 1024 }) satisfies Uint8Array<ArrayBuffer>;`,
});
});
});

await using proc = Bun.spawn({
cmd: [bunExe(), join(BASE_FIXTURE_DIR, "node_modules", "typescript", "bin", "tsc"), "-p", "."],
env: bunEnv,
cwd: checkDir,
stdout: "pipe",
stderr: "pipe",
describe("RedisClient", () => {
test("the redis fixture type-checks (pubsub and select overloads)", async () => {
await expectTscToAccept("redis-fixture-check", {
"utilities.ts": readFileSync(join(FIXTURE_SOURCE_DIR, "utilities.ts"), "utf8"),
"redis.ts": readFileSync(join(FIXTURE_SOURCE_DIR, "redis.ts"), "utf8"),
});

const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);

expect(stderr.trim()).toBe("");
expect(stdout.trim()).toBe("");
expect(exitCode).toBe(0);
});
});

Expand Down
31 changes: 31 additions & 0 deletions test/integration/bun-types/fixture/redis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,34 @@ await copy.unsubscribe();
await copy.unsubscribe("hello");

expectType(copy.unsubscribe("hello", () => {})).is<Promise<void>>();

// The PUBSUB subcommands the runtime is known to forward get typed replies;
// anything else (including lowercase spellings) goes through the untyped overload.
expectType(Bun.redis.pubsub("CHANNELS")).is<Promise<string[]>>();
expectType(Bun.redis.pubsub("CHANNELS", "news.*")).is<Promise<string[]>>();
expectType(Bun.redis.pubsub("NUMSUB")).is<Promise<(string | number)[]>>();
expectType(Bun.redis.pubsub("NUMSUB", "news", "sports")).is<Promise<(string | number)[]>>();
expectType(Bun.redis.pubsub("NUMPAT")).is<Promise<number>>();
expectType(Bun.redis.pubsub("SHARDCHANNELS")).is<Promise<string[]>>();
expectType(Bun.redis.pubsub("SHARDCHANNELS", "orders-*")).is<Promise<string[]>>();
expectType(Bun.redis.pubsub("SHARDNUMSUB")).is<Promise<(string | number)[]>>();
expectType(Bun.redis.pubsub("SHARDNUMSUB", "orders", "payments")).is<Promise<(string | number)[]>>();
expectType(Bun.redis.pubsub("HELP")).is<Promise<any>>();
expectType(Bun.redis.pubsub("channels", "news.*")).is<Promise<any>>();
expectType(copy.pubsub("NUMPAT")).is<Promise<number>>();

// @ts-expect-error a subcommand is required
Bun.redis.pubsub();
// @ts-expect-error the runtime rejects undefined arguments instead of skipping them
Bun.redis.pubsub("CHANNELS", undefined);

expectType(Bun.redis.select(1)).is<Promise<"OK">>();
expectType(Bun.redis.select("1")).is<Promise<"OK">>();
expectType(copy.select(2)).is<Promise<"OK">>();

// @ts-expect-error the database index is required
Bun.redis.select();
// @ts-expect-error the runtime rejects undefined arguments instead of skipping them
Bun.redis.select(undefined);
// @ts-expect-error SELECT takes exactly one argument
Bun.redis.select(1, "extra");
Loading