node の S3Client から S3, GCS, R2 を操作する
S3、というより S3 API は、AWS というよりオブジェクトストレージ界の標準になりつつあります。S3 互換を謳うオブジェクトストレージはそれこそ毎月一個増えてるんじゃないでしょうか。
複数の CDN 構成をとるとき、クラウドごとのSDK のコードを書くのではなく、一つの実装(自分の場合 node の @aws-sdk/client-s3)で、全部のバケット処理を統一できないか考えて実験してみました。
APIごとに有効な機能、無効な機能で細かい差がありますが、この記事ではそこまで踏み込みません。
記事中の <key>
や <secret>
は自分で取得してください。
AWS S3
まずは普通に AWS に繋ぐコードです。 accessKeyId や secrentAccessKey の取得方法は省略します。
import { ListObjectsV2Command, S3Client } from "@aws-sdk/client-s3";
// R2
const client = new S3Client({
region: 'ap-northeast-1',
credentials: {
accessKeyId: `<key>`,
secretAccessKey: '<secret>',
},
});
const res = await client.send(new ListObjectsV2Command({
Bucket: 'my-bucket',
}));
console.log(res.Contents!);
my-bucket の ファイル一覧が取れます。
当たり前ですが、API KEY に対して Bucket 操作の権限が足りてないと動きません。これは他のエンドポイントも同様の概念が発生しています。
GCS with S3Client
GCS にも s3 互換エンドポイントがあります。 https://storage.googleapis.com
認証には、通常の認証方式ではなく、プリンシパル等に対して HMAC キーを発行する必要があります。
発行した key/secret を credentials に使います。
import { ListObjectsV2Command, S3Client } from "@aws-sdk/client-s3";
const client = new S3Client({
region: 'auto',
endpoint: "https://storage.googleapis.com",
credentials: {
accessKeyId: '<key>',
secretAccessKey: `<secret>`,
}
});
const res = await client.send(new ListObjectsV2Command({
Bucket: 'my-bucket',
}));
console.log(res.Contents!);
余談: この記事では省略しますが、 ListObjectsV2Command では、 一回あたりのリクエスト上限が違う、という差がありました。実際500件を超えるとページネーションして取り直す処理が必要なのですが、S3 API経由だとGCS本来の1000件から500件まで減りました。
Cloudflare R2 on s3client
Cloudflare R2 も S3 互換です。https://www.cloudflare.com/ja-jp/products/r2/
こちらも GCS と同じく R2 用の API_TOKEN を発行してから利用します。
発行方法: https://developers.cloudflare.com/r2/platform/s3-compatibility/tokens/
endpoint に https://<client_id>.r2.cloudflarestorage.com
を指定します。これは自分の client_id を入力してください。
import { ListObjectsV2Command, S3Client } from "@aws-sdk/client-s3";
// R2
const client = new S3Client({
endpoint: `https://<client_id>.r2.cloudflarestorage.com`,
region: 'auto',
credentials: {
// NOTE: 1年ごとにトークンの更新が必要
accessKeyId: `<id>`,
secretAccessKey: '<secret>',
// @ts-ignore
signatureVersion: 'v4', // いらないかも?
},
});
const res = await client.send(new ListObjectsV2Command({
Bucket: 'my-bucket',
}));
console.log(res.Contents);
参考: https://developers.cloudflare.com/r2/examples/aws-sdk-js/
Minio with S3Client
書こうと思ったけど略。docker でテスト用途に動かす分には便利そう。
おまけ: S3 Client を Mock する
aws-sdk-client-mock を使うと、この s3 client の返り値をモックしてテストを返すことができます
import { mockClient } from 'aws-sdk-client-mock';
import { ListObjectsV2Command, S3Client } from '@aws-sdk/client-s3';
const mockS3Client = mockClient(S3Client);
mockS3Client.on(ListObjectsV2Command).resolves({
$metadata: {
httpStatusCode: 200,
},
Contents: []
});
// mock されている
const client = new S3Client({ /* 略 */ });
const res = await client.send(new ListObjectsV2Command({
Bucket: 'my-bucket',
}));
console.log(res.Contents);
自分で型を調べて書かないといけないのがちょっと面倒でした。テストコード書く範囲では、必要な部分だけ書くと割り切ってます。
Discussion
使うAPIをリファクタして lib.ts に置いた。
r2 を s3 client から叩くコードも追加している。
lib.ts
これを使って shadcn-ui のドキュメントを vectorize に叩き込んでみる。
実際にベクトル検索してみる。
ボタンについて聞いてみる。
ボタン要素の使い方が説明された。
embedding vector を生成する方法として、cloudflare ではなく openai api のモデルを使ってみる。
ここで vectorize を作り直した。ベクトル長が openai の text-embedding-3-small は 1536 で、text-embedding-3-large がその2倍の 3072。
ちなみに 3072 の vectorize は生成できなかった。
これで保存できる。
一応ローカルでコサイン類似度を計算できるようにしておく。ローカルと vectorize の実行結果が一致するかも確認する。
Result