# @baukit/data-contracts 中文文档

规范地址：https://npmcn.com/package/%40baukit/data-contracts/

版本：0\.18\.3
npm 元数据更新时间：2026-11-07T02:36:01.348Z
中文译文更新时间：2026-11-07T18:25:54.928Z
软件包许可：MIT

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40baukit/data-contracts
原文对照：https://npmcn.com/package/%40baukit/data-contracts/?lang=original
项目主页：https://github.com/PatrickKoss/baukit#readme
源码仓库：https://github.com/PatrickKoss/baukit

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：580 kB，79 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- Node.js 要求：\>=24\.0\.0
- 运行依赖：0 个
- 首次发布：2026-08-25T18:45:58.242Z
- 最新版本发布：2026-11-07T02:36:00.985Z
- 近 30 天下载量：9230（2026-10-08 至 2026-11-06，npm 官方下载统计）
- 已发布版本数：67（近 12 个月 67 个）

## 中文简介

运行时间中性数据合同,测量辅助器,以及可执行适配器兼容性测试.

## 安装

```sh
npm install @baukit/data-contracts
```

也可以使用：`pnpm add @baukit/data-contracts`、`yarn add @baukit/data-contracts`、`bun add @baukit/data-contracts`

## README

# `@baukit/data-contracts`

运行时间中性数据合同,测量辅助器,以及可执行适配器兼容性测试.

软件包定义了 JSON 密钥/值存储,ID顺序记录存储,并用有边框的密钥集 pagination,原子交易回调,以及chema元数据/迁移常规. 它刻意包含没有产品实体,也没有Expo SQLite,Dexie,或节点数据库适配器. 船上的储能适配器是 `WebStorageKeyValueStore`,因为Web存储不需要依赖.

## 使用合同

```ts
import type { StoredRecord, TransactionalStorageStore } from '@baukit/data-contracts';

interface Note extends StoredRecord {
  title: string;
}

type NoteDatabase = TransactionalStorageStore<Note>;
```

`TransactionalStorageStore` 使筑巢和生命周期行为明确. 内召回,呼叫 `withTransaction` 交易范围上下文 加入环境交易。 调用根库是独立的,必须 由适配器序列化. 之后 `close()` 解决, 所有操作拒绝 与 `StorageError.code === "storage_closed"`. 使用配额失败 `storage_quota_exceeded`;呼叫者不需要解析提供者错误文本.

`KeyValueStore.clearPrefix(prefix)` 删除开头的每个密钥 `prefix`,例如所有 一个账户的草稿,当密钥看起来像 `draft:<account>:<document>`。匹配为 uTF-16 代码单位精确且区分大小写, 无字符为通配符, 且前缀为空 清理商店. `clear()` 整个店都空了.

`WebStorageKeyValueStore` 是一个 `KeyValueStore` 结束 `sessionStorage` 或者说 `localStorage`它存储 每个值作为 JSON 文本在您通过的名称空间下, 所以 `clear()` 财务报告和审定财务报表 `clearPrefix()` 永不删除 来源的其他密钥。 空名空间丢出 `RangeError`。存储失败拒绝 以满载的、 `StorageError.code === "storage_quota_exceeded"`。 。 。 .

```ts
import { WebStorageKeyValueStore } from '@baukit/data-contracts';

const drafts = new WebStorageKeyValueStore(sessionStorage, 'app:drafts:v1:');
await drafts.set('note:new', { title: '' });
await drafts.clearPrefix('note:');
```

年长者 `StorageTransaction` 财务报告和审定财务报表 `Transaction` 接口仍然可用 对于只执行原始表面的适配器.

## 资源预算计量

进口生产计量 `/limits` 子路径。 检查返回测量并允许 值,或抛出 `LimitExceededError` 和同样的田地。 产品选择每个允许的值和 将错误映射到他们自己的原因代码中.

```ts
import {
  checkCompactJsonUtf8Bytes,
  checkTrimmedUnicodeScalars,
} from '@baukit/data-contracts/limits';

checkTrimmedUnicodeScalars('  e\u0301  ', 2);
checkCompactJsonUtf8Bytes({ value: 'é' }, 14);
```

