agentsclimarketplace

Unstorage

Skill BetaHuhn/skills/unstorage

A collection of skills for use with AI coding tools

Install
npx -y skills add BetaHuhn/skills --skill unstorage

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Guide for setting up and using the unstorage npm package — a universal key-value storage API with 20+ built-in drivers. Use this skill when the user is working with unstorage, importing from "unstorage" or "unstorage/drivers/*", asking about storage drivers (memory, filesystem, Redis, Cloudflare KV, MongoDB, etc.), key-value persistence, multi-storage mounting, prefixStorage, snapshots, or building a custom driver. Trigger on mentions of "unstorage", "createStorage", "storage driver", or "universal key-value".

SKILL.md

12.9 KB, as published. Nobody here has run it

unstorage

unstorage is a runtime-agnostic universal key-value storage API with a unified interface and 20+ built-in drivers. It works on Node.js, Bun, Deno, and edge Workers.

Install:

npm install unstorage

Core concepts

  • All keys use foo:bar colon-separated convention (equivalent to foo/bar path-style)
  • The default driver is in-memory (Map-based) — no config needed to get started
  • All methods are async and return Promises
  • Values are automatically JSON-serialized/deserialized
  • Drivers are mounted Unix-style at arbitrary namespace prefixes

Basic usage

import { createStorage } from "unstorage";

const storage = createStorage(/* opts */);

await storage.setItem("foo:bar", { hello: "world" });
await storage.getItem("foo:bar"); // => { hello: "world" }
await storage.hasItem("foo:bar"); // => true
await storage.removeItem("foo:bar");

Fetching up-to-date documentation

unstorage provides LLM-optimized docs. Fetch these when you need precise details on a specific driver or topic:

  • Full docs index: https://unstorage.unjs.io/llms.txt
  • Complete docs (single file): https://unstorage.unjs.io/llms-full.txt

Individual pages (Markdown):

  • https://unstorage.unjs.io/raw/guide.md — Getting started
  • https://unstorage.unjs.io/raw/guide/utils.md — Utilities
  • https://unstorage.unjs.io/raw/guide/http-server.md — HTTP server
  • https://unstorage.unjs.io/raw/guide/custom-driver.md — Custom driver
  • https://unstorage.unjs.io/raw/drivers.md — All drivers overview
  • https://unstorage.unjs.io/raw/drivers/{driver-name}.md — Specific driver (e.g. redis, fs, cloudflare, mongodb)

Always fetch the relevant driver's raw Markdown before writing driver-specific code — driver options, peer dependencies, and examples can vary significantly.

Full API reference

Read

await storage.hasItem("key");           // boolean — also: storage.has()
await storage.getItem("key");           // value | null — also: storage.get()
await storage.getItems([               // batch read (experimental)
  "key1",
  { key: "key2", options: {} },
]); // => [{ key, value }, ...]
await storage.getItemRaw("file.bin");  // raw Buffer/Uint8Array
await storage.getKeys("base?");        // string[] of all keys — also: storage.keys()
await storage.getMeta("key");          // { mtime, atime, size, ...custom }

Write

await storage.setItem("key", value);          // also: storage.set()
await storage.setItems([{ key, value }]);     // batch write (experimental)
await storage.setItemRaw("file.bin", buffer); // raw binary write
await storage.setMeta("key", { flag: 1 });   // custom metadata (stored at key$)

Delete

await storage.removeItem("key");             // also: storage.del(), storage.remove()
await storage.removeItem("key", { removeMeta: true }); // also remove metadata
await storage.removeMeta("key");
await storage.clear("base?");               // remove all (optionally under a base)

Lifecycle

await storage.dispose(); // close open handles (call before process exit)

Watch

const unwatch = await storage.watch((event, key) => {
  // event: "update" | "remove"
});
await unwatch();        // stop this specific watcher
await storage.unwatch(); // stop all watchers

TypeScript generics

// Type the return value of getItem
await storage.getItem<string>("k");     // => string | null

// Type an entire storage instance
const storage = createStorage<string>();
storage.setItem("k", "val");  // OK
storage.setItem("k", 123);   // TS error

