Open1

Bun の S3 API を試す

Yoshiaki KawazuYoshiaki Kawazu

BunにS3 APIが生えたので試してみた。
https://bun.sh/docs/api/s3

基本的な使い方

  • S3Client をimportして const s3 = new S3Client({...}) して使うのが基本ぽい
  • s3.file(key) して S3File オブジェクトのインターフェースを通して読み書きできる
  • presign/exists/stat/delete については S3File を作らずに S3Client の静的メソッドからも使える
  • fetchBun.files3://プロトコルを使う方法もある

Bun S3 APIのドキュメント等では S3Client ではなく s3 をimportして使ってるサンプルも多いが、これは単に const s3 = new S3Client() のショートハンドで、実際にはバケット名やクレデンシャル情報が足りないので実際に動くコードにするには結局 S3Client の方をimportして使う事になる。s3をimportしてるサンプルはスッキリして面白く見えるが、初学者には混乱の元なので const s3 = new S3Client({...}) するコードが省略されてると思って見るのが良いと思う。

クレデンシャルについて

何もしないとインスタンスメタデータとかiniファイルとかを見てクレデンシャルを解決してくれたりしてくれたりはしないので、BunネイティブにS3APIが生えたと言っても @‍aws-sdk/credential-providers は大抵のケースで必要で、実際は以下のようなコードを書く事になる。

import { S3Client } from 'bun'
import { fromNodeProviderChain } from '@aws-sdk/credential-providers'

const s3 = new S3Client({
  bucket: 'MY_BUCKET',
  // Bun S3 API はバケットのリージョンを自動で探してくれないので us-east-1 以外なら指定が必要
  region: 'ap-northeast-1',
  // 環境変数やiniやメタデータとか全部入りでチェックしてクレデンシャルを取るならコレが楽
  ...(await fromNodeProviderChain()()),
})

こうしてs3を整えたら後は大抵のサンプル通りに const s3file = s3.file('foo.json') とかすれば良い。

S3Fileへのストリーム書き込み

逐次読み込みについては s3file.stream()ReadableStream が取得できるが、少しずつ書き込むには s3file.writer() を取得して writer.write(chunk) を繰り返して最後に writer.end() するようなコードを書く必要がある。具体的にはこんな感じ。

const fileFrom = s3.file('big-file.zip')
const fileTo = s3.file('big-file-copy.zip')
const w = fileTo.writer()
for await (const chunk of fileFrom.stream()) {
    w.write(chunk)
}
await w.end()

w.write(chunk)await する必要はなく、w.end()Promise を返す。

WritableStream を作るパターン

S3File オブジェクトには一括書き込みの write() と逐次書き込み用の writer() しか生えてないので WritableStreamが欲しければ自分で作ってやる必要がある。

const fileFrom = s3.file('big-file.zip')
const fileTo = s3.file('big-file-copy.zip')
const createWS = (w) => new WritableStream({
    write: (chunk) => w.write(chunk),
    close: () => w.end(),
    abort: (reason) => w.end(reason),
})
await fileFrom.stream().pipeTo(createWS(fileTo.writer()))

createWS は更にシンプルにこうしても良いかも(ちゃんと動く事は確認済み)。

const createWS = (w) => new WritableStream({
    write: w.write.bind(w),
    close: w.end.bind(w),
    abort: w.end.bind(w),
})