@russ-b/nestjs-common-tools
Version:
NestJS utility tools
221 lines • 9.77 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var S3Service_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.S3Service = void 0;
const common_1 = require("@nestjs/common");
const client_s3_1 = require("@aws-sdk/client-s3");
const s3_request_presigner_1 = require("@aws-sdk/s3-request-presigner");
const stream_1 = require("stream");
const lib_storage_1 = require("@aws-sdk/lib-storage");
const s3_constants_1 = require("./s3.constants");
let S3Service = S3Service_1 = class S3Service {
constructor(options, s3Client) {
this.options = options;
this.s3Client = s3Client;
this.logger =
options.logger === true ? new common_1.Logger(S3Service_1.name) : options.logger || undefined;
}
async upload(key, body, options) {
const upload = new lib_storage_1.Upload({
client: this.s3Client,
params: this.createPutObjectParams(key, body, options, true),
});
const response = await upload.done();
this.log(`Uploaded object with multipart upload: ${key}`);
return response;
}
async putObject(key, body, options) {
const command = new client_s3_1.PutObjectCommand(this.createPutObjectParams(key, body, options, true));
try {
const response = await this.s3Client.send(command);
this.log(`Uploaded object: ${key}`);
return response;
}
catch (error) {
this.logError(`Failed to upload object: ${key}`, error);
throw error;
}
}
async uploadObject(key, body, options) {
return this.putObject(key, body, options);
}
async deleteObject(key, options) {
const command = new client_s3_1.DeleteObjectCommand({
Bucket: this.resolveBucket(options?.bucket),
Key: key,
});
try {
const response = await this.s3Client.send(command);
this.log(`Deleted object: ${key}`);
return response;
}
catch (error) {
this.logError(`Failed to delete object: ${key}`, error);
throw error;
}
}
async copyObject(sourceKey, destinationKey, options) {
const command = new client_s3_1.CopyObjectCommand(this.createCopyObjectParams(sourceKey, destinationKey, options));
try {
const response = await this.s3Client.send(command);
this.log(`Copied object: ${sourceKey} -> ${destinationKey}`);
return response;
}
catch (error) {
this.logError(`Failed to copy object: ${sourceKey} -> ${destinationKey}`, error);
throw error;
}
}
async getObject(key, options) {
const command = new client_s3_1.GetObjectCommand({
Bucket: this.resolveBucket(options?.bucket),
Key: key,
});
try {
const response = await this.s3Client.send(command);
this.log(`Fetched object: ${key}`);
if (!response.Body) {
throw new Error(`S3 object "${key}" returned an empty body.`);
}
if (!(response.Body instanceof stream_1.Readable)) {
throw new Error(`S3 object "${key}" did not return a Node.js readable stream.`);
}
return {
body: response.Body,
cacheControl: response.CacheControl,
contentLength: response.ContentLength,
contentType: response.ContentType,
eTag: response.ETag,
lastModified: response.LastModified,
metadata: response.Metadata,
};
}
catch (error) {
this.logError(`Failed to get object: ${key}`, error);
throw error;
}
}
async listObjects(options) {
const command = new client_s3_1.ListObjectsV2Command({
Bucket: this.resolveBucket(options?.bucket),
...(options?.continuationToken
? { ContinuationToken: options.continuationToken }
: {}),
...(options?.delimiter ? { Delimiter: options.delimiter } : {}),
...(options?.maxKeys ? { MaxKeys: options.maxKeys } : {}),
...(options?.prefix ? { Prefix: options.prefix } : {}),
});
try {
const response = await this.s3Client.send(command);
this.log(`Listed objects for bucket: ${command.input.Bucket}`);
return response;
}
catch (error) {
this.logError(`Failed to list objects for bucket: ${command.input.Bucket}`, error);
throw error;
}
}
async getSignedUrl(key, options) {
const expiresIn = options?.expiresIn ?? 900;
const operation = options?.operation ?? 'getObject';
const s3Client = this.resolveSigningClient(options?.endpoint);
const command = operation === 'putObject'
? new client_s3_1.PutObjectCommand(this.createPutObjectParams(key, undefined, options))
: new client_s3_1.GetObjectCommand({
Bucket: this.resolveBucket(options?.bucket),
Key: key,
});
return (0, s3_request_presigner_1.getSignedUrl)(s3Client, command, { expiresIn });
}
createCopyObjectParams(sourceKey, destinationKey, options) {
const destinationBucket = this.resolveBucket(options?.bucket);
const sourceBucket = this.resolveBucket(options?.sourceBucket ?? options?.bucket);
const shouldReplaceMetadata = !!(options?.cacheControl ||
options?.contentType ||
options?.metadata);
const metadataDirective = options?.metadataDirective ?? (shouldReplaceMetadata ? 'REPLACE' : undefined);
return {
Bucket: destinationBucket,
CopySource: this.buildCopySource(sourceBucket, sourceKey),
Key: destinationKey,
...(options?.acl ? { ACL: options.acl } : {}),
...(options?.cacheControl ? { CacheControl: options.cacheControl } : {}),
...(options?.contentType ? { ContentType: options.contentType } : {}),
...(metadataDirective ? { MetadataDirective: metadataDirective } : {}),
...(options?.metadata ? { Metadata: options.metadata } : {}),
};
}
createPutObjectParams(key, body, options, useDefaultContentType = false) {
const contentType = options?.contentType ??
(useDefaultContentType ? 'application/octet-stream' : undefined);
return {
Bucket: this.resolveBucket(options?.bucket),
Key: key,
...(body !== undefined ? { Body: body } : {}),
...(contentType ? { ContentType: contentType } : {}),
...(options?.acl ? { ACL: options.acl } : {}),
...(options?.cacheControl ? { CacheControl: options.cacheControl } : {}),
...(options?.metadata ? { Metadata: options.metadata } : {}),
};
}
buildCopySource(bucket, key) {
return `${encodeURIComponent(bucket)}/${key
.split('/')
.map((part) => encodeURIComponent(part))
.join('/')}`;
}
resolveBucket(bucket) {
const resolvedBucket = bucket ?? this.options.defaultBucket ?? this.options.bucket;
if (!resolvedBucket) {
throw new Error('S3 bucket is not configured. Pass a bucket to the method call or set S3ModuleOptions.defaultBucket.');
}
return resolvedBucket;
}
resolveSigningClient(endpoint) {
if (!endpoint || endpoint === this.options.endpoint) {
return this.s3Client;
}
const clientConfig = this.s3Client.config;
return new client_s3_1.S3Client({
region: this.options.region ?? 'us-east-1',
endpoint,
forcePathStyle: this.options.forcePathStyle ?? true,
requestChecksumCalculation: this.options.requestChecksumCalculation,
responseChecksumValidation: this.options.responseChecksumValidation,
...(clientConfig?.credentials ? { credentials: clientConfig.credentials } : {}),
});
}
log(message) {
this.logger?.log(message);
}
logError(message, error) {
if (!this.logger) {
return;
}
if (error instanceof Error) {
this.logger.error(message, error.stack ?? error.message);
return;
}
this.logger.error(message, error);
}
};
exports.S3Service = S3Service;
exports.S3Service = S3Service = S3Service_1 = __decorate([
(0, common_1.Injectable)(),
__param(0, (0, common_1.Inject)(s3_constants_1.S3_MODULE_OPTIONS)),
__param(1, (0, common_1.Inject)(s3_constants_1.S3_CLIENT)),
__metadata("design:paramtypes", [Object, client_s3_1.S3Client])
], S3Service);
//# sourceMappingURL=s3.service.js.map