@travetto/model-s3 8.0.0-alpha.2 → 8.0.0-alpha.21
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 +12 -4
- package/package.json +7 -7
- package/src/config.ts +13 -5
- package/src/service.ts +34 -22
- package/support/service.s3.ts +1 -1
package/README.md
CHANGED
|
@@ -65,11 +65,11 @@ export class S3ModelConfig {
|
|
|
65
65
|
modifyStorage?: boolean;
|
|
66
66
|
|
|
67
67
|
/**
|
|
68
|
-
* Provide
|
|
68
|
+
* Provide base URL for public access
|
|
69
69
|
*/
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
@Url()
|
|
71
|
+
@Required(false)
|
|
72
|
+
publicBaseUrl: string;
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
75
|
* Produces the s3 config from the provide details, post construction
|
|
@@ -81,6 +81,14 @@ export class S3ModelConfig {
|
|
|
81
81
|
this.bucket ??= 'app';
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
if (!this.publicBaseUrl) {
|
|
85
|
+
if (this.endpoint?.includes('localhost')) {
|
|
86
|
+
this.publicBaseUrl = this.endpoint;
|
|
87
|
+
} else {
|
|
88
|
+
this.publicBaseUrl = `https://${this.bucket}.s3.amazonaws.com`;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
84
92
|
if (!this.accessKeyId && !this.secretAccessKey) {
|
|
85
93
|
const creds = await fromIni({ profile: this.profile })();
|
|
86
94
|
this.accessKeyId = creds.accessKeyId;
|
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.21",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "S3 backing for the travetto model module.",
|
|
6
6
|
"keywords": [
|
|
@@ -26,14 +26,14 @@
|
|
|
26
26
|
"directory": "module/model-s3"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@aws-sdk/client-s3": "^3.
|
|
30
|
-
"@aws-sdk/credential-provider-ini": "^3.972.
|
|
31
|
-
"@aws-sdk/s3-request-presigner": "^3.
|
|
32
|
-
"@travetto/config": "^8.0.0-alpha.
|
|
33
|
-
"@travetto/model": "^8.0.0-alpha.
|
|
29
|
+
"@aws-sdk/client-s3": "^3.1063.0",
|
|
30
|
+
"@aws-sdk/credential-provider-ini": "^3.972.50",
|
|
31
|
+
"@aws-sdk/s3-request-presigner": "^3.1063.0",
|
|
32
|
+
"@travetto/config": "^8.0.0-alpha.19",
|
|
33
|
+
"@travetto/model": "^8.0.0-alpha.20"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
|
-
"@travetto/cli": "^8.0.0-alpha.
|
|
36
|
+
"@travetto/cli": "^8.0.0-alpha.25"
|
|
37
37
|
},
|
|
38
38
|
"peerDependenciesMeta": {
|
|
39
39
|
"@travetto/cli": {
|
package/src/config.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { fromIni } from '@aws-sdk/credential-provider-ini';
|
|
|
2
2
|
import type s3 from '@aws-sdk/client-s3';
|
|
3
3
|
|
|
4
4
|
import { Config, EnvVar } from '@travetto/config';
|
|
5
|
-
import { Required } from '@travetto/schema';
|
|
5
|
+
import { Required, Url } from '@travetto/schema';
|
|
6
6
|
import { Runtime } from '@travetto/runtime';
|
|
7
7
|
import { PostConstruct } from '@travetto/di';
|
|
8
8
|
|
|
@@ -33,11 +33,11 @@ export class S3ModelConfig {
|
|
|
33
33
|
modifyStorage?: boolean;
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
|
-
* Provide
|
|
36
|
+
* Provide base URL for public access
|
|
37
37
|
*/
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
@Url()
|
|
39
|
+
@Required(false)
|
|
40
|
+
publicBaseUrl: string;
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
43
|
* Produces the s3 config from the provide details, post construction
|
|
@@ -49,6 +49,14 @@ export class S3ModelConfig {
|
|
|
49
49
|
this.bucket ??= 'app';
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
if (!this.publicBaseUrl) {
|
|
53
|
+
if (this.endpoint?.includes('localhost')) {
|
|
54
|
+
this.publicBaseUrl = this.endpoint;
|
|
55
|
+
} else {
|
|
56
|
+
this.publicBaseUrl = `https://${this.bucket}.s3.amazonaws.com`;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
52
60
|
if (!this.accessKeyId && !this.secretAccessKey) {
|
|
53
61
|
const creds = await fromIni({ profile: this.profile })();
|
|
54
62
|
this.accessKeyId = creds.accessKeyId;
|
package/src/service.ts
CHANGED
|
@@ -7,7 +7,8 @@ import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
9
|
type ModelCrudSupport, type ModelStorageSupport, type ModelType, ModelRegistryIndex, ExistsError, NotFoundError, type OptionalId,
|
|
10
|
-
type ModelBlobSupport, type ModelExpirySupport, ModelCrudUtil, ModelExpiryUtil, ModelStorageUtil
|
|
10
|
+
type ModelBlobSupport, type ModelExpirySupport, ModelCrudUtil, ModelExpiryUtil, ModelStorageUtil,
|
|
11
|
+
type ModelListOptions
|
|
11
12
|
} from '@travetto/model';
|
|
12
13
|
import { Injectable, PostConstruct } from '@travetto/di';
|
|
13
14
|
import {
|
|
@@ -21,7 +22,7 @@ function isMetadataBearer(value: unknown): value is MetadataBearer {
|
|
|
21
22
|
return !!value && typeof value === 'object' && '$metadata' in value;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
function
|
|
25
|
+
function hasLowerContentType<T>(value: T): value is T & { contenttype?: string } {
|
|
25
26
|
return value !== undefined && value !== null && Object.hasOwn(value, 'contenttype');
|
|
26
27
|
}
|
|
27
28
|
|
|
@@ -93,16 +94,26 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
|
|
|
93
94
|
return {};
|
|
94
95
|
}
|
|
95
96
|
|
|
96
|
-
async * #iterateBucket(cls?: string | Class): AsyncIterable<{ Key: string, id: string }[]> {
|
|
97
|
+
async * #iterateBucket(cls?: string | Class, options?: ModelListOptions): AsyncIterable<{ Key: string, id: string }[]> {
|
|
97
98
|
let Marker: string | undefined;
|
|
98
|
-
|
|
99
|
+
const batchSize = options?.batchSizeHint ?? 100;
|
|
100
|
+
const maxCount = options?.limit ?? Number.MAX_SAFE_INTEGER;
|
|
101
|
+
let produced = 0;
|
|
102
|
+
for (; !(options?.abort?.aborted) && produced < maxCount;) {
|
|
99
103
|
const items = await this.client.listObjects({
|
|
100
104
|
Bucket: this.config.bucket,
|
|
101
105
|
Prefix: cls ? this.#resolveKey(cls) : this.config.namespace,
|
|
102
|
-
Marker
|
|
106
|
+
Marker,
|
|
107
|
+
MaxKeys: batchSize
|
|
103
108
|
});
|
|
104
|
-
|
|
105
|
-
|
|
109
|
+
|
|
110
|
+
let toSend = items.Contents?.map(item => ({ Key: item.Key!, id: item.Key!.split(':').pop()! }));
|
|
111
|
+
if (toSend) {
|
|
112
|
+
produced += toSend.length;
|
|
113
|
+
if (produced > maxCount) {
|
|
114
|
+
toSend = toSend.slice(0, maxCount - (produced - toSend.length));
|
|
115
|
+
}
|
|
116
|
+
yield toSend;
|
|
106
117
|
}
|
|
107
118
|
if (items.NextMarker) {
|
|
108
119
|
Marker = items.NextMarker;
|
|
@@ -293,17 +304,9 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
|
|
|
293
304
|
await this.client.deleteObject(this.#query(cls, id));
|
|
294
305
|
}
|
|
295
306
|
|
|
296
|
-
async * list<T extends ModelType>(cls: Class<T
|
|
297
|
-
for await (const batch of this.#iterateBucket(cls)) {
|
|
298
|
-
|
|
299
|
-
try {
|
|
300
|
-
yield await this.get(cls, id);
|
|
301
|
-
} catch (error) {
|
|
302
|
-
if (!(error instanceof NotFoundError)) {
|
|
303
|
-
throw error;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
+
async * list<T extends ModelType>(cls: Class<T>, options?: ModelListOptions): AsyncIterable<T[]> {
|
|
308
|
+
for await (const batch of this.#iterateBucket(cls, options)) {
|
|
309
|
+
yield ModelCrudUtil.filterOutNotFound(batch.map(item => this.get(cls, item.id)));
|
|
307
310
|
}
|
|
308
311
|
}
|
|
309
312
|
|
|
@@ -358,7 +361,7 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
|
|
|
358
361
|
return BinaryMetadataUtil.makeBlob(() => this.#getObject(location, final), { ...metadata, range: final });
|
|
359
362
|
}
|
|
360
363
|
|
|
361
|
-
async headBlob(location: string): Promise<{ Metadata?: BinaryMetadata, ContentLength?: number }> {
|
|
364
|
+
async headBlob(location: string): Promise<{ Metadata?: BinaryMetadata, ContentLength?: number, ContentType?: string }> {
|
|
362
365
|
const query = this.#queryBlob(location);
|
|
363
366
|
try {
|
|
364
367
|
return (await this.client.headObject(query));
|
|
@@ -377,11 +380,11 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
|
|
|
377
380
|
|
|
378
381
|
if (blob) {
|
|
379
382
|
const metadata: BinaryMetadata = {
|
|
380
|
-
contentType: '',
|
|
383
|
+
contentType: blob.ContentType ?? '',
|
|
381
384
|
...blob.Metadata,
|
|
382
385
|
size: blob.ContentLength!,
|
|
383
386
|
};
|
|
384
|
-
if (
|
|
387
|
+
if (hasLowerContentType(metadata)) {
|
|
385
388
|
metadata['contentType'] = metadata['contenttype']!;
|
|
386
389
|
delete metadata['contenttype'];
|
|
387
390
|
}
|
|
@@ -406,7 +409,16 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
|
|
|
406
409
|
}
|
|
407
410
|
|
|
408
411
|
// Signed urls
|
|
409
|
-
async getBlobReadUrl(location: string, expiresIn: TimeSpan = '1h'): Promise<string> {
|
|
412
|
+
async getBlobReadUrl(location: string, expiresIn: TimeSpan | false = '1h'): Promise<string> {
|
|
413
|
+
if (expiresIn === false) {
|
|
414
|
+
const key = this.#basicKey(location);
|
|
415
|
+
const baseUrl = this.config.publicBaseUrl;
|
|
416
|
+
if (this.config.config.forcePathStyle) {
|
|
417
|
+
return `${baseUrl}/${this.config.bucket}/${key}`;
|
|
418
|
+
} else {
|
|
419
|
+
return `${baseUrl}/${key}`;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
410
422
|
return await getSignedUrl(
|
|
411
423
|
this.client,
|
|
412
424
|
new GetObjectCommand(this.#queryBlob(location)),
|