@foal/aws-s3
Version:
AWS S3 storage components for FoalTS
125 lines (124 loc) • 4.77 kB
JavaScript
;
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;
}
const forcePathStyle = core_1.Config.get('settings.disk.s3.forcePathStyle', 'boolean');
if (typeof forcePathStyle !== 'undefined') {
s3Config.forcePathStyle = forcePathStyle;
}
this._s3 = new client_s3_1.S3Client(s3Config);
}
return this._s3;
}
}
exports.S3Disk = S3Disk;