npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

atorage

v0.0.4

Published

Type-safe, middleware-composable storage layer for localStorage, sessionStorage, and IndexedDB

Readme

Atorage

CI npm version npm downloads codecov license bundle size

原子化存储管理库。为 localStoragesessionStorageIndexedDB 提供统一的、可组合的、类型安全的管理方案。

English

特性

  • 原子化 — 每个存储键是独立的 Atom,拥有自己的 driver、中间件和生命周期
  • 统一异步 APIget() / set() / del() / has() / update() 均返回 Promise
  • Driver 抽象 — 自由切换 localStorage、sessionStorage、IndexedDB 或自定义后端
  • 运行时降级 — 定义 driver 链,高优先级失败时自动降级到下一个
  • 中间件管线 — 洋葱模型,支持 TTL、校验、加密、压缩、缓存、防抖、锁等
  • 作用域管理 — 通过 Scope 将 atom 分组,一键清理(类似 AbortController
  • 可选同步sync() 同 tab 多实例、tabSync() 跨 tab;默认互不感知,按需挂载
  • 类型安全 — 完整的 TypeScript 类型推导,strict 模式
  • 极致轻量 — 核心 ~2 kB min+brotli,含全部 driver 和中间件 < 5 kB
  • 零依赖 — 没有任何运行时依赖,包括 IndexedDB driver 在内全部自实现
  • 框架无关 — 纯 ESM,tree-shaking 友好,适配任意框架或原生 JS

安装

pnpm add atorage

快速开始

import { atom, withDriver } from 'atorage';
import { localStorageDriver } from 'atorage/drivers';

const token = atom<string>('auth-token', withDriver(localStorageDriver()));

await token.set('abc123');
console.log(await token.get()); // 'abc123'
console.log(await token.has()); // true
await token.del();

目录

核心概念

Atom

Atom 是 atorage 的核心抽象——一个独立的存储单元,绑定唯一 key,可配置 driver、中间件和作用域。

interface Atom<T> extends EventTarget {
  readonly key: string;

  get(): Promise<T | undefined>;
  set(value: T): Promise<void>;
  del(): Promise<void>;
  has(): Promise<boolean>;
  update(updater: (prev: T | undefined) => T | Promise<T>): Promise<T>;
  getMeta(): Promise<Record<string, unknown> | undefined>;
  peek(): T | undefined;
  dispose(): void;
}

| 方法 | 说明 | | ------------ | -------------------------------------------------------------------- | | get() | 始终从 driver 读取,经过完整中间件链 | | set(value) | 写入 driver,经过中间件链处理。成功后触发 change 事件 | | del() | 删除值及关联元数据。成功后触发 delete 事件 | | has() | 判断值是否存在,走完整中间件链。TTL 过期的 key 返回 false | | update(fn) | 读-改-写原子操作,内部串行队列保证并发安全。支持异步 updater | | getMeta() | 读取持久化元数据(TTL 过期时间、版本号等),只读,用于调试 | | peek() | 同步返回本实例最后一次成功观测到的值;未观测或已删除则为 undefined | | dispose() | 销毁 atom,清理所有订阅和资源。后续操作抛出 AtomDisposedError |

Modifier

Modifier 是纯函数,用于配置 atom 的 driver、scope 和中间件:

import { atom, withDriver, withScope, withMiddleware, withPreMiddleware } from 'atorage';

const a = atom<string>(
  'key',
  withDriver(driver), // 指定存储后端(支持数组,表示降级链)
  withScope(scope1, scope2), // 指定作用域,决定 key 前缀
  withMiddleware(ttl(1000), sync()), // 中间件链(追加)
  withPreMiddleware(validate(fn)), // 中间件链(前置插入)
);

defineAtom — 预配置工厂

为一组 atom 共享配置:

import { defineAtom, withDriver, withMiddleware } from 'atorage';
import { localStorageDriver } from 'atorage/drivers';
import { encrypt } from 'atorage/middleware';

const secureAtom = defineAtom(() => [
  withDriver(localStorageDriver()),
  withMiddleware(encrypt({ encrypt: myEncrypt, decrypt: myDecrypt })),
]);

const token = secureAtom<string>('token');
const refresh = secureAtom<string>(
  'refresh',
  withPreMiddleware(validate(schema)), // 在 encrypt 之前执行
  withMiddleware(logger()), // 在 encrypt 之后执行
);
// token  链:encrypt → driver
// refresh 链:validate → encrypt → logger → driver

工厂函数接收 key 作为参数,支持按 key 差异化配置:

const scopedAtom = defineAtom((key) => [
  withDriver(key.startsWith('temp:') ? memoryDriver() : localStorageDriver()),
]);

Driver(存储后端)

内置 Driver

import {
  memoryDriver,
  localStorageDriver,
  sessionStorageDriver,
  indexedDBDriver,
} from 'atorage/drivers';

| Driver | 存储介质 | 序列化 | batch | 适用场景 | | ------------------------ | -------------- | ---------- | ----- | ------------------- | | memoryDriver() | 内存 Map | 无 | ✓ | 测试、SSR、临时数据 | | localStorageDriver() | localStorage | JSON | — | 小型持久化数据 | | sessionStorageDriver() | sessionStorage | JSON | — | Tab 级别的会话数据 | | indexedDBDriver(opts?) | IndexedDB | 结构化克隆 | ✓ | 大数据、Blob/File |

indexedDBDriver 选项:

interface IndexedDBDriverOptions {
  dbName?: string; // 默认 'atorage'
  storeName?: string; // 默认 'kv'
}

dbName 是 IndexedDB 数据库名,storeName 是库内的 object store(KV 分区)。同一 dbName 下可以使用不同的 storeName;若 store 尚不存在,driver 会自动升版本并创建空 KV store。同库的多个 driver 实例共享底层连接;dispose() 释放本实例的占用,最后一个空闲时关闭连接。版本号由库内部管理,无需也不支持手动配置。

Driver 接口——所有 driver 接收 unknown 类型的值,内部自行处理序列化:

  • localStorage / sessionStorage:自动 JSON.stringify / JSON.parse
  • IndexedDB:原生结构化克隆,可直接存储 FileBlobArrayBuffer
  • memory:直接存引用

运行时降级

传入 driver 数组,形成降级链:

const data = atom(
  'user-prefs',
  withDriver([localStorageDriver(), indexedDBDriver(), memoryDriver()]),
);

降级行为:

  • 初始化:调用每个 driver 的 available() 过滤不支持的 driver(如 Node.js 中过滤 localStorage)
  • 写入:依次尝试,第一个成功的 driver 存储数据,同时清理其他 driver 中的同 key 副本
  • 读取:依次尝试,返回第一个非 undefined 的结果
  • 删除:所有 driver 都执行删除

核心保证:一个 key 在稳态下只存在于一个 driver 中。高优先级 driver 恢复可用时,新写入自然回流。

自定义 Driver

实现 Driver 接口即可:

import type { Driver } from 'atorage';

function redisDriver(url: string): Driver {
  const client = createClient({ url });
  const connected = client.connect();
  return {
    name: 'redis',
    async get(key) {
      await connected;
      return client.get(key);
    },
    async set(key, value) {
      await connected;
      await client.set(key, JSON.stringify(value));
    },
    async del(key) {
      await connected;
      await client.del(key);
    },
    async has(key) {
      await connected;
      return (await client.exists(key)) > 0;
    },
    async keys(prefix) {
      await connected;
      return client.keys(prefix ? `${prefix}*` : '*');
    },
    async dispose() {
      await client.quit();
    },
  };
}

可选方法:available()(环境检测)和 batch(ops)(批量操作)。

中间件

中间件采用洋葱模型,在 next() 前修改 ctx.value 影响下游,next() 后修改影响上游返回值。

import { withMiddleware } from 'atorage';
import {
  ttl,
  validate,
  versioned,
  debounce,
  lock,
  logger,
  sync,
  tabSync,
  compress,
  encrypt,
  crossTabLock,
} from 'atorage/middleware';

ttl — 过期控制

const session = atom(
  'session',
  withDriver(d),
  withMiddleware(
    ttl(30 * 60 * 1000), // 30 分钟
  ),
);

// 启用过期后主动删除 driver 数据
const temp = atom('temp', withDriver(d), withMiddleware(ttl(5000, { deleteOnExpire: true })));
  • 写入:在 meta 中记录 exp(过期时间戳)
  • 读取:过期返回 undefineddeleteOnExpire: true 时主动清理 driver 数据
  • has():过期的 key 返回 false
  • 默认惰性过期(不主动删除),这是业界标准(Redis、Guava),减少 I/O 开销

validate — 运行时校验

import { validate, ValidationError } from 'atorage/middleware';

const age = atom<number>(
  'age',
  withDriver(d),
  withMiddleware(validate((v) => typeof v === 'number' && v >= 0 && v <= 150)),
);

await age.set(-1); // 抛出 ValidationError

try {
  await age.set(-1);
} catch (err) {
  if (err instanceof ValidationError) {
    // 处理非法写入
  }
}
  • 写入:校验失败抛出 ValidationError,阻止写入
  • 读取:校验失败返回 undefined,通过 error 事件通知
  • ValidationErroratorage/middleware 导出(不在 atorage 主入口)——仅在使用 validate() 中间件时需要 import

versioned — 版本迁移

const prefs = atom(
  'prefs',
  withDriver(d),
  withMiddleware(
    versioned({
      current: 3,
      migrate: {
        0: (data) => ({ ...data, theme: 'light' }),
        1: (data) => ({ ...data, locale: 'en' }),
        2: (data) => ({ ...data, notifications: true }),
      },
    }),
  ),
);
  • 写入:标记 meta.ver = current
  • 读取:检测旧版本,依次执行 migrate 函数升级,然后回写
  • ver 字段的旧数据视为 v0
  • 不支持降级(高版本数据读取时抛错)
  • 回写走完整中间件链,保证加密/压缩等中间件正确处理

debounce — 写入防抖

const myDebounce = debounce(500);
const draft = atom('draft', withDriver(d), withMiddleware(myDebounce));

// 500ms 内多次 set() 只触发一次 driver 写入
await draft.set('v1');
await draft.set('v2');
await draft.set('v3');
// 只有 'v3' 会被写入 driver

// 防抖期间 get() 返回最新 pending 值(内存中),保证读写一致
const val = await draft.get(); // 'v3'

// 手动立即落盘
await myDebounce.flush();
  • 适用于拖拽位置、输入框实时保存等高频写入场景
  • change 事件在实际落盘后触发,非调用时
  • del() 取消 pending 写入
  • await set() 在注册 timer 后即 resolve,不等待实际落盘。落盘失败通过 atom 的 error 事件传播
  • versioned() 组合时,版本迁移回写也会被延迟。关键路径建议在 get() 后手动 flush()

compress — 压缩

const large = atom(
  'large',
  withDriver(d),
  withMiddleware(
    compress({
      compress: (data) => myCompress(data),
      decompress: (data) => myDecompress(data),
    }),
  ),
);
  • 写入JSON.stringifycompress → 存储字符串
  • 读取decompressJSON.parse
  • 通过 meta.cmp = 1 标记已压缩数据

encrypt — 加密

const secret = atom(
  'secret',
  withDriver(d),
  withMiddleware(
    encrypt({
      encrypt: (data) => myEncrypt(data),
      decrypt: (data) => myDecrypt(data),
    }),
  ),
);
  • 与 compress 同构,通过 meta.enc = 1 标记已加密数据
  • 加解密实现完全由用户提供,建议使用 AES-GCM 等带认证的算法

lock — 单 Tab 异步锁

const counter = atom('counter', withDriver(d), withMiddleware(lock()));
// 同一 atom 实例上的所有操作串行化

crossTabLock — 跨 Tab 锁

const shared = atom('shared', withDriver(d), withMiddleware(crossTabLock()));
// 通过 Web Locks API 跨 tab 互斥(仅 set/del 操作)

sync — 同 Tab 同步

Atom 默认是纯存储句柄,不会自动同步同 key 的其他实例。需要时显式挂载:

import { sync } from 'atorage/middleware';

const a = atom('token', withDriver(d), withMiddleware(sync()));
const b = atom('token', withDriver(d), withMiddleware(sync()));
await a.set('x'); // b 收到 change(经 refresh 读穿 driver)
  • 只按 同 key 通知 peer 走 operation: 'refresh'(不比较 driver/backend)
  • Peer 各自读自己的 driver;若后端不同,调用方应保证配置一致
  • refresh:与 get 同类(读穿、发事件;可经中间件标志 writeback/delete)。不是用户 set/delsync/tabSync 只在非 writeback 的 set/del 后广播(writeback 带 isWriteback

tabSync — 跨 Tab 同步

const settings = atom('settings', withDriver(d), withMiddleware(tabSync()));
// set()/del() 后通过 BroadcastChannel 通知其他 tab
  • sync 平行:只发信号,对端对本实例 refresh()
  • 仅广播 key 和操作类型,不传 value——接收端从 driver 重新读取
  • 因此与 encrypt 等中间件的顺序无关,不存在明文泄露风险
  • 可自定义 channel 名:tabSync('my-channel')

logger — 日志

const item = atom('item', withDriver(d), withMiddleware(logger()));
// [atorage] set item (0.12ms)

// 自定义日志函数
const item2 = atom(
  'item2',
  withDriver(d),
  withMiddleware(logger({ log: (msg) => myLogger.info(msg) })),
);

中间件顺序指南

中间件顺序由用户负责,推荐顺序:

validate → ttl → versioned → compress → encrypt → sync / tabSync → logger

get() 始终走完整管线。需要同步读取本实例最后一次成功观测的值时,使用 atom.peek()(不是中间件)。

自定义中间件

中间件可以是纯函数或带生命周期钩子的对象:

import type { MiddlewareFunction, MiddlewareWithHooks, MiddlewareContext } from 'atorage';

// 纯函数形式
const myMiddleware: MiddlewareFunction = async (ctx, next) => {
  // next() 前:拦截/修改请求
  console.log(`${ctx.operation} ${ctx.key}`);
  await next();
  // next() 后:处理/修改响应
};

// 带钩子的对象形式
function myStatefulMiddleware(): MiddlewareWithHooks {
  let cache: unknown = undefined;
  return {
    handle: async (ctx, next) => {
      // 当次 IO 上下文;长期状态用 onInit / onDispose
      if (ctx.operation === 'refresh') {
        cache = undefined;
      }
      await next();
    },
    onInit(init) {
      // { key, atomId, refresh } — stable refresh for pools/subscriptions
    },
    onDispose() {
      cache = undefined;
    },
  };
}

MiddlewareContext(当次 IO):

| 属性/方法 | 说明 | | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | key / atomId | 存储 key 与实例 id | | operation | 'get' / 'set' / 'del' / 'has' / 'refresh'refresh 非公开 Atom API;协调器优先用 onInit.refresh,管线内也可用 ctx.refresh()) | | value / meta | 当前值与元数据(meta 保留键:exp / ver / enc / cmp) | | refresh() | 触发本实例 refresh(与 onInit.refresh 同源;长期 peer 注册优先用 onInit) | | requestWriteback() | get/refresh 结束后请求带 isWriteback 的回写 set(变换仍跑;sync/tabSync 不广播) | | requestDelete() | get/refresh/has 结束后请求静默删盘(不走 del 管线) | | isWriteback | 仅对 set 有意义:自动回写(如 migrate)——协调器不得再广播 | | reportError(error) | 报告非致命错误(管线结束后刷成 atom error 事件) |

Scope(作用域)

Scope 是注册表,做两件事:贡献 key 前缀clear() 时删除已登记 atom

import { createScope, withScope, atom, withDriver } from 'atorage';

const userScope = createScope('user:123');

const prefs = atom('prefs', withDriver(d), withScope(userScope));
// 实际存储 key: "user:123:prefs"

const history = atom('history', withDriver(d), withScope(userScope));

// 用户登出:一键清理所有用户数据
const { errors } = await userScope.clear();
// prefs 和 history 都会执行 del()(走完整中间件链)
if (errors.length > 0) {
  // 部分 atom 删除失败,每个 error 对应一个失败的 del()
  console.warn('部分清理失败:', errors);
}

多级 scope 通过参数顺序拼接前缀:

const auth = createScope('auth');
const session = createScope('session');

const token = atom('token', withScope(auth, session), withDriver(d));
// 实际存储 key: "auth:session:token"

Scope 之间无层级关系,扁平独立。

批量操作

import { batch } from 'atorage';

await batch(async () => {
  await counter.set(1);
  await counter.set(2);
  await counter.set(3);
});
// counter 的 change 事件只派发一次,value = 3

语义:

  • 不保证事务原子性——某个操作失败后,后续操作继续执行
  • 事件合并——batch 期间所有 change / delete 事件暂存,结束后统一派发
  • 同一 atom 多次 set——只派发最后一次的 change 事件
  • 嵌套 batch——合并到外层,事件延迟到最外层 batch 结束后派发

工具函数

import { snapshot, restore, clearByPrefix } from 'atorage/utils';

snapshot — 快照

const data = await snapshot({ driver: d, prefix: 'myapp:' });
// → { 'myapp:key1': value1, 'myapp:key2': value2, ... }

直接操作 driver 层原始数据,不经过中间件。

restore — 恢复

await restore(data, { driver: d });

将快照数据写入 driver。

clearByPrefix — 按前缀清理

const count = await clearByPrefix('user:oldId:', { driver: d });
// 返回删除的 key 数量

用于清理孤儿数据(如用户切换场景)。

调试工具

import { inspect, raw } from 'atorage/debug';

inspect — 检查存储数据

const info = await inspect(driver, 'my-key');
// → { exists: true, raw: { $v: 'hello', $m: { exp: 1749600000 } }, value: 'hello', meta: { exp: 1749600000 } }

raw — 原始 driver 操作

绕过中间件和 { $v, $m } 包裹,直接操作 driver:

await raw.get(driver, 'key'); // 读取原始存储值
await raw.set(driver, 'key', value); // 直接写入
await raw.del(driver, 'key'); // 直接删除

用于调试、数据迁移、与外部系统互通。

测试工具

import { testDriver } from 'atorage/test';

testDriver — Driver 一致性测试

对自定义 driver 运行标准合规测试套件:

import { testDriver } from 'atorage/test';
import { describe } from 'vitest';

testDriver('myDriver', () => createMyDriver());

覆盖:get 缺失 key、set/get 往返、has、del、keys、keys(prefix)、覆盖写入等。

事件系统

Atom 继承 EventTarget,支持标准的事件监听:

const token = atom<string>('token', withDriver(d));

token.addEventListener('change', (e) => {
  const { value } = (e as CustomEvent).detail;
  console.log('值变更为:', value);
});

token.addEventListener('delete', () => {
  console.log('值被删除');
});

token.addEventListener('error', (e) => {
  const { error } = (e as CustomEvent).detail;
  console.error('操作出错:', error);
});

// 支持 AbortController
const ac = new AbortController();
token.addEventListener('change', handler, { signal: ac.signal });
ac.abort(); // 移除监听

| 事件 | 触发时机 | detail | | -------- | ------------------------------------------------------------------ | --------------------------- | | change | 本实例 set 成功,或经 refresh(如 sync / tabSync)读到新值 | { value: T \| undefined } | | delete | 本实例 del 成功,或 refresh 发现值已不存在,或 scope clear | — | | error | 中间件或 driver 抛出异常 | { error: Error } |

无隐式同 tab 互通: Atom 不感知其他实例。需要同步时显式使用 sync() / tabSync(),它们通过对端 refresh() 触发上述事件。

生命周期

dispose() 调用后,atom 进入已销毁状态:

const token = atom<string>('token', withDriver(d));
await token.set('abc');
token.dispose();
await token.get(); // 抛出 AtomDisposedError
  • 后续 get() / set() / del() / has() / update() 抛出 AtomDisposedError
  • 当前正在执行的异步操作会等待完成,排队中的操作被 reject
  • 移除 scope 登记、调用中间件 onDispose()
  • 不释放 driver——driver 可能被多个 atom 共享,生命周期由用户管理

架构

┌─────────────────────────────────────────────────┐
│  Atom API(纯句柄)                              │  atom(), defineAtom(), batch()
├─────────────────────────────────────────────────┤
│  Middleware Pipeline(洋葱模型)                  │  validate, ttl, sync, ...
├─────────────────────────────────────────────────┤
│  Scope(注册表)                                  │  createScope(), key 前缀 + clear()
├─────────────────────────────────────────────────┤
│  Coordination(可选中间件私有 pool)               │  sync, tabSync, lock
├─────────────────────────────────────────────────┤
│  Driver(持久化后端)                             │  localStorage, indexedDB, memory
└─────────────────────────────────────────────────┘

存储格式

Value 和 meta 合并为一个结构体存储,单次 I/O 读写:

{ $v: actualValue, $m: { exp: 1749600000, ver: 3, ... } }
  • $v — 实际值
  • $m — 元数据(无 meta 时省略)
  • 核心层自动包裹/解包,中间件和用户 API 看到的都是干净的分离视图

导出结构

| 导入路径 | 内容 | | -------------------- | ----------------------------------------------------------------------------------------------------------- | | atorage | 核心 API:atomdefineAtomcreateScopebatch、modifier、类型、AtomDisposedErrorStorageError | | atorage/drivers | memoryDriverlocalStorageDriversessionStorageDriverindexedDBDriver | | atorage/middleware | 全部预设中间件(含 synctabSyncvalidatettlencrypt 等) | | atorage/utils | snapshotrestoreclearByPrefix | | atorage/debug | rawinspect — 调试工具 | | atorage/test | testDriver — driver 一致性测试 |

设计决策与 Trade-offs

统一全异步 API

所有操作返回 Promise,即使底层 driver(如 localStorage)是同步的。这保证了 API 一致性,使得切换 driver 不需要修改调用代码。代价是简单场景有微小的 await 开销。

get 不做透明缓存

正确性优先。每次 get() 都从 driver 经中间件真实读取。需要同步的内存视图时,在成功观测后使用实例上的 peek()

元数据与值合并存储

{ $v, $m } 信封格式保证 value 和 meta 的原子一致——不可能出现 value 写入但 meta 丢失的情况。单次 I/O 读写,无双写问题。

TTL 惰性过期

过期数据不主动删除,只在读取时遮蔽。这是业界标准做法(Redis、Guava),减少每次 get 的 I/O 开销。需要主动清理时使用 { deleteOnExpire: true } 或定期调用 clearByPrefix

tabSync 不传 value

跨 tab 同步只广播 key + 操作类型,接收端从 driver 重新读取。原因:

  1. BroadcastChannel 的 structured clone 对大对象有性能开销
  2. 加密/压缩后的值在另一个 tab 直接使用无意义,仍需走中间件链

中间件顺序是用户责任

与 Express / Koa 中间件模型一致,顺序由用户控制。库不做隐式重排,因为不同场景可能需要不同的执行顺序。

del() 不返回旧值

del() 仅删除值和元数据,不返回被删除的值。如需旧值,先通过 get() 读取,或使用 update() 实现原子的读-删模式。

has() 需要读取完整值

has() 需要走完整中间件链(TTL 需要检查过期时间),因此不能在 driver 层直接检查 key 存在性。对大值有额外开销,但保证了语义正确性。

dispose 不释放 driver

Driver 可能被多个 atom 共享,一个 atom dispose 不应销毁共享资源。Driver 的生命周期由用户管理。

零运行时依赖

包括 IndexedDB driver 在内的所有功能均自实现,不引入 idb-keyval 等外部依赖。核心逻辑约 100 行,保持库的轻量定位。

错误处理策略

中间件或 driver 抛出的异常同时通过两个通道传播:

  1. throw — 调用者可 try-catch 捕获
  2. error 事件 — 通过 EventTarget 派发,供全局监听

降级链中所有 driver 全部失败时,抛出 StorageError。部分 driver 失败(如主 driver 抛错但备用 driver 成功)通过 atom 的 error 事件上报,不中断操作。

核心错误(AtomDisposedErrorStorageError)从 atorage 导出;中间件专属错误(如 validate()ValidationError)从 atorage/middleware 导出。

scope.clear() 返回 ClearResult,包含各个 atom 删除失败的错误列表,不会静默吞掉失败。每个失败的 atom 也会独立派发自己的 error 事件。

非目标

以下明确不在 atorage 范围内:

  • 状态管理 — 不做 computed / derived atom,这是 Jotai / Zustand 的职责
  • 框架绑定 — 不内置 React / Vue hook,用户或社区通过 EventTarget 对接
  • 配额管理 / LRU — atom 原子化独立,无法跨 atom 决策淘汰;存储满时由 driver 降级链处理
  • 外部变更检测 — 不监听 DevTools 或第三方库对存储的直接修改(无法在所有 driver 上统一实现)
  • DevTools 浏览器扩展 — 当前版本不做,后续迭代

License

MIT