
# Unstorage

[unstorage](https://unstorage.unjs.io) provides a common API for Redis, filesystem, S3, Cloudflare KV, and many other storage drivers. Adapt an unstorage instance to ocache's small [`StorageInterface`](/docs/storage#the-storage-interface), then pass that adapter to any cached function or handler.

## Install

:pm-install{name="ocache unstorage"}

Install any additional package required by your chosen unstorage driver.

## JSON adapter

Use `getItem` and `setItem` for the simplest adapter:

```ts
import type { StorageInterface } from "ocache";
import { createStorage } from "unstorage";
import redisDriver from "unstorage/drivers/redis";

const unstorage = createStorage({
  driver: redisDriver({
    url: process.env.REDIS_URL,
  }),
});

export const cacheStorage: StorageInterface = {
  get: (key) => unstorage.getItem(key),
  set: (key, value, options) =>
    value === null || value === undefined
      ? unstorage.removeItem(key)
      : unstorage.setItem(key, value, { ttl: options?.ttl }),
};
```

Use the same adapter instance wherever entries should share the backend:

```ts
import { defineCachedFunction, defineCachedHandler } from "ocache";
import { cacheStorage } from "./cache-storage";

export const getProduct = defineCachedFunction(loadProduct, {
  name: "product",
  maxAge: 60,
  storage: cacheStorage,
});

export const productPage = defineCachedHandler(renderProductPage, {
  name: "product-page",
  maxAge: 60,
  storage: cacheStorage,
});
```

A nullish value means deletion in ocache, including during invalidation, so the adapter must call `removeItem`. The TTL passed to `set` is in seconds and can be forwarded to unstorage unchanged.

> [!IMPORTANT]
> Do not set `binary: true` on this adapter. `setItem` serializes the complete cache entry, so a `Uint8Array` does not necessarily return as a byte view. ocache uses base64 for binary payloads when `binary` is unset.

## Blob adapter

For drivers with native `getItemRaw` and `setItemRaw`, use `createBlobStorage`. It stores each cache entry as one framed blob while keeping a handler's response body as bytes:

```ts
import { createBlobStorage } from "ocache";
import { createStorage } from "unstorage";
import fsDriver from "unstorage/drivers/fs";

const unstorage = createStorage({
  driver: fsDriver({ base: "./.cache" }),
});

export const cacheStorage = createBlobStorage({
  get: (key) => unstorage.getItemRaw(key),
  set: (key, value, options) =>
    value === null
      ? unstorage.removeItem(key)
      : unstorage.setItemRaw(key, value, { ttl: options?.ttl }),
});
```

This avoids base64 expansion for binary responses and JSON escaping for text response bodies. `createBlobStorage` declares binary support itself; do not wrap it in an adapter that removes that declaration.

Many drivers implement raw operations natively, including filesystem and byte-oriented object stores. Other drivers may emulate them with base64. Check the selected driver's behavior before choosing between the JSON and raw adapters.

> [!NOTE]
> `getItemRaw` and `setItemRaw` move one raw value, not an object graph. Do not pass a complete ocache entry directly to them; `createBlobStorage` supplies the required frame codec.

## Backend limits

Most unstorage drivers do not expose a per-entry limit through the common API. Set [`maxBodySize`](/docs/handler#response-size) on cached handlers when upstream response sizes are not trusted:

```ts
const image = defineCachedHandler(renderImage, {
  maxAge: 300,
  maxBodySize: 5 * 1024 * 1024,
  storage: cacheStorage,
});
```

If your backend has a known enforced ceiling, expose it through `maxEntryBytes` on a plain adapter or on the backend passed to `createBlobStorage`. ocache then derives a safe default response-body limit from it.

## See also

- [Storage](/docs/storage) — storage semantics, binary values, and shared instances
- [h3 integration](/integrations/h3) — use the adapter with an h3 cached handler
- [Invalidation & Expiration](/docs/invalidation) — removing or expiring stored entries