// Type a storage namespace (key map)
type StorageDefinition = {
  items: { foo: string; count: number };
};
const storage = createStorage<StorageDefinition>();

Drivers

Pass a driver to createStorage({ driver }) to change the backend. The default is memory.

Memory (default)

import { createStorage } from "unstorage";
import memoryDriver from "unstorage/drivers/memory";

const storage = createStorage({ driver: memoryDriver() });

Filesystem (Node.js)

Requires no extra install. Maps keys to files on disk. Supports watch via chokidar.

import { createStorage } from "unstorage";
import fsDriver from "unstorage/drivers/fs";

const storage = createStorage({
  driver: fsDriver({ base: "./data" }),
});
// Options: base, ignore, watchOptions

Lite variant (no extra dependencies):

import fsLiteDriver from "unstorage/drivers/fs-lite";
const storage = createStorage({ driver: fsLiteDriver({ base: "./data" }) });

Redis

npm install ioredis
import { createStorage } from "unstorage";
import redisDriver from "unstorage/drivers/redis";

const storage = createStorage({
  driver: redisDriver({
    base: "app",        // key namespace prefix
    host: "localhost",
    port: 6379,
    // password, tls, url, ttl, scanCount, preConnect, cluster, clusterOptions
  }),
});

// Per-item TTL
await storage.setItem("session:abc", data, { ttl: 3600 }); // seconds

Cloudflare KV

import cloudflareKVBindingDriver from "unstorage/drivers/cloudflare-kv-binding";

// Inside a Worker:
const storage = createStorage({
  driver: cloudflareKVBindingDriver({ binding: "MY_KV" }),
});

MongoDB

npm install mongodb
import mongodbDriver from "unstorage/drivers/mongodb";

const storage = createStorage({
  driver: mongodbDriver({
    connectionString: "mongodb://localhost:27017",
    databaseName: "mydb",
    collectionName: "storage",
  }),
});

Browser (localStorage / sessionStorage / IndexedDB)

import localStorageDriver from "unstorage/drivers/localstorage";
import sessionStorageDriver from "unstorage/drivers/session-storage";
import indexedDbDriver from "unstorage/drivers/indexedb";

const storage = createStorage({ driver: localStorageDriver({ base: "app:" }) });

Other built-in drivers

DriverImportNotes
LRU Cacheunstorage/drivers/lru-cacheIn-memory, requires lru-cache
GitHubunstorage/drivers/githubRead-only, maps repo files
HTTPunstorage/drivers/httpRemote HTTP endpoint
Vercel KVunstorage/drivers/vercel-kvRequires @vercel/kv
Netlify Blobsunstorage/drivers/netlify-blobs
Azureunstorage/drivers/azure-*Multiple Azure storage services
SQL Databaseunstorage/drivers/dbRequires db0
PlanetScaleunstorage/drivers/planetscale

Full list: https://unstorage.unjs.io/drivers

Multi-storage mounting

Mount additional drivers at specific key namespaces. Keys routed by longest matching prefix.

import { createStorage } from "unstorage";
import fsDriver from "unstorage/drivers/fs";
import redisDriver from "unstorage/drivers/redis";

const storage = createStorage({}); // default: memory

// Filesystem for assets
storage.mount("assets", fsDriver({ base: "./public" }));

// Redis for sessions
storage.mount("sessions", redisDriver({ host: "localhost" }));

// => storage.setItem("assets:logo.png", ...)  → writes to ./public/logo.png
// => storage.setItem("sessions:abc", ...)      → writes to Redis
// => storage.setItem("config:foo", ...)        → writes to memory

Mount options:

storage.mount("logs", fsDriver({ base: "./logs" }), {
  readOnly: true,   // disable setItem / setItemRaw
  noClear: true,    // disable clear()
});

Inspect mounts:

storage.getMount("sessions:abc");
// => { base: "sessions:", driver: ... }

storage.getMounts("sessions");
// => [{ base: "sessions:", driver }]

await storage.unmount("sessions"); // unregister + dispose

Namespaced sub-storage

Create a scoped instance that prefixes all operations — useful for module isolation:

import { createStorage, prefixStorage } from "unstorage";

