> ## Documentation Index
> Fetch the complete documentation index at: https://bun.zhcndoc.com/llms.txt
> Use this file to discover all available pages before exploring further.

# S3

> Bun 提供了快速的原生绑定，用于与兼容 S3 的对象存储服务交互。

生产服务器通常读取、上传和写入文件到兼容 S3 的对象存储服务，而不是本地文件系统。历史上这意味着开发时使用的本地文件系统 API 无法在生产环境中使用。但使用 Bun，情况则有所不同。

### Bun 的 S3 API 很快

<Frame caption="左：Bun v1.1.44。右：Node.js v23.6.0">
  <img src="https://mintcdn.com/bun-zhcndoc/7hwCkUCcx3ux5DPj/images/bun-s3-node.gif?s=50fbf5dffc94dfaff3d284849e8ec18f" alt="Bun 的 S3 API 很快" width="1800" height="1095" data-path="images/bun-s3-node.gif" />
</Frame>

Bun 为与兼容 S3 的对象存储服务交互提供了快速的原生绑定。其 S3 API 设计得感觉类似于 fetch 的 `Response` 和 `Blob` API（就像 Bun 的本地文件系统 API 一样）。

```ts s3.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { s3, write, S3Client } from "bun";

// Bun.s3 会读取环境变量中的凭据
// file() 返回对 S3 上文件的惰性引用
const metadata = s3.file("123.json");

// 从 S3 下载 JSON
const data = await metadata.json();

// 上传到 S3
await write(metadata, JSON.stringify({ name: "John", age: 30 }));

// 预签名 URL（同步 - 无需网络请求）
const url = metadata.presign({
  acl: "public-read",
  expiresIn: 60 * 60 * 24, // 1 天
});

// 删除文件
await metadata.delete();
```