文本测量修饰 `White_Space` 两端有相机 它没有使案文正常化. 压缩 JSON 测量接受无效的,布尔的,有限度的数,只使用scalar的字符串,密集的数组, 和纯洁的物体。 普通对象可能使用可假设的自有字符串键. 不可承担财产是: 被忽略 。 符号密钥,访问器,自定义原型,循环引用,不支持的值, 非无限数量, 和无奖代孕投掷 `ResourceMeasurementError`。紧凑编码器 用途 `JSON.stringify` 属性命令,尽管属性命令不能改变所计量的字节数.

现有的产品帮助者可以一次迁移一次呼叫. 替换 `codePointLength(value.trim())` 与 `trimmedUnicodeScalarCount(value)`替换一个 `JSON.stringify` 加号 `TextEncoder` 字节计数 与 `compactJsonUtf8Bytes(value)`。不同于原始 `JSON.stringify`,帮助者拒绝 JSON 的值 将省略或替换为 `null`。 。 。 .

`checkRange(measured: number, minimum: number, maximum: number): LimitRange` 包括检查 边框和返回 `{ measured, minimum, maximum }`最低值或最高值 最大抛出量 `LimitOutOfRangeError` 和那些田园。 所有论据都必须是安全的 整数. 倒转边框 `RangeError`。零和等界限是有效的.

```ts
import { checkRange, enforceLimit } from '@baukit/data-contracts/limits';

enforceLimit('intervalSeconds', 'out_of_range', () => checkRange(3600, 3600, 2678400));
```

`enforceLimit` 地图 `LimitOutOfRangeError` 改为 `LimitRangeError`,一个子类 `LimitError`。 。 。 . 它携带 `reason: 'out_of_range'`, (中文(简体) ), `field`, (中文(简体) ), `measured`, (中文(简体) ), `minimum`,以及 `maximum`继承 `allowed` 等于 `maximum` 对于已读取最大限制错误的呼叫者。 无效的参数 和倒置的界限 `RangeError` 和通过而不成为政策拒绝.

### 限制政策文件

`parseLimitsPolicy(value, schema)` 验证产品的限制政策文件,例如 `limits.json` 模板生成。 计划名称为支持的版本和每个版本的密钥 节。 解析器需要字符串 `$comment`准确的版本 准确的章节和钥匙集, 和正安全整数。 所列密钥 `allowZero` 作为 `section.key` 还接受零。 任何其他 形状抛出 `LimitsPolicyError`,其信息将失败的路径命名,但绝不是一个值。 通过 计划 `as const` 因此结果类型跟随声明的密钥。 添加范围路径到 `rangeKeys` 作为 `section.key`。这些条目完全包含 `{ minimum, maximum }`,具有非负安全性 整数界限和 `minimum <= maximum`。不能在 `allowZero`。 。 。 .

```ts
const schema = {
  version: 1,
  sections: { text: ['max_characters'], ranges: ['interval_seconds'] },
  rangeKeys: ['ranges.interval_seconds'],
} as const;
const policy = parseLimitsPolicy({
  $comment: 'Reviewed limits.',
  version: 1,
  text: { max_characters: 200 },
  ranges: { interval_seconds: { minimum: 3600, maximum: 2678400 } },
}, schema);
// policy.ranges.interval_seconds has minimum and maximum number properties.
```

`enforceLimit(field, reason, check)` 地图 `LimitExceededError` 输入 `LimitError`,包含 `reason`, (中文(简体) ), `field`, (中文(简体) ), `measured`,以及 `allowed`。它绘制失败范围 `LimitRangeError` 作为 说明。 其他错误通过.

```ts
import { checkTrimmedUnicodeScalars, enforceLimit, parseLimitsPolicy } from '@baukit/data-contracts/limits';

const schema = { version: 1, sections: { text: ['max_characters'] } } as const;
const policy = parseLimitsPolicy(limitsJson, schema);

enforceLimit('title', 'text_too_long', () =>
  checkTrimmedUnicodeScalars(title, policy.text.max_characters),
);
```

跨领域规则,例如一个限制不得超过另一个限制,在产品代码之后保留 剖析.

## 导入信封

这个 `/import-envelope` 子路径将导入安全与产品的文件格式区分开来. 产品 解码器返回安全上下文和可移动的已命名行对象. `prepareImportEnvelope` 检查 调用解码器前的源字节限制, 当超过行限制时停止, 拒绝 允许列表以外的字段,检查被允许的字段内的字符串,解码每行,以及 运行产品预览规划器。 计划员接收解码行,但没有交易适配器.

