@cutticat/storages
Key-value storage library with TTL support.
Contents
Overview
The library provides a JsonStorage<T> type (set, get, delete, clear). SchemaJsonStorage is the shared base: it loads a raw entry, optionally runs a Zod schema, and returns T. NullJsonStorage implements the type directly and stores nothing. The other backends extend the base:
- InMemoryJsonStorage — stores values in memory (any type, no serialization)
- WebJsonStorage — adapts a browser
Storage(localStorage,sessionStorage) - FileJsonStorage — stores JSON files on disk (Node.js)
- RedisJsonStorage — stores JSON in Redis
- PgJsonStorage — stores JSON in PostgreSQL
All storages support optional time-to-live (TTL) for entries.
Installation
pnpm i @cutticat/storages
Node.js storages require peer dependencies:
pnpm i redis # for RedisJsonStorage
pnpm i pg # for PgJsonStorage
FileJsonStorage uses @cutticat/fs-extras (included as a dependency).
Quick start
import { InMemoryJsonStorage } from "@cutticat/storages"
const storage = new InMemoryJsonStorage<string>()
await storage.set("key", "value", 60000)
const value = await storage.get("key")
With Redis and a Zod schema:
import { z } from "zod"
import { createClient } from "redis"
import { RedisJsonStorage } from "@cutticat/storages/node"
const client = createClient({ url: "redis://localhost:6379" })
await client.connect()
const schema = z.object({ id: z.number(), name: z.string() })
const storage = new RedisJsonStorage({
client,
prefix: "app:",
schema,
})
await storage.set("user:1", { id: 1, name: "John" }, 60000)
const user = await storage.get("user:1")
Entry points
@cutticat/storages— neutral:JsonStorage,SchemaJsonStorage,InMemoryJsonStorage,NullJsonStorage,isUsableTtl,JsonStorageEntrySchema.@cutticat/storages/node— node: same plusFileJsonStorage,RedisJsonStorage,PgJsonStorage(requireredis/pg).@cutticat/storages/browser— browser: same as the neutral entry plusWebJsonStorage.
API
JsonStorage<T>
set(key, value, ttl?)— stores a value with optional TTL (ms)get(key)— returns the value or undefineddelete(key)— removes an entryclear()— clears the storage
SchemaJsonStorage<T>
Abstract base. A subclass implements set, delete, clear, and _getRaw. get treats undefined from _getRaw as a missing key, then runs an optional Zod schema.
Options:
schema— optional Zod schema for validation on readthrowOnParseError— when true, throws on invalid data (default false, returns undefined)prepareRaw— turns the raw entry into the value the schema sees (default parses JSON strings)
isUsableTtl
Returns whether a TTL is a finite number greater than zero. A missing TTL and Infinity mean the value stays.
JsonStorageEntrySchema
Envelope { value, expiresAt } written by FileJsonStorage and WebJsonStorage. JsonStorageEntry is the type inferred from the schema.
NullJsonStorage<T>
Empty storage: stores nothing, always returns undefined. Implements JsonStorage directly. For tests and mocks.
InMemoryJsonStorage<T>
In-memory storage. Stores values as-is (no JSON). Suitable for any type. Optional schema and throwOnParseError, same as the base. TTL is a timer: get does not recheck the deadline. The runtime clamps a delay above 2^31−1 ms (about 24.8 days), so a longer TTL can expire immediately.
FileJsonStorage<T>
File system storage (Node.js). Stores each value in a separate JSON file. Uses @cutticat/fs-extras.
Options:
rootDir— root directory for filesschema— optional Zod schema for validation on readthrowOnParseError— when true, throws on invalid data (default false)throwOnReadError— when true, throws on file read errors (default false)now— function to get the current time (defaultDate.now, useful for tests)keyToPath— function to map a key to a file path (default base64url, useful for tests)
clear deletes rootDir and everything inside it. A file that is not the { value, expiresAt } envelope is removed on read. A value that fails schema stays on disk; get returns undefined.
RedisJsonStorage<T>
Redis storage. Serializes to JSON on write, deserializes on read.
Options:
client— Redis clientprefix— prefix for all keys (default"", soclearmatches every key)schema— optional Zod schema for validation on readthrowOnParseError— when true, throws on invalid data (default false, returns undefined)
TTL is milliseconds, sent as SET with expiration type PX. clear runs KEYS for the prefix and deletes the matches. KEYS walks the keyspace.
WebJsonStorage<T>
Adapter over a browser Storage (localStorage, sessionStorage, or the same shape). Each value is a JSON envelope { value, expiresAt }. clear deletes keys that start with the prefix.
Options:
storage— browser storageprefix— prefix for all keys (default"", soclearmatches every key)schema— optional Zod schema for validation on readthrowOnParseError— when true, throws on invalid data (default false, returns undefined)now— function to get the current time (defaultDate.now, useful for tests)
A value that is not the { value, expiresAt } envelope is removed on read. A value that fails schema stays in storage; get returns undefined.
PgJsonStorage<T>
PostgreSQL storage. Serializes to JSON on write, deserializes on read. Creates the table on first access.
Options:
pool— pg connection pooltableName— table name (defaultcutticat_pg_storage)schema— optional Zod schema for validation on readthrowOnParseError— when true, throws on invalid data (default false, returns undefined)now— function to get the current time (defaultDate.now, useful for tests)
Expiry reads expires_at as a Date, a finite timestamp in milliseconds, or a date string.
Documentation
See also:
Full API documentation is available in JSDoc comments.
Requirements
- Node.js >= 22.18.0
- pnpm >= 10.17.0
- TypeScript >= 5.9.0
- redis >= 4.7.0 (for RedisJsonStorage)
- pg >= 8.0.0 (for PgJsonStorage)