ultrastorage README

The project's README from github.com/yangshun/ultrastorage. As one markdown file: README.md.

ultrastorage

Adds rich-value serialization, expiration, namespacing, and schema validation to localStorage.

Get started at ultrastorage.dev for setup instructions, guides, and examples.

Documentation · API reference · React guide

Features

Installation

npm install ultrastorage

ultrastorage was previously published as greatstorage. Existing users can follow the migration guide.

How it works

Internally, every value is stored as an object rather than being written to the storage raw. That object carries the user value, an optional expiry, a version, and an internal __us marker. The marker is there so ultrastorage can reliably tell its own entries apart from unrelated keys already living in the same Storage. The version is there so the on-disk format can evolve later without guessing which shape an older entry used if this library ever needs to change the internal storage structure.

New writes use __us; existing entries marked with __gs remain fully supported without migration.

Refer to the documentation for more explanation regarding internals and design decisions.

Usage

The examples below cover the basics. Follow the getting started guide for a walkthrough, or browse the documentation for detailed guides.

Basic

Store and retrieve objects without calling JSON.stringify() and JSON.parse().

// lib/app-storage.ts
import { createStorage } from 'ultrastorage';

// Create an app-wide singleton instance.
export const appStorage = createStorage({ prefix: 'acme' });
// app.ts
import { appStorage } from './lib/app-storage';

appStorage.setItem('user', { name: 'Alice', age: 30 });
appStorage.getItem('user'); // { name: 'Alice', age: 30 }

Array keys

Use an array of strings when a key has several structural parts. Arrays with the same ordered segments address the same entry, even when they are different array instances.

appStorage.setItem(['users', userId, 'preferences'], { theme: 'dark' });
appStorage.getItem(['users', userId, 'preferences']); // { theme: 'dark' }

Array keys use an opaque internal encoding rather than the configured namespace separator, so separator characters and empty strings inside segments are preserved.

Store anything

Store Set, Map, Date, and other values that JSON does not preserve.

Uses devalue by default, but you can bring your own serializer.

appStorage.setItem('tags', new Set(['a', 'b', 'c']));
appStorage.getItem('tags'); // Set {'a', 'b', 'c'}

appStorage.setItem('metadata', new Map([['key', 'value']]));
appStorage.getItem('metadata'); // Map {'key' => 'value'}

appStorage.setItem('date', new Date('2025-01-01'));
appStorage.getItem('date'); // Date 2025-01-01T00:00:00.000Z

TTL / expiration

Set a lifetime or an absolute expiration time for stored data.

Expired data behaves like a missing key. getItem() removes expired entries on read, while has(), key(), keys(), and length simply ignore them. Use clearExpired() to proactively sweep them.

// Expires in 60 seconds
appStorage.setItem('token', 'abc123', { ttl: 60_000 });

// Expires at a specific date
appStorage.setItem('session', { id: 1 }, { expiresAt: new Date('2025-12-31') });

// Expired items return null
appStorage.getItem('token'); // null (after 60s)

Namespacing

Add a prefix to group related keys and avoid collisions with other storage users.

const appStorage = createStorage({ prefix: 'acme' });

appStorage.setItem('theme', 'dark'); // stored as "acme:theme"
appStorage.getItem('theme'); // 'dark'

localStorage.getItem('theme'); // null

// clear() only removes keys within the namespace
appStorage.clear();

Subscribe to changes

Subscribe to a key to update other parts of the page when its storage entry changes. Other ultrastorage instances using the same Storage object and fully prefixed key notify the same listeners, even if those instances never subscribe themselves.

const appStorage = createStorage({ prefix: 'app' });

function applyTheme() {
  document.documentElement.dataset.theme = appStorage.getItem<string>('theme') ?? 'system';
}

const unsubscribe = appStorage.subscribe('theme', (change) => {
  console.log(change.key, change.type, change.source);
  // 'theme', 'set' | 'remove' | 'expire', 'local' | 'external'
  applyTheme();
});

applyTheme(); // Subscribing does not invoke the listener immediately.

Another module can create its own instance with the same prefix and notify the subscriber above:

import { createStorage } from 'ultrastorage';

const appStorage = createStorage({ prefix: 'app' });
appStorage.setItem('theme', 'dark'); // Notifies this page synchronously.

When the subscriber is no longer needed, clean it up in the subscribing module:

unsubscribe(); // Safe to call again; each subscription is independent.

Tabs sharing localStorage receive changes asynchronously through browser storage events. sessionStorage remains scoped to its tab; in-memory and custom Storage objects support same-page notifications between instances using the exact same object.

Events describe storage changes, without carrying old/new values. Read the current value using getItem(), optionally with a schema. It may reflect a later write by the time you read it. Expiration stays lazy by default. Pass { reactiveExpiration: true } as the third argument to subscribe() to receive expire when the deadline passes, without deleting the entry. React hooks accept the same option. Later cleanup through getItem() or clearExpired() may emit another expire event. Timers may run late.

Check, remove, and clear

Check for entries, remove individual entries, or clear a namespace.

appStorage.has('user'); // true
appStorage.removeItem('user');
appStorage.has('user'); // false

appStorage.clear(); // remove all entries written by `ultrastorage`
appStorage.clearExpired(); // remove only expired entries

