# Storage

## Description

Storage 模块提供围绕运行时受控数据驻留（controlled runtime data residency）的通用建模能力，用于把一组需要在应用运行过程中被稳定放置、命名、查询、更新、删除与观察的数据，组织为清楚且可复用的公共语义。

它关注的不是某个具体宿主提供的存储 API，也不是某个数据库产品、浏览器介质或网络协议本身，而是“数据在应用运行时如何以某种稳定结构驻留、如何成为多个上层消费者共同依赖的受控对象、以及这些结构本身该暴露什么样的长期语义”这一类更一般的问题。因此，这个模块更适合被理解为一组运行时存储模型，而不是浏览器存储工具箱、数据库客户端封装层或临时缓存函数集合。

## For Understanding

理解 Storage 模块时，应先把它看作“运行时受控数据驻留”的建模层，而不是把任意能把值放起来的能力都塞进来的杂项容器。它要解决的问题，不只是某条数据该如何增删改查，也包括一组数据应如何在运行时被稳定地放置为某种结构、如何通过清楚边界对外暴露、以及如何成为 UI、业务逻辑或其它运行时协作方可以共同消费的对象。

理解这个模块时，不适合把它简单归入“状态管理”一类过于宽泛的讨论。几乎所有应用都可以被描述为数据流动与状态变化，因此“状态管理”本身很难为模块提供足够稳定的立足点。相比之下，Storage 模块真正的立足点更具体：数据以什么结构驻留在运行时，这些结构如何被查询、更新、删除与观察，以及它们如何成为应用内部长期可依赖的消费面。

在很多实际场景里，应用不会直接消费一次请求的返回结果，而是先把这些结果收束进运行时存储结构，再由界面层和业务层读取这份经过统一管理的数据驻留对象。这样做的价值，不在于单纯“缓存一下结果”，而在于把查询结果、本地可见状态、用户操作产生的临时变更以及后续可能发生的协调过程，统一纳入一个稳定的运行时存储模型。像乐观更新、局部回滚、统一订阅、跨视图共享、局部合并、以及与外部数据来源或持久化介质对接，也因此更容易被组织成清楚的公共语义。

从这个角度看，当前已实现的 `table` 更适合被理解为 Storage 模块下的一个子形态：它表达的是“以唯一标识组织行数据集合”的表式运行时存储模型，而不是 Storage 模块全部问题域本身。未来若出现 `KeyValueStore`、命名空间存储或其它存储结构，它们也应与 `table` 一样，被理解为 Storage 父边界下的不同子模型，而不是彼此互不相干的零散工具。

理解这个模块时，还应守住几条边界原则：

- Storage 模块表达的是运行时存储结构、驻留边界、变更管理与可观察语义，而不是某个宿主存储 API 的直接包装。
- 浏览器的 `localStorage`、`indexedDB`，或服务端数据库、远程接口、文件系统等具体介质与数据来源，通常属于宿主环境能力或外部系统；Storage 可以与它们对接，但不应把它们的临时接口细节直接上升为模块公共语义。
- 它适合表达“数据如何在运行时被组织”“结构内部如何查询、更新、删除、观察”“模型如何与外部系统形成接点”，但不应直接承担传输协议、连接管理、数据库迁移、分布式一致性、权限体系、查询规划器或业务专属缓存规则。
- 它可以天然承载运行时副本、乐观更新中介、多来源合流或本地持久化桥接等场景，但这些都应被理解为该问题域上的典型使用方式，而不是 Storage 模块得以成立的唯一理由。
- 它未来也可以自然吸收数据对齐、传播与副本协同之类的能力；像 `sync` 或 `replication` 这样的方向，可以成为 Storage 模块的一部分功能，但不应反过来成为当前模块定义本身的前提。
- 它关心的是“存储模型如何在运行时稳定存在”，而不是完整替代服务端数据库、ORM、状态管理框架或同步引擎的全部职责。

## For Using

当你希望在应用内部维护一组真正可被持续消费的受控数据驻留结构，而不是让 UI 和业务逻辑直接依赖零散变量、一次性请求结果或宿主介质的原始接口时，可以使用 Storage 模块。

从使用角度看，这个模块大致可以分为三类能力：