```ts
import {
  commitImportEnvelope,
  prepareImportEnvelope,
} from '@baukit/data-contracts/import-envelope';

const preview = await prepareImportEnvelope({
  source,
  limits: { maxSourceBytes: 2_097_152, maxRows: 5_000, maxStringBytes: 8_192 },
  decodeEnvelope: decodeProductExport,
  fieldAllowlist: { notes: ['id', 'title', 'body'] },
  decodeRow: decodePortableRow,
  plan: planImport,
});

await commitImportEnvelope({
  preview,
  transaction: repositories,
  write: writeImportPlan,
  afterCommit: () => requestSync(),
});
```

解码器仍然拥有格式和计划版本,需要的字段,重复的ID,墓碑 政策和实体解码。 策划者拥有冲突,出处,以及删除顺序. `commitImportEnvelope` 将完整的产品写到一个后面 `withTransaction` 电话 它引用 `afterCommit` 只有在那个电话决定之后 保留 `afterCommit` 因为它的失败是无能的 不退缩承诺的交易.

这个API是添加剂。 现有的导入代码可以通过包装其当前解码器和预览来迁移 首先计划者,然后将写入循环移动到 `commitImportEnvelope`。不需要文件 格式或数据库迁移.

## 出口

这个 `/export` 子路径是导入信封的对应 . `encodeCsv` 将行变为 RFC 4180 CSV 字符串:每个记录以 CRLF 结尾,当其中包含逗号时引用一个单元格,双键 引文, CR, 或 LF 。 一个空单元格的一行写为 `""` 所以读者仍然看到一个领域. 帮助者没有运行时间依赖性;产品将字符串写入文件并共享.

```ts
import { csvNumeric, encodeCsv } from '@baukit/data-contracts/export';

const csv = encodeCsv(
  [
    ['note', 'amount', 'price'],
    ['=HYPERLINK("https://example.test")', -5, csvNumeric('1.50')],
  ],
  { byteOrderMark: true },
);
```

公式在默认情况下中和 。 文本单元格在第一个时会得到一个主原子 字符为 `=`, (中文(简体) ), `+`, (中文(简体) ), `-`, (中文(简体) ), `@`,标签,或CR,或何时 `=`, (中文(简体) ), `+`, (中文(简体) ), `-`,或 `@` 跟踪主空格, 选项卡, CR, 或 LF。 只检查这些 ASCII 字符 。 气压会改变值 所以 文档中您自己的进口商必须读取它或者用 `neutralizeFormulas: false`。仅对从未打开的机器读取文件使用选择退出 电子表格.

文本看起来像负数,例如 `'-5'`,是中和的。 传递真实数字为 有限制 `number` 或作为 `csvNumeric(text)` 格式化时,例如 `'1.50'`数字 单元格必须与 JSON 数字语法相匹配,并且不作修改. `null` 是一个空单元格. `byteOrderMark: true` 以 U+FEFF 为电子表格程序启动输出,该电子表格程序需要它来检测 UTF-8 (英语).

默认 `null` 财务报告和审定财务报表 `''` 两者都写出一个空域,所以读者不能将它们区分开来. 两个选择 为您自己的进口商读取的文件保留它们:

-   `quoteAllCells: true` 引用每个文本和数字单元格。 虚无的细胞没有被引用,所以 `''` 变成 `""` 财务报告和审定财务报表 `null` 保持空闲。 一行只保留一个空格 `""`。 。 。 .
-   `nullMarker: '\\N'` 写入未引用的无单元格标记并引用相同的文本单元格 内容,所以 `"\N"` 读回为文本。 标记必须非空而无双引号, 逗号、CR和LF; `encodeCsv` 丢出一个 `RangeError` 否则.

```ts
encodeCsv([[null, '\\N', '', 'x']], { nullMarker: '\\N' }); // \N,"\N",,x
encodeCsv([[null, '', -5]], { quoteAllCells: true }); // ,"","-5"
```

`CsvEncodeError.code` 实值 `invalid_numeric_cell` 非无限数字或外部数字单元格 jSON语法, `invalid_unicode` 和未受宠的代孕者在一起 `unsupported_cell` 对于任何其他运行时间值。 错误带有 `rowIndex` 财务报告和审定财务报表 `columnIndex` 永远不要牢房 数值. `baukit_core::export::encode_csv` 在 Rust 中通过相同的向量 (单位:千美元)`fixtures/export-csv/csv-encoding-v1.json`) (中文(简体) ).

`ShareOutcome` 产品将文件交给平台后发生的事情:

