@rytass/storages-adapter-r2
Version:
Cloudflare R2 storage adapter
159 lines (155 loc) • 5.07 kB
JavaScript
;
var storages = require('@rytass/storages');
var awsSdk = require('aws-sdk');
var stream = require('stream');
var uuid = require('uuid');
class StorageR2Service extends storages.Storage {
bucket;
s3;
parseSignedURL;
constructor(options){
super(options);
this.bucket = options.bucket;
if (options.customDomain) {
const re = new RegExp(`^https://${options.bucket}.${options.account}.r2.cloudflarestorage.com`);
this.parseSignedURL = (url)=>{
return url.replace(re, options.customDomain);
};
}
this.s3 = new awsSdk.S3({
endpoint: `https://${options.account}.r2.cloudflarestorage.com`,
credentials: new awsSdk.Credentials({
accessKeyId: options.accessKey,
secretAccessKey: options.secretKey
}),
region: 'auto',
signatureVersion: 'v4'
});
}
async url(key, options) {
const signedURL = await this.s3.getSignedUrlPromise('getObject', {
Bucket: this.bucket,
Key: key,
...options?.expires ? {
Expires: options?.expires
} : {}
});
if (this.parseSignedURL) {
return this.parseSignedURL(signedURL);
}
return signedURL;
}
async read(key, options) {
try {
const response = await this.s3.getObject({
Bucket: this.bucket,
Key: key
}).promise();
if (options?.format === 'buffer') {
return response.Body;
}
return stream.Readable.from(response.Body);
} catch (ex) {
if (ex && typeof ex === 'object' && 'name' in ex && ex.name === 'NoSuchKey') {
throw new storages.StorageError(storages.ErrorCode.READ_FILE_ERROR, 'File not found');
}
throw ex;
}
}
async writeStreamFile(stream$1, options) {
const givenFilename = options?.filename;
if (givenFilename) {
await this.s3.upload({
Bucket: this.bucket,
Key: givenFilename,
Body: stream$1,
...options?.contentType ? {
ContentType: options?.contentType
} : {}
}).promise();
return {
key: givenFilename
};
}
const tempFilename = uuid.v4();
const uploadStream = new stream.PassThrough();
const getFilenamePromise = this.getStreamFilename(stream$1);
const uploadPromise = this.s3.upload({
Bucket: this.bucket,
Key: tempFilename,
Body: uploadStream,
...options?.contentType ? {
ContentType: options?.contentType
} : {}
}).promise();
stream$1.pipe(uploadStream);
const [[filename, mime]] = await Promise.all([
getFilenamePromise,
uploadPromise
]);
await this.s3.copyObject({
Bucket: this.bucket,
CopySource: `/${this.bucket}/${tempFilename}`,
Key: filename,
...mime ? {
ContentType: mime
} : {},
...options?.contentType ? {
ContentType: options?.contentType
} : {}
}).promise();
await this.s3.deleteObject({
Bucket: this.bucket,
Key: tempFilename
}).promise();
return {
key: filename
};
}
async writeBufferFile(buffer, options) {
const fileInfo = options?.filename || await this.getBufferFilename(buffer);
const filename = Array.isArray(fileInfo) ? fileInfo[0] : fileInfo;
await this.s3.upload({
Key: filename,
Bucket: this.bucket,
Body: buffer,
...Array.isArray(fileInfo) && fileInfo[1] ? {
ContentType: fileInfo[1]
} : {},
...options?.contentType ? {
ContentType: options?.contentType
} : {}
}).promise();
return {
key: filename
};
}
write(file, options) {
if (file instanceof Buffer) {
return this.writeBufferFile(file, options);
}
return this.writeStreamFile(file, options);
}
batchWrite(files) {
return Promise.all(files.map((file)=>this.write(file)));
}
async remove(key) {
await this.s3.deleteObject({
Bucket: this.bucket,
Key: key
}).promise();
}
async isExists(key) {
try {
await this.s3.headObject({
Bucket: this.bucket,
Key: key
}).promise();
return true;
} catch (ex) {
if (ex && typeof ex === 'object' && 'name' in ex && ex.name === 'NotFound') return false;
throw ex;
}
}
}
exports.StorageR2Service = StorageR2Service;