- 运行时存储结构能力：用于表达数据在应用内如何被组织、命名、查询、更新、删除、批量处理以及如何保持结构本身的稳定边界。`table` 属于这一类，它适合承载具备唯一标识的一组记录。
- 变更管理能力：用于表达插入、更新、删除、覆盖、合并、乐观修改、回滚以及变更通知等语义，使运行时驻留数据能够成为应用内部真正可信的消费对象，而不是一次性响应结果的临时中转站。
- 集成接点能力：用于让运行时存储结构与外部系统形成清楚边界，例如吸收来自远端或其它来源的数据，或把内部数据继续写入本地持久化介质、离线缓存层或其它端侧环境中。

更合适的接入方式，通常是先判断你的核心对象是不是“运行时中的一份受控数据驻留结构”。如果你需要的是：

- 请求结果先进入一份本地副本，再由多个界面和业务层共同消费；
- 某组数据需要在运行时被稳定放置为表、键值集合或其它结构，而不是散落在临时变量里；
- 用户操作要先体现在本地，再异步与远端对齐；
- 同一份数据需要同时连接上游远端与下游本地持久化；
- 你希望把查询、更新、订阅和外部集成入口都收束到一份统一模型中；

那么这个模块就是合适的边界。

相反，如果你的需求只是某个宿主环境里对具体介质进行直接读写，例如浏览器 `localStorage` 的原生操作、IndexedDB 事务细节、某个数据库驱动的连接与 SQL 交互，那么这些通常不应直接落在 Storage 模块本身，而应放在对应宿主模块、适配层或更外层系统中。

## For Contributing

为 Storage 模块贡献内容时，优先判断新增能力是否真的在澄清“运行时受控数据如何被建模为稳定存储结构”这一问题，而不是只是在补一个看起来方便的宿主 API 包装或某个业务缓存技巧。这个模块应长期服务于“数据如何在运行时稳定驻留并持续消费”“变更如何被统一管理”“不同存储结构如何表达各自稳定语义”“模型如何与外部系统形成清楚接点”这几类问题。

扩展这个模块时，应特别警惕以下倾向：把 Storage 退化成浏览器存储工具集合；把某个数据库产品的接口直接平移成公共 API；把网络请求、同步协议、重试策略、权限判断或业务缓存失效规则无差别地下沉到存储模型内部；或者为了某个当前实现方便而暴露临时内部结构。文档应说明哪些运行时存储边界是长期成立的，以及为什么这些边界成立，而不是复述某个版本里的局部实现技巧。

当前已经落地的 `table` 应被理解为 Storage 模块下的一个具体子模型，而不是整个模块的全部定义。后续若新增 `KeyValueStore`、命名空间存储、快照模型或其它存储结构，应优先判断它们是否都属于“运行时受控数据驻留结构”这一父问题域；如果只是某个特定宿主介质的读写封装，通常不应直接成为 Storage 模块的顶级公共能力。

如果后续在这个模块中继续生长出数据对齐、传播、远端回灌、副本协同或持久化桥接能力，应优先把它们理解为“围绕运行时存储结构发生的后续语义”，而不是让这些能力反过来改写 Storage 的父边界。换句话说，`sync`、`replication` 或类似方向可以成为这个模块未来的组成部分，但它们应建立在清楚的存储结构语义之上，而不是取代这些结构语义本身。

### JSDoc 注释格式要求

- 每个公开导出的目标（类型、函数、变量、类等）都应包含 JSDoc 注释，让人在不跳转实现的情况下就能理解用途。
- JSDoc 注释第一行应为清晰且简洁的描述，该描述优先使用中文（英文也可以）。
- 如果描述后还有其他内容，应在描述后加一个空行。
- 如果有示例，应使用 `@example` 标签，后接三重反引号代码块（不带语言标识）。
- 如果有示例，应包含多个场景，展示不同用法，尤其要覆盖常见组合方式或边界输入。
- 如果有示例，应使用注释格式说明每个场景：`// Expect: <result>`。
- 如果有示例，应将结果赋值给 `example1`、`example2` 之类的变量，以保持示例易读。
- 如果有示例，`// Expect: <result>` 应该位于 `example1`、`example2` 之前，以保持示例的逻辑清晰。
- 如果有示例，应优先使用确定性示例；避免断言精确的随机输出。
- 如果函数返回结构化字符串，应展示其预期格式特征。
- 如果有参考资料，应将 `@see` 放在 `@example` 代码块之后，并用一个空行分隔。

### 实现规范要求

