Bun の S3 API を試す
BunにS3 APIが生えたので試してみた。
基本的な使い方
-
S3Client
をimportしてconst s3 = new S3Client({...})
して使うのが基本ぽい -
s3.file(key)
して S3File オブジェクトのインターフェースを通して読み書きできる -
presign/exists/stat/delete
については S3File を作らずに S3Client の静的メソッドからも使える -
fetch
やBun.file
でs3://
プロトコルを使う方法もある
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),
})