豫言 / 豫言操作系统对象存储适配 @ 0.1.0

以云工 R2 绑定句柄实现对象存储接口。

源码浏览已生成;不能解析的文件以纯文本展示,不表示通过类型检查。

说明.汉语.md

# 对象存储云工适配

把公共[对象存储接口](../../../豫言操作系统接口/对象存储/规范.汉语.md)映射到当前 Worker 许可中的 R2 绑定(`许可.json` 例:`{"R2":["PACKAGES"]}`,桶名由应用传入),全部用豫言写在 `云工宿主`的通用句柄桥之上(`云工绑定句柄('R2', 桶名)` 与 `云工调用方法安全`),不含宿主 JS。

| 接口函数 | R2 调用 |
| --- | --- |
| `取对象元数据` | `head(键)`,读 `size`、`etag`、`httpEtag`、`uploaded`(Date 经 `getTime`)、`httpMetadata`、`customMetadata` |
| `读取对象文字` | `get(键)`;`size` 超过上限则不读正文;否则 `arrayBuffer()` 后用 `new TextDecoder('utf-8', {fatal: true, ignoreBOM: true})` 严格解码 |
| `读取对象字节` | `get(键)`;`size` 超过上限则不读正文并抛异常;否则 `arrayBuffer()` 后用 `云工句柄取字节` 直接取字节串(不经 JSON) |
| `写入对象`、`写入对象文字` | `put(键, Uint8Array, {onlyIf: Headers, sha256, httpMetadata, customMetadata})`;返回 `null` 即前置条件不满足 |
| `删除对象` | `delete([键…])` |
| `列举对象` | `list({limit, prefix, cursor, delimiter, include})` |
| `回应入站以对象存储对象` | GET:`get(键)` 后把 `body` 流交给 `new Response(body, {status, headers})` 并 `设置响应句柄`;HEAD:`head(键)` 与无体的 Response |

要点:

- **JSON 字面原样传出**:对象的键、`etag`、`httpEtag`、`customMetadata`、正文文字都是宿主 `JSON.stringify` 给出的合法字面,适配不在豫言里解码再重新编码(豫言的 JSON 编码器对控制字符的转义不可依赖,且大文字的逐字符转义太慢);只有整数(大小、毫秒)和 `httpMetadata` 的五个字符串字段会解析。因此 `读取对象文字` 的上限取 2 MiB:控制字符最坏膨胀六倍,仍低于值桥的 16 MiB。
- **条件写用真正的 `Headers`**:本地 workerd 的探测确认,`onlyIf` 传纯对象时 R2 静默忽略条件、照样覆盖,所以适配用 `构造对象('Headers', …)` 造 `If-None-Match: *` 或 `If-Match: "etag"`。
- **`list` 的结果是纯对象,其中 `objects` 的元素是 `HeadResult` 类实例**(宿主桥发句柄);未截断时 `cursor` 键存在而值为 `undefined`(桥写成 `{"$未定义":true}`),适配只在 `truncated` 为真时取续标。含 `include` 时本地 R2 每页约 100 项,应用必须按 `cursor` 续列。
- **句柄**:宿主事件的句柄表有 4096 项上限;`head`、`get`、`put` 的结果、`uploaded` 的 Date、`arrayBuffer`、`TextDecoder`、直通的响应与头、请求体流都在用毕后释放;桶句柄不释放(同一桶的句柄在事件内复用)。测试在同一事件里重复取元数据 6000 次、列举 200 次不触及上限。
- **校验在豫言里做**:桶名、键、选项 JSON(未知字段、类型、互斥、长度、保留名)、标头名与值、状态;违规抛出豫言异常,平台故障的消息保留原文。校验通过之前不创建任何宿主句柄。
- **直通不进 Wasm 内存**:GET 路径只读 `size`、`httpEtag`、`body`,正文流交给 Response;测试对 64 MiB 对象断言 `arrayBuffer/text/bytes/json/blob` 一次也没有被调用。

验证:同一份测试在 Node(R2 为形状取自真实 workerd 的类实例模拟)与 `wrangler 4.129.0` 的本地 workerd(真实 R2 绑定,本地存储)里都通过,见 `一致性验证/`。平台观察:`sha256` 不符的错误文字含 `(10037)`;键超过 1024 字节为 `(10020)`;`list` 的 `limit` 超过 1000 为 `(10022)`;本地 R2 先校验摘要再判前置条件;对可压缩类型且客户端接受 gzip 时,workerd 会改用分块传输而不保留 `Content-Length`。

