> ## Documentation Index
> Fetch the complete documentation index at: https://bun.ll1025.cn/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/span-inc/82N53aP7NFbaCVSl/images/bun-s3-node.gif?s=124e15928642b4da35aa4960c3a9ec2b" 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/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
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.S3Client` 和 `Bun.s3`

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

要显式设置凭据，将其传递给 `Bun.S3Client` 构造函数。

```ts s3.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
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/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
// 对 S3 上文件的惰性引用
const s3file: S3File = client.file("123.json");
```

与 `Bun.file(path)` 一样，`S3Client` 的 `file` 方法是同步的。在调用需要网络请求的方法之前，它不会发起任何网络请求。

### 从 S3 读取文件

`S3File` 继承自 `Blob`，因此适用于 `Blob` 的方法同样适用于 `S3File`。

```ts s3.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
// 将 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()` 也同样避免在内存中复制字节。

### 写入和上传文件到 S3

写入 S3 的方式相同。

```ts s3.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
// 写入字符串（替换文件）
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/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
// 写入大文件
const bigFile = Buffer.alloc(10 * 1024 * 1024); // 10MB
const writer = s3file.writer({
  // 网络错误时自动重试，最多 3 次
  retry: 3,

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

  // 以 5 MB 为分片上传
  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，而无需暴露你的凭据或授予他们对存储桶的不必要访问权限。

默认情况下，Bun 生成一个 24 小时后过期的 `GET` URL。

```ts s3.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
import { s3 } from "bun";

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

const upload = s3.presign("my-file", {
  expiresIn: 3600, // 1 小时
  method: "PUT",
  type: "application/json", // 在预签名 URL 中设置 response-content-type
});

// 带内容配置的预签名（例如强制以特定文件名下载）
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/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
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 设置过期时间，传递 `expiresIn` 选项。

```ts s3.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
const url = s3file.presign({
  // 秒
  expiresIn: 3600, // 1 小时

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

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

### `method`

要为预签名 URL 设置 HTTP 方法，传递 `method` 选项。

```ts s3.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
const url = s3file.presign({
  method: "PUT",
  // method: "DELETE",
  // method: "GET",
  // method: "HEAD",
  // method: "POST",
  // method: "PUT",
});
```

### `new Response(S3File)`

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

该响应会将用户重定向到 S3 文件的预签名 URL，从而节省将文件下载到服务器再发送给用户的内存、时间和带宽成本。

```ts s3.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
const response = new Response(s3file);
console.log(response);
```

```txt theme={null}
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：

### 在 AWS S3 上使用 Bun 的 S3Client

AWS S3 是默认值。使用 AWS S3 时，可以传递 `region` 选项而不是 `endpoint` 选项。

```ts s3.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
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",
});
```

### 在 Google Cloud Storage 上使用 Bun 的 S3Client

