locstore
v1.3.0
Published
A tiny TypeScript-first browser storage library with TTL, TimeSpan, sync/async APIs, adapters, and optional encryption.
Maintainers
Readme
locstore
locstore 是一个极简、类型友好、支持过期时间和可选加密的前端缓存库。
它的核心目标是让缓存操作足够自然:
store.set("user", user);
store.get<User>("user");
store.del("user");异步存储统一使用 Async 后缀:
await store.setAsync("list", list);
await store.getAsync<ListItem[]>("list");特性
- 极简 API:
set、get、del、has、clear - 同步和异步方法边界清晰:
set/setAsync - 内置
TimeSpan,不依赖dayjs - 支持
localStorage、sessionStorage、cookie、memory、url、history - 支持异步存储
indexedDB、Cache API - 支持自定义同步/异步 storage adapter
- 支持全局加密、自定义加密和异步加密
- 读取加密数据时自动解密,不需要手动传
decrypt: true - 保留旧版
StoreType兼容写法 - TypeScript 类型由源码生成
安装
npm install locstorepnpm add locstoreyarn add locstore快速开始
import store, { TimeSpan } from "locstore";
store.set("name", "张三");
const name = store.get<string>("name");
store.set("code", "123456", TimeSpan.fromSeconds(60));
const code = store.get<string>("code");带默认值:
const theme = store.get("theme", "light");删除:
store.del("name");清空当前存储:
store.clear();TimeSpan
TimeSpan 表示一段时间,而不是某个绝对日期。它的设计接近 C#,但推荐使用 TypeScript/JavaScript 常见的小驼峰命名。
TimeSpan.fromMilliseconds(500);
TimeSpan.fromSeconds(60);
TimeSpan.fromMinutes(5);
TimeSpan.fromHours(2);
TimeSpan.fromDays(7);也支持 C# 风格别名:
TimeSpan.FromSeconds(60);
TimeSpan.FromMinutes(5);字符串解析:
TimeSpan.parse("500ms");
TimeSpan.parse("10s");
TimeSpan.parse("5m");
TimeSpan.parse("2h");
TimeSpan.parse("7d");组合时间:
const ttl = TimeSpan.from({
days: 1,
hours: 2,
minutes: 30,
});读取总时长:
const ttl = TimeSpan.fromMinutes(5);
ttl.totalMilliseconds; // 300000
ttl.totalSeconds; // 300
ttl.totalMinutes; // 5设置过期时间
最简写法:
store.set("sms-code", "123456", TimeSpan.fromSeconds(60));完整配置:
store.set("token", token, {
ttl: TimeSpan.fromHours(2),
});ttl 支持三种形式:
store.set("a", 1, { ttl: 1000 }); // 毫秒
store.set("b", 2, { ttl: "10s" }); // 字符串
store.set("c", 3, { ttl: TimeSpan.fromMinutes(5) }); // TimeSpan兼容旧字段 expires:
store.set("legacy", "value", {
expires: TimeSpan.FromDays(1),
});也可以传绝对时间:
store.set("deadline", "value", {
expires: new Date("2026-12-31T23:59:59.000Z"),
});支持的存储
同步存储:
| 名称 | 说明 |
| --- | --- |
| "local" | 使用 localStorage,默认存储 |
| "session" | 使用 sessionStorage |
| "cookie" | 使用 document.cookie |
| "memory" | 内存存储,刷新页面后丢失 |
| "url" | 使用 URL query 参数保存状态 |
| "history" | 使用 history.state 保存状态 |
异步存储:
| 名称 | 说明 |
| --- | --- |
| "indexedDB" | 使用 IndexedDB,适合较大数据 |
| "cache" | 使用 Browser Cache API,适合缓存响应类数据 |
示例:
store.set("user", user, { storage: "local" });
store.set("tab", tabState, { storage: "session" });
store.set("theme", "dark", { storage: "cookie" });
store.set("draft", draft, { storage: "memory" });异步存储需要使用 Async 方法:
await store.setAsync("articles", articles, {
storage: "indexedDB",
ttl: TimeSpan.fromMinutes(30),
});
const articles = await store.getAsync<Article[]>("articles", {
storage: "indexedDB",
});同步方法遇到异步存储会抛出明确错误:
store.set("articles", articles, { storage: "indexedDB" });
// Error: Storage "indexedDB" is asynchronous. Use the Async API.兼容 StoreType
推荐新写法使用字符串:
store.set("name", "张三", { storage: "session" });旧写法仍然可用:
import store, { StoreType } from "locstore";
store.set("name", "张三", {
storage: StoreType.SESSIONSTORAGE,
});
store.get<string>("name", {
storage: StoreType.SESSIONSTORAGE,
});创建实例
默认导出的 store 已经可以直接使用。复杂项目推荐用 createStore 创建自己的实例:
import { createStore, TimeSpan } from "locstore";
const userStore = createStore({
name: "user",
storage: "local",
ttl: TimeSpan.fromDays(7),
});
userStore.set("profile", profile);
userStore.get<UserProfile>("profile");使用 with 派生配置:
const sessionStore = store.with({ storage: "session" });
const cookieStore = store.with({ storage: "cookie" });使用命名空间:
const authStore = store.use("auth");
authStore.set("token", token);
authStore.get<string>("token");命名空间会隔离 key。上面的实际 key 类似:
auth:token命名空间可以继续嵌套:
const adminAuthStore = store.use("admin").use("auth");同步 API
set
store.set(key, value);
store.set(key, value, ttl);
store.set(key, value, options);示例:
store.set("user", user);
store.set("code", "123456", TimeSpan.fromSeconds(60));
store.set("token", token, {
ttl: "2h",
storage: "session",
encrypt: true,
});get
store.get<T>(key);
store.get<T>(key, defaultValue);
store.get<T>(key, options);
store.get<T>(key, defaultValue, options);示例:
const user = store.get<User>("user");
const theme = store.get("theme", "light");
const token = store.get<string>("token", {
storage: "session",
});
const lang = store.get("lang", "zh-CN", {
storage: "cookie",
});del / remove
store.del("token");
store.remove("token"); // alias指定存储:
store.del("token", { storage: "session" });has
if (store.has("token")) {
// ...
}keys
const keys = store.keys();指定存储:
const keys = store.keys({ storage: "session" });clear
store.clear();清空指定存储:
store.clear({ storage: "session" });如果实例设置了命名空间,clear 只清理当前命名空间。
ttl
获取剩余过期时间,单位为毫秒:
const ttl = store.ttl("token");返回值:
number: 剩余毫秒数null: 不会过期undefined: key 不存在或已经过期
expire
设置或刷新过期时间:
store.expire("token", TimeSpan.fromHours(1));设置为永不过期:
store.expire("token", null);meta
读取缓存元信息:
const meta = store.meta("token");返回示例:
{
key: "token",
storage: "local",
namespace: "auth",
v: 1,
createdAt: 1779780000000,
updatedAt: 1779780000000,
expiresAt: 1779783600000,
encrypted: true,
ttl: 3600000
}update
基于旧值更新:
store.update<number>("count", (value) => (value ?? 0) + 1);getOrSet
缓存不存在时创建:
const config = store.getOrSet("config", () => {
return createDefaultConfig();
});setMany / getMany / delMany
store.setMany({
token: "a",
refreshToken: "b",
});
const values = store.getMany<string>(["token", "refreshToken"]);
store.delMany(["token", "refreshToken"]);clearExpired
清理已经过期的缓存:
const count = store.clearExpired();返回被清理的数量。
异步 API
异步方法和同步方法一一对应,统一加 Async 后缀。
await store.setAsync("key", value);
await store.getAsync<T>("key");
await store.delAsync("key");
await store.hasAsync("key");
await store.keysAsync();
await store.clearAsync();
await store.ttlAsync("key");
await store.expireAsync("key", TimeSpan.fromMinutes(10));
await store.metaAsync("key");批量异步方法:
await store.setManyAsync({
a: 1,
b: 2,
});
const values = await store.getManyAsync<number>(["a", "b"]);
await store.delManyAsync(["a", "b"]);setAsync/getAsync 可以操作同步存储,也可以操作异步存储:
await store.setAsync("user", user, { storage: "local" });
await store.setAsync("articles", articles, { storage: "indexedDB" });加密
写入时声明 encrypt: true,读取时自动解密。
import { createStore } from "locstore";
const secureStore = createStore({
crypto: {
secret: "your-secret",
},
});
secureStore.set("token", token, {
encrypt: true,
});
const token = secureStore.get<string>("token");如果写入时设置了 encrypt: true,但没有配置加密器或密钥,会抛出错误:
No crypto provider configured.也就是说,locstore 保留默认 AES 能力,但不会内置任何默认密钥。必须由用户显式传入 secret,或提供自定义 encrypt / decrypt 方法后,才能启用加密。
安全边界
locstore 的加密能力用于降低本地存储中明文暴露的风险,但不能提供绝对安全。
只要数据需要在浏览器中被解密并使用,拥有前端运行环境控制权的攻击者理论上就可能获取明文或调用解密流程。
对于真正敏感的数据,请优先由服务端保存;并由后端完成权限校验、过期控制和风控。
自定义同步加密
const store = createStore({
crypto: {
secret: "app-secret",
encrypt(text, ctx) {
return customEncrypt(text, ctx.secret!);
},
decrypt(text, ctx) {
return customDecrypt(text, ctx.secret!);
},
},
});
store.set("profile", profile, {
encrypt: true,
});
const profile = store.get<UserProfile>("profile");ctx 包含当前 key、存储类型、命名空间、密钥和自定义元信息:
type CryptoContext = {
key: string;
storage: "local" | "session" | "cookie" | "memory" | "url" | "history" | "indexedDB" | "cache" | "custom";
namespace?: string;
secret?: string;
meta?: Record<string, unknown>;
};自定义异步加密
异步加密只能通过 Async 方法使用。
const store = createStore({
cryptoAsync: {
async encrypt(text, ctx) {
return webCryptoEncrypt(text, ctx.secret!);
},
async decrypt(text, ctx) {
return webCryptoDecrypt(text, ctx.secret!);
},
},
});
await store.setAsync("token", token, {
encrypt: true,
});
const token = await store.getAsync<string>("token");规则:
set/get只使用同步加密器setAsync/getAsync优先使用异步加密器- 如果没有异步加密器,
setAsync/getAsync可以使用同步加密器 - 加密数据读取失败时返回默认值,不会让业务直接崩溃
Cookie 配置
store.set("theme", "dark", {
storage: "cookie",
ttl: TimeSpan.fromDays(7),
cookie: {
path: "/",
sameSite: "lax",
secure: true,
},
});支持字段:
type CookieOptions = {
path?: string;
domain?: string;
sameSite?: "strict" | "lax" | "none";
secure?: boolean;
};自定义存储 Adapter
同步 adapter:
import type { SyncAdapter } from "locstore";
const adapter: SyncAdapter = {
type: "sync",
name: "custom",
get(key) {
return myStorage.get(key);
},
set(key, value) {
myStorage.set(key, value);
},
del(key) {
myStorage.delete(key);
},
clear(prefix) {
myStorage.clear(prefix);
},
keys(prefix) {
return myStorage.keys(prefix);
},
};
const store = createStore({
storage: adapter,
});异步 adapter:
import type { AsyncAdapter } from "locstore";
const adapter: AsyncAdapter = {
type: "async",
name: "custom",
async get(key) {
return await myAsyncStorage.get(key);
},
async set(key, value) {
await myAsyncStorage.set(key, value);
},
async del(key) {
await myAsyncStorage.delete(key);
},
async clear(prefix) {
await myAsyncStorage.clear(prefix);
},
async keys(prefix) {
return await myAsyncStorage.keys(prefix);
},
};
const store = createStore({
storage: adapter,
});
await store.setAsync("key", "value");内部数据格式
locstore 会统一保存为 payload:
type CachePayload<T = unknown> = {
v: 1;
value: T;
createdAt: number;
updatedAt: number;
expiresAt: number | null;
encrypted: boolean;
meta?: Record<string, unknown>;
};如果 encrypt: true,默认只加密 value,保留基础元信息用于判断过期和调试。
SSR 和降级
在没有浏览器 API 的环境中:
localStorage/sessionStorage不可用时会降级到内存存储cookie不可用时会降级到内存存储indexedDB/Cache API不可用时会降级到内存存储
这让库在 SSR、测试环境、隐私模式等场景里更稳定。
从旧版迁移
旧写法:
import store, { StoreType, TimeSpan } from "locstore";
store.set("name", "张三", {
storage: StoreType.LOCALSTORAGE,
expires: TimeSpan.FromDays(1),
encrypt: true,
});
store.get<string>("name", {
decrypt: true,
});推荐新写法:
import store, { TimeSpan } from "locstore";
store.set("name", "张三", {
storage: "local",
ttl: TimeSpan.fromDays(1),
encrypt: true,
});
store.get<string>("name");变化点:
expires推荐改为ttlTimeSpan.FromDays推荐改为TimeSpan.fromDaysdecrypt不再需要,读取时会自动根据 payload 解密remove仍然可用,但推荐使用delStoreType仍然可用,但推荐使用字符串存储名
开发
安装依赖:
pnpm install类型检查:
pnpm typecheck测试:
pnpm test构建:
pnpm buildLicense
MIT
