@travetto/model-s3 2.0.2 → 2.1.0
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 +4 -2
- package/package.json +5 -5
- package/src/config.ts +4 -2
- package/src/service.ts +26 -26
package/README.md
CHANGED
|
@@ -39,7 +39,7 @@ import * as S3 from '@aws-sdk/client-s3';
|
|
|
39
39
|
|
|
40
40
|
import { EnvUtil } from '@travetto/boot';
|
|
41
41
|
import { Config } from '@travetto/config';
|
|
42
|
-
import { Field } from '@travetto/schema';
|
|
42
|
+
import { Field, Required } from '@travetto/schema';
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
45
|
* S3 Support as an Asset Source
|
|
@@ -55,7 +55,8 @@ export class S3ModelConfig {
|
|
|
55
55
|
secretAccessKey = EnvUtil.get('AWS_SECRET_ACCESS_KEY', '');
|
|
56
56
|
|
|
57
57
|
@Field(Object)
|
|
58
|
-
|
|
58
|
+
@Required(false)
|
|
59
|
+
config: S3.S3ClientConfig; // Additional s3 config
|
|
59
60
|
|
|
60
61
|
chunkSize = 5 * 2 ** 20; // Chunk size in bytes
|
|
61
62
|
|
|
@@ -80,6 +81,7 @@ export class S3ModelConfig {
|
|
|
80
81
|
|
|
81
82
|
this.config = {
|
|
82
83
|
...(this.config ?? {}),
|
|
84
|
+
region: this.region,
|
|
83
85
|
endpoint: this.endpoint,
|
|
84
86
|
credentials: {
|
|
85
87
|
accessKeyId: this.accessKeyId,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/model-s3",
|
|
3
3
|
"displayName": "S3 Model Support",
|
|
4
|
-
"version": "2.0
|
|
4
|
+
"version": "2.1.0",
|
|
5
5
|
"description": "S3 backing for the travetto model module.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"s3",
|
|
@@ -25,10 +25,10 @@
|
|
|
25
25
|
"directory": "module/model-s3"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@aws-sdk/client-s3": "^3.
|
|
29
|
-
"@aws-sdk/credential-provider-ini": "^3.
|
|
30
|
-
"@travetto/config": "^2.0
|
|
31
|
-
"@travetto/model": "^2.0
|
|
28
|
+
"@aws-sdk/client-s3": "^3.49.0",
|
|
29
|
+
"@aws-sdk/credential-provider-ini": "^3.49.0",
|
|
30
|
+
"@travetto/config": "^2.1.0",
|
|
31
|
+
"@travetto/model": "^2.1.0"
|
|
32
32
|
},
|
|
33
33
|
"publishConfig": {
|
|
34
34
|
"access": "public"
|
package/src/config.ts
CHANGED
|
@@ -3,7 +3,7 @@ import * as S3 from '@aws-sdk/client-s3';
|
|
|
3
3
|
|
|
4
4
|
import { EnvUtil } from '@travetto/boot';
|
|
5
5
|
import { Config } from '@travetto/config';
|
|
6
|
-
import { Field } from '@travetto/schema';
|
|
6
|
+
import { Field, Required } from '@travetto/schema';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* S3 Support as an Asset Source
|
|
@@ -19,7 +19,8 @@ export class S3ModelConfig {
|
|
|
19
19
|
secretAccessKey = EnvUtil.get('AWS_SECRET_ACCESS_KEY', '');
|
|
20
20
|
|
|
21
21
|
@Field(Object)
|
|
22
|
-
|
|
22
|
+
@Required(false)
|
|
23
|
+
config: S3.S3ClientConfig; // Additional s3 config
|
|
23
24
|
|
|
24
25
|
chunkSize = 5 * 2 ** 20; // Chunk size in bytes
|
|
25
26
|
|
|
@@ -44,6 +45,7 @@ export class S3ModelConfig {
|
|
|
44
45
|
|
|
45
46
|
this.config = {
|
|
46
47
|
...(this.config ?? {}),
|
|
48
|
+
region: this.region,
|
|
47
49
|
endpoint: this.endpoint,
|
|
48
50
|
credentials: {
|
|
49
51
|
accessKeyId: this.accessKeyId,
|
package/src/service.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { MetadataBearer } from '@aws-sdk/types';
|
|
|
4
4
|
import { StreamUtil } from '@travetto/boot';
|
|
5
5
|
import {
|
|
6
6
|
ModelCrudSupport, ModelStreamSupport, ModelStorageSupport, StreamMeta,
|
|
7
|
-
ModelType, ModelRegistry, ExistsError, NotFoundError,
|
|
7
|
+
ModelType, ModelRegistry, ExistsError, NotFoundError, OptionalId
|
|
8
8
|
} from '@travetto/model';
|
|
9
9
|
import { Injectable } from '@travetto/di';
|
|
10
10
|
import { Class, AppError, Util } from '@travetto/base';
|
|
@@ -23,6 +23,8 @@ function hasContenttype<T>(o: T): o is T & { contenttype?: string } {
|
|
|
23
23
|
return o && 'contenttype' in o;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
const STREAM_SPACE = '@trv:stream';
|
|
27
|
+
|
|
26
28
|
/**
|
|
27
29
|
* Asset source backed by S3
|
|
28
30
|
*/
|
|
@@ -34,9 +36,15 @@ export class S3ModelService implements ModelCrudSupport, ModelStreamSupport, Mod
|
|
|
34
36
|
constructor(public readonly config: S3ModelConfig) { }
|
|
35
37
|
|
|
36
38
|
#resolveKey(cls: Class | string, id?: string) {
|
|
37
|
-
let key
|
|
38
|
-
if (
|
|
39
|
-
key =
|
|
39
|
+
let key: string;
|
|
40
|
+
if (cls === STREAM_SPACE) { // If we are streaming, treat as primary use case
|
|
41
|
+
key = id!; // Store it directly at root
|
|
42
|
+
} else {
|
|
43
|
+
key = typeof cls === 'string' ? cls : ModelRegistry.getStore(cls);
|
|
44
|
+
if (id) {
|
|
45
|
+
key = `${key}:${id}`;
|
|
46
|
+
}
|
|
47
|
+
key = `_data/${key}`; // Separate data
|
|
40
48
|
}
|
|
41
49
|
if (this.config.namespace) {
|
|
42
50
|
key = `${this.config.namespace}/${key}`;
|
|
@@ -78,7 +86,7 @@ export class S3ModelService implements ModelCrudSupport, ModelStreamSupport, Mod
|
|
|
78
86
|
* Write multipart file upload, in chunks
|
|
79
87
|
*/
|
|
80
88
|
async #writeMultipart(id: string, input: NodeJS.ReadableStream, meta: StreamMeta): Promise<void> {
|
|
81
|
-
const { UploadId } = await this.client.createMultipartUpload(this.#q(
|
|
89
|
+
const { UploadId } = await this.client.createMultipartUpload(this.#q(STREAM_SPACE, id, {
|
|
82
90
|
ContentType: meta.contentType,
|
|
83
91
|
ContentLength: meta.size,
|
|
84
92
|
}));
|
|
@@ -89,7 +97,7 @@ export class S3ModelService implements ModelCrudSupport, ModelStreamSupport, Mod
|
|
|
89
97
|
let n = 1;
|
|
90
98
|
const flush = async () => {
|
|
91
99
|
if (!total) { return; }
|
|
92
|
-
const part = await this.client.uploadPart(this.#q(
|
|
100
|
+
const part = await this.client.uploadPart(this.#q(STREAM_SPACE, id, {
|
|
93
101
|
Body: Buffer.concat(buffers),
|
|
94
102
|
PartNumber: n,
|
|
95
103
|
UploadId
|
|
@@ -109,12 +117,12 @@ export class S3ModelService implements ModelCrudSupport, ModelStreamSupport, Mod
|
|
|
109
117
|
}
|
|
110
118
|
await flush();
|
|
111
119
|
|
|
112
|
-
await this.client.completeMultipartUpload(this.#q(
|
|
120
|
+
await this.client.completeMultipartUpload(this.#q(STREAM_SPACE, id, {
|
|
113
121
|
UploadId,
|
|
114
122
|
MultipartUpload: { Parts: parts }
|
|
115
123
|
}));
|
|
116
124
|
} catch (e) {
|
|
117
|
-
await this.client.abortMultipartUpload(this.#q(
|
|
125
|
+
await this.client.abortMultipartUpload(this.#q(STREAM_SPACE, id, { UploadId }));
|
|
118
126
|
throw e;
|
|
119
127
|
}
|
|
120
128
|
}
|
|
@@ -213,9 +221,7 @@ export class S3ModelService implements ModelCrudSupport, ModelStreamSupport, Mod
|
|
|
213
221
|
}
|
|
214
222
|
|
|
215
223
|
async update<T extends ModelType>(cls: Class<T>, item: T) {
|
|
216
|
-
|
|
217
|
-
throw new SubTypeNotSupportedError(cls);
|
|
218
|
-
}
|
|
224
|
+
ModelCrudUtil.ensureNotSubType(cls);
|
|
219
225
|
if (!(await this.head(cls, item.id))) {
|
|
220
226
|
throw new NotFoundError(cls, item.id);
|
|
221
227
|
}
|
|
@@ -223,25 +229,19 @@ export class S3ModelService implements ModelCrudSupport, ModelStreamSupport, Mod
|
|
|
223
229
|
}
|
|
224
230
|
|
|
225
231
|
async upsert<T extends ModelType>(cls: Class<T>, item: OptionalId<T>) {
|
|
226
|
-
|
|
227
|
-
throw new SubTypeNotSupportedError(cls);
|
|
228
|
-
}
|
|
232
|
+
ModelCrudUtil.ensureNotSubType(cls);
|
|
229
233
|
return this.store(cls, item);
|
|
230
234
|
}
|
|
231
235
|
|
|
232
236
|
async updatePartial<T extends ModelType>(cls: Class<T>, item: Partial<T> & { id: string }, view?: string) {
|
|
233
|
-
|
|
234
|
-
throw new SubTypeNotSupportedError(cls);
|
|
235
|
-
}
|
|
237
|
+
ModelCrudUtil.ensureNotSubType(cls);
|
|
236
238
|
const id = item.id;
|
|
237
239
|
item = await ModelCrudUtil.naivePartialUpdate(cls, item, view, () => this.get(cls, id)) as T;
|
|
238
240
|
return this.store<T>(cls, item as T, false);
|
|
239
241
|
}
|
|
240
242
|
|
|
241
243
|
async delete<T extends ModelType>(cls: Class<T>, id: string) {
|
|
242
|
-
|
|
243
|
-
throw new SubTypeNotSupportedError(cls);
|
|
244
|
-
}
|
|
244
|
+
ModelCrudUtil.ensureNotSubType(cls);
|
|
245
245
|
if (!(await this.head(cls, id))) {
|
|
246
246
|
throw new NotFoundError(cls, id);
|
|
247
247
|
}
|
|
@@ -270,7 +270,7 @@ export class S3ModelService implements ModelCrudSupport, ModelStreamSupport, Mod
|
|
|
270
270
|
async upsertStream(location: string, input: NodeJS.ReadableStream, meta: StreamMeta) {
|
|
271
271
|
if (meta.size < this.config.chunkSize) { // If bigger than 5 mb
|
|
272
272
|
// Upload to s3
|
|
273
|
-
await this.client.putObject(this.#q(
|
|
273
|
+
await this.client.putObject(this.#q(STREAM_SPACE, location, {
|
|
274
274
|
Body: await StreamUtil.toBuffer(input),
|
|
275
275
|
ContentType: meta.contentType,
|
|
276
276
|
ContentLength: meta.size,
|
|
@@ -286,7 +286,7 @@ export class S3ModelService implements ModelCrudSupport, ModelStreamSupport, Mod
|
|
|
286
286
|
|
|
287
287
|
async getStream(location: string) {
|
|
288
288
|
// Read from s3
|
|
289
|
-
const res = await this.client.getObject(this.#q(
|
|
289
|
+
const res = await this.client.getObject(this.#q(STREAM_SPACE, location));
|
|
290
290
|
if (res.Body instanceof Buffer || // Buffer
|
|
291
291
|
typeof res.Body === 'string' || // string
|
|
292
292
|
res.Body && ('pipe' in res.Body) // Stream
|
|
@@ -297,13 +297,13 @@ export class S3ModelService implements ModelCrudSupport, ModelStreamSupport, Mod
|
|
|
297
297
|
}
|
|
298
298
|
|
|
299
299
|
async headStream(location: string) {
|
|
300
|
-
const query = this.#q(
|
|
300
|
+
const query = this.#q(STREAM_SPACE, location);
|
|
301
301
|
try {
|
|
302
302
|
return await this.client.headObject(query);
|
|
303
303
|
} catch (e) {
|
|
304
304
|
if (isMetadataBearer(e)) {
|
|
305
305
|
if (e.$metadata.httpStatusCode === 404) {
|
|
306
|
-
e = new NotFoundError(
|
|
306
|
+
e = new NotFoundError(STREAM_SPACE, location);
|
|
307
307
|
}
|
|
308
308
|
}
|
|
309
309
|
throw e;
|
|
@@ -324,7 +324,7 @@ export class S3ModelService implements ModelCrudSupport, ModelStreamSupport, Mod
|
|
|
324
324
|
}
|
|
325
325
|
return ret;
|
|
326
326
|
} else {
|
|
327
|
-
throw new NotFoundError(
|
|
327
|
+
throw new NotFoundError(STREAM_SPACE, location);
|
|
328
328
|
}
|
|
329
329
|
}
|
|
330
330
|
|
|
@@ -335,7 +335,7 @@ export class S3ModelService implements ModelCrudSupport, ModelStreamSupport, Mod
|
|
|
335
335
|
}
|
|
336
336
|
|
|
337
337
|
async deleteStream(location: string) {
|
|
338
|
-
await this.client.deleteObject(this.#q(
|
|
338
|
+
await this.client.deleteObject(this.#q(STREAM_SPACE, location));
|
|
339
339
|
}
|
|
340
340
|
|
|
341
341
|
async createStorage() {
|