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

Bun 的 S3 API 很快

Bun 的 S3 API 很快

左:Bun v1.1.44。右:Node.js v23.6.0

Bun 提供了快速的原生绑定,用于与兼容 S3 的对象存储服务交互。Bun 的 S3 API 被设计得十分简单,且感觉类似于 fetch 的 ResponseBlob API(比如 Bun 的本地文件系统 API)。
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79s3.ts
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 是 事实上的标准互联网文件系统。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
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79s3.ts
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 上文件的 惰性引用
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79s3.ts
// 对 S3 上文件的惰性引用
const s3file: S3File = client.file("123.json");
类似于 Bun.file(path)S3Clientfile 方法是同步的。直到调用依赖网络请求的方法前,它不会进行任何网络请求。

从 S3 读取文件

如果你用过 fetch API,就对 ResponseBlob API 非常熟悉。S3File 继承自 Blob。所有适用于 Blob 的方法也适用于 S3File
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79s3.ts
// 将 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 同样简单。
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79s3.ts
// 写入一个字符串(替换文件)
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",
});

// 使用写入器(流式写入)
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 文件。
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79s3.ts
// 写入大文件
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
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79s3.ts
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", // 无扩展名,指定内容类型为 JSON
});

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

设置 ACL

要为预签名 URL 设置 ACL(访问控制列表),传入 acl 选项:
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79s3.ts
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 选项。
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79s3.ts
const url = s3file.presign({
  // 秒数
  expiresIn: 3600, // 1 小时

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

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

method

设置预签名 URL 的 HTTP 方法,传入 method 选项。
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79s3.ts
const url = s3file.presign({
  method: "PUT",
  // method: "DELETE",
  // method: "GET",
  // method: "HEAD",
  // method: "POST",
  // method: "PUT",
});

new Response(S3File)

要快速重定向用户到 S3 文件的预签名 URL,可以将一个 S3File 实例作为响应体传给 Response 这会自动将用户重定向到该预签名 URL,节省了服务器下载文件再发送给用户的内存、时间和带宽开销。
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79s3.ts
const response = new Response(s3file);
console.log(response);
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。
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79s3.ts
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,在 S3Client 构造器中将 endpoint 设置为 "https://storage.googleapis.com"
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79s3.ts
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,在 S3Client 构造器中将 endpoint 设置为包含你的账号 ID 的 R2 端点。
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79s3.ts
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,在 S3Client 构造器中将 endpoint 设置为对应区域的 DigitalOcean Spaces 端点。
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79s3.ts
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,在 S3Client 构造器中将 endpoint 设置为 MinIO 运行的 URL。
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79s3.ts
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,在 S3Client 构造器中将 endpoint 设置为包含账号 ID 及 /storage/v1/s3 路径的 supabase 端点。确保在 supabase 控制台 https://supabase.com/dashboard/project/<account-id>/settings/storage 中启用 “Enable connection via S3 protocol”,并设置相应区域。
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79s3.ts
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
  • 如果不指定 endpoint,Bun 会根据 region 和 bucket 自动确定 AWS S3 端点。
  • 如果不指定 region,Bun 默认使用 us-east-1。
  • 如果明确指定 endpoint,则无需指定 bucket 名称。
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79s3.ts
import { S3Client } from "bun";

// 从 region 和 bucket 推断的 AWS S3 端点
const s3 = new S3Client({
  accessKeyId: "access-key",
  secretAccessKey: "secret-key",
  bucket: "my-bucket",
  virtualHostedStyle: true, 
  // 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, 
});

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

凭据

凭据是使用 S3 中最难处理的部分之一,我们已尽力简化。默认情况下,Bun 读取以下环境变量作为凭据。
选项名称环境变量
accessKeyIdS3_ACCESS_KEY_ID
secretAccessKeyS3_SECRET_ACCESS_KEY
regionS3_REGION
endpointS3_ENDPOINT
bucketS3_BUCKET
sessionTokenS3_SESSION_TOKEN
若未设置 S3_* 环境变量,Bun 会依次检查对应的 AWS_* 环境变量。
选项名称备用环境变量
accessKeyIdAWS_ACCESS_KEY_ID
secretAccessKeyAWS_SECRET_ACCESS_KEY
regionAWS_REGION
endpointAWS_ENDPOINT
bucketAWS_BUCKET
sessionTokenAWS_SESSION_TOKEN
这些环境变量可以从 .env 文件 或初始化时的进程环境读取(不会使用 process.env)。 你传给 s3.file(credentials)new Bun.S3Client(credentials) 或其他接收凭据的方法的选项会覆盖默认值。举例来说,如果不同的桶使用相同凭据,你仅需在 .env 中设置凭据一次,然后调用 s3.file() 时只传 bucket: "my-bucket",无需重复所有凭据。

S3Client 对象

当你不使用环境变量或使用多个桶时,可创建 S3Client 对象来显式设置凭据。
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79s3.ts
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 方法。
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79s3.ts
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 方法。
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79s3.ts
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 方法。
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79s3.ts
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 实例是惰性创建的。它们不一定指向创建时已存在的文件。因此,所有不涉及网络请求的方法都是完全同步的。
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79Type Reference
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,因此所有 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 方法。
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79s3.ts
const partial = s3file.slice(0, 1024);

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

// 将部分范围以字符串读取
const text = await partial.text();
内部通过 HTTP 的 Range 头部请求你想要的字节。此 slice 方法与 Blob.prototype.slice 相同。

从 S3 删除文件

要删除 S3 文件,可以使用 delete 方法。
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79s3.ts
await s3file.delete();
// await s3File.unlink();
deleteunlink 同义。

错误代码

当 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
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79s3.ts
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
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79s3.ts
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
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79s3.ts
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
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79s3.ts
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 实例。
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79s3.ts
import { s3 } from "bun";

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

const exists = await s3file.exists();

静态方法 S3Client.size

快速检查 S3 文件大小(无需下载),使用静态方法 S3Client.size
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79s3.ts
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
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79s3.ts
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);
{
  etag: "\"7a30b741503c0b461cc14157e2df4ad8\"",
  lastModified: 2025-01-07T00:19:10.000Z,
  size: 1024,
  type: "text/plain;charset=utf-8",
}

静态方法 S3Client.delete

删除 S3 文件,使用静态方法 S3Client.delete
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79s3.ts
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:// 协议在 fetchBun.file() 中得到支持。
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79s3.ts
const response = await fetch("s3://my-bucket/my-file.txt");
const file = Bun.file("s3://my-bucket/my-file.txt");
你还可以给 fetchBun.file 传递 s3 选项。
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79s3.ts
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(字节顺序标记)

ResponseBlob 一样,S3File 默认假定为 UTF-8 编码。 调用 S3Filetext()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。