@travetto/model-s3 5.0.6 → 5.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -4
- package/src/service.ts +64 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/model-s3",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.8",
|
|
4
4
|
"description": "S3 backing for the travetto model module.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"s3",
|
|
@@ -26,12 +26,13 @@
|
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@aws-sdk/client-s3": "^3.658.1",
|
|
29
|
+
"@aws-sdk/s3-request-presigner": "^3.658.1",
|
|
29
30
|
"@aws-sdk/credential-provider-ini": "^3.609.0",
|
|
30
|
-
"@travetto/config": "^5.0.
|
|
31
|
-
"@travetto/model": "^5.0.
|
|
31
|
+
"@travetto/config": "^5.0.7",
|
|
32
|
+
"@travetto/model": "^5.0.8"
|
|
32
33
|
},
|
|
33
34
|
"peerDependencies": {
|
|
34
|
-
"@travetto/command": "^5.0.
|
|
35
|
+
"@travetto/command": "^5.0.7"
|
|
35
36
|
},
|
|
36
37
|
"peerDependenciesMeta": {
|
|
37
38
|
"@travetto/command": {
|
package/src/service.ts
CHANGED
|
@@ -2,16 +2,17 @@ import { Readable } from 'node:stream';
|
|
|
2
2
|
import { text as toText } from 'node:stream/consumers';
|
|
3
3
|
import { Agent } from 'node:https';
|
|
4
4
|
|
|
5
|
-
import { S3, CompletedPart, type CreateMultipartUploadRequest } from '@aws-sdk/client-s3';
|
|
5
|
+
import { S3, CompletedPart, type CreateMultipartUploadRequest, GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';
|
|
6
6
|
import type { MetadataBearer } from '@aws-sdk/types';
|
|
7
7
|
import { NodeHttpHandler } from '@smithy/node-http-handler';
|
|
8
|
+
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
|
8
9
|
|
|
9
10
|
import {
|
|
10
11
|
ModelCrudSupport, ModelStorageSupport, ModelType, ModelRegistry, ExistsError, NotFoundError, OptionalId,
|
|
11
12
|
ModelBlobSupport
|
|
12
13
|
} from '@travetto/model';
|
|
13
14
|
import { Injectable } from '@travetto/di';
|
|
14
|
-
import { Class, AppError, castTo, asFull, BlobMeta, ByteRange, BinaryInput, BinaryUtil } from '@travetto/runtime';
|
|
15
|
+
import { Class, AppError, castTo, asFull, BlobMeta, ByteRange, BinaryInput, BinaryUtil, TimeSpan, TimeUtil } from '@travetto/runtime';
|
|
15
16
|
|
|
16
17
|
import { MODEL_BLOB, ModelBlobUtil } from '@travetto/model/src/internal/service/blob';
|
|
17
18
|
import { ModelCrudUtil } from '@travetto/model/src/internal/service/crud';
|
|
@@ -68,6 +69,9 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
|
|
|
68
69
|
}
|
|
69
70
|
key = `_data/${key}`; // Separate data
|
|
70
71
|
}
|
|
72
|
+
if (key?.startsWith('/')) {
|
|
73
|
+
key = key.substring(1);
|
|
74
|
+
}
|
|
71
75
|
if (this.config.namespace) {
|
|
72
76
|
key = `${this.config.namespace}/${key}`;
|
|
73
77
|
}
|
|
@@ -152,12 +156,26 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
|
|
|
152
156
|
}
|
|
153
157
|
|
|
154
158
|
async #deleteKeys(items: { Key: string }[]): Promise<void> {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
+
try {
|
|
160
|
+
await this.client.deleteObjects({
|
|
161
|
+
Bucket: this.config.bucket,
|
|
162
|
+
Delete: {
|
|
163
|
+
Objects: items
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
} catch (err) {
|
|
167
|
+
// Handle GCS
|
|
168
|
+
if (err instanceof Error && err.name === 'NotImplemented') {
|
|
169
|
+
for (const item of items) {
|
|
170
|
+
await this.client.deleteObject({
|
|
171
|
+
Bucket: this.config.bucket,
|
|
172
|
+
Key: item.Key
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
throw err;
|
|
159
177
|
}
|
|
160
|
-
}
|
|
178
|
+
}
|
|
161
179
|
}
|
|
162
180
|
|
|
163
181
|
async postConstruct(): Promise<void> {
|
|
@@ -293,7 +311,7 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
|
|
|
293
311
|
|
|
294
312
|
// Blob support
|
|
295
313
|
async upsertBlob(location: string, input: BinaryInput, meta?: BlobMeta, overwrite = true): Promise<void> {
|
|
296
|
-
if (!overwrite && await this.
|
|
314
|
+
if (!overwrite && await this.getBlobMeta(location).then(() => true, () => false)) {
|
|
297
315
|
return;
|
|
298
316
|
}
|
|
299
317
|
|
|
@@ -332,7 +350,7 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
|
|
|
332
350
|
}
|
|
333
351
|
|
|
334
352
|
async getBlob(location: string, range?: ByteRange): Promise<Blob> {
|
|
335
|
-
const meta = await this.
|
|
353
|
+
const meta = await this.getBlobMeta(location);
|
|
336
354
|
const final = range ? ModelBlobUtil.enforceRange(range, meta.size!) : undefined;
|
|
337
355
|
const res = (): Promise<Readable> => this.#getObject(location, final);
|
|
338
356
|
return BinaryUtil.readableBlob(res, { ...meta, range: final });
|
|
@@ -352,7 +370,7 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
|
|
|
352
370
|
}
|
|
353
371
|
}
|
|
354
372
|
|
|
355
|
-
async
|
|
373
|
+
async getBlobMeta(location: string): Promise<BlobMeta> {
|
|
356
374
|
const obj = await this.headBlob(location);
|
|
357
375
|
|
|
358
376
|
if (obj) {
|
|
@@ -375,6 +393,42 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
|
|
|
375
393
|
await this.client.deleteObject(this.#q(MODEL_BLOB, location));
|
|
376
394
|
}
|
|
377
395
|
|
|
396
|
+
async updateBlobMeta(location: string, meta: BlobMeta): Promise<void> {
|
|
397
|
+
await this.client.copyObject({
|
|
398
|
+
Bucket: this.config.bucket,
|
|
399
|
+
Key: this.#resolveKey(MODEL_BLOB, location),
|
|
400
|
+
CopySource: `/${this.config.bucket}/${this.#resolveKey(MODEL_BLOB, location)}`,
|
|
401
|
+
...this.#getMetaBase(meta),
|
|
402
|
+
MetadataDirective: 'REPLACE'
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Signed urls
|
|
407
|
+
async getBlobReadUrl(location: string, exp: TimeSpan = '1h'): Promise<string> {
|
|
408
|
+
return await getSignedUrl(
|
|
409
|
+
this.client,
|
|
410
|
+
new GetObjectCommand(this.#q(MODEL_BLOB, location)),
|
|
411
|
+
{ expiresIn: TimeUtil.asSeconds(exp) }
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
async getBlobWriteUrl(location: string, meta: BlobMeta, exp: TimeSpan = '1h'): Promise<string> {
|
|
416
|
+
const base = this.#getMetaBase(meta);
|
|
417
|
+
return await getSignedUrl(
|
|
418
|
+
this.client,
|
|
419
|
+
new PutObjectCommand({
|
|
420
|
+
...this.#q(MODEL_BLOB, location),
|
|
421
|
+
...base,
|
|
422
|
+
...(meta.size ? { ContentLength: meta.size } : {}),
|
|
423
|
+
...((meta.hash && meta.hash !== '-1') ? { ChecksumSHA256: meta.hash } : {}),
|
|
424
|
+
}),
|
|
425
|
+
{
|
|
426
|
+
expiresIn: TimeUtil.asSeconds(exp),
|
|
427
|
+
...(meta.contentType ? { signableHeaders: new Set(['content-type']) } : {})
|
|
428
|
+
}
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
|
|
378
432
|
// Storage
|
|
379
433
|
async truncateModel<T extends ModelType>(model: Class<T>): Promise<void> {
|
|
380
434
|
for await (const items of this.#iterateBucket(model)) {
|