@travetto/model-s3 6.0.0-rc.0 → 6.0.0-rc.2

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 CHANGED
@@ -16,9 +16,9 @@ yarn add @travetto/model-s3
16
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/service/crud.ts#L11)
20
- * [Blob](https://github.com/travetto/travetto/tree/main/module/model/src/service/blob.ts#L8)
21
- * [Expiry](https://github.com/travetto/travetto/tree/main/module/model/src/service/expiry.ts#L11)
19
+ * [CRUD](https://github.com/travetto/travetto/tree/main/module/model/src/types/crud.ts#L11)
20
+ * [Blob](https://github.com/travetto/travetto/tree/main/module/model/src/types/blob.ts#L8)
21
+ * [Expiry](https://github.com/travetto/travetto/tree/main/module/model/src/types/expiry.ts#L10)
22
22
  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.
23
23
 
24
24
  **Code: Wiring up a custom Model Source**
@@ -40,16 +40,6 @@ where the [S3ModelConfig](https://github.com/travetto/travetto/tree/main/module/
40
40
 
41
41
  **Code: Structure of S3ModelConfig**
42
42
  ```typescript
43
- import { fromIni } from '@aws-sdk/credential-provider-ini';
44
- import type s3 from '@aws-sdk/client-s3';
45
-
46
- import { Config, EnvVar } from '@travetto/config';
47
- import { Field, Required } from '@travetto/schema';
48
- import { Runtime } from '@travetto/runtime';
49
-
50
- /**
51
- * S3 Support as an Asset Source
52
- */
53
43
  @Config('model.s3')
54
44
  export class S3ModelConfig {
55
45
  region = 'us-east-1'; // AWS Region
package/__index__.ts CHANGED
@@ -1,2 +1,2 @@
1
- export * from './src/config';
2
- export * from './src/service';
1
+ export * from './src/config.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": "6.0.0-rc.0",
3
+ "version": "6.0.0-rc.2",
4
4
  "description": "S3 backing for the travetto model module.",
5
5
  "keywords": [
6
6
  "s3",
@@ -25,12 +25,12 @@
25
25
  "directory": "module/model-s3"
26
26
  },
27
27
  "dependencies": {
28
- "@aws-sdk/client-s3": "^3.731.1",
29
- "@aws-sdk/credential-provider-ini": "^3.713.0",
30
- "@aws-sdk/s3-request-presigner": "^3.731.1",
31
- "@travetto/cli": "^6.0.0-rc.0",
32
- "@travetto/config": "^6.0.0-rc.0",
33
- "@travetto/model": "^6.0.0-rc.0"
28
+ "@aws-sdk/client-s3": "^3.796.0",
29
+ "@aws-sdk/credential-provider-ini": "^3.796.0",
30
+ "@aws-sdk/s3-request-presigner": "^3.796.0",
31
+ "@travetto/cli": "^6.0.0-rc.2",
32
+ "@travetto/config": "^6.0.0-rc.2",
33
+ "@travetto/model": "^6.0.0-rc.2"
34
34
  },
35
35
  "travetto": {
36
36
  "displayName": "S3 Model Support"
package/src/service.ts CHANGED
@@ -9,18 +9,13 @@ import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
9
9
 
10
10
  import {
11
11
  ModelCrudSupport, ModelStorageSupport, ModelType, ModelRegistry, ExistsError, NotFoundError, OptionalId,
12
- ModelBlobSupport
12
+ ModelBlobSupport, ModelExpirySupport, ModelBlobUtil, ModelCrudUtil, ModelExpiryUtil, ModelStorageUtil,
13
+
13
14
  } from '@travetto/model';
14
15
  import { Injectable } from '@travetto/di';
15
16
  import { Class, AppError, castTo, asFull, BlobMeta, ByteRange, BinaryInput, BinaryUtil, TimeSpan, TimeUtil } from '@travetto/runtime';
16
17
 
17
- import { MODEL_BLOB, ModelBlobUtil } from '@travetto/model/src/internal/service/blob';
18
- import { ModelCrudUtil } from '@travetto/model/src/internal/service/crud';
19
- import { ModelExpirySupport } from '@travetto/model/src/service/expiry';
20
- import { ModelExpiryUtil } from '@travetto/model/src/internal/service/expiry';
21
- import { ModelStorageUtil } from '@travetto/model/src/internal/service/storage';
22
-
23
- import { S3ModelConfig } from './config';
18
+ import { S3ModelConfig } from './config.ts';
24
19
 
25
20
  function isMetadataBearer(o: unknown): o is MetadataBearer {
26
21
  return !!o && typeof o === 'object' && '$metadata' in o;
@@ -46,7 +41,7 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
46
41
 
47
42
  constructor(config: S3ModelConfig) { this.config = config; }
48
43
 
49
- #getMetaBase({ range, size, ...meta }: BlobMeta): MetaBase {
44
+ #getMetaBase({ range: _, size, ...meta }: BlobMeta): MetaBase {
50
45
  return {
51
46
  ContentType: meta.contentType,
52
47
  ...(meta.contentEncoding ? { ContentEncoding: meta.contentEncoding } : {}),
@@ -59,17 +54,7 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
59
54
  };
60
55
  }
61
56
 
62
- #resolveKey(cls: Class | string, id?: string): string {
63
- let key: string;
64
- if (cls === MODEL_BLOB) { // If we are streaming, treat as primary use case
65
- key = id!; // Store it directly at root
66
- } else {
67
- key = typeof cls === 'string' ? cls : ModelRegistry.getStore(cls);
68
- if (id) {
69
- key = `${key}:${id}`;
70
- }
71
- key = `_data/${key}`; // Separate data
72
- }
57
+ #basicKey(key: string): string {
73
58
  if (key?.startsWith('/')) {
74
59
  key = key.substring(1);
75
60
  }
@@ -79,11 +64,26 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
79
64
  return key;
80
65
  }
81
66
 
67
+ #resolveKey(cls: Class | string, id?: string): string {
68
+ let key = typeof cls === 'string' ? cls : ModelRegistry.getStore(cls);
69
+ if (id) {
70
+ key = `${key}:${id}`;
71
+ }
72
+ key = `_data/${key}`; // Separate data
73
+ return this.#basicKey(key);
74
+ }
75
+
82
76
  #q<U extends object>(cls: string | Class, id: string, extra: U = asFull({})): (U & { Key: string, Bucket: string }) {
83
77
  const key = this.#resolveKey(cls, id);
84
78
  return { Key: key, Bucket: this.config.bucket, ...extra };
85
79
  }
86
80
 
81
+ #qBlob<U extends object>(id: string, extra: U = asFull({})): (U & { Key: string, Bucket: string }) {
82
+ const key = this.#basicKey(id);
83
+ return { Key: key, Bucket: this.config.bucket, ...extra };
84
+ }
85
+
86
+
87
87
  #getExpiryConfig<T extends ModelType>(cls: Class<T>, item: T): { Expires?: Date } {
88
88
  if (ModelRegistry.get(cls).expiresAt) {
89
89
  const { expiresAt } = ModelExpiryUtil.getExpiryState(cls, item);
@@ -117,7 +117,7 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
117
117
  * Write multipart file upload, in chunks
118
118
  */
119
119
  async #writeMultipart(id: string, input: Readable, meta: BlobMeta): Promise<void> {
120
- const { UploadId } = await this.client.createMultipartUpload(this.#q(MODEL_BLOB, id, this.#getMetaBase(meta)));
120
+ const { UploadId } = await this.client.createMultipartUpload(this.#qBlob(id, this.#getMetaBase(meta)));
121
121
 
122
122
  const parts: CompletedPart[] = [];
123
123
  let buffers: Buffer[] = [];
@@ -125,7 +125,7 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
125
125
  let n = 1;
126
126
  const flush = async (): Promise<void> => {
127
127
  if (!total) { return; }
128
- const part = await this.client.uploadPart(this.#q(MODEL_BLOB, id, {
128
+ const part = await this.client.uploadPart(this.#qBlob(id, {
129
129
  Body: Buffer.concat(buffers),
130
130
  PartNumber: n,
131
131
  UploadId
@@ -146,12 +146,12 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
146
146
  }
147
147
  await flush();
148
148
 
149
- await this.client.completeMultipartUpload(this.#q(MODEL_BLOB, id, {
149
+ await this.client.completeMultipartUpload(this.#qBlob(id, {
150
150
  UploadId,
151
151
  MultipartUpload: { Parts: parts }
152
152
  }));
153
153
  } catch (err) {
154
- await this.client.abortMultipartUpload(this.#q(MODEL_BLOB, id, { UploadId }));
154
+ await this.client.abortMultipartUpload(this.#qBlob(id, { UploadId }));
155
155
  throw err;
156
156
  }
157
157
  }
@@ -196,9 +196,9 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
196
196
 
197
197
  async head<T extends ModelType>(cls: Class<T>, id: string): Promise<boolean> {
198
198
  try {
199
- const res = await this.client.headObject(this.#q(cls, id));
199
+ const result = await this.client.headObject(this.#q(cls, id));
200
200
  const { expiresAt } = ModelRegistry.get(cls);
201
- if (expiresAt && res.Expires && res.Expires.getTime() < Date.now()) {
201
+ if (expiresAt && result.Expires && result.Expires.getTime() < Date.now()) {
202
202
  return false;
203
203
  }
204
204
  return true;
@@ -308,7 +308,7 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
308
308
  }
309
309
 
310
310
  // Expiry
311
- async deleteExpired<T extends ModelType>(cls: Class<T>): Promise<number> {
311
+ async deleteExpired<T extends ModelType>(_cls: Class<T>): Promise<number> {
312
312
  return -1;
313
313
  }
314
314
 
@@ -322,7 +322,7 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
322
322
 
323
323
  if (blobMeta.size && blobMeta.size < this.config.chunkSize) { // If smaller than chunk size
324
324
  // Upload to s3
325
- await this.client.putObject(this.#q(MODEL_BLOB, location, {
325
+ await this.client.putObject(this.#qBlob(location, {
326
326
  Body: await toBuffer(stream),
327
327
  ContentLength: blobMeta.size,
328
328
  ...this.#getMetaBase(blobMeta),
@@ -334,39 +334,39 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
334
334
 
335
335
  async #getObject(location: string, range?: Required<ByteRange>): Promise<Readable> {
336
336
  // Read from s3
337
- const res = await this.client.getObject(this.#q(MODEL_BLOB, location, range ? {
337
+ const result = await this.client.getObject(this.#qBlob(location, range ? {
338
338
  Range: `bytes=${range.start}-${range.end}`
339
339
  } : {}));
340
340
 
341
- if (!res.Body) {
341
+ if (!result.Body) {
342
342
  throw new AppError('Unable to read type: undefined');
343
343
  }
344
344
 
345
- if (typeof res.Body === 'string') { // string
346
- return Readable.from(res.Body, { encoding: castTo<string>(res.Body).endsWith('=') ? 'base64' : 'utf8' });
347
- } else if (res.Body instanceof Buffer) { // Buffer
348
- return Readable.from(res.Body);
349
- } else if ('pipe' in res.Body) { // Stream
350
- return castTo<Readable>(res.Body);
345
+ if (typeof result.Body === 'string') { // string
346
+ return Readable.from(result.Body, { encoding: castTo<string>(result.Body).endsWith('=') ? 'base64' : 'utf8' });
347
+ } else if (result.Body instanceof Buffer) { // Buffer
348
+ return Readable.from(result.Body);
349
+ } else if ('pipe' in result.Body) { // Stream
350
+ return castTo<Readable>(result.Body);
351
351
  }
352
- throw new AppError(`Unable to read type: ${typeof res.Body}`);
352
+ throw new AppError(`Unable to read type: ${typeof result.Body}`);
353
353
  }
354
354
 
355
355
  async getBlob(location: string, range?: ByteRange): Promise<Blob> {
356
356
  const meta = await this.getBlobMeta(location);
357
357
  const final = range ? ModelBlobUtil.enforceRange(range, meta.size!) : undefined;
358
- const res = (): Promise<Readable> => this.#getObject(location, final);
359
- return BinaryUtil.readableBlob(res, { ...meta, range: final });
358
+ const result = (): Promise<Readable> => this.#getObject(location, final);
359
+ return BinaryUtil.readableBlob(result, { ...meta, range: final });
360
360
  }
361
361
 
362
362
  async headBlob(location: string): Promise<{ Metadata?: BlobMeta, ContentLength?: number }> {
363
- const query = this.#q(MODEL_BLOB, location);
363
+ const query = this.#qBlob(location);
364
364
  try {
365
365
  return (await this.client.headObject(query));
366
366
  } catch (err) {
367
367
  if (isMetadataBearer(err)) {
368
368
  if (err.$metadata.httpStatusCode === 404) {
369
- err = new NotFoundError(MODEL_BLOB, location);
369
+ err = new NotFoundError('Blob', location);
370
370
  }
371
371
  }
372
372
  throw err;
@@ -388,19 +388,19 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
388
388
  }
389
389
  return ret;
390
390
  } else {
391
- throw new NotFoundError(MODEL_BLOB, location);
391
+ throw new NotFoundError('Blob', location);
392
392
  }
393
393
  }
394
394
 
395
395
  async deleteBlob(location: string): Promise<void> {
396
- await this.client.deleteObject(this.#q(MODEL_BLOB, location));
396
+ await this.client.deleteObject(this.#qBlob(location));
397
397
  }
398
398
 
399
399
  async updateBlobMeta(location: string, meta: BlobMeta): Promise<void> {
400
400
  await this.client.copyObject({
401
401
  Bucket: this.config.bucket,
402
- Key: this.#resolveKey(MODEL_BLOB, location),
403
- CopySource: `/${this.config.bucket}/${this.#resolveKey(MODEL_BLOB, location)}`,
402
+ Key: this.#basicKey(location),
403
+ CopySource: `/${this.config.bucket}/${this.#basicKey(location)}`,
404
404
  ...this.#getMetaBase(meta),
405
405
  MetadataDirective: 'REPLACE'
406
406
  });
@@ -410,7 +410,7 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
410
410
  async getBlobReadUrl(location: string, exp: TimeSpan = '1h'): Promise<string> {
411
411
  return await getSignedUrl(
412
412
  this.client,
413
- new GetObjectCommand(this.#q(MODEL_BLOB, location)),
413
+ new GetObjectCommand(this.#qBlob(location)),
414
414
  { expiresIn: TimeUtil.asSeconds(exp) }
415
415
  );
416
416
  }
@@ -420,7 +420,7 @@ export class S3ModelService implements ModelCrudSupport, ModelBlobSupport, Model
420
420
  return await getSignedUrl(
421
421
  this.client,
422
422
  new PutObjectCommand({
423
- ...this.#q(MODEL_BLOB, location),
423
+ ...this.#qBlob(location),
424
424
  ...base,
425
425
  ...(meta.size ? { ContentLength: meta.size } : {}),
426
426
  ...((meta.hash && meta.hash !== '-1') ? { ChecksumSHA256: meta.hash } : {}),