-   `shared`: 共享表或Web Share API接受文件. 一些本地共享表报告 即使用户没有选择目标就关闭了它们.
-   `saved`:文件被写到用户可以打开的地方,如浏览器下载.
-   `cancelled`:用户解除共享或保存UI,平台也这么说,例如用一个 网络共享 `AbortError`。 。 。 .
-   `unavailable`:运行时间没有共享或保存机制,所以没有尝试.
-   `failed`:尝试了一种机制,并报告了取消以外的错误.

`SHARE_OUTCOMES` 列出相同的数值。 软件包尚未执行 Expo 或浏览器.

## 已修改写入

这个 `/revisioned-writes` 子路径每次发送一个服务器文档的编辑, 每个选中 反对修改。 它没有反应依赖。 用钩子把它包起来 `useSyncExternalStore`页:1 抓取对象仅在状态变化时更改 .

```ts
import { createRevisionedWriteQueue } from '@baukit/data-contracts/revisioned-writes';

const queue = createRevisionedWriteQueue({
  initial: { scope: accountId, document: noteId, acknowledgedRevision: loaded.revision },
  write: async ({ scope, document, value, expectedRevision, afterUnknownOutcome, signal }) => {
    const response = await api.saveNote(scope, document, value, {
      ifMatch: expectedRevision,
      idempotencyKey: await keyFor(document, value, expectedRevision),
      signal,
    });
    if (response.status === 409) {
      return { kind: 'conflict', currentRevision: response.revision, conflict: response.body };
    }
    if (!response.ok) {
      return { kind: 'rejected', error: response.problem };
    }
    return { kind: 'accepted', revision: response.revision };
  },
  signal: pageLifetime.signal,
});

queue.enqueue(editedValue);
await queue.flush();
```

该快照将两个值分开. `acknowledgedRevision` 是服务器的最后一个版本 已经确认. `unsent` 是尚未写入的本地值。 写出它所花费的值 从 `unsent` 经确认的修订为 `expectedRevision`编辑时绘制的 飞去新鲜的 `unsent` 状态 `dirty`承认老年人 值从不标出保存的较新的值。 排队编辑与 `coalesce(older, newer)`, (中文(简体) ), 默认保存新值。 写入补丁时传递自己的功能 页:1.

`enqueue` 从不发送。 在产品中跳出并呼叫 `flush`并行 `flush` 呼叫共享一个 排出,当队列被闲置或被暂停时,排出排出排出即可解决快照.

书面回调报告三种结果之一:

-   `accepted` 存储新修订并发送下一个未发送的值.
-   `rejected` 表示服务器在接受之前拒绝写入. 值会回到 `unsent`,与较新的编辑合并,状态是 `failed`。 。 。 . `flush` 或者说 `retry` 又发来了.
-   `conflict` 以服务器的修订和冲突有效载荷暂停队列 。 没有更多的发送 直到产品调用 `reset` 和它相信的修订。 为了保持本地价值, `reset` 改为 当前服务器修改和 `enqueue` 又是这个值.

从回信书中扔出,或者拒绝承诺,意味着结果不明. 服务器可能 已经应用了文字并丢失了回复。 队列移动到 `unknown-outcome` 并保留 准确价值和预期订正 `uncertain`,除了较新的编辑. `flush` 无所事事 那个状态. `retry` 发送相同的值和修订版 `afterUnknownOutcome: true`,所以请 产品可以重用其一能键。 否则读取服务器并拨打 `reset`。队列永远不会 将未知结果视为在服务器接受写入之前发生的故障.

`reset(next)` 是账户或文档切换器的生成栅栏。 它中止当前 通过 `signal` 回到了召回, 放弃了不在场和不确定的状态, 并忽略 旧世代的任何后期结果. `cancel()` 相同,并保留队列 `cancelled` 直到下一个 `reset`终止注入 `signal` 选项调用 `cancel`。 。 。 .

状态为 `cancelled`, (中文(简体) ), `writing`, (中文(简体) ), `failed`, (中文(简体) ), `unknown-outcome`, (中文(简体) ), `conflict`, (中文(简体) ), `dirty`,或 `idle`输入 优先级. `error` 拿着 `rejected` 错误或抛出值.

## 持久草案

这个 `/durable-draft` 子路径在任意 `KeyValueStore`所以它活下来了 重新装入或崩溃 。 它没有反应依赖.

