-
Notifications
You must be signed in to change notification settings - Fork 0
storage
io.cache reads and writes entries through a swappable storage interface — get / set / delete / clear / keys, sync or async. The TTL, revalidation, and invalidation logic is backend-agnostic; the backend only stores bytes. Four backends ship under double-meh/storage/:
| Backend | Import | Platform | Persistence |
|---|---|---|---|
| memory | double-meh/storage/memory.js |
everywhere | none — per instance (the default) |
| filesystem | double-meh/storage/fs.js |
Node, Bun, Deno | survives runs; shared across processes |
| SQLite | double-meh/storage/sqlite.js |
Node ≥ 22.5, Bun | survives runs; many small entries, concurrent writers |
| Cache API | double-meh/storage/cache-api.js |
browsers, Deno | survives reloads |
Swap one in by assigning io.cache.storage:
import io from 'double-meh';
import {fsStorage} from 'double-meh/storage/fs.js';
io.cache.storage = fsStorage({name: 'my-app'});The default: a plain Map behind the interface. Zero setup, zero persistence — the right fit for a long-running service and intra-run dedup.
import {memoryStorage} from 'double-meh/storage/memory.js';
io.cache.storage = memoryStorage(); // a fresh, empty storeOne file per entry in the OS cache directory — $XDG_CACHE_HOME / ~/.cache on Linux, ~/Library/Caches on macOS, %LOCALAPPDATA% on Windows — namespaced by name. Filenames are SHA-256 hashes of the cache key; each file is a JSON metadata line followed by the raw body bytes. Writes go to a temp file and are renamed into place, so concurrent processes never observe a torn entry. Corrupt or foreign files degrade to a cache miss.
import {fsStorage} from 'double-meh/storage/fs.js';
io.cache.storage = fsStorage({name: 'my-app'}); // <OS cache dir>/my-app/
io.cache.storage = fsStorage({directory: '/tmp/scratch-cache'}); // explicit locationOptions: name (app namespace inside the OS cache dir, default 'double-meh') or directory (explicit path, overrides name). The resolved path is exposed as storage.directory.
An optional backend for heavy use: many small entries, concurrent writers, transactional atomicity. It is feature-detected — bun:sqlite on Bun, node:sqlite on Node (≥ 22.5) — and deliberately not supported on Deno, which ships no built-in driver: a dependency would leave the zero-dependency path. The factory is async because the driver import is.
import {sqliteStorage} from 'double-meh/storage/sqlite.js';
io.cache.storage = await sqliteStorage({name: 'my-app'}); // <OS cache dir>/my-app/cache.sqlite
io.cache.storage = await sqliteStorage({database: ':memory:'}); // explicit databaseOptions: name (as above) or database (a file path or ':memory:'). The storage object adds close() and exposes the resolved path as storage.database.
The browser backend: entries persist in caches.open(name) and survive reloads, so a returning user starts warm. Entry metadata rides as synthetic x-io-* headers on the stored Response (stripped on read). Requires a secure context (HTTPS or localhost).
import {cacheApiStorage} from 'double-meh/storage/cache-api.js';
io.cache.storage = cacheApiStorage({name: 'my-app'});Deno also ships the Cache API, but without cache.keys() — keys, sweep, and prefix eviction need a browser there; exact get/set/remove work everywhere.
Freshness on reload is a recipe, not built-in behavior: to let a full reload clear the app cache, call caches.delete('my-app') (optionally gated on a real reload via the Navigation Timing API), or scope it with prefix eviction.
Any object with the five methods works; each may return a value or a promise:
io.cache.storage = {
get: key => myStore.read(key), // CacheEntry | undefined
set: (key, entry) => myStore.write(key, entry),
delete: key => myStore.drop(key),
clear: () => myStore.reset(),
keys: () => myStore.list() // string[]
};An entry is {status, statusText, headers, body, etag?, lastModified?, expiresAt, vary?} — headers as an array of pairs, body an ArrayBuffer, expiresAt an absolute timestamp (Infinity = never expires), and vary an optional selecting-header snapshot (see Cache § Representations and Vary) that must round-trip as stored. Keep etag / lastModified mirrored in headers (the built-in entries always are — both fields are derived from them).
Guides
Concepts
Services
Modules
- fetch transport
- keys & URLs
- helpers
- records
- sse
- storage backends
- compression encoders
- Service Worker integration
Cookbook