要在 [Google Cloud Storage](https://cloud.google.com/storage) 上使用 Bun 的 S3 客户端，在 `S3Client` 构造函数中将 `endpoint` 设置为 `"https://storage.googleapis.com"`。

```ts s3.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" highlight={8} theme={null}
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",
});
```

### 在 Cloudflare R2 上使用 Bun 的 S3Client

要在 [Cloudflare R2](https://developers.cloudflare.com/r2/) 上使用 Bun 的 S3 客户端，在 `S3Client` 构造函数中将 `endpoint` 设置为 R2 endpoint。R2 endpoint 包含你的账户 ID。

```ts s3.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" highlight={8} theme={null}
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",
});
```

### 在 DigitalOcean Spaces 上使用 Bun 的 S3Client

要在 [DigitalOcean Spaces](https://www.digitalocean.com/products/spaces/) 上使用 Bun 的 S3 客户端，在 `S3Client` 构造函数中将 `endpoint` 设置为 DigitalOcean Spaces endpoint。

```ts s3.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" highlight={8} theme={null}
import { S3Client } from "bun";

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

### 在 MinIO 上使用 Bun 的 S3Client

要在 [MinIO](https://min.io/) 上使用 Bun 的 S3 客户端，在 `S3Client` 构造函数中将 `endpoint` 设置为 MinIO 运行的 URL。

```ts s3.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" highlight={10} theme={null}
import { S3Client } from "bun";

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

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

### 在 Supabase 上使用 Bun 的 S3Client

要在 [Supabase](https://supabase.com/) 上使用 Bun 的 S3 客户端，在 `S3Client` 构造函数中将 `endpoint` 设置为 Supabase endpoint。Supabase endpoint 包含你的账户 ID 和 `/storage/v1/s3` 路径。在 Supabase 控制台的 `https://supabase.com/dashboard/project/<account-id>/settings/storage` 中，打开 **Enable connection via S3 protocol** 并使用该部分显示的 region。

```ts s3.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" highlight={3-10} theme={null}
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 虚拟主机样式 endpoint

使用虚拟主机样式 endpoint 时，将 `virtualHostedStyle` 选项设置为 `true`。

<Note>
  * 如果不指定 endpoint，Bun 会根据提供的 region 和 bucket 确定 AWS S3 endpoint。 - 如果未指定 region，Bun 默认使用 `us-east-1`。 - 如果显式提供了 endpoint，则无需指定 bucket 名称。
</Note>

```ts s3.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" highlight={17, 25} theme={null}
import { S3Client } from "bun";

// AWS S3 endpoint 根据 region 和 bucket 推断
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
const s3WithEndpoint = new S3Client({
  accessKeyId: "access-key",
  secretAccessKey: "secret-key",
  endpoint: "https://<bucket-name>.s3.<region>.amazonaws.com",
  virtualHostedStyle: true, // [!code ++]
});

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

***

## 凭据

默认情况下，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`     |

Bun 在初始化时从 [`.env` 文件](/runtime/environment-variables)或进程环境中读取这些环境变量（不通过 `process.env` 读取）。

传递给你调用 `s3.file(credentials)`、`new Bun.S3Client(credentials)` 或任何接受凭据的方法的选项会覆盖这些默认值。因此，如果你对不同的 bucket 使用相同的凭据，可以在 `.env` 文件中设置一次凭据，然后只向 `s3.file()` 传递 `bucket: "my-bucket"`。

### `S3Client` 对象

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

```ts s3.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" highlight={3-11} theme={null}
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/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" highlight={8, 9} theme={null}
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/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" highlight={7} theme={null}
const client = new 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/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" highlight={7} theme={null}
const client = new 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`

在 `S3Client` 实例上调用 `file()`，或调用 `s3.file()`，会返回一个 `S3File`。与 `Bun.file()` 一样，`S3File` 实例是惰性的：它们并不指向创建时必然存在的对象。这就是为什么所有不涉及网络请求的方法都是完全同步的。

```ts Type Reference icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" expandable theme={null}
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/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" highlight={1} theme={null}
const partial = s3file.slice(0, 1024);

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

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

在内部，Bun 使用 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/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" highlight={1} theme={null}
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` 实例（名称为 `"S3Error"` 的 `Error` 实例）。

## `S3Client` 静态方法

`S3Client` 类提供了几个用于与 S3 交互的静态方法。

### `S3Client.write`（静态）

要将数据直接写入 bucket 中的路径，使用 `S3Client.write` 静态方法。

```ts s3.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" highlight={12, 15-18, 22, 25-29} theme={null}
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/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" highlight={11-14} theme={null}
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`（静态）

要列出 bucket 中的部分或全部对象（最多 1000 个），使用 `S3Client.list` 静态方法。

```ts s3.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" highlight={12, 15-20, 24-29} theme={null}
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
};

// 列出 bucket 中（最多）1000 个对象
const allObjects = await S3Client.list(null, credentials);

// 列出 `uploads/` 前缀下（最多）500 个对象，每个对象包含 owner 字段
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/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" highlight={11} theme={null}
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/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" highlight=7} theme={null}
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/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" highlight={11} theme={null}
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/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
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={null}
{
  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/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" highlight={10, 15} theme={null}
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://` 协议

`fetch` 和 `Bun.file()` 支持 `s3://` 协议，因此同一套代码既适用于本地文件也适用于 S3 文件。

```ts s3.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
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/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" highlight={2-6} theme={null}
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()` 时：

* 当 Bun 检测到 UTF-16 字节顺序标记（BOM）时，它会将数据视为 UTF-16 编码。JavaScriptCore 原生支持 UTF-16，因此 Bun 跳过 UTF-8 转码步骤（并去除 BOM）。一个后果：UTF-16 字符串中的无效代理对会原样传递到 JavaScriptCore（与源代码相同）。
* 当 Bun 检测到 UTF-8 BOM 时，它会去除 BOM，并用 Unicode 替换字符（`\uFFFD`）替换无效的 UTF-8 码点，然后将字符串传递给 JavaScriptCore。
* 不支持 UTF-32。