```ts
import { createDurableDraft, type DraftCodec } from '@baukit/data-contracts/durable-draft';

const noteCodec: DraftCodec<NoteDraft> = {
  version: 2,
  encode: (value) => ({ body: value.body, baseRevision: value.baseRevision }),
  decode: (value, version) => decodeNoteDraft(value, version),
};

const draft = createDurableDraft({
  store,
  key: (scope: { accountId: string; noteId: string }) =>
    `note-draft:${scope.accountId}:${scope.noteId}`,
  codec: noteCodec,
});

await draft.open({ accountId, noteId }, { body: loaded.body, baseRevision: loaded.revision });
draft.update({ body: editedBody, baseRevision: loaded.revision });
await draft.save();
```

储存库 `{ version, value }`。解码器拥有该值 : `encode` 返回 JSON,并 `decode` 接收未选中的 JSON 加上存储的版本并返回 `decoded`, (中文(简体) ), `corrupt`,或 `unsupported-version`中验证每个字段 `decode`从旧版本解码的值 算作升级,所以草稿打开了脏而下一个 `save` 重写. 帮助者返回 `unsupported-version` 不打电话 `decode` 当存储的版本在上方 `codec.version`, (中文(简体) ), 财务报告和审定财务报表 `corrupt` 当信封不整齐或 `decode` 掷出. 保留服务器基础修订版或 ETag 当产品需要后期冲突检查时,在值内.

一个公开的快照有两组状态. `recovery` 说什么 `open` 已找到 : `none`, (中文(简体) ), `restored`, (中文(简体) ), `corrupt`, (中文(简体) ), `unsupported-version`,或 `unavailable` 当读取失败时. `persistence` 说什么 存储正在进行中 : `loading`, (中文(简体) ), `idle`, (中文(简体) ), `saving`, (中文(简体) ), `clearing`,或 `failed`。 。 。 . `dirty` 当 价值与存储的数值不同 `localRevision` 计数本地更改。 一个恢复 草案也可能是肮脏的.

`open` 从不写或删除。 虽然复苏 `corrupt` 或者说 `unsupported-version`, (中文(简体) ), `save` 决定 `blocked` 因此,无法读取的数据保持不变。 打电话 `clear({ reason: 'discarded' })` 改为 拿开.

`clear` 取出删除的理由 :

-   `{ reason: 'submitted', localRevision }` 报告服务器在本地端确认值 修订。 快照显示 `submission: 'confirmed'` 并保持该值可见。 如果用户 修订后编辑了已储存的草案 `clear` 决定 `newer-edits-kept`。 。 。 .
-   `{ reason: 'discarded' }` 删除草稿, 并将值返回给 `open`。 。 。 .

以 a 拒绝删除失败 `DraftPersistenceError` 并停留在快照中作为 `persistence: 'failed'`在呈文之后, `submission` 留下来 `confirmed`,所以产品可以分辨 "服务器有,但本地副本可能会回来",除了"没有发送". 读,写, 删除失败使用 `DraftPersistenceError` 与 `code: 'draft_persistence_failed'` 还有一个 `operation`。信息从不包含密钥或值.

`open` 一个新的瞄准镜 栅栏前一个。 尚未开始的旧范围的储存工作 跳过并解析 `stale`。已经开始的工作已经完成,但不会改变新的 示意图. 所有存储工作都按顺序进行,所以从A切换到B,然后回到A读取A的最后一个 写左. `close()` 不保存; 调用 `save` 打开 `pagehide` 或当应用程序移动到 背景情况.

`move(scope)` 将开放的草案交给另一个范围。 通常的情况是"新注"草稿 服务器创建刚刚成功 :

```ts
await draft.clear({ reason: 'submitted', localRevision });
await draft.move({ accountId, noteId: created.id });
```

当值是脏的或来自存储, `move` 首先在新密钥下写入, 替换 任何存储在那里。 然后删除每个案件中的旧密钥。 价值, `localRevision`,以及 `submission` 保持原样,然后保存到新键。 腐败或不支持的草案 决定 `blocked`。如果写入或删除失败, `move` 以 `DraftPersistenceError`, (中文(简体) ), 草案保留在旧范围,呼吁 `move` 再来一遍. `move` 不记得在哪里 草稿不见了 如果第二个编辑器仍然持有旧范围,产品会储存自己的指针 旧范围到新范围.

