@tni/cache
v2.0.2
Published
浏览器端模块化缓存与存储抽象(localStorage / sessionStorage / Cookie / IndexedDB)。
Downloads
59
Readme
@tni/cache
浏览器端模块化缓存与存储抽象:在 localStorage、sessionStorage、Cookie、IndexedDB 上提供统一的 StorageAdapter 异步接口,便于按场景切换后端而少改业务代码。
设计目标
- 统一契约:
set/get/remove/getAll/clear/getSize等行为在各后端一致,均为 Promise。 - 可扩展:Web Storage 场景可通过
createWebStorageAdapter+createCachedStorageResolver接入自定义Storage实现(含测试替身)。 - 可维护:序列化、过期(ttl / expires)、键前缀、配额相关工具集中在内部管线,各后端只处理字符串载荷。
- 无内置加解密:若要对落盘字符串加密,由业务实现
encrypt/decrypt或cipher并传入配置;本包不提供算法实现。 - 零运行时依赖:发布产物不捆绑第三方库;需在支持
window/document/indexedDB的浏览器(或等价 polyfill)中使用。
包结构(源码)
src/
index.ts # 对外统一导出
shared/
types.ts # StorageAdapter、BaseStorageOptions、错误类型等
base.ts # 序列化/过期/配额辅助(含 estimateQuota 等)
modules/
browser/ # localStorage / sessionStorage / 通用 Web Storage 适配器
cookie/ # document.cookie 封装
indexed-db/ # 单库单 object store 的 KV 封装安装
pnpm add @tni/cache(Monorepo 内也可通过 workspace 协议引用本包。)
快速开始
localStorage / sessionStorage
import { createLocalStorage, createSessionStorage } from "@tni/cache";
const local = createLocalStorage<{ token: string }>({ prefix: "myapp:" });
await local.set("token", "xxx", { ttl: 3600 });
const token = await local.get("token");
const session = createSessionStorage<number>({ prefix: "myapp:" });
await session.set("step", 2);包内也导出默认实例 localStorage、sessionStorage(与全局同名,建议按需 as 别名 导入,避免与 window.localStorage 混淆):
import { localStorage as appLocal } from "@tni/cache";
await appLocal.set("k", "v");Cookie
import { createCookieStorage } from "@tni/cache";
const cookies = createCookieStorage<string>({
prefix: "app_",
defaultAttributes: { path: "/", sameSite: "lax" },
});
await cookies.set("sid", "abc123", { maxAge: 86400 });
const sid = await cookies.get("sid");set 支持第三个参数覆盖单次写入的 Cookie 属性(path、domain、secure、sameSite、maxAge、expires、ttl 等)。
IndexedDB
import { createIndexedDBStorage } from "@tni/cache";
const idb = createIndexedDBStorage<Record<string, unknown>>({
dbName: "my_app_kv",
storeName: "entries",
version: 1,
prefix: "cache:",
});
await idb.set("prefs", { theme: "dark" });
const prefs = await idb.get("prefs");默认库名为 __tni_cache__,仓库名为 kv;同一站点多业务建议显式设置 dbName / prefix 隔离。
统一接口 StorageAdapter
| 方法 | 说明 |
| ------------------------ | ----------------------------------------------------------------------------------- |
| set(key, value, opts?) | 写入;opts 可含 ttl(秒)或 expires(Date 或毫秒时间戳) |
| get(key) | 读取;无键或已过期返回 null(Cookie / Web Storage 等对「无值」可能表现为 null) |
| remove(key) | 删除单键 |
| getAll() | 当前前缀下键值对(已过期条目会在读取时尽量清理) |
| clear() | 清空当前前缀(无前缀的 Web Storage 会 storage.clear()) |
| getSize() | 当前命名空间占用估算(UTF-16 字符长度 ×2);IndexedDB 上为异步 |
| isAvailable() | 当前环境是否可使用该后端 |
| isQuotaExceeded() | 最近一次写入是否因配额失败(具体行为因后端略有差异) |
通用配置 BaseStorageOptions
| 字段 | 说明 |
| -------------------------------- | -------------------------------------------------------------- |
| prefix | 键前缀,隔离命名空间 |
| defaultTtl | 默认存活时间(秒),可被单次 set 的选项覆盖 |
| serialize / deserialize | 自定义序列化,默认 JSON.stringify / JSON.parse |
| cipher / encrypt / decrypt | 对「序列化后的字符串」再加工;需自行实现,本包无内置加解密 |
| isAvailable | 额外开关(例如 SSR 下返回 false) |
错误与配额
- 写入时若判定为存储配额不足,可能抛出
StorageQuotaExceededError(storage字段标识后端,如localStorage、indexedDB)。 - 工具函数
isQuotaExceededError(error)用于统一识别原生QuotaExceededError与上述包装错误。 estimateQuota()/formatBytes()基于navigator.storage?.estimate(),用于展示或监控(不支持时返回null)。
扩展 Web Storage
若需在测试或非浏览器环境挂载自定义 Storage:
import { createCachedStorageResolver, createWebStorageAdapter } from "@tni/cache";
const getStorage = createCachedStorageResolver("localStorage");
const adapter = createWebStorageAdapter({
storageName: "memory",
getStorage: () => myFakeStorage,
prefix: "t:",
});开发与构建
本仓库使用 Vite+(vp)。
vp install # 在仓库根目录安装依赖
cd packages/cache
vp test # 单元测试
vp check # 格式、Lint、类型检查
vp pack # 构建 dist(ESM + CJS + 类型声明),对应 package.json 中 build 脚本库包推荐使用 vp pack 作为发布构建;vp build 面向带 index.html 的应用入口,在未单独配置库模式时可能报错。
许可证
MIT