const storage = createStorage();
const userStorage = prefixStorage(storage, "users");

await userStorage.setItem("alice", { role: "admin" });
// Same as: storage.setItem("users:alice", { role: "admin" })

await userStorage.getKeys();
// Returns keys WITHOUT the "users:" prefix

Snapshots

Serialize all items to/from a plain object — useful for testing, seeding, or backups:

import { snapshot, restoreSnapshot } from "unstorage";

// Capture
const data = await snapshot(storage, "config"); // base is optional
// => { "theme": "dark", "lang": "en" }

// Restore
await restoreSnapshot(storage, data, "config");

Custom driver

Implement a driver using defineDriver. Only implement the methods you need.

import { createStorage, defineDriver } from "unstorage";

const myDriver = defineDriver((options: { prefix?: string } = {}) => {
  const map = new Map<string, string>();

  return {
    name: "my-driver",
    options,

    async hasItem(key) { return map.has(key); },
    async getItem(key) { return map.get(key) ?? null; },
    async setItem(key, value) { map.set(key, value); },
    async removeItem(key) { map.delete(key); },
    async getKeys(base) {
      return [...map.keys()].filter(k => !base || k.startsWith(base));
    },
    async clear(base) {
      for (const key of await this.getKeys(base)) map.delete(key);
    },
    async dispose() { map.clear(); },
    // async watch(callback) { ... }  // optional
  };
});

const storage = createStorage({ driver: myDriver({ prefix: "app" }) });

Custom driver rules:

  • Keys follow foo:bar colon convention
  • getItem returns a string, serializable object, or null
  • If you implement watch, unstorage's default change events are disabled — you must emit them manually on getItem, setItem, and removeItem
  • Clean up open handles (timers, connections) in dispose()

HTTP server

Expose a storage instance over HTTP for remote access:

npm install listhen
// server.ts
import { listen } from "listhen";
import { createStorage } from "unstorage";
import { createStorageServer } from "unstorage/server";

const storage = createStorage();
const storageServer = createStorageServer(storage, {
  authorize(req) {
    // req: { key, type, event }
    if (req.type === "read" && req.key.startsWith("private:")) {
      throw new Error("Unauthorized");
    }
  },
});

await listen(storageServer.handle);

HTTP method mapping:

  • GET /keygetItem(key)
  • GET /base/getKeys(base)
  • HEAD /keyhasItem(key) (returns 404 if missing)
  • PUT /keysetItem(key, body)
  • DELETE /keyremoveItem(key)
  • DELETE /base/clear(base)

Connect a client using the HTTP driver:

import httpDriver from "unstorage/drivers/http";

const client = createStorage({
  driver: httpDriver({ base: "http://localhost:3000" }),
});

Common patterns

Caching with TTL (Redis)

const cache = createStorage({
  driver: redisDriver({ host: "localhost", ttl: 60 }),
});

await cache.setItem("result:1", data, { ttl: 300 }); // 5 min TTL

Feature flag / config store

const config = prefixStorage(storage, "config");

await config.setItem("feature:dark-mode", true);
const enabled = await config.getItem<boolean>("feature:dark-mode");

Layered storage (overlay pattern)

Use overlayDriver to stack a writeable layer on top of a read-only base:

import overlayDriver from "unstorage/drivers/overlay";
import fsDriver from "unstorage/drivers/fs";
import memoryDriver from "unstorage/drivers/memory";

const storage = createStorage({
  driver: overlayDriver({
    layers: [memoryDriver(), fsDriver({ base: "./defaults" })],
  }),
});

Common pitfalls

  • Missing await — all storage methods are async; forgetting await silently does nothing
  • Key format inconsistency — prefer foo:bar over foo/bar; both work but mixing causes confusion
  • Not calling dispose() — drivers with open handles (Redis, DB connections, FS watchers) will leak if not disposed before process exit
  • Mutating objects from getItem — the returned value may be a reference; clone it if needed
  • No TTL support in all drivers — TTL is only supported where the underlying store supports it (e.g. Redis); memory driver ignores ttl
  • watch in custom drivers — if you implement watch, you must emit events yourself; unstorage's default handler is disabled

Further reading

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.