UNPKG

@rytass/storages-adapter-r2

Version:

Cloudflare R2 storage adapter

188 lines (184 loc) 6.36 kB
'use strict'; 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;