---
title: "@unikvs/redis.bun"
description: "Learn how to use the storage plugin that saves byte sequences to Redis with Bun's Redis client."
---

## Overview [#overview]

`@unikvs/redis.bun` is a storage plugin that saves data to Redis using Bun's built-in Redis client. Keys are namespaced with a prefix.Bun only

It handles byte sequences only. `write` accepts a `Uint8Array<ArrayBuffer>` and `read` returns a `Uint8Array<ArrayBuffer>`. Bytes are stored as-is in Redis string values and fetched with `getBuffer`. Connections use the `RedisClient` from the `bun` module via dynamic import. Redis server 7.2 or later is required.

Install it as follows.

```package-install
npm install @unikvs/redis.bun
```

It depends on `@unikvs/core`.

## Usage [#usage]

The central class is `Redis`.

```ts
import { Redis } from "@unikvs/redis.bun";
```

```ts
public constructor(url?: string, options?: RedisStorageOptions)
```

`url` is the connection target. When omitted, Bun resolves it from `REDIS_URL`, `VALKEY_URL`, and then `redis://localhost:6379`, in that order.

`options` is `RedisOptions` plus `keyPrefix` and `allowRepair`.

- `keyPrefix` is a string prepended to every key. The default is `"unikvs:"`, which limits what `clear()` deletes to keys under this prefix. An empty string uses keys as-is.
- `allowRepair` controls whether repair writes are allowed. The default is `false`. When set to `false`, repair `write` and `getWritable` calls fail with `RepairNotAllowedError`.
- All other properties are passed to Bun's `RedisClient`, such as `connectionTimeout`, `autoReconnect`, `maxRetries`, and `tls`.

The main members are as follows.

- `name` is `"Redis"`.
- `isOpen` indicates whether the storage is open.
- `open()` creates the client and establishes the connection.
- `close()` closes the connection.
- `write`, `read`, `exists`, `delete`, and `clear` handle data.

```ts
import { Redis } from "@unikvs/redis.bun";

const storage = new Redis("redis://localhost:6379", { keyPrefix: "myapp:" });
await storage.open();

await storage.write({
  key: "hello.bin",
  data: new TextEncoder().encode("hello"),
});

const data = await storage.read({ key: "hello.bin" });
console.log(new TextDecoder().decode(data));

storage.close();
```

## Key Layout [#keys]

With the default prefix, the key `hello.bin` becomes the Redis key `unikvs:hello.bin`. No conversion or escaping beyond the prefix is applied.

```text
unikvs:hello.bin
unikvs:greeting.bin
```

- Keys are built with `` `${this.keyPrefix}${key}` ``.
- Redis keys are binary-safe, so unlike `@unikvs/fs.bun`, keys containing slashes, spaces, or `:` work as-is.
- `clear()` uses `SCAN` to enumerate keys matching `` `${this.keyPrefix}*` `` and deletes them with `DEL` in batches of 1000. When the prefix is empty, the pattern is `"*"`.
- The temporary key for `getWritable` is `` `${dest}.${Bun.randomUUIDv7()}.tmp` ``. It lives under the same prefix, so `clear()` also removes temporary keys left behind by aborts.

:::warning
Do not include glob characters such as `*` or `?` in `keyPrefix`. The `SCAN` pattern used by `clear()` would match unintended keys.
:::

## Streams [#streams]

Both readable and writable streams are supported.

Obtain a writable stream with the following method.

```ts
public getWritable(
  args: Pick<IStorage.GetWritableArgs, "key"> & { signal?: AbortSignal },
): WritableStream<Uint8Array<ArrayBuffer>>
```

It creates an empty temporary key on start and appends each chunk with `APPEND`. It uses swap-on-close: data is renamed to the final key with `RENAME` on successful close. On failure or abort, the temporary key is removed, preserving existing data. `RENAME` is atomic, so readers never observe partially written contents. Concurrent writes to the same key don't collide thanks to unique temporary keys, and the last completed `RENAME` wins.

Obtain a readable stream with the following method.

```ts
public getReadable(
  args: Pick<IStorage.GetReadableArgs, "key" | "signal">,
): ReadableStream<Uint8Array<ArrayBuffer>>
```

Redis `GET` returns the whole value at once, so the bytes from `getBuffer` are emitted as a single chunk.

## Notes [#notes]

- Bun only. `open()` throws `UnsupportedRuntimeError` on runtimes without the `Bun` global.
- Redis server 7.2 or later is required.
- Don't call `write` or `read` before `open()`. The internal connection is `null` until then. Check with `isOpen`.
- `open()` waits until the connection is established before setting `isOpen` to true. If connecting fails, `isOpen` stays false and the error propagates.
- Calling `open()` while already open closes the old connection and opens a new one.
- Calling `close()` a second time throws a `TypeError`, as do operations after `close`.
- `write`, `read`, `exists`, `delete`, and `clear` reject when given an already aborted `AbortSignal`. Redis commands cannot be cancelled after they are sent, so in-flight commands are not interrupted.
- `read` throws `KeyNotFoundError` for missing keys. `getReadable` surfaces the same error when the stream is read.
- `delete` does not error for missing keys.
- `exists` returns the boolean converted by Bun's client.
- `read` and `getReadable` normalize the `Buffer` returned by Bun into a `Uint8Array`.
- Persistence follows the Redis server configuration. Servers with RDB and AOF disabled lose data on restart.

:::warning
With an empty `keyPrefix`, `clear()` deletes every key in the selected database. Always use a unique prefix on shared databases.
:::

## Errors [#errors]

| Error | Condition |
| --- | --- |
| `UnsupportedRuntimeError` | Thrown by `open()` on runtimes without the `Bun` global. A shared error from `@unikvs/core`. |
| `KeyNotFoundError` | Thrown when reading a missing key with `read` or `getReadable`. A shared error from `@unikvs/core`. |

```ts
import { KeyNotFoundError, Redis, UnsupportedRuntimeError } from "@unikvs/redis.bun";

const storage = new Redis();

try {
  await storage.open();
  await storage.read({ key: "missing.bin" });
} catch (error) {
  if (error instanceof UnsupportedRuntimeError) {
    console.error("Run this on the Bun runtime");
  } else if (error instanceof KeyNotFoundError) {
    console.error(`Key ${error.meta.key} was not found`);
  } else {
    throw error;
  }
}
```

Connection, authentication, and server-side rejections propagate the errors thrown by Bun's client. You can distinguish them by `code`, such as `ERR_REDIS_CONNECTION_CLOSED` or `ERR_REDIS_SERVER_ERROR`.

## Examples [#examples]

A minimal example storing to `redis://localhost:6379`.

```ts
import { Redis } from "@unikvs/redis.bun";

const storage = new Redis("redis://localhost:6379", { keyPrefix: "unikvs-example:" });
await storage.open();

const key = "greeting.bin";
await storage.write({
  key,
  data: new TextEncoder().encode("hello, Redis"),
});

if (await storage.exists({ key })) {
  const data = await storage.read({ key });
  console.log(new TextDecoder().decode(data));
}

const writer = storage.getWritable({ key: "stream.bin" }).getWriter();
await writer.write(new Uint8Array([1, 2, 3]));
await writer.write(new Uint8Array([4, 5, 6]));
await writer.close();

await storage.delete({ key });
await storage.clear();
storage.close();
```
