外观
Storage
Storage 是 localStorage / sessionStorage 的 JSON 读写封装。业务负责 key 命名;实例不创建命名空间,不提供跨 tab 原子更新或数据库事务。
创建与类型约束
ts
import { createStorage } from '@freenotes/web-runtime';
interface AppStorage {
ready: boolean;
profile: { name: string } | null;
}
const storage = createStorage<AppStorage>('local');
storage.set('ready', true);
const ready = storage.get('ready'); // boolean | undefined
const profile = storage.get('profile'); // { name: string } | null | undefined传入 schema 时,get、set、update、has 和 remove 只接受 schema 中的 key,值完全由 key 推导,不允许另指定值类型。拼错 key、传入错误值或错误 fallback 都会产生类型错误。schema 只提供编译期约束,不验证已存储的数据。
不传 schema 时,允许任意 key,读取默认返回 unknown,可以显式指定值类型或通过 parser 推导类型:
ts
const untyped = createStorage(); // 默认 local
untyped.set('theme', 'dark');
const theme = untyped.get<string>('theme');
const session = createStorage('session');读取与解析
ts
const ready = storage.get('ready', { fallback: false });
const profile = storage.get('profile', {
parse(value) {
if (value === null) return null;
if (
typeof value === 'object' &&
value !== null &&
'name' in value &&
typeof value.name === 'string'
) {
return { name: value.name };
}
return undefined;
},
fallback: null
});parse 返回解析后的值,undefined 表示无效;null 是合法业务值。读取缺失、过期、版本不匹配、解析返回 undefined 或解析抛异常时,返回 fallback 或 undefined。
get() 和 has() 不删除数据。 版本或解析规则由每次调用指定,一个调用者的读取要求不应删除另一个调用者的数据。业务可以显式调用 remove() 或 clearExpired() 清理。
没有 runtime 元数据的历史值仍可读取;原始非 JSON 字符串会按字符串返回。带 version 要求读取裸历史值时,因为无法确认版本,会走 fallback,保留原数据。
写入、过期与版本
ts
storage.set('ready', true, { ttlMs: 60_000, version: 1 });
storage.get('ready', { version: 1, fallback: false });
storage.set('ready', true, { expiresAt: Date.now() + 60_000 });ttlMs 的单位是毫秒;expiresAt 是 Unix 毫秒时间戳。同时提供时,expiresAt 优先。version 是非负整数。写入 undefined 或无法 JSON 序列化的值返回 false。
只有配置了元数据时,存储值才包裹 runtime record;无元数据时直接存储 JSON。set() 是完整替换,不继承旧元数据。expiresAt: null、version: null 可显式清除对应元数据。
更新
ts
const counters = createStorage<{ count: number }>();
counters.set('count', 1, { ttlMs: 60_000, version: 1 });
counters.update('count', (value) => (value ?? 0) + 1);
counters.update('count', (value) => (value ?? 0) + 1, {
read: { version: 1, fallback: 0 },
write: { version: 2 }
});update 是读取、计算、写入三个步骤,不是跨 tab 的原子操作。成功返回新值,写入失败返回 undefined。业务 updater 抛出的异常会原样传播。
未显式覆盖时,更新保留尚未过期记录的绝对过期时间和版本,不会延长 TTL。write.ttlMs 从本次更新时重新计算过期时间;write.expiresAt: null 清除过期时间;write.version: null 清除版本。过期记录被视为缺失,不继承其元数据。read.version 与 write.version 相互独立。
存储区域与清理
getInfo() 返回 { available, storageType }。available 通过一次临时写入探测当前是否可读写,不承诺数据永不丢失。
| 方法 | 行为 |
|---|---|
get(key, options?) | 读取有效值,失败走 fallback,不删除记录。 |
set(key, value, options?) | 完整替换值和元数据,返回 boolean。 |
update(key, updater, options?) | 更新值,默认继承有效记录的元数据。 |
has(key) | 当前是否存在未过期的值,不删除记录。 |
remove(key) | 显式删除指定 key。 |
keys() | 当前原生存储区域的全部 key,包含过期记录和 schema 外的 key。 |
clearExpired() | 删除过期的 runtime record,返回删除数量。 |
clear() | 清空整个原生存储区域,返回 boolean。 |
类型 schema 不是物理命名空间,clear() 不会只清理声明的 key。使用这个方法前,业务必须明确当前 origin 的共享存储范围。
SSR、受限 WebView、存储配额不足时,写入和删除返回 false,读取走 fallback;不会偷偷使用内存替代持久化。浏览器错误和 parser 异常不会进入业务流程。
失败诊断
Storage 是容错存储封装:读取失败仍走 fallback,写入/删除失败仍返回 false。重要数据的调用方可以通过创建时配置的 onError 获取失败原因:
ts
const storage = createStorage<AppStorage>('local', {
onError(error) {
reportStorageError({
kind: error.kind,
code: error.code,
operation: error.operation,
key: error.key,
cause: error.cause
});
}
});回调参数为 StorageError,包含 kind、ERR_STORAGE_* code、storageType、底层操作 operation、key(适用时)和原始 cause。覆盖环境不可用、非法输入、读写/枚举/删除失败以及 parser 拒绝或抛错。正常缺失、过期和版本不匹配不报告错误。批量操作可能报告多次底层失败。
onError 在创建时捕获并同步调用;回调抛错会被隔离。回调不要再次执行同一存储操作,以免递归。该机制不改变 fallback、删除或写入的行为,也不将 Storage 变成事务数据库。