`isScopeActive` 当几个编辑可以拥有相同范围时, 例如两个标签时, 栏杆会写 与共享租赁。 帮助者在每次写入或删除前拨打 `save`, (中文(简体) ), `clear`,以及 `move`,对于移动的两个范围,当排队工作开始。 当它返回错误的操作 决定 `stale` 而存储是不被触及的。 读书不是被围住的.

```ts
const draft = createDurableDraft({
  store,
  key: noteDraftKey,
  codec: noteCodec,
  isScopeActive: (scope) => lease.holds(scope.accountId),
});
```

截图通过阅读 `getSnapshot()` 财务报告和审定财务报表 `subscribe`,哪个西装 `useSyncExternalStore`编辑 存储值本身总是同步读取,因为 `KeyValueStore` 是一个同步结束 有索引的DB和SQLite. 装入状态 `persistence` 实值 `loading`与 `WebStorageKeyValueStore` 维持一个小任务.

## 认证分区

`deriveScopedStoreName(namespace, subject)` 修剪有长度限制的条子 身份与 SHA-256 并返回不透明名称。 浏览器和节点运行时间 `globalThis.crypto.subtle`。反应土著必须安装符合标准 调用默认帮助器前的 Web Crypto 复方填充, 或注入一个 `ScopedPersistenceDigest`; 生成的世博会模板注入 `expo-crypto`。 。 。 .