S3 是 [事实上的标准](https://en.wikipedia.org/wiki/De_facto_standard)互联网文件系统。Bun 的 S3 API 支持与以下兼容 S3 的存储服务协作：

* AWS S3
* Cloudflare R2
* DigitalOcean Spaces
* MinIO
* Backblaze B2
* …以及其他任何兼容 S3 的存储服务

## 基本用法

有多种方式可与 Bun 的 S3 API 进行交互。

### `Bun.S3Client` & `Bun.s3`

`Bun.s3` 等同于 `new Bun.S3Client()`，依赖环境变量中的凭据。

若需显式设置凭据，可通过构造函数传递给 `Bun.S3Client`。

```ts s3.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { S3Client } from "bun";

const client = new S3Client({
  accessKeyId: "your-access-key",
  secretAccessKey: "your-secret-key",
  bucket: "my-bucket",
  // sessionToken: "..."
  // acl: "public-read",
  // endpoint: "https://s3.us-east-1.amazonaws.com",
  // endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
  // endpoint: "https://<region>.digitaloceanspaces.com", // DigitalOcean Spaces
  // endpoint: "http://localhost:9000", // MinIO
});

// Bun.s3 是一个全局单例，等同于 `new Bun.S3Client()`
```

### 处理 S3 文件

`S3Client` 中的 **`file`** 方法返回对 S3 上文件的 **惰性引用**。

```ts s3.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
// 对 S3 上文件的惰性引用
const s3file: S3File = client.file("123.json");
```

类似于 `Bun.file(path)`，`S3Client` 的 `file` 方法是同步的。直到调用依赖网络请求的方法前，它不会进行任何网络请求。

### 从 S3 读取文件

如果你用过 `fetch` API，就对 `Response` 和 `Blob` API 非常熟悉。`S3File` 继承自 `Blob`。所有适用于 `Blob` 的方法也适用于 `S3File`。

```ts s3.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
// 将 S3File 作为文本读取
const text = await s3file.text();

// 将 S3File 作为 JSON 读取
const json = await s3file.json();

// 将 S3File 作为 ArrayBuffer 读取
const buffer = await s3file.arrayBuffer();

// 仅获取前 1024 字节
const partial = await s3file.slice(0, 1024).text();

// 流式读取文件
const stream = s3file.stream();
for await (const chunk of stream) {
  console.log(chunk);
}
```

#### 内存优化

像 `text()`、`json()`、`bytes()` 或 `arrayBuffer()` 这样的方法会尽量避免在内存中复制字符串或字节。

如果文本恰好是 ASCII，Bun 会直接将字符串传输给 JavaScriptCore（引擎），无需转码，也不会复制字符串副本。使用 `.bytes()` 或 `.arrayBuffer()` 时，也会避免复制字节。

这些辅助方法不仅让 API 更简洁，还提高了速度。

### 写入与上传文件至 S3

写入到 S3 的方式也一样。

```ts s3.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
// 写入一个字符串（替换文件）
await s3file.write("Hello World!");

// 写入一个 Buffer（替换文件）
await s3file.write(Buffer.from("Hello World!"));

// 写入一个 Response（替换文件）
await s3file.write(new Response("Hello World!"));

// 带内容类型写入
await s3file.write(JSON.stringify({ name: "John", age: 30 }), {
  type: "application/json",
});

// 使用内容编码写入（例如用于预压缩数据）
await s3file.write(compressedData, {
  type: "application/json",
  contentEncoding: "gzip",
});

// 使用内容处置写入
await s3file.write(pdfData, {
  type: "application/pdf",
  contentDisposition: 'attachment; filename="report.pdf"',
});

// 使用 writer（流式）
const writer = s3file.writer({ type: "application/json" });
writer.write("Hello");
writer.write(" World!");
await writer.end();

// 使用 Bun.write 写入
await Bun.write(s3file, "Hello World!");
```

### 处理大文件（流）

Bun 会自动处理大文件的分块上传，并支持流式写入。本地文件适用的 API 同样适用于 S3 文件。

```ts s3.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
// 写入大文件
const bigFile = Buffer.alloc(10 * 1024 * 1024); // 10MB
const writer = s3file.writer({
  // 遇到网络错误自动重试最多 3 次
  retry: 3,

  // 同时排队最多 10 个请求
  queueSize: 10,

  // 以 5MB 分块上传
  partSize: 5 * 1024 * 1024,
});
for (let i = 0; i < 10; i++) {
  writer.write(bigFile);
  await writer.flush();
}
await writer.end();
```

***

## 预签名 URL

当你的生产服务需要允许用户上传文件时，通常让用户直接上传到 S3 比你的服务器作为中介更可靠。

为此，你可以为 S3 文件预签名 URL。此 URL 包含签名，允许用户安全地上传指定文件到 S3，而不会暴露你的凭据或授予他们对桶的额外访问权限。

默认情况下，预签名 URL 是一个 24 小时后过期的 `GET` 请求。Bun 会尝试根据文件扩展名推断内容类型。如果不能推断，默认使用 `application/octet-stream`。

```ts s3.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { s3 } from "bun";

// 生成一个 24 小时后过期的预签名 URL（默认）
const download = s3.presign("my-file.txt"); // GET，text/plain，24 小时后过期

const upload = s3.presign("my-file", {
  expiresIn: 3600, // 1 小时
  method: "PUT",
  type: "application/json", // 设置预签名 URL 中的响应内容类型
});

// 使用内容处置进行预签名（例如强制使用特定文件名进行下载）
const downloadUrl = s3.presign("report.pdf", {
  expiresIn: 3600,
  contentDisposition: 'attachment; filename="quarterly-report.pdf"',
});

// 如果已有文件引用，也可以调用 .presign()，但应避免频繁这样做
// 以减少内存占用。
const myFile = s3.file("my-file.txt");
const presignedFile = myFile.presign({
  expiresIn: 3600, // 1 小时
});
```

### 设置 ACL

要为预签名 URL 设置 ACL（访问控制列表），传入 `acl` 选项：

```ts s3.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
const url = s3file.presign({
  acl: "public-read",
  expiresIn: 3600,
});
```

你可以传入以下 ACL：

| ACL                           | 说明                   |
| ----------------------------- | -------------------- |
| `"public-read"`               | 对象可被公开读取。            |
| `"private"`                   | 对象仅桶所有者可读取。          |
| `"public-read-write"`         | 对象可被公开读取和写入。         |
| `"authenticated-read"`        | 桶所有者和已认证用户可读取。       |
| `"aws-exec-read"`             | 发送请求的 AWS 账户可读取对象。   |
| `"bucket-owner-read"`         | 桶所有者可读取对象。           |
| `"bucket-owner-full-control"` | 桶所有者可读写对象。           |
| `"log-delivery-write"`        | AWS 用于日志传递的服务可以写入对象。 |

### 设置 URL 过期时间

设置预签名 URL 的过期时间，传入 `expiresIn` 选项。

```ts s3.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
const url = s3file.presign({
  // 秒数
  expiresIn: 3600, // 1 小时

  // 访问控制列表
  acl: "public-read",

  // HTTP 方法
  method: "PUT",
});
```

### `method`

设置预签名 URL 的 HTTP 方法，传入 `method` 选项。

```ts s3.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
const url = s3file.presign({
  method: "PUT",
  // method: "DELETE",
  // method: "GET",
  // method: "HEAD",
  // method: "POST",
  // method: "PUT",
});
```

### `new Response(S3File)`

要将用户重定向到 S3 文件的预签名 URL，请将一个 `S3File` 实例作为 body 传给 `Response` 对象。

这会自动将用户重定向到该预签名 URL，节省了服务器下载文件再发送给用户的内存、时间和带宽开销。

```ts s3.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
const response = new Response(s3file);
console.log(response);
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
Response (0 KB) {
  ok: false,
  url: "",
  status: 302,
  statusText: "",
  headers: Headers {
    "location": "https://<account-id>.r2.cloudflarestorage.com/...",
  },
  redirected: true,
  bodyUsed: false
}
```

***

## 对兼容 S3 服务的支持

Bun 的 S3 实现支持任何兼容 S3 的存储服务。只需指定对应的 endpoint：

### 使用 Bun 的 S3Client 连接 AWS S3

AWS S3 是默认选项。你可传入 `region` 代替 `endpoint` 以使用 AWS S3。

```ts s3.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { S3Client } from "bun";

// AWS S3
const s3 = new S3Client({
  accessKeyId: "access-key",
  secretAccessKey: "secret-key",
  bucket: "my-bucket",
  // endpoint: "https://s3.us-east-1.amazonaws.com",
  // region: "us-east-1",
});
```

### 使用 Bun 的 S3Client 连接 Google Cloud Storage

使用 Bun 的 S3 client 连接 [Google Cloud Storage](https://cloud.google.com/storage)，在 `S3Client` 构造器中将 `endpoint` 设置为 `"https://storage.googleapis.com"`。

```ts s3.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" highlight={8} theme={"theme":{"light":"github-light","dark":"dracula"}}
import { S3Client } from "bun";

// Google Cloud Storage
const gcs = new S3Client({
  accessKeyId: "access-key",
  secretAccessKey: "secret-key",
  bucket: "my-bucket",
  endpoint: "https://storage.googleapis.com",
});
```

### 使用 Bun 的 S3Client 连接 Cloudflare R2

使用 Bun 的 S3 client 连接 [Cloudflare R2](https://developers.cloudflare.com/r2/)，在 `S3Client` 构造器中将 `endpoint` 设置为包含你的账号 ID 的 R2 端点。

```ts s3.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" highlight={8} theme={"theme":{"light":"github-light","dark":"dracula"}}
import { S3Client } from "bun";

// CloudFlare R2
const r2 = new S3Client({
  accessKeyId: "access-key",
  secretAccessKey: "secret-key",
  bucket: "my-bucket",
  endpoint: "https://<account-id>.r2.cloudflarestorage.com",
});
```

### 使用 Bun 的 S3Client 连接 DigitalOcean Spaces

使用 Bun 的 S3 client 连接 [DigitalOcean Spaces](https://www.digitalocean.com/products/spaces/)，在 `S3Client` 构造器中将 `endpoint` 设置为对应区域的 DigitalOcean Spaces 端点。

```ts s3.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" highlight={8} theme={"theme":{"light":"github-light","dark":"dracula"}}
import { S3Client } from "bun";

const spaces = new S3Client({
  accessKeyId: "access-key",
  secretAccessKey: "secret-key",
  bucket: "my-bucket",
  // region: "nyc3",
  endpoint: "https://<region>.digitaloceanspaces.com",
});
```

### 使用 Bun 的 S3Client 连接 MinIO

使用 Bun 的 S3 client 连接 [MinIO](https://min.io/)，在 `S3Client` 构造器中将 `endpoint` 设置为 MinIO 运行的 URL。

```ts s3.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" highlight={10} theme={"theme":{"light":"github-light","dark":"dracula"}}
import { S3Client } from "bun";

const minio = new S3Client({
  accessKeyId: "access-key",
  secretAccessKey: "secret-key",
  bucket: "my-bucket",

  // 确保使用正确的 endpoint URL
  // 在生产环境中可能不是 localhost！
  endpoint: "http://localhost:9000",
});
```

### 使用 Bun 的 S3Client 连接 supabase

使用 Bun 的 S3 client 连接 [supabase](https://supabase.com/)，在 `S3Client` 构造器中将 `endpoint` 设置为包含账号 ID 及 `/storage/v1/s3` 路径的 supabase 端点。确保在 supabase 控制台  `https://supabase.com/dashboard/project/<account-id>/settings/storage` 中启用 “Enable connection via S3 protocol”，并设置相应区域。

```ts s3.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" highlight={3-10} theme={"theme":{"light":"github-light","dark":"dracula"}}
import { S3Client } from "bun";

const supabase = new S3Client({
  accessKeyId: "access-key",
  secretAccessKey: "secret-key",
  bucket: "my-bucket",
  region: "us-west-1",
  endpoint: "https://<account-id>.supabase.co/storage/v1/s3/storage",
});
```

### 使用 Bun 的 S3Client 连接 S3 虚拟主机式端点

使用 S3 虚拟主机式端点时，需要将 `virtualHostedStyle` 选项设置为 `true`。

<Note>
  * 如果不指定 endpoint，Bun 会根据 region 和 bucket 自动确定 AWS S3 端点。
  * 如果不指定 region，Bun 默认使用 us-east-1。
  * 如果明确指定 endpoint，则无需指定 bucket 名称。
</Note>

```ts s3.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" highlight={17, 25} theme={"theme":{"light":"github-light","dark":"dracula"}}
import { S3Client } from "bun";

// 从 region 和 bucket 推断的 AWS S3 端点
const s3 = new S3Client({
  accessKeyId: "access-key",
  secretAccessKey: "secret-key",
  bucket: "my-bucket",
  virtualHostedStyle: true, // [!code ++]
  // endpoint: "https://my-bucket.s3.us-east-1.amazonaws.com",
  // region: "us-east-1",
});

// AWS S3 指定 endpoint
const s3WithEndpoint = new S3Client({
  accessKeyId: "access-key",
  secretAccessKey: "secret-key",
  endpoint: "https://<bucket-name>.s3.<region>.amazonaws.com",
  virtualHostedStyle: true, // [!code ++]
});

// Cloudflare R2 指定 endpoint
const r2WithEndpoint = new S3Client({
  accessKeyId: "access-key",
  secretAccessKey: "secret-key",
  endpoint: "https://<bucket-name>.<account-id>.r2.cloudflarestorage.com",
  virtualHostedStyle: true, // [!code ++]
});
```

***

## 凭据

使用 S3 时，凭据是最难处理的部分之一。默认情况下，Bun 会读取以下用于凭据的环境变量。

| 选项名称              | 环境变量                   |
| ----------------- | ---------------------- |
| `accessKeyId`     | `S3_ACCESS_KEY_ID`     |
| `secretAccessKey` | `S3_SECRET_ACCESS_KEY` |
| `region`          | `S3_REGION`            |
| `endpoint`        | `S3_ENDPOINT`          |
| `bucket`          | `S3_BUCKET`            |
| `sessionToken`    | `S3_SESSION_TOKEN`     |

如果未设置 `S3_*` 环境变量，Bun 会依次检查对应的 `AWS_*` 环境变量。

| 选项名称              | 备用环境变量                  |
| ----------------- | ----------------------- |
| `accessKeyId`     | `AWS_ACCESS_KEY_ID`     |
| `secretAccessKey` | `AWS_SECRET_ACCESS_KEY` |
| `region`          | `AWS_REGION`            |
| `endpoint`        | `AWS_ENDPOINT`          |
| `bucket`          | `AWS_BUCKET`            |
| `sessionToken`    | `AWS_SESSION_TOKEN`     |

这些环境变量可以从 [`.env` 文件](/runtime/environment-variables) 或初始化时的进程环境读取（不会使用 `process.env`）。

你传给 `s3.file(credentials)`、`new Bun.S3Client(credentials)` 或其他接收凭据的方法的选项会覆盖默认值。举例来说，如果不同的桶使用相同凭据，你仅需在 `.env` 中设置凭据一次，然后调用 `s3.file()` 时只传 `bucket: "my-bucket"`，无需重复所有凭据。

### `S3Client` 对象

当你不使用环境变量或使用多个桶时，可创建 `S3Client` 对象来显式设置凭据。

```ts s3.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" highlight={3-11} theme={"theme":{"light":"github-light","dark":"dracula"}}
import { S3Client } from "bun";

const client = new S3Client({
  accessKeyId: "your-access-key",
  secretAccessKey: "your-secret-key",
  bucket: "my-bucket",
  // sessionToken: "..."
  endpoint: "https://s3.us-east-1.amazonaws.com",
  // endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
  // endpoint: "http://localhost:9000", // MinIO
});

// 使用 Response 写入
await file.write(new Response("Hello World!"));

// 预签名 URL
const url = file.presign({
  expiresIn: 60 * 60 * 24, // 1 天
  acl: "public-read",
});

// 删除文件
await file.delete();
```

### `S3Client.prototype.write`

向 S3 上传或写入文件，可调用 `S3Client` 实例的 `write` 方法。

```ts s3.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" highlight={8, 9} theme={"theme":{"light":"github-light","dark":"dracula"}}
const client = new Bun.S3Client({
  accessKeyId: "your-access-key",
  secretAccessKey: "your-secret-key",
  endpoint: "https://s3.us-east-1.amazonaws.com",
  bucket: "my-bucket",
});

await client.write("my-file.txt", "Hello World!");
await client.write("my-file.txt", new Response("Hello World!"));

// 等价于
// await client.file("my-file.txt").write("Hello World!");
```

### `S3Client.prototype.delete`

删除 S3 文件，调用 `S3Client` 实例的 `delete` 方法。

```ts s3.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" highlight={7} theme={"theme":{"light":"github-light","dark":"dracula"}}
const client = new Bun.S3Client({
  accessKeyId: "your-access-key",
  secretAccessKey: "your-secret-key",
  bucket: "my-bucket",
});

await client.delete("my-file.txt");
// 等价于
// await client.file("my-file.txt").delete();
```

### `S3Client.prototype.exists`

检查 S3 文件是否存在，调用 `S3Client` 实例的 `exists` 方法。

```ts s3.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" highlight={7} theme={"theme":{"light":"github-light","dark":"dracula"}}
const client = new Bun.S3Client({
  accessKeyId: "your-access-key",
  secretAccessKey: "your-secret-key",
  bucket: "my-bucket",
});

const exists = await client.exists("my-file.txt");
// 等价于
// const exists = await client.file("my-file.txt").exists();
```

## `S3File`

`S3File` 实例通过调用 `S3Client` 的实例方法或 `s3.file()` 函数创建。类似 `Bun.file()`，`S3File` 实例是惰性创建的。它们不一定指向创建时已存在的文件。因此，所有不涉及网络请求的方法都是完全同步的。

```ts Type Reference icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" expandable theme={"theme":{"light":"github-light","dark":"dracula"}}
interface S3File extends Blob {
  slice(start: number, end?: number): S3File;
  exists(): Promise<boolean>;
  unlink(): Promise<void>;
  presign(options: S3Options): string;
  text(): Promise<string>;
  json(): Promise<any>;
  bytes(): Promise<Uint8Array>;
  arrayBuffer(): Promise<ArrayBuffer>;
  stream(options: S3Options): ReadableStream;
  write(
    data: string | Uint8Array | ArrayBuffer | Blob | ReadableStream | Response | Request,
    options?: BlobPropertyBag,
  ): Promise<number>;

  exists(options?: S3Options): Promise<boolean>;
  unlink(options?: S3Options): Promise<void>;
  delete(options?: S3Options): Promise<void>;
  presign(options?: S3Options): string;

  stat(options?: S3Options): Promise<S3Stat>;
  /**
   * 由于需要网络请求，大小无法同步获取。
   *
   * @deprecated 请使用 `stat()` 替代。
   */
  size: NaN;

  // …为简洁起见省略部分内容
}
```

和 `Bun.file()` 一样，`S3File` 继承自 [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob)，因此所有 `Blob` 上可用的方法也适用于 `S3File`。读取本地文件数据同样的 API 也适用于从 S3 读取数据。

| 方法                           | 输出类型             |
| ---------------------------- | ---------------- |
| `await s3File.text()`        | `string`         |
| `await s3File.bytes()`       | `Uint8Array`     |
| `await s3File.json()`        | `JSON`           |
| `await s3File.stream()`      | `ReadableStream` |
| `await s3File.arrayBuffer()` | `ArrayBuffer`    |

这意味着将 `S3File` 实例与 `fetch()`、`Response` 以及其他接受 `Blob` 实例的 Web API 一起使用可以开箱即用。

### 使用 `slice` 部分读取

要读取文件的部分范围，可使用 `slice` 方法。

```ts s3.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" highlight={1} theme={"theme":{"light":"github-light","dark":"dracula"}}
const partial = s3file.slice(0, 1024);

// 将部分范围以 Uint8Array 读取
const bytes = await partial.bytes();

// 将部分范围以字符串读取
const text = await partial.text();
```

内部通过 HTTP 的 `Range` 头部请求你想要的字节。此 `slice` 方法与 [`Blob.prototype.slice`](https://developer.mozilla.org/en-US/docs/Web/API/Blob/slice) 相同。

### 从 S3 删除文件

要删除 S3 文件，可以使用 `delete` 方法。

```ts s3.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" highlight={1} theme={"theme":{"light":"github-light","dark":"dracula"}}
await s3file.delete();
// await s3File.unlink();
```

`delete` 与 `unlink` 同义。

## 错误代码

当 Bun 的 S3 API 抛出错误时，错误对象会带有 `code` 属性，值为以下之一：

* `ERR_S3_MISSING_CREDENTIALS`
* `ERR_S3_INVALID_METHOD`
* `ERR_S3_INVALID_PATH`
* `ERR_S3_INVALID_ENDPOINT`
* `ERR_S3_INVALID_SIGNATURE`
* `ERR_S3_INVALID_SESSION_TOKEN`

当 S3 对象存储服务返回错误（即非 Bun 错误），错误实例为名称为 `"S3Error"` 的 `Error`。

## `S3Client` 静态方法

`S3Client` 类提供了多个静态方法用于操作 S3。

### 静态方法 `S3Client.write`

直接写入数据到存储桶的路径，可使用静态方法 `S3Client.write`。

```ts s3.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" highlight={12, 15-18, 22, 25-29} theme={"theme":{"light":"github-light","dark":"dracula"}}
import { S3Client } from "bun";

const credentials = {
  accessKeyId: "your-access-key",
  secretAccessKey: "your-secret-key",
  bucket: "my-bucket",
  // endpoint: "https://s3.us-east-1.amazonaws.com",
  // endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
};

// 写入字符串
await S3Client.write("my-file.txt", "Hello World");

// 指定类型写入 JSON
await S3Client.write("data.json", JSON.stringify({ hello: "world" }), {
  ...credentials,
  type: "application/json",
});

// fetch 后写入
const res = await fetch("https://example.com/data");
await S3Client.write("data.bin", res, credentials);

// 写入并设置 ACL
await S3Client.write("public.html", html, {
  ...credentials,
  acl: "public-read",
  type: "text/html",
});
```

这等价于调用 `new S3Client(credentials).write("my-file.txt", "Hello World")`。

### 静态方法 `S3Client.presign`

生成 S3 文件预签名 URL，可使用静态方法 `S3Client.presign`。

```ts s3.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" highlight={11-14} theme={"theme":{"light":"github-light","dark":"dracula"}}
import { S3Client } from "bun";

const credentials = {
  accessKeyId: "your-access-key",
  secretAccessKey: "your-secret-key",
  bucket: "my-bucket",
  // endpoint: "https://s3.us-east-1.amazonaws.com",
  // endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
};

const url = S3Client.presign("my-file.txt", {
  ...credentials,
  expiresIn: 3600,
});
```

这等价于调用 `new S3Client(credentials).presign("my-file.txt", { expiresIn: 3600 })`。

### 静态方法 `S3Client.list`

列出存储桶中的部分或所有（最多 1000 个）对象，使用静态方法 `S3Client.list`。

```ts s3.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" highlight={12, 15-20, 24-29} theme={"theme":{"light":"github-light","dark":"dracula"}}
import { S3Client } from "bun";

const credentials = {
  accessKeyId: "your-access-key",
  secretAccessKey: "your-secret-key",
  bucket: "my-bucket",
  // endpoint: "https://s3.us-east-1.amazonaws.com",
  // endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
};

// 列出最多 1000 个存储桶中的对象
const allObjects = await S3Client.list(null, credentials);

// 列出 `uploads/` 前缀下最多 500 个对象，并获取所有者字段
const uploads = await S3Client.list({
  prefix: 'uploads/',
  maxKeys: 500,
  fetchOwner: true,
}, credentials);

// 检查是否有更多结果
if (uploads.isTruncated) {
  // 列出下一批 `uploads/` 下的对象
  const moreUploads = await S3Client.list({
    prefix: 'uploads/',
    maxKeys: 500,
    startAfter: uploads.contents!.at(-1).key,
    fetchOwner: true,
  }, credentials);
}
```

这等价于调用 `new S3Client(credentials).list()`。

### 静态方法 `S3Client.exists`

检查 S3 文件是否存在，使用静态方法 `S3Client.exists`。

```ts s3.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" highlight={11} theme={"theme":{"light":"github-light","dark":"dracula"}}
import { S3Client } from "bun";

const credentials = {
  accessKeyId: "your-access-key",
  secretAccessKey: "your-secret-key",
  bucket: "my-bucket",
  // endpoint: "https://s3.us-east-1.amazonaws.com",
  // endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
};

const exists = await S3Client.exists("my-file.txt", credentials);
```

同样方法也适用于 `S3File` 实例。

```ts s3.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" highlight=7} theme={"theme":{"light":"github-light","dark":"dracula"}}
import { s3 } from "bun";

const s3file = s3.file("my-file.txt", {
  // ...credentials,
});

const exists = await s3file.exists();
```

### 静态方法 `S3Client.size`

如果不下载即可检查 S3 文件的大小，可以使用静态方法 `S3Client.size`。

```ts s3.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" highlight={11} theme={"theme":{"light":"github-light","dark":"dracula"}}
import { S3Client } from "bun";

const credentials = {
  accessKeyId: "your-access-key",
  secretAccessKey: "your-secret-key",
  bucket: "my-bucket",
  // endpoint: "https://s3.us-east-1.amazonaws.com",
  // endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
};

const bytes = await S3Client.size("my-file.txt", credentials);
```

这等价于调用 `new S3Client(credentials).size("my-file.txt")`。

### 静态方法 `S3Client.stat`

获取 S3 文件的大小、etag 和其他元数据，使用静态方法 `S3Client.stat`。

```ts s3.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { S3Client } from "bun";

const credentials = {
  accessKeyId: "your-access-key",
  secretAccessKey: "your-secret-key",
  bucket: "my-bucket",
  // endpoint: "https://s3.us-east-1.amazonaws.com",
  // endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
};

const stat = await S3Client.stat("my-file.txt", credentials);
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
{
  etag: "\"7a30b741503c0b461cc14157e2df4ad8\"",
  lastModified: 2025-01-07T00:19:10.000Z,
  size: 1024,
  type: "text/plain;charset=utf-8",
}
```

### 静态方法 `S3Client.delete`

删除 S3 文件，使用静态方法 `S3Client.delete`。

```ts s3.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" highlight={10, 15} theme={"theme":{"light":"github-light","dark":"dracula"}}
import { S3Client } from "bun";

const credentials = {
  accessKeyId: "your-access-key",
  secretAccessKey: "your-secret-key",
  bucket: "my-bucket",
  // endpoint: "https://s3.us-east-1.amazonaws.com",
};

await S3Client.delete("my-file.txt", credentials);
// 等价于
// await new S3Client(credentials).delete("my-file.txt");

// S3Client.unlink 是 S3Client.delete 的别名
await S3Client.unlink("my-file.txt", credentials);
```

## `s3://` 协议

为了方便本地文件和 S3 文件复用同样的代码，`s3://` 协议在 `fetch` 和 `Bun.file()` 中得到支持。

```ts s3.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
const response = await fetch("s3://my-bucket/my-file.txt");
const file = Bun.file("s3://my-bucket/my-file.txt");
```

你还可以给 `fetch` 和 `Bun.file` 传递 `s3` 选项。

```ts s3.ts icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" highlight={2-6} theme={"theme":{"light":"github-light","dark":"dracula"}}
const response = await fetch("s3://my-bucket/my-file.txt", {
  s3: {
    accessKeyId: "your-access-key",
    secretAccessKey: "your-secret-key",
    endpoint: "https://s3.us-east-1.amazonaws.com",
  },
  headers: {
    range: "bytes=0-1023",
  },
});
```

### UTF-8、UTF-16 和 BOM（字节顺序标记）

跟 `Response` 和 `Blob` 一样，`S3File` 默认假定为 UTF-8 编码。

调用 `S3File` 的 `text()` 或 `json()` 方法时：

* 如果检测到 UTF-16 字节顺序标记（BOM），则按 UTF-16 处理。JavaScriptCore 原生支持 UTF-16，因此跳过 UTF-8 转码过程（并剥离 BOM）。这通常很好，但如果 UTF-16 字符中有无效代理对，则会传递给 JavaScriptCore（与源码情况相同）。
* 如果检测到 UTF-8 BOM，则在传给 JavaScriptCore 前剥离 BOM，并将无效的 UTF-8 代码点替换为 Unicode 替代字符 (`\uFFFD`)。
* 不支持 UTF-32。
