/*! * @file S3ExternalStorage.ts * @description This file contains the S3ExternalStorage class, which is used to store and retrieve page contents from S3. * @author Dean Heffernan * @copyright 2025 Squiz */ // External import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3'; /** * Represents the storage location of page contents in S3. */ export interface S3StorageLocation { type: 's3'; key: string; } /** * The S3ExternalStorage class is used to store and retrieve page contents from S3. * @class S3ExternalStorage * @constructor * @param {string} bucket - The name of the S3 bucket to use for storing page contents. * @param {number} thresholdBytes - The threshold in bytes above which page contents will be stored externally. * @param {S3Client} s3Client - The S3 client to use for storing and retrieving page contents. * @returns {S3ExternalStorage} A new instance of the S3ExternalStorage class. */ export class S3ExternalStorage { /** * The config to use for storing and retrieving page contents. * @type {Config} */ private readonly tenantId: string; /** * The name of the S3 bucket to use for storing page contents. * @type {string} */ private readonly bucket: string; /** * The S3 client to use for storing and retrieving page contents. * @type {S3Client} */ private readonly s3Client: S3Client; /** * Creates a new instance of the S3ExternalStorage class. * @constructor * @param {S3Client} s3Client - The S3 client to use for storing and retrieving page contents. * @returns {S3ExternalStorage} A new instance of the S3ExternalStorage class. */ constructor(tenantId: string, s3Client: S3Client, bucket: string) { this.tenantId = tenantId; this.bucket = bucket; this.s3Client = s3Client; } /** * Saves a payload to S3. * @param {string} entityName - The name of the entity to save the payload for. * @param {string} referenceId - The reference ID of the payload. * @param {object} payload - The payload to save. * @returns {Promise<{ location: S3StorageLocation; size: number }>} A promise that resolves to the location and size of the saved payload. */ public async save( entityName: string, referenceId: string, payload: object, ): Promise<{ location: S3StorageLocation; size: number }> { const body = JSON.stringify(payload); const key = `${this.tenantId}/${entityName}/${referenceId}.json`; await this.s3Client.send( new PutObjectCommand({ Bucket: this.bucket, Key: key, Body: body, ContentType: 'application/json', }), ); return { location: { type: 's3', key, }, size: Buffer.byteLength(body, 'utf8'), }; } /** * Loads a payload from S3. * @param location - The location of the payload to load. * @returns {Promise>} A promise that resolves to the loaded payload. */ public async load(location: S3StorageLocation): Promise> { const response = await this.s3Client.send( new GetObjectCommand({ Bucket: this.bucket, Key: location.key, }), ); if (!response.Body) { throw new Error(`Failed to load externalised items from S3 object ${location.key}`); } const contents = await response.Body.transformToString(); return JSON.parse(contents); } public async delete(location?: S3StorageLocation) { if (!location || !this.bucket) { return; } await this.s3Client.send( new DeleteObjectCommand({ Bucket: this.bucket, Key: location.key, }), ); } }