- 不同程序元素之间使用一个空行分隔，保持结构清楚。这里的程序元素，通常指函数、类型、常量，以及直接服务于它们的辅助元素。
- 某程序元素独占的辅助元素与该程序元素本身视为一个整体，不要在它们之间添加空行。
- 程序元素的辅助元素应该放置在该程序元素的上方，以保持阅读时的逻辑顺序。
- 若辅助元素被多个程序元素共享，则应将其视为独立的程序元素，放在这些程序元素中第一个相关目标的上方，并与后续程序元素之间保留一个空行。
- 辅助元素也应该像其它程序元素一样，保持清晰的命名和适当的注释，以便在需要阅读实现细节时能够快速理解它们的作用和使用方式。
- 辅助元素的命名必须以前缀 `internal` 开头（或 `Internal`，大小写不敏感）。
- 辅助元素永远不要公开导出。
- 被模块内多个不同文件中的程序元素共享的辅助元素，应该放在一个单独的文件中，例如 `./src/storage/internal.ts`。
- 模块内可以包含子模块。只有当某个子目录表达一个稳定、可单独理解、且可能被父模块重导出的子问题域时，才应将其视为子模块。
- 子模块包含多个文件时，应该为其单独创建子文件夹，并为其创建单独的 Barrel 文件；父模块的 Barrel 文件再重导出子模块的 Barrel 文件。
- 子模块不需要有自己的 `README.md`。
- 子模块可以有自己的 `internal.ts` 文件，多个子模块共享的辅助元素应该放在父模块的 `internal.ts` 文件中，单个子模块共享的辅助元素应该放在该子模块的 `internal.ts` 文件中。
- 对模块依赖关系的要求（通常是不循环依赖或不反向依赖）与对 DRY 的要求可能产生冲突。此时，若复用的代码数量不大，可以适当牺牲 DRY，复制粘贴并保留必要的注释说明；若复用的代码数量较大，则可以将其抽象到新的文件或子模块中，如 `common.ts`，并在需要的地方导入使用。
- 与 storage 相关的实现应优先围绕存储结构边界、唯一标识或命名语义、变更管理、查询与订阅、快照隔离、以及与外部系统的集成接点组织，避免把具体宿主介质细节或业务缓存策略混入模块边界。

### 导出策略要求

- 保持内部辅助项和内部符号为私有，不要让外部接入依赖临时性的内部结构。
- 每个模块都应有一个用于重导出所有公共 API 的 Barrel 文件。
- Barrel 文件应命名为 `index.ts`，放在模块目录根部，并且所有公共 API 都应从该文件导出。
- 新增公共能力时，应优先检查它是否表达稳定、清楚且值得长期维护的运行时存储语义，而不是某段宿主实现细节的便捷暴露；仅在确认需要长期对外承诺时再加入 Barrel 导出。

### 测试要求

- 若程序元素是函数，则只为该函数编写一个测试，如果该函数需要测试多个用例，应放在同一个测试中。
- 若程序元素是类，则至少要为该类的每一个方法编写一个测试，如果该方法需要测试多个用例，应放在同一个测试中。
- 若程序元素是类，除了为该类的每一个方法编写至少一个测试之外，还可以为该类编写任意多个测试，以覆盖该类的不同使用场景或边界情况。
- 若编写测试时需要用到辅助元素（Mock 或 Spy 等），可以在测试文件中直接定义这些辅助元素。若辅助元素较为简单，则可以直接放在每一个测试内部，优先保证每个测试的独立性，而不是追求极致 DRY；若辅助元素较为复杂或需要在多个测试中复用，则可以放在测试文件顶部，供该测试文件中的所有测试使用。
- 测试顺序应与源文件中被测试目标的原始顺序保持一致。
- 若该模块不需要测试，必须在说明文件中明确说明该模块不需要测试，并说明理由。一般来说，只有在该模块没有可执行的公共函数、只承载类型层表达，或其语义已被上层模块的测试完整覆盖且重复测试几乎不再带来额外价值时，才适合这样处理。
- 模块的单元测试文件目录是 `./tests/unit/storage`，若模块包含子模块，则子模块的单元测试文件目录为 `./tests/unit/storage/<sub-module-name>`。
- 对这个模块来说，测试应优先覆盖唯一标识约束、查询与批量操作、变更事件通知、快照隔离、乐观更新或合并更新的语义边界、删除与回滚路径，以及运行时存储结构与外部系统接点之间的协同行为。
