@travetto/model-s3 8.0.0-alpha.2 → 8.0.0-alpha.20

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
@@ -65,11 +65,10 @@ export class S3ModelConfig {
65
65
  modifyStorage?: boolean;
66
66
 
67
67
  /**
68
- * Provide host to bucket
68
+ * Provide base URL for public access
69
69
  */
70
- get hostName(): string {
71
- return `${this.bucket}.s3.amazonaws.com`;
72
- }
70
+ @Required(false)
71
+ publicBaseUrl: string;
73
72
 
74
73
  /**
75
74
  * Produces the s3 config from the provide details, post construction
@@ -81,6 +80,26 @@ export class S3ModelConfig {
81
80
  this.bucket ??= 'app';
82
81
  }
83
82
 
83
+ if (!this.publicBaseUrl) {
84
+ if (this.endpoint) {
85
+ if (this.endpoint.includes('localhost')) {
86
+ this.publicBaseUrl = this.endpoint;
87
+ } else {
88
+ try {
89
+ this.publicBaseUrl = new URL(this.endpoint).origin;
90
+ } catch {
91
+ this.publicBaseUrl = this.endpoint;
92
+ }
93
+ }
94
+ } else {
95
+ this.publicBaseUrl = `https://${this.bucket}.s3.amazonaws.com`;
96
+ }
97
+ }
98
+
99
+ if (this.publicBaseUrl && !this.publicBaseUrl.includes('://')) {
100
+ this.publicBaseUrl = `https://${this.publicBaseUrl}`;
101
+ }
102
+
84
103
  if (!this.accessKeyId && !this.secretAccessKey) {
85
104
  const creds = await fromIni({ profile: this.profile })();
86
105
  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.2",
3
+ "version": "8.0.0-alpha.20",
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.1004.0",
30
- "@aws-sdk/credential-provider-ini": "^3.972.17",
31
- "@aws-sdk/s3-request-presigner": "^3.1004.0",
32
- "@travetto/config": "^8.0.0-alpha.2",
33
- "@travetto/model": "^8.0.0-alpha.2"
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.3"
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 host to bucket
36
+ * Provide base URL for public access
37
37
  */
38
- get hostName(): string {
39
- return `${this.bucket}.s3.amazonaws.com`;
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 {
@@ -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
- for (; ;) {
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
- if (items.Contents?.length) {
105
- yield items.Contents.map(item => ({ Key: item.Key!, id: item.Key!.split(':').pop()! }));
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>): AsyncIterable<T> {
297
- for await (const batch of this.#iterateBucket(cls)) {
298
- for (const { id } of batch) {
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
 
@@ -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)),
@@ -1,6 +1,6 @@
1
1
  import type { ServiceDescriptor } from '@travetto/cli';
2
2
 
3
- const version = '4.11.0';
3
+ const version = '4.12.4';
4
4
 
5
5
  export const service: ServiceDescriptor = {
6
6
  name: 's3',