说明.文言.md

# 对象存储云工适配

以当前 Worker 许可中之 R2 绑定承公共[对象存储公约](../../../豫言操作系统接口/对象存储/规范.文言.md)(`许可.json` 例:`{"R2":["PACKAGES"]}`,桶名由客授),悉以豫言书于 `云工宿主`通用句柄桥之上(`云工绑定句柄('R2', 桶名)` 与 `云工调用方法安全`),不藏宿主 JS。

| 公约之术 | R2 之召 |
| --- | --- |
| `取对象元数据` | `head(键)`,读 `size`、`etag`、`httpEtag`、`uploaded`(Date 以 `getTime` 取之)、`httpMetadata`、`customMetadata` |
| `读取对象文字` | `get(键)`;`size` 逾上限则不读正文;否则 `arrayBuffer()` 后以 `new TextDecoder('utf-8', {fatal: true, ignoreBOM: true})` 严解 |
| `读取对象字节` | `get(键)`;`size` 逾上限则不读正文而抛异;否则 `arrayBuffer()` 后以 `云工句柄取字节` 径取字节串(不经 JSON) |
| `写入对象`、`写入对象文字` | `put(键, Uint8Array, {onlyIf: Headers, sha256, httpMetadata, customMetadata})`;返 `null` 即前提不合 |
| `删除对象` | `delete([键…])` |
| `列举对象` | `list({limit, prefix, cursor, delimiter, include})` |
| `回应入站以对象存储对象` | GET:`get(键)` 后以 `body` 流授 `new Response(body, {status, headers})` 而 `设置响应句柄`;HEAD:`head(键)` 与无体之 Response |

要点:

- **JSON 字面原样而出**:对象之键、`etag`、`httpEtag`、`customMetadata`、正文文字,皆宿主 `JSON.stringify` 所给之合法字面,适配不于豫言解而复书(豫言 JSON 编码器于控制字符之转义不可恃,且大文逐字符转义太慢);惟整数(大小、毫秒)与 `httpMetadata` 之五字符串字段乃解之。故 `读取对象文字` 之限取二 MiB:控制字符最坏膨胀六倍,仍低于值桥之十六 MiB。
- **条件写用真 `Headers`**:本地 workerd 探之,`onlyIf` 授纯对象则 R2 静默忽其条件而照覆,故适配以 `构造对象('Headers', …)` 造 `If-None-Match: *` 或 `If-Match: "etag"`。
- **`list` 之果为纯对象,其 `objects` 之元乃 `HeadResult` 类实例**(宿主桥发柄);未截断时 `cursor` 键在而值为 `undefined`(桥书为 `{"$未定义":true}`),适配惟于 `truncated` 为真时取续标。含 `include` 时本地 R2 每页约百项,客当循 `cursor` 续列。
- **柄**:宿主事之柄表有四零九六项之限;`head`、`get`、`put` 之果、`uploaded` 之 Date、`arrayBuffer`、`TextDecoder`、直通之答与头、请求体之流,用毕皆释;桶柄不释(同桶之柄于事内复用)。试验于同一事内重取元六千次、列二百次,不触其限。
- **校在豫言**:桶名、键、选项 JSON(未知字段、型、互斥、长、保留名)、标头名与值、状;违者抛豫言之异,平台之故之辞存原文。校未过,不造任何宿主柄。
- **直通不入 Wasm 内存**:GET 之径惟读 `size`、`httpEtag`、`body`,正文流授 Response;试验于六十四 MiB 之对象断言 `arrayBuffer/text/bytes/json/blob` 一次未召。

验:同一试验于 Node(R2 为形取自真 workerd 之类实例仿造)与 `wrangler 4.129.0` 之本地 workerd(真 R2 绑定,本地存储)皆过,见 `一致性验证/`。平台所察:`sha256` 不合之辞含 `(10037)`;键逾一零二四字节为 `(10020)`;`list` 之 `limit` 逾一千为 `(10022)`;本地 R2 先验摘要而后判前提;可压缩之类而客户端受 gzip 时,workerd 改用分块传输而不存 `Content-Length`。