@travetto/model-s3 8.0.0-alpha.23 → 8.0.0-alpha.25
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/README.md +3 -3
- package/__index__.ts +1 -1
- package/package.json +4 -4
- package/src/config.ts +4 -4
- package/src/service.ts +118 -73
- package/support/service.s3.ts +1 -1
package/README.md
CHANGED
|
@@ -13,11 +13,11 @@ npm install @travetto/model-s3
|
|
|
13
13
|
yarn add @travetto/model-s3
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
This module provides an [s3](https://aws.amazon.com/documentation/s3/)-based implementation for the [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations.").
|
|
16
|
+
This module provides an [s3](https://aws.amazon.com/documentation/s3/)-based implementation for the [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations."). This source allows the [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations.") module to read, write and stream against [s3](https://aws.amazon.com/documentation/s3/).
|
|
17
17
|
|
|
18
18
|
Supported features:
|
|
19
|
-
* [CRUD](https://github.com/travetto/travetto/tree/main/module/model/src/types/crud.ts#L11)
|
|
20
19
|
* [Blob](https://github.com/travetto/travetto/tree/main/module/model/src/types/blob.ts#L8)
|
|
20
|
+
* [CRUD](https://github.com/travetto/travetto/tree/main/module/model/src/types/crud.ts#L10)
|
|
21
21
|
* [Expiry](https://github.com/travetto/travetto/tree/main/module/model/src/types/expiry.ts#L10)
|
|
22
22
|
|
|
23
23
|
Out of the box, by installing the module, everything should be wired up by default.If you need to customize any aspect of the source or config, you can override and register it with the [Dependency Injection](https://github.com/travetto/travetto/tree/main/module/di#readme "Dependency registration/management and injection support.") module.
|
|
@@ -114,4 +114,4 @@ export class S3ModelConfig {
|
|
|
114
114
|
|
|
115
115
|
Additionally, you can see that the class is registered with the [@Config](https://github.com/travetto/travetto/tree/main/module/config/src/decorator.ts#L13) annotation, and so these values can be overridden using the standard [Configuration](https://github.com/travetto/travetto/tree/main/module/config#readme "Configuration support") resolution paths.
|
|
116
116
|
|
|
117
|
-
**Note**: Do not commit your `accessKeyId` or `secretAccessKey` values to your source repository, especially if it is public facing.
|
|
117
|
+
**Note**: Do not commit your `accessKeyId` or `secretAccessKey` values to your source repository, especially if it is public facing. Not only is it a security risk, but Amazon will scan public repos, looking for keys, and if found will react swiftly.
|
package/__index__.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export * from './src/config.ts';
|
|
2
|
-
export * from './src/service.ts';
|
|
2
|
+
export * from './src/service.ts';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/model-s3",
|
|
3
|
-
"version": "8.0.0-alpha.
|
|
3
|
+
"version": "8.0.0-alpha.25",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "S3 backing for the travetto model module.",
|
|
6
6
|
"keywords": [
|
|
@@ -29,11 +29,11 @@
|
|
|
29
29
|
"@aws-sdk/client-s3": "^3.1081.0",
|
|
30
30
|
"@aws-sdk/credential-provider-ini": "^3.972.62",
|
|
31
31
|
"@aws-sdk/s3-request-presigner": "^3.1081.0",
|
|
32
|
-
"@travetto/config": "^8.0.0-alpha.
|
|
33
|
-
"@travetto/model": "^8.0.0-alpha.
|
|
32
|
+
"@travetto/config": "^8.0.0-alpha.23",
|
|
33
|
+
"@travetto/model": "^8.0.0-alpha.24"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
|
-
"@travetto/cli": "^8.0.0-alpha.
|
|
36
|
+
"@travetto/cli": "^8.0.0-alpha.29"
|
|
37
37
|
},
|
|
38
38
|
"peerDependenciesMeta": {
|
|
39
39
|
"@travetto/cli": {
|
package/src/config.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { fromIni } from '@aws-sdk/credential-provider-ini';
|
|
2
1
|
import type s3 from '@aws-sdk/client-s3';
|
|
2
|
+
import { fromIni } from '@aws-sdk/credential-provider-ini';
|
|
3
3
|
|
|
4
4
|
import { Config, EnvVar } from '@travetto/config';
|
|
5
|
-
import { Required, Url } from '@travetto/schema';
|
|
6
|
-
import { Runtime } from '@travetto/runtime';
|
|
7
5
|
import { PostConstruct } from '@travetto/di';
|
|
6
|
+
import { Runtime } from '@travetto/runtime';
|
|
7
|
+
import { Required, Url } from '@travetto/schema';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* S3 Support as an Asset Source
|
|
@@ -77,4 +77,4 @@ export class S3ModelConfig {
|
|
|
77
77
|
this.config.forcePathStyle ??= true;
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
|
-
}
|
|
80
|
+
}
|
package/src/service.ts
CHANGED
|
@@ -1,19 +1,42 @@
|
|
|
1
1
|
import { Agent } from 'node:https';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { type CompletedPart, type CreateMultipartUploadRequest, GetObjectCommand, PutObjectCommand, S3 } from '@aws-sdk/client-s3';
|
|
4
|
+
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
|
4
5
|
import type { MetadataBearer } from '@aws-sdk/types';
|
|
5
6
|
import { NodeHttpHandler } from '@smithy/node-http-handler';
|
|
6
|
-
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
|
7
7
|
|
|
8
|
+
import { Injectable, PostConstruct } from '@travetto/di';
|
|
8
9
|
import {
|
|
9
|
-
|
|
10
|
-
type ModelBlobSupport,
|
|
11
|
-
type
|
|
10
|
+
ExistsError,
|
|
11
|
+
type ModelBlobSupport,
|
|
12
|
+
type ModelCrudSupport,
|
|
13
|
+
ModelCrudUtil,
|
|
14
|
+
type ModelExpirySupport,
|
|
15
|
+
ModelExpiryUtil,
|
|
16
|
+
type ModelListOptions,
|
|
17
|
+
ModelRegistryIndex,
|
|
18
|
+
type ModelStorageSupport,
|
|
19
|
+
ModelStorageUtil,
|
|
20
|
+
type ModelType,
|
|
21
|
+
NotFoundError,
|
|
22
|
+
type OptionalId
|
|
12
23
|
} from '@travetto/model';
|
|
13
|
-
import { Injectable, PostConstruct } from '@travetto/di';
|
|
14
24
|
import {
|
|
15
|
-
|
|
16
|
-
|
|
25
|
+
asFull,
|
|
26
|
+
type BinaryArray,
|
|
27
|
+
type BinaryMetadata,
|
|
28
|
+
BinaryMetadataUtil,
|
|
29
|
+
type BinaryType,
|
|
30
|
+
BinaryUtil,
|
|
31
|
+
type ByteRange,
|
|
32
|
+
type Class,
|
|
33
|
+
CodecUtil,
|
|
34
|
+
castTo,
|
|
35
|
+
JSONUtil,
|
|
36
|
+
RuntimeError,
|
|
37
|
+
type TimeSpan,
|
|
38
|
+
TimeUtil,
|
|
39
|
+
TypedObject
|
|
17
40
|
} from '@travetto/runtime';
|
|
18
41
|
|
|
19
42
|
import type { S3ModelConfig } from './config.ts';
|
|
@@ -26,7 +49,8 @@ function hasLowerContentType<T>(value: T): value is T & { contenttype?: string }
|
|
|
26
49
|
return value !== undefined && value !== null && Object.hasOwn(value, 'contenttype');
|
|
27
50
|
}
|
|
28
51
|
|
|
29
|
-
type S3Metadata = Pick<
|
|
52
|
+
type S3Metadata = Pick<
|
|
53
|
+
CreateMultipartUploadRequest,
|
|
30
54
|
'ContentType' | 'Metadata' | 'ContentEncoding' | 'ContentLanguage' | 'CacheControl' | 'ContentDisposition'
|
|
31
55
|
>;
|
|
32
56
|
|
|
@@ -35,12 +59,13 @@ type S3Metadata = Pick<CreateMultipartUploadRequest,
|
|
|
35
59
|
*/
|
|
36
60
|
@Injectable()
|
|
37
61
|
export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, ModelStorageSupport, ModelExpirySupport {
|
|
38
|
-
|
|
39
62
|
idSource = ModelCrudUtil.uuidSource();
|
|
40
63
|
client: S3;
|
|
41
64
|
config: S3ModelConfig;
|
|
42
65
|
|
|
43
|
-
constructor(config: S3ModelConfig) {
|
|
66
|
+
constructor(config: S3ModelConfig) {
|
|
67
|
+
this.config = config;
|
|
68
|
+
}
|
|
44
69
|
|
|
45
70
|
#getMetadata(metadata: BinaryMetadata): S3Metadata {
|
|
46
71
|
return {
|
|
@@ -49,8 +74,7 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
|
|
|
49
74
|
...(metadata.contentLanguage ? { ContentLanguage: metadata.contentLanguage } : {}),
|
|
50
75
|
...(metadata.cacheControl ? { CacheControl: metadata.cacheControl } : {}),
|
|
51
76
|
Metadata: TypedObject.fromEntries(
|
|
52
|
-
TypedObject.entries(metadata)
|
|
53
|
-
.map(([key, value]) => [key, typeof value === 'string' ? value : JSONUtil.toUTF8(value)] as const)
|
|
77
|
+
TypedObject.entries(metadata).map(([key, value]) => [key, typeof value === 'string' ? value : JSONUtil.toUTF8(value)] as const)
|
|
54
78
|
)
|
|
55
79
|
};
|
|
56
80
|
}
|
|
@@ -74,12 +98,12 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
|
|
|
74
98
|
return this.#basicKey(key);
|
|
75
99
|
}
|
|
76
100
|
|
|
77
|
-
#query<U extends object>(cls: string | Class, id: string, extra: U = asFull({})):
|
|
101
|
+
#query<U extends object>(cls: string | Class, id: string, extra: U = asFull({})): U & { Key: string; Bucket: string } {
|
|
78
102
|
const key = this.#resolveKey(cls, id);
|
|
79
103
|
return { Key: key, Bucket: this.config.bucket, ...extra };
|
|
80
104
|
}
|
|
81
105
|
|
|
82
|
-
#queryBlob<U extends object>(id: string, extra: U = asFull({})):
|
|
106
|
+
#queryBlob<U extends object>(id: string, extra: U = asFull({})): U & { Key: string; Bucket: string } {
|
|
83
107
|
const key = this.#basicKey(id);
|
|
84
108
|
return { Key: key, Bucket: this.config.bucket, ...extra };
|
|
85
109
|
}
|
|
@@ -94,12 +118,12 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
|
|
|
94
118
|
return {};
|
|
95
119
|
}
|
|
96
120
|
|
|
97
|
-
async
|
|
121
|
+
async *#iterateBucket(cls?: string | Class, options?: ModelListOptions): AsyncIterable<{ Key: string; id: string }[]> {
|
|
98
122
|
let Marker: string | undefined;
|
|
99
123
|
const batchSize = options?.batchSizeHint ?? 100;
|
|
100
124
|
const maxCount = options?.limit ?? Number.MAX_SAFE_INTEGER;
|
|
101
125
|
let produced = 0;
|
|
102
|
-
for (; !
|
|
126
|
+
for (; !options?.abort?.aborted && produced < maxCount; ) {
|
|
103
127
|
const items = await this.client.listObjects({
|
|
104
128
|
Bucket: this.config.bucket,
|
|
105
129
|
Prefix: cls ? this.#resolveKey(cls) : this.config.namespace,
|
|
@@ -134,12 +158,16 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
|
|
|
134
158
|
let total = 0;
|
|
135
159
|
let i = 1;
|
|
136
160
|
const flush = async (): Promise<void> => {
|
|
137
|
-
if (!total) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
161
|
+
if (!total) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const part = await this.client.uploadPart(
|
|
165
|
+
this.#queryBlob(id, {
|
|
166
|
+
Body: BinaryUtil.binaryArrayToUint8Array(BinaryUtil.combineBinaryArrays(buffers)),
|
|
167
|
+
PartNumber: i,
|
|
168
|
+
UploadId
|
|
169
|
+
})
|
|
170
|
+
);
|
|
143
171
|
parts.push({ PartNumber: i, ETag: part.ETag });
|
|
144
172
|
i += 1;
|
|
145
173
|
buffers = [];
|
|
@@ -156,10 +184,12 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
|
|
|
156
184
|
}
|
|
157
185
|
await flush();
|
|
158
186
|
|
|
159
|
-
await this.client.completeMultipartUpload(
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
187
|
+
await this.client.completeMultipartUpload(
|
|
188
|
+
this.#queryBlob(id, {
|
|
189
|
+
UploadId,
|
|
190
|
+
MultipartUpload: { Parts: parts }
|
|
191
|
+
})
|
|
192
|
+
);
|
|
163
193
|
} catch (error) {
|
|
164
194
|
await this.client.abortMultipartUpload(this.#queryBlob(id, { UploadId }));
|
|
165
195
|
throw error;
|
|
@@ -193,14 +223,18 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
|
|
|
193
223
|
async initializeClient(): Promise<void> {
|
|
194
224
|
this.client = new S3({
|
|
195
225
|
...this.config.config,
|
|
196
|
-
...('requestHandler' in this.config.config
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
226
|
+
...('requestHandler' in this.config.config
|
|
227
|
+
? {
|
|
228
|
+
requestHandler: new NodeHttpHandler({
|
|
229
|
+
...this.config.config.requestHandler,
|
|
230
|
+
...('httpsAgent' in this.config.config.requestHandler!
|
|
231
|
+
? {
|
|
232
|
+
httpsAgent: new Agent({ ...(this.config.config.requestHandler?.httpsAgent ?? {}) })
|
|
233
|
+
}
|
|
234
|
+
: {})
|
|
235
|
+
})
|
|
236
|
+
}
|
|
237
|
+
: {})
|
|
204
238
|
});
|
|
205
239
|
ModelStorageUtil.storageInitialization(this);
|
|
206
240
|
}
|
|
@@ -243,10 +277,8 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
|
|
|
243
277
|
}
|
|
244
278
|
throw new NotFoundError(cls, id);
|
|
245
279
|
} catch (error) {
|
|
246
|
-
if (isMetadataBearer(error)) {
|
|
247
|
-
|
|
248
|
-
error = new NotFoundError(cls, id);
|
|
249
|
-
}
|
|
280
|
+
if (isMetadataBearer(error) && error.$metadata.httpStatusCode === 404) {
|
|
281
|
+
throw new NotFoundError(cls, id);
|
|
250
282
|
}
|
|
251
283
|
throw error;
|
|
252
284
|
}
|
|
@@ -258,12 +290,14 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
|
|
|
258
290
|
prepped = await ModelCrudUtil.preStore(cls, item, this);
|
|
259
291
|
}
|
|
260
292
|
const content = JSONUtil.toBinaryArray(prepped);
|
|
261
|
-
await this.client.putObject(
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
293
|
+
await this.client.putObject(
|
|
294
|
+
this.#query(cls, prepped.id, {
|
|
295
|
+
Body: BinaryUtil.binaryArrayToUint8Array(content),
|
|
296
|
+
ContentType: 'application/json',
|
|
297
|
+
ContentLength: content.byteLength,
|
|
298
|
+
...this.#getExpiryConfig(cls, prepped)
|
|
299
|
+
})
|
|
300
|
+
);
|
|
267
301
|
return prepped;
|
|
268
302
|
}
|
|
269
303
|
|
|
@@ -304,7 +338,7 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
|
|
|
304
338
|
await this.client.deleteObject(this.#query(cls, id));
|
|
305
339
|
}
|
|
306
340
|
|
|
307
|
-
async *
|
|
341
|
+
async *list<T extends ModelType>(cls: Class<T>, options?: ModelListOptions): AsyncIterable<T[]> {
|
|
308
342
|
for await (const batch of this.#iterateBucket(cls, options)) {
|
|
309
343
|
yield ModelCrudUtil.filterOutNotFound(batch.map(item => this.get(cls, item.id)));
|
|
310
344
|
}
|
|
@@ -317,7 +351,13 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
|
|
|
317
351
|
|
|
318
352
|
// Blob support
|
|
319
353
|
async upsertBlob(location: string, input: BinaryType, metadata?: BinaryMetadata, overwrite = true): Promise<void> {
|
|
320
|
-
if (
|
|
354
|
+
if (
|
|
355
|
+
!overwrite &&
|
|
356
|
+
(await this.getBlobMetadata(location).then(
|
|
357
|
+
() => true,
|
|
358
|
+
() => false
|
|
359
|
+
))
|
|
360
|
+
) {
|
|
321
361
|
return;
|
|
322
362
|
}
|
|
323
363
|
|
|
@@ -325,11 +365,12 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
|
|
|
325
365
|
|
|
326
366
|
const length = BinaryMetadataUtil.readLength(resolved);
|
|
327
367
|
|
|
328
|
-
if (length && length < this.config.chunkSize) {
|
|
368
|
+
if (length && length < this.config.chunkSize) {
|
|
369
|
+
// If smaller than chunk size
|
|
329
370
|
const blob = this.#queryBlob(location, {
|
|
330
371
|
Body: BinaryUtil.toReadable(input),
|
|
331
372
|
ContentLength: length,
|
|
332
|
-
...this.#getMetadata(resolved)
|
|
373
|
+
...this.#getMetadata(resolved)
|
|
333
374
|
});
|
|
334
375
|
// Upload to s3
|
|
335
376
|
await this.client.putObject(blob);
|
|
@@ -340,18 +381,26 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
|
|
|
340
381
|
|
|
341
382
|
async #getObject(location: string, range?: Required<ByteRange>): Promise<BinaryType> {
|
|
342
383
|
// Read from s3
|
|
343
|
-
const result = await this.client.getObject(
|
|
344
|
-
|
|
345
|
-
|
|
384
|
+
const result = await this.client.getObject(
|
|
385
|
+
this.#queryBlob(
|
|
386
|
+
location,
|
|
387
|
+
range
|
|
388
|
+
? {
|
|
389
|
+
Range: `bytes=${range.start}-${range.end}`
|
|
390
|
+
}
|
|
391
|
+
: {}
|
|
392
|
+
)
|
|
393
|
+
);
|
|
346
394
|
|
|
347
395
|
const body: BinaryType | string | undefined = castTo(result.Body);
|
|
348
396
|
|
|
349
397
|
switch (typeof body) {
|
|
350
|
-
case 'undefined':
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
CodecUtil.fromUTF8String(body);
|
|
354
|
-
default:
|
|
398
|
+
case 'undefined':
|
|
399
|
+
throw new RuntimeError('Unable to read type: undefined');
|
|
400
|
+
case 'string':
|
|
401
|
+
return body.endsWith('=') ? CodecUtil.fromBase64String(body) : CodecUtil.fromUTF8String(body);
|
|
402
|
+
default:
|
|
403
|
+
return body;
|
|
355
404
|
}
|
|
356
405
|
}
|
|
357
406
|
|
|
@@ -361,15 +410,13 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
|
|
|
361
410
|
return BinaryMetadataUtil.makeBlob(() => this.#getObject(location, final), { ...metadata, range: final });
|
|
362
411
|
}
|
|
363
412
|
|
|
364
|
-
async headBlob(location: string): Promise<{ Metadata?: BinaryMetadata
|
|
413
|
+
async headBlob(location: string): Promise<{ Metadata?: BinaryMetadata; ContentLength?: number; ContentType?: string }> {
|
|
365
414
|
const query = this.#queryBlob(location);
|
|
366
415
|
try {
|
|
367
|
-
return
|
|
416
|
+
return await this.client.headObject(query);
|
|
368
417
|
} catch (error) {
|
|
369
|
-
if (isMetadataBearer(error)) {
|
|
370
|
-
|
|
371
|
-
error = new NotFoundError('Blob', location);
|
|
372
|
-
}
|
|
418
|
+
if (isMetadataBearer(error) && error.$metadata.httpStatusCode === 404) {
|
|
419
|
+
throw new NotFoundError('Blob', location);
|
|
373
420
|
}
|
|
374
421
|
throw error;
|
|
375
422
|
}
|
|
@@ -382,11 +429,11 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
|
|
|
382
429
|
const metadata: BinaryMetadata = {
|
|
383
430
|
contentType: blob.ContentType ?? '',
|
|
384
431
|
...blob.Metadata,
|
|
385
|
-
size: blob.ContentLength
|
|
432
|
+
size: blob.ContentLength!
|
|
386
433
|
};
|
|
387
434
|
if (hasLowerContentType(metadata)) {
|
|
388
|
-
metadata
|
|
389
|
-
delete metadata
|
|
435
|
+
metadata.contentType = metadata.contenttype!;
|
|
436
|
+
delete metadata.contenttype;
|
|
390
437
|
}
|
|
391
438
|
return metadata;
|
|
392
439
|
} else {
|
|
@@ -419,11 +466,9 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
|
|
|
419
466
|
return `${baseUrl}/${key}`;
|
|
420
467
|
}
|
|
421
468
|
}
|
|
422
|
-
return await getSignedUrl(
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
{ expiresIn: TimeUtil.duration(expiresIn, 's') }
|
|
426
|
-
);
|
|
469
|
+
return await getSignedUrl(this.client, new GetObjectCommand(this.#queryBlob(location)), {
|
|
470
|
+
expiresIn: TimeUtil.duration(expiresIn, 's')
|
|
471
|
+
});
|
|
427
472
|
}
|
|
428
473
|
|
|
429
474
|
async getBlobWriteUrl(location: string, metadata: BinaryMetadata, expiresIn: TimeSpan = '1h'): Promise<string> {
|
|
@@ -434,7 +479,7 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
|
|
|
434
479
|
...this.#queryBlob(location),
|
|
435
480
|
...base,
|
|
436
481
|
...(metadata.size ? { ContentLength: metadata.size } : {}),
|
|
437
|
-
...(
|
|
482
|
+
...(metadata.hash && metadata.hash !== '-1' ? { ChecksumSHA256: metadata.hash } : {})
|
|
438
483
|
}),
|
|
439
484
|
{
|
|
440
485
|
expiresIn: TimeUtil.duration(expiresIn, 's'),
|
|
@@ -467,4 +512,4 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
|
|
|
467
512
|
await this.client.deleteBucket({ Bucket: this.config.bucket });
|
|
468
513
|
}
|
|
469
514
|
}
|
|
470
|
-
}
|
|
515
|
+
}
|
package/support/service.s3.ts
CHANGED