保留 `ScopedPersistenceRegistryStore` 在域数据库之外(例如, 安全 Store 或 本地 Storage . `resolveScopedStore` 通过一个序列化访问 注册实例并验证所有版本元数据. 格式错误、 未知版本、 不一致、 或 文摘相匹配的元数据 `PersistenceIdentityMismatchError` 有代码 `persistence_identity_mismatch` 在域名商店打开之前. 商店名称可能只属于一个登记册条目, 包括跨名称空间.

`ScopedPersistenceLifecycle` 立刻藏起坚固的手柄,紧紧地关上 打开另一个主题, 重置产品提供的用户扫描内存,并发布 只有开口/移入/水分。 打电话 `handleSessionExpired()` (单位:千美元) 终端认证过期; 它关闭和块而不创建 主题切换。 为了证明理由 `profile_erased`电话 `handleProfileErased()` 带有配置的 `erase` 钩. 它关闭并删除最后一个选中分区, 删除其注册项, 结束时以理由被屏蔽 `profile-erased`编辑 `cleanup` 字段为 `pending` 当过渡被排队或运行时, `complete` 在消除后或当没有分区需要消除时 `failed` 如果关闭或关闭 擦除失败或无 `erase` 钩已配置 。 这个 `error` 是一个提示 除非清理失败, 当它持有清理错误时。 显示清理失败 仅在 `cleanup === 'failed'`。寿命周期保留分区 后边上下文 `clear()` 或者说 `handleSessionExpired()`,所以可以进行符号清除 在清除事件之前 清理失败保留了登记册条目。 选择 清除对象再次保留其清理结果和错误。 同时 选择不能重开已删除的配置文件; 新配置文件 ID 打开新文件 清除后分区. 清除后保留身份账户的产品必须按键分区 服务器配置文件ID,而不是身份提供者的主题.

产品可供应 `resolveStore` 在保存时选择自己的商店名称 共享的关闭/重置/被推出生命周期。 解析器验证了自己的 元数据与失败关闭 。 使用 `recheckServerSubjectBeforeSyncAdoption` 在服务器身份之前 收养或推出箱.

`RecordStore.list` 命令使用 JavaScript 字符串比较来升起不可移动字符串 ID。 页面大小限于 `1..MAX_PAGE_SIZE`,继续光标对呼叫者来说必须是不透明的。 适配器应该执行基于上一个返回的ID的密钥集光标,而不是数字偏移,所以在光标不能在后页移动之前插入.

## 证明适配器

维特帮手被隔离在一个只有测试的子路径中. Vitest是有意的不是同龄人依赖,因此它从未被安装在Jest或生产消费者中. 子路径没有 `default` 出口条件,所以杰斯特套房不能偶然解决. 在导入子路径之前, 在适配器项目的开发依赖性中安装 Vitest, 然后注册 :

```ts
import {
  describeImportEnvelopeContract,
  describeKeyValueContract,
  describeRecordStoreContract,
  describeSchemaMetadataContract,
  describeScopedPersistenceContract,
  describeTransactionContract,
  describeTransactionalStorageContract,
} from '@baukit/data-contracts/vitest';

const makeDatabase = () => new MyAdapter();

describeKeyValueContract(() => makeDatabase().keyValues);
describeRecordStoreContract(() => makeDatabase().records);
describeTransactionContract(makeDatabase);
describeTransactionalStorageContract(makeDatabase);
describeScopedPersistenceContract(makeNamedDatabaseAdapter);
describeSchemaMetadataContract(() => makeDatabase().schemaMetadata);
describeImportEnvelopeContract(importEnvelopeOptions);
```

每个工厂必须归还一个新鲜而空的商店. 记录套房提供 拥有 JSON 形状 `{ id, label, payload }` 记录。 交易执行 必须显示回调范围视图, 并显示所有回调写入 一起使用,或者如果回调/拒绝,则不使用。 更强大的复合套房 还证明了回调结果、复合物和外加箱形原子学, 重新加入、配额正常化、密切行为和同时进行 交易系列化。 测距套房证明E-F-E记录和 收发箱隔离 打开前关闭 内存重置, 腐败的注册屏蔽 服务器主体检查 终端失效 内容 适配器厂必须重开相同同名的耐用数据. 这个 包含 `InMemoryStore` 曝光情况 `keyValues`, (中文(简体) ), `records`,以及 `schemaMetadata` 名字空间本身就在每个套房测试. 进口信封套件使用呼叫器提供的固定装置和一副新鲜的吊带. 它检查畸形 文件、无写预览、字段过滤、中途失败后回滚、光标或同步 承诺后状态变化.

持久性适配器和产品特有迁移逻辑属于产品或未来适配器包. 生产进入点没有运行时间依赖关系;只有 `/vitest` 子路径期望消费者的维特安装.

## 描述文件擦除

打电话 `eraseProductProfile` 仅在明确用户确认后。 编曲 `eraseServerProfile` 与 `@baukit/api-runtime/erasure`, (中文(简体) ), `eraseLocalPartition` 与 `ScopedPersistenceLifecycle.eraseActivePartition()`, (中文(简体) ), 财务报告和审定财务报表 `signOut` auth 客户端本地会话的清理.

已完成或待收到确认已承诺删除服务器配置文件 . 两者都管理本地分区的去除,然后签出. 尚未收到的收据报告a `pending` 因此 UI 可以解释登录账户删除仍然 结束。 当服务器运行时, 其操作 ID 可以无效 `profile_erased` 围栏 在不暴露行动身份的情况下确认犯罪.

一个明确的服务器故障或模糊的结果保留了本地数据和 届会。 重审一项含混不清的要求,即持续存在的一能钥匙. 服务器上钩前的故障仍然是安全的诊断警告. 局部失败 仍在尝试退出并报告两次失败;退出失败将保持不变 可见。 无论是清理失败, 都不应该触发新的服务器删除 .

## 常见问题

### @baukit/data-contracts 是什么？

运行时间中性数据合同,测量辅助器,以及可执行适配器兼容性测试\.

### 如何安装 @baukit/data-contracts？

在项目目录运行 npm install @baukit/data-contracts，或使用 pnpm add / yarn add / bun add。

### @baukit/data-contracts 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### @baukit/data-contracts 支持 ESM 还是 CommonJS？

以 ES Module 形式发布，推荐使用 import 引入。在 CommonJS 项目中，Node\.js 20\.19\+ / 22\.12\+ 可以直接 require\(\) 不含顶层 await 的 ES Module，更早的版本需要改用动态 import\(\)。

### @baukit/data-contracts 需要什么版本的 Node\.js？

package\.json 的 engines 字段要求 Node\.js \>=24\.0\.0。版本不符时 npm 默认只给出警告，开启 engine-strict 后会拒绝安装。

### @baukit/data-contracts 可以商用吗？

@baukit/data-contracts 使用 MIT 许可。宽松许可：允许商业使用、修改和再分发，通常只需在分发时保留原版权与许可声明。以上为概括，不构成法律意见，请以许可原文为准。

### @baukit/data-contracts 的下载量有多少？

根据 npm 官方下载统计，@baukit/data-contracts 在 2026/10/08 – 2026/11/06 这 30 天内被下载 9,230 次，日均约 308 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入，适合横向比较热度，不能直接等同于使用人数。

### @baukit/data-contracts 还在维护吗？

最新版本 0\.18\.3 发布于 2026/11/07（今天）；至今共发布 67 个版本，其中过去 12 个月发布了 67 个；首次发布于 2026/08/26。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

