import { S3 } from "@aws-sdk/client-s3"; import { Readable } from "stream"; import { Storage } from "./Storage"; const readableToBuffer = (readable: Readable): Promise => new Promise((resolve, reject) => { const chunks: Buffer[] = []; readable.on("data", (chunk) => chunks.push(chunk)); readable.on("error", reject); readable.on("end", () => resolve(Buffer.concat(chunks))); }); export class S3Storage implements Storage { public constructor( private client: S3, private bucket: string, private basePath?: string ) {} /** * Always return a string, to ensure consistency. */ get bucketBasePath() { return this.basePath ?? ""; } async set(path: string, data: Buffer): Promise { await this.client.putObject({ Bucket: this.bucket, Key: `${this.bucketBasePath}${path}`, Body: data, }); } async get(path: string): Promise { try { const s3Object = await this.client.getObject({ Bucket: this.bucket, Key: `${this.bucketBasePath ?? ""}${path}`, }); if (!s3Object.Body) return null; const body = s3Object.Body; return await readableToBuffer(body); } catch (err) { console.error(`[CDN] Unable to get S3 object at path ${path}.`); console.error(err); return null; } } async delete(path: string): Promise { await this.client.deleteObject({ Bucket: this.bucket, Key: `${this.bucketBasePath}${path}`, }); } }