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