# @broberg/media 中文文档

规范地址：https://npmcn.com/package/%40broberg/media/

版本：0\.7\.0
npm 元数据更新时间：2026-11-03T15:16:05.516Z
中文译文更新时间：2026-11-08T16:12:03.810Z
软件包许可：MIT

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

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

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

- 包体积（解压后）：221 kB，14 个文件
- 模块格式：ESM + CJS
- TypeScript 类型：包内自带
- 运行依赖：2 个
- 首次发布：2026-06-15T15:33:47.903Z
- 最新版本发布：2026-11-03T15:16:05.063Z
- 近 30 天下载量：3203（2026-10-09 至 2026-11-07，npm 官方下载统计）
- 已发布版本数：9（近 12 个月 9 个）

## 中文简介

车队的提供者-不可知媒体存储外观——一个创建Media() API (upload = 获得 = 署名 Url = 删除 = public Url) 超越可互换存储提供者, 因此提供者之间的后一动作从不触及呼叫站点 。 a Cloudflare R2供应商(S3-co)

## 安装

```sh
npm install @broberg/media
```

也可以使用：`pnpm add @broberg/media`、`yarn add @broberg/media`、`bun add @broberg/media`

## README

# @broberg/媒体

舰队的 **提供者不可知的媒体存储外观** 一个 `createMedia()` API 密码 (单位:千美元)`upload` • 妇女 `signedUrl` • 妇女 `delete`超过可互换存储供应商,所以稍后 在后端之间移动从不触及呼叫站( `@broberg/ai-sdk` 模式, 为对象存储。 船舶 **云纹R2**; 配置扩展为 S3 / Supabase / GCS 不改变代码.

```bash
npm i @broberg/media
```

R2供应商会说 S3 API 并用 [`aws4fetch`](https://github.com/mhart/aws4fetch) (tiny, 0-dep,运行于节点 ^ Bun → 边缘 / Workers)-无 AWS SDK.

## 使用量

```ts
import { createMedia } from "@broberg/media";

const media = createMedia({
  provider: "r2",
  accountId: process.env.R2_ACCOUNT_ID!,
  accessKeyId: process.env.R2_ACCESS_KEY_ID!,
  secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
  bucket: "assets",
  jurisdiction: "eu",          // pin EU data-residency (GDPR); must match how the bucket was created
  keyPrefix: "tenants/acme/",  // optional multi-tenant isolation
  publicBaseUrl: "https://media.example.com", // optional — enables publicUrl() (public bucket)
});

const { key } = await media.upload("logo.png", bytes, { contentType: "image/png" });
const signed = await media.signedUrl(key, { expiresIn: 600 }); // presigned GET, no public bucket
const stable = media.publicUrl(key);                            // stable public URL (needs publicBaseUrl)
await media.delete(key);                                        // idempotent — a missing key is not an error
```

## API 密码

| 方法 | 回返 | 页:1 |
| --- | --- | --- |
| `upload(key, body, opts?)` | `{ key }` | 返回时 **逻辑** 键( 安全地反馈给其他人); `opts.contentType` 编号 : `opts.cacheControl` |
| `signedUrl(key, opts?)` | `string` (同步) | 限时预签名获取( E)`opts.expiresIn` 秒,默认为3600 ——不需要公共桶 |
| `publicUrl(key)` | `string` (单位:千美元)**同步**(中文(简体) ) | 稳定、非过期的公共 URL; 需要 `publicBaseUrl` + 公共桶; **掷出** 若为 `publicBaseUrl` 未设置 |
| `delete(key)` | `void` | 一能者(404人容忍) |

每一种方法都采用 **逻辑** 键; `keyPrefix` 内部应用(带有 单人 `/`被剥去的鞭子 `keyPrefix:"tenants/acme"` + 键 `"/logo.png"` → `tenants/acme/logo.png`。因为 `upload()` 返回逻辑密钥,并存储 传回它,从不双前缀.

> **v.2.0(行为):** `upload()` 现在返回 **逻辑** (无前缀)密钥, 而不是前缀的,所以绕行进入 `signedUrl`编号 :`delete`编号 :`publicUrl` 实值 对称 仅在使用时可观察到 `keyPrefix` (和圆通被打破) (见前文)。 还新建: `publicUrl(key)` + 键 `publicBaseUrl`。 。 。 .

## 公用URL (v

`signedUrl()` 过期 — 嵌入已发布内容的图像错误 (新闻丰富的文本,a **发送** (电子邮件)。 准备 `publicBaseUrl` (这桶的R2) 自定义域或 `r2.dev` URL)和使用 `publicUrl(key)` ——稳定而无期 URL, 构建同步( 没有签名/ IO) , 因此它会直接降入模板 .

仅包 **构造** uRL——水桶必须实际公开 可读取. 绑定自定义域名( 通过 `dns-mcp`,比如说. `media.example.com`)或启用 这桶 `r2.dev` 公共 URL,然后设置为 `publicBaseUrl`直到它被设定, `publicUrl()` 扔出(公众不要走——没有意外暴露).

## 信件的附件(v. 4.0, F006.7)

`@broberg/media/attachments` 是让存储文件成为保险箱的部分 _附录_:文件的真面目是什么, 无论它可能进来, 它居住的地方, 和如何服务它。 纯函数,无 I/O;从卡片上传路径被解除.

```ts
import { createMedia } from "@broberg/media";
import { validateAttachment, attachmentKey, attachmentHeaders, sha256Hex, type AttachmentRecord } from "@broberg/media/attachments";

const store = createMedia({ provider: "r2", /* … */ jurisdiction: "eu", keyPrefix: `tenants/${tenantId}/` });

const v = validateAttachment({ bytes, filename });          // the BYTES decide
if (!v.ok) return new Response(v.reason, { status: 400 });
const id = crypto.randomUUID();
const key = attachmentKey(id);                               // attachments/<id> — never the file name
await store.upload(key, bytes, { contentType: v.contentType });
const row: AttachmentRecord = { id, messageId, key, contentType: v.contentType, bytes: v.bytes, filename, sha256: await sha256Hex(bytes) };

// serving it through your app:
const obj = await store.get(row.key);
return new Response(obj!.bytes, { headers: attachmentHeaders(row.contentType, row.bytes, { filename: row.filename }) });
```

-   **字节决定类型.** 第一个字节是嗅觉(png, jpeg, gif, webp,), (原始内容存档于2018-09-28). heic, pdf, office. 存储 `v.contentType`,从来没有浏览器。 一个名字 承诺一个图像或一个PDF 字节不交付会被拒绝,因此 程序,无论它叫什么。 故意没有参数 索赔类型.
-   **默认列表 :** 图像,pdf, doc/docx,xls/xlsx,ppt/pptx,txt,csv,md,json. 没有 svg/html(他们可以携带脚本),也没有档案. 每个应用程序扩展 `allowedExt`禁止的地板(exe, s, js, dmg,.)永远不能重新开放.
-   **限制** 默认的 25 MB(`maxBytes`) (中文(简体) ).
-   **服务:** 图像和 PDF 渲染内入; 其他的都是后方下载 社区支助方案 `sandbox` + 键 `nosniff`文件名通过 `@broberg/http` `contentDisposition`这样 `faktura-æøå.pdf` 保留它的名字.
-   **不在这里:** hTTP 路由和数据库表格(您的), PDF 缩略图, 病毒扫描、图像重编码( name`@broberg/media-transform`) (中文(简体) ).

## 列出并删除文件夹( v0. 5.0, F006. 8)

```ts
const { keys, cursor } = await media.list("kunder/42", { limit: 1000 }); // logical keys, sorted
const { deleted } = await media.deletePrefix("kunder/42");              // e.g. a GDPR erasure
```

-   **前缀为文件夹.** `"kunder/1"` 意味着一切在 `kunder/1/`永远 `kunder/10/…` ——作为平庸的字符串匹配,一个GDPR删除会占用另一个客户的文件.
-   钥匙回来 **逻辑** (键前缀剥去),喜欢 `upload()` 返回它们。 页码 1–1000( 默认为 1000); `cursor` 仅当更多时才设置.
-   `deletePrefix("")` 拒绝:它会清空商店。 如果一个删除报告成功但对象保留, `deletePrefix` 停止错误而不是循环.
-   r2使用S3 List ObjectsV2;卷行文件夹. 从0.7.0 r2's开始 `list()` 以欧盟活桶衡量(见下文).

## 大小、时间、底格里斯和桶清单(0.7.0、F059.4)

```ts
const { objects = [] } = await media.list("db");          // [{ key, size, lastModified }] — same order as keys
const newest = [...objects].sort((a, b) => b.lastModified.localeCompare(a.lastModified))[0];

const tigris = createMedia({ provider: "tigris", accessKeyId, secretAccessKey, bucket: "backups" }); // fly.storage.tigris.dev
const buckets = await listBuckets({ provider: "r2", accountId, jurisdiction: "eu", accessKeyId, secretAccessKey });
// [{ name, createdAt }]
```

-   **`objects`** 坐在旁边 `keys` 与 `size` (字节)和 `lastModified` (国际标准化组织,协调世界时)。 每个内置提供者都填充它;它只是可选的,所以手写测试双还原 `{ keys }` 仍在编译.
-   **`tigris`** s3 代码与 `r2` 在另一个主机上`endpoint` 覆盖 `https://fly.storage.tigris.dev`因此,每个 `MediaStore` 方法也一样。 错误说 `media(tigris)`。 。 。 .
-   **`listBuckets()`** 是一个单独的函数,因为一个桶列表属于账户,而不是一个桶。 为R2, `jurisdiction` 选择主机:一个欧盟桶只在欧盟主机上出现. 它需要一个 **账户级别** 密钥 —— 被桶镜瞄准的 R2 令牌 `403 AccessDenied`,然后被扔出,从不作为空列表返回.
-   **测量了 3/11 2026:** `list()` 对欧盟活桶 `broberg-call-recordings` 返回大小、时间和光标的顺序与 `keys`。 。 。 . **未进行现场测量:** 一个成功的 `listBuckets()` (我们的钥匙被用桶镜扫描并得到了上面的403)和底格里斯(我们没有底格里斯钥匙)——两者都用被接住的取来来测试真实的S3 XML.

## 提供水桶

这个包 **消耗量** 现有桶 + S3 证书。 创建 欧盟管辖的R2桶+范围认证 100%在程序上(没有仪表板), 使用舰队的 `dns-mcp` R2 Client / MCP 工具(`r2_create_bucket`, (中文(简体) ), `r2_create_scoped_token`)——见 **云雾花** 开卡 [broberg.ai (英语)](https://discovery.broberg.ai/api/infra/cloudflare)。 。 。 . 欧盟的管辖范围设定在创建时,而且不可改变.

* * *

部分 [broberg.ai 共享库存](https://discovery.broberg.ai/)搜索 在构建之前 : `GET https://discovery.broberg.ai/api/search?q=storage`。 。 。 .

## 多段上传——一个请求的文件太大了(0.6.0,F099.20)

对于1GB的视频,字节应该从客户端直接到桶上,部分,从不通过你的服务器. `r2` 商店有 `store.multipart` (单位:千美元)`volume` 不——客户端不能 PUT 到磁盘 :

```ts
const { key, uploadId } = await media.multipart!.create("clips/film.mp4", { contentType: "video/mp4" });
const urls = await Promise.all([1, 2, 3].map((n) => media.multipart!.presignPart(key, uploadId, n, { expiresIn: 900 })));
// the client PUTs each part (≥ 5 MiB except the last) to its url and keeps the ETag header of each answer
await media.multipart!.complete(key, uploadId, [{ partNumber: 1, etag }, /* … ascending */]);
// or, on failure: await media.multipart!.abort(key, uploadId);
```

服务器保留了 R2 密钥;客户端只看到短寿命的预签名部分地址. 对于浏览器客户端,桶的 CORS 必须允许 `PUT` 从客户的来源并曝光 `ETag`。 。 。 . `complete` 治疗200 携带一个 `<Error>` 就像失败一样.

## 常见问题

### @broberg/media 是什么？

车队的提供者-不可知媒体存储外观——一个创建Media\(\) API \(upload = 获得 = 署名 Url = 删除 = public Url\) 超越可互换存储提供者, 因此提供者之间的后一动作从不触及呼叫站点 。 a Cloudflare R2供应商\(S3-co\)

### 如何安装 @broberg/media？

在项目目录运行 npm install @broberg/media，或使用 pnpm add / yarn add / bun add。

### @broberg/media 支持 TypeScript 吗？

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

### @broberg/media 支持 ESM 还是 CommonJS？

同时提供 ES Module 与 CommonJS 入口，import 和 require\(\) 都可以使用，打包工具会自动选择合适的版本。

### @broberg/media 可以商用吗？

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

### @broberg/media 的下载量有多少？

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

### @broberg/media 还在维护吗？

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