Type-safe access

Provide a type argument to type values returned from storage.

interface User {
  name: string;
  age: number;
}

const user = appStorage.getItem<User>('user');
// user is typed as User | null

appStorage.getOrInit<User>('user', () => ({ name: 'Alice', age: 30 }));

appStorage.updateItem<User>('user', (current) => ({
  ...current!,
  age: current!.age + 1,
}));

However, the true safe way is to validate with a schema during read.

Schema validation

Browser storage can be modified outside your application. Validate reads with any library that supports synchronous Standard Schema validation.

import { z } from 'zod';

const UserSchema = z.object({ name: z.string(), age: z.number() });

// Returns typed value if valid, null if validation fails
const user = appStorage.getItem('user', { schema: UserSchema });

Not just localStorage

Pass localStorage, sessionStorage, or any object that implements Storage. Use createMemoryStorage() for tests and server-side rendering.

import { createStorage, createMemoryStorage } from 'ultrastorage';

const appStorage = createStorage({
  prefix: 'acme',
  storage: typeof window === 'undefined' ? createMemoryStorage() : undefined,
});

Custom serializer

Provide custom stringify and parse methods to use another serialization format. Reads use only that parser by default. To try a second synchronous parser after it throws, set fallbackParser, for example fallbackParser: JSON.parse for older JSON envelopes. See fallback parsing and migration.

import superjson from 'superjson';

const appStorage = createStorage({
  prefix: 'acme',
  serializer: { stringify: superjson.stringify, parse: superjson.parse },
});

If you provide your own serializer and want to keep devalue out of your bundle entirely, import from ultrastorage/core instead. The only difference is that serializer is required.

import { createStorage } from 'ultrastorage/core';
import superjson from 'superjson';

const appStorage = createStorage({
  prefix: 'acme',
  serializer: { stringify: superjson.stringify, parse: superjson.parse },
});

Additional APIs

Use these helpers for common initialization and update operations.

appStorage.getOrInit()

Return the existing value, or create, store, and return a new value when the key is missing.

const prefs = appStorage.getOrInit('prefs', () => ({
  theme: 'light',
  lang: 'en',
}));

appStorage.updateItem()

Read, update, and write a value in one call.

appStorage.updateItem('count', (current) => (current ?? 0) + 1);

See the documentation for the full API reference, React integration, and caveats.

See also

Development

See the roadmap for planned features, the API reference for current behavior, and how it works for design explanations.

This project uses Vite+ with the Node.js version in .node-version and the package manager declared in package.json.

vp install
vp check
vp test run
vp run test:coverage # enforces 100% runtime coverage
vp pack

Use vp pack or vp run build to build this library, including ESM, CJS, and TypeScript declarations. vp build runs Vite's application build and expects an HTML entry point. Formatting, linting, and packaging options live in vite.config.ts.

License

MIT

ultrastorage (yangshun/ultrastorage)

Searching Database…

这是什么?一个增强 localStorage 功能的 TypeScript 库:它可以序列化丰富的数据类型,并在浏览器默认存储的基础上增加了键过期、命名空间、订阅和 schema 校验功能。
这是要打包进我前端应用的库,还是某种托管存储服务?它是一个客户端库,需要打包进你自己的 JavaScript 或 TypeScript 代码中——不在服务器上托管或安装任何东西。它提供支持 tree-shaking 的 ESM 和 CJS 构建,并带有完整的 TypeScript 类型定义。
我怎么把它加到项目里?通过 npm 安装 ultrastorage 包,并按照 ultrastorage.dev 上的入门指南操作。要注意一点:它之前是以 greatstorage 这个名字发布的,所以老用户应该查看迁移指南。
它会不会把我绑死在 React 或某个特定框架上?不会。核心是纯的、与框架无关的 TypeScript;React 支持是一个可选的 ultrastorage/react 适配器,提供带类型的值、函数式更新和服务端渲染支持。像 Zod 这类 schema 库同样是可选的。
在采用它之前,有什么坑值得提前知道?过期默认是惰性的:条目只有在被读取时或运行 clearExpired() 时才会消失。变更事件只描述发生了什么变化,但不携带旧值或新值,而且过期定时器可能会延迟执行。
我可以用 sessionStorage 或内存存储来代替 localStorage 吗?可以——传入 localStorage、sessionStorage,或任何实现了 Storage 接口的对象。createMemoryStorage() 提供一个内存后端,专门用于测试和服务端渲染。
我能不能不把 devalue 打包进我的产物?可以。从 ultrastorage/core 导入而不是主入口——唯一的区别是 serializer 选项变为必填,而 devalue 就完全不会进入你的打包产物。
有没有针对常见初始化和更新流程的辅助方法?有。appStorage.getOrInit() 返回已有的值,如果键不存在则创建、存储并返回一个新值;appStorage.updateItem() 用一次调用完成读取、更新和写入。
作者推荐了哪些类似的库?README 的“另见”部分列出了 store2、store.js、unstorage、storage-box 和 lscache——它们都是其他 localStorage 封装库,提供命名空间、TTL、可插拔驱动等特性。
Creator
yangshun
Size
0.6 MB
Stars
164
Age
7mo
Commits · 90 Days