@itwin/object-storage-s3
Version:
Object storage implementation base for S3 compatible providers
193 lines • 8.37 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.S3ClientWrapper = void 0;
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
const stream_1 = require("stream");
const client_s3_1 = require("@aws-sdk/client-s3");
const lib_storage_1 = require("@aws-sdk/lib-storage");
const internal_1 = require("@itwin/object-storage-core/lib/common/internal");
const internal_2 = require("@itwin/object-storage-core/lib/server/internal");
class S3ClientWrapper {
_client;
_bucket;
constructor(_client, _bucket) {
this._client = _client;
this._bucket = _bucket;
}
async download(reference, options) {
/* eslint-disable @typescript-eslint/naming-convention */
const { Body } = await this._client.send(new client_s3_1.GetObjectCommand({
Bucket: this._bucket,
Key: (0, internal_1.buildObjectKey)(reference),
}), options);
/* eslint-enable @typescript-eslint/naming-convention */
if (Body instanceof stream_1.Readable)
return Body;
throw new Error("Unexpected body type");
}
async upload(reference, data, metadata, headers) {
/* eslint-disable @typescript-eslint/naming-convention */
const input = {
Bucket: this._bucket,
Key: (0, internal_1.buildObjectKey)(reference),
Metadata: metadata,
};
/* eslint-enable @typescript-eslint/naming-convention */
if (data)
input.Body = data instanceof stream_1.Readable ? await (0, internal_2.streamToBuffer)(data) : data;
else
input.ContentLength = 0;
input.ContentEncoding = headers?.contentEncoding;
input.CacheControl = headers?.cacheControl;
input.ContentType = headers?.contentType;
await this._client.send(new client_s3_1.PutObjectCommand(input));
}
async uploadInMultipleParts(reference, data, options, headers) {
const { queueSize, partSize, metadata } = options ?? {};
/* eslint-disable @typescript-eslint/naming-convention */
const upload = new lib_storage_1.Upload({
client: this._client,
queueSize,
partSize,
leavePartsOnError: false,
params: {
Bucket: this._bucket,
Key: (0, internal_1.buildObjectKey)(reference),
Body: data,
Metadata: metadata,
ContentEncoding: headers?.contentEncoding,
CacheControl: headers?.cacheControl,
ContentType: headers?.contentType,
},
});
/* eslint-enable @typescript-eslint/naming-convention */
await upload.done();
}
async getDirectoriesNextPage(options) {
/* eslint-disable @typescript-eslint/naming-convention */
const response = await this._client.send(new client_s3_1.ListObjectsV2Command({
Bucket: this._bucket,
ContinuationToken: options.continuationToken,
// add delimiter to get list of directories in the response.
// See https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-prefixes.html
Delimiter: "/",
MaxKeys: options.maxPageSize,
}));
const directories = response.CommonPrefixes?.map((entry) =>
// removing last character which is a slash to have directory name
({ baseDirectory: entry.Prefix?.slice(0, -1) })) ?? [];
const continuationToken = response.NextContinuationToken;
const uniqueBaseDirectories = Array.from(new Set(directories).values());
const page = {
entities: uniqueBaseDirectories,
next: continuationToken == undefined
? undefined
: () => this.getDirectoriesNextPage({
maxPageSize: options.maxPageSize,
continuationToken: continuationToken,
}),
};
return page;
}
async getObjectsNextPage(directory, options) {
/* eslint-disable @typescript-eslint/naming-convention */
const response = await this._client.send(new client_s3_1.ListObjectsV2Command({
Bucket: this._bucket,
Prefix: directory.baseDirectory,
ContinuationToken: options.continuationToken,
MaxKeys: options.maxPageSize,
}));
/* eslint-disable @typescript-eslint/naming-convention */
let references = response.Contents?.map((object) => (0, internal_1.buildObjectReference)(object.Key)) ??
[];
const continuationToken = response.NextContinuationToken;
if (!options?.includeEmptyFiles) {
references = references.filter((ref) => !!ref.objectName);
}
const page = {
entities: references,
next: continuationToken == undefined
? undefined
: () => this.getObjectsNextPage(directory, {
maxPageSize: options.maxPageSize,
continuationToken: continuationToken,
includeEmptyFiles: options.includeEmptyFiles,
}),
};
return page;
}
async deleteObject(reference) {
/* eslint-disable @typescript-eslint/naming-convention */
await this._client.send(new client_s3_1.DeleteObjectCommand({
Bucket: this._bucket,
Key: (0, internal_1.buildObjectKey)(reference),
}));
/* eslint-enable @typescript-eslint/naming-convention */
}
async copyObject(bucket, sourceReference, targetReference) {
/* eslint-disable @typescript-eslint/naming-convention */
await this._client.send(new client_s3_1.CopyObjectCommand({
CopySource: `${bucket}/${(0, internal_1.buildObjectKey)(sourceReference)}`,
Bucket: this._bucket,
Key: (0, internal_1.buildObjectKey)(targetReference),
}));
/* eslint-enable @typescript-eslint/naming-convention */
}
async updateMetadata(reference, metadata) {
/* eslint-disable @typescript-eslint/naming-convention */
await this._client.send(new client_s3_1.CopyObjectCommand({
Bucket: this._bucket,
Key: (0, internal_1.buildObjectKey)(reference),
CopySource: `${this._bucket}/${(0, internal_1.buildObjectKey)(reference)}`,
Metadata: metadata,
MetadataDirective: "REPLACE",
}));
/* eslint-enable @typescript-eslint/naming-convention */
}
async getObjectProperties(reference) {
/* eslint-disable @typescript-eslint/naming-convention */
const { LastModified, ContentLength, Metadata: metadata, ContentEncoding: contentEncoding, ContentType: contentType, CacheControl: cacheControl, } = await this._client.send(new client_s3_1.HeadObjectCommand({
Bucket: this._bucket,
Key: (0, internal_1.buildObjectKey)(reference),
}));
/* eslint-enable @typescript-eslint/naming-convention */
return {
reference,
lastModified: LastModified,
size: ContentLength,
metadata,
contentEncoding,
contentType,
cacheControl,
};
}
async objectExists(reference) {
try {
return !!(await this.getObjectProperties(reference));
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}
catch (error) {
if (error.name === "NotFound")
return false;
throw error;
}
}
async prefixExists(directory) {
const filesWithPrefix = await this.getObjectsNextPage(directory, {
maxPageSize: 1,
includeEmptyFiles: true,
});
return filesWithPrefix.entities.length !== 0;
}
get bucketName() {
return this._bucket;
}
releaseResources() {
this._client.destroy();
}
}
exports.S3ClientWrapper = S3ClientWrapper;
//# sourceMappingURL=S3ClientWrapper.js.map