@rytass/storages-adapter-r2
Version:
Cloudflare R2 storage adapter
188 lines (184 loc) • 6.36 kB
JavaScript
;
var storages = require('@rytass/storages');
var clientS3 = require('@aws-sdk/client-s3');
var libStorage = require('@aws-sdk/lib-storage');
var s3RequestPresigner = require('@aws-sdk/s3-request-presigner');
var stream = require('stream');
var uuid = require('uuid');
class StorageR2Service extends storages.Storage {
bucket;
client;
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.client = new clientS3.S3Client({
endpoint: `https://${options.account}.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: options.accessKey,
secretAccessKey: options.secretKey
},
region: 'auto',
forcePathStyle: true
});
}
async url(key, options) {
const command = new clientS3.GetObjectCommand({
Bucket: this.bucket,
Key: key
});
const signedURL = await s3RequestPresigner.getSignedUrl(this.client, command, {
...options?.expires ? {
expiresIn: options.expires
} : {}
});
if (this.parseSignedURL) {
return this.parseSignedURL(signedURL);
}
return signedURL;
}
async read(key, options) {
try {
const command = new clientS3.GetObjectCommand({
Bucket: this.bucket,
Key: key
});
const response = await this.client.send(command);
if (!response.Body) {
throw new storages.StorageError(storages.ErrorCode.READ_FILE_ERROR, 'Empty response body');
}
// AWS SDK v3 returns a ReadableStream (web) or Readable (node)
const bodyStream = response.Body;
if (options?.format === 'buffer') {
const chunks = [];
for await (const chunk of bodyStream){
chunks.push(Buffer.from(chunk));
}
return Buffer.concat(chunks);
}
return bodyStream;
} 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) {
const upload = new libStorage.Upload({
client: this.client,
params: {
Bucket: this.bucket,
Key: givenFilename,
Body: stream$1,
...options?.contentType ? {
ContentType: options?.contentType
} : {}
}
});
await upload.done();
return {
key: givenFilename
};
}
const tempFilename = uuid.v4();
const uploadStream = new stream.PassThrough();
const getFilenamePromise = this.getStreamFilename(stream$1);
const upload = new libStorage.Upload({
client: this.client,
params: {
Bucket: this.bucket,
Key: tempFilename,
Body: uploadStream,
...options?.contentType ? {
ContentType: options?.contentType
} : {}
}
});
stream$1.pipe(uploadStream);
const [[filename, mime]] = await Promise.all([
getFilenamePromise,
upload.done()
]);
const copyCommand = new clientS3.CopyObjectCommand({
Bucket: this.bucket,
CopySource: `/${this.bucket}/${tempFilename}`,
Key: filename,
...mime ? {
ContentType: mime
} : {},
...options?.contentType ? {
ContentType: options?.contentType
} : {}
});
await this.client.send(copyCommand);
const deleteCommand = new clientS3.DeleteObjectCommand({
Bucket: this.bucket,
Key: tempFilename
});
await this.client.send(deleteCommand);
return {
key: filename
};
}
async writeBufferFile(buffer, options) {
const fileInfo = options?.filename || await this.getBufferFilename(buffer);
const filename = Array.isArray(fileInfo) ? fileInfo[0] : fileInfo;
const upload = new libStorage.Upload({
client: this.client,
params: {
Key: filename,
Bucket: this.bucket,
Body: buffer,
...Array.isArray(fileInfo) && fileInfo[1] ? {
ContentType: fileInfo[1]
} : {},
...options?.contentType ? {
ContentType: options?.contentType
} : {}
}
});
await upload.done();
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) {
const command = new clientS3.DeleteObjectCommand({
Bucket: this.bucket,
Key: key
});
await this.client.send(command);
}
async isExists(key) {
try {
const command = new clientS3.HeadObjectCommand({
Bucket: this.bucket,
Key: key
});
await this.client.send(command);
return true;
} catch (ex) {
if (ex && typeof ex === 'object' && 'name' in ex && ex.name === 'NotFound') return false;
throw ex;
}
}
}
exports.StorageR2Service = StorageR2Service;