UNPKG

@foal/aws-s3

Version:

AWS S3 storage components for FoalTS

121 lines (120 loc) 4.54 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.S3Disk = void 0; // 3p const core_1 = require("@foal/core"); const storage_1 = require("@foal/storage"); const client_s3_1 = require("@aws-sdk/client-s3"); const lib_storage_1 = require("@aws-sdk/lib-storage"); /** * File storage to read, write and delete files in AWS S3. * * @export * @class S3Disk * @extends {Disk} */ class S3Disk extends storage_1.Disk { async write(dirname, content, options = {}) { let name = this.hasName(options) ? options.name : await (0, core_1.generateToken)(); if (this.hasExtension(options)) { name = `${name}.${options.extension}`; } const path = `${dirname}/${name}`; // We cannot use a "PutObjectCommand" command here // because we would need to know the file size when "content" is a stream. const parallelUploads = new lib_storage_1.Upload({ client: this.s3, params: { Body: content, Bucket: this.bucket, Key: path, ServerSideEncryption: core_1.Config.get('settings.disk.s3.serverSideEncryption', 'string'), } }); await parallelUploads.done(); return { path }; } async read(path, content) { try { const command = new client_s3_1.GetObjectCommand({ Bucket: this.bucket, Key: path, }); const { Body, ContentLength } = await this.s3.send(command); if (ContentLength === undefined) { throw new Error('Expected to have a content-length header in HTTP response.'); } if (Body === undefined) { throw new Error('Expected to have a body in HTTP response.'); } // "Body" is of type Readable on Node and of type ReadableStream | Blob on the browser. // See https://github.com/aws/aws-sdk-js-v3/issues/1877. const stream = Body; // Do not kill the process (and crash the server) if the stream emits an error. // Note: users can still add other listeners to the stream to "catch" the error. // TODO: test this line. stream.on('error', () => { }); return { file: (content === 'buffer' ? await (0, core_1.streamToBuffer)(stream) : stream), size: ContentLength }; } catch (error) { if (error.name === 'NoSuchKey') { throw new storage_1.FileDoesNotExist(path); } // TODO: test this line. throw error; } } async readSize(path) { try { const command = new client_s3_1.HeadObjectCommand({ Bucket: this.bucket, Key: path, }); const { ContentLength } = await this.s3.send(command); if (ContentLength === undefined) { throw new Error('Expected to have a content-length header in HTTP response.'); } return ContentLength; } catch (error) { if (error.name === 'NotFound') { throw new storage_1.FileDoesNotExist(path); } // TODO: test this line. throw error; } } async delete(path) { const command = new client_s3_1.DeleteObjectCommand({ Bucket: this.bucket, Key: path }); await this.s3.send(command); } get bucket() { return core_1.Config.getOrThrow('settings.disk.s3.bucket', 'string', 'You must provide a bucket name when using AWS S3 file storage (S3Disk).'); } _s3; get s3() { if (!this._s3) { const s3Config = { endpoint: core_1.Config.get('settings.aws.endpoint', 'string'), }; const accessKeyId = core_1.Config.get('settings.aws.accessKeyId', 'string'); const secretAccessKey = core_1.Config.get('settings.aws.secretAccessKey', 'string'); if (accessKeyId && secretAccessKey) { s3Config.credentials = { accessKeyId, secretAccessKey }; } const region = core_1.Config.get('settings.aws.region', 'string'); if (region) { s3Config.region = region; } this._s3 = new client_s3_1.S3Client(s3Config); } return this._s3; } } exports.S3Disk = S3Disk;