@travetto/model-s3 2.1.5 → 2.2.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
@@ -51,8 +51,8 @@ export class S3ModelConfig {
51
51
  bucket = ''; // S3 bucket
52
52
  endpoint = ''; // Endpoint url
53
53
 
54
- accessKeyId = EnvUtil.get('AWS_ACCESS_KEY_ID', '');
55
- secretAccessKey = EnvUtil.get('AWS_SECRET_ACCESS_KEY', '');
54
+ accessKeyId: string = EnvUtil.get('AWS_ACCESS_KEY_ID', '');
55
+ secretAccessKey: string = EnvUtil.get('AWS_SECRET_ACCESS_KEY', '');
56
56
 
57
57
  @Field(Object)
58
58
  @Required(false)
@@ -65,14 +65,14 @@ export class S3ModelConfig {
65
65
  /**
66
66
  * Provide host to bucket
67
67
  */
68
- get hostName() {
68
+ get hostName(): string {
69
69
  return `${this.bucket}.s3.amazonaws.com`;
70
70
  }
71
71
 
72
72
  /**
73
73
  * Produces the s3 config from the provide details, post construction
74
74
  */
75
- async postConstruct() {
75
+ async postConstruct(): Promise<void> {
76
76
  if (!this.accessKeyId && !this.secretAccessKey) {
77
77
  const creds = await fromIni({ profile: EnvUtil.get('AWS_PROFILE') })();
78
78
  this.accessKeyId = creds.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.1.5",
4
+ "version": "2.2.2",
5
5
  "description": "S3 backing for the travetto model module.",
6
6
  "keywords": [
7
7
  "s3",
@@ -27,8 +27,8 @@
27
27
  "dependencies": {
28
28
  "@aws-sdk/client-s3": "^3.131.0",
29
29
  "@aws-sdk/credential-provider-ini": "^3.131.0",
30
- "@travetto/config": "^2.1.5",
31
- "@travetto/model": "^2.1.5"
30
+ "@travetto/config": "^2.2.2",
31
+ "@travetto/model": "^2.2.2"
32
32
  },
33
33
  "publishConfig": {
34
34
  "access": "public"
package/src/config.ts CHANGED
@@ -15,8 +15,8 @@ export class S3ModelConfig {
15
15
  bucket = ''; // S3 bucket
16
16
  endpoint = ''; // Endpoint url
17
17
 
18
- accessKeyId = EnvUtil.get('AWS_ACCESS_KEY_ID', '');
19
- secretAccessKey = EnvUtil.get('AWS_SECRET_ACCESS_KEY', '');
18
+ accessKeyId: string = EnvUtil.get('AWS_ACCESS_KEY_ID', '');
19
+ secretAccessKey: string = EnvUtil.get('AWS_SECRET_ACCESS_KEY', '');
20
20
 
21
21
  @Field(Object)
22
22
  @Required(false)
@@ -29,14 +29,14 @@ export class S3ModelConfig {
29
29
  /**
30
30
  * Provide host to bucket
31
31
  */
32
- get hostName() {
32
+ get hostName(): string {
33
33
  return `${this.bucket}.s3.amazonaws.com`;
34
34
  }
35
35
 
36
36
  /**
37
37
  * Produces the s3 config from the provide details, post construction
38
38
  */
39
- async postConstruct() {
39
+ async postConstruct(): Promise<void> {
40
40
  if (!this.accessKeyId && !this.secretAccessKey) {
41
41
  const creds = await fromIni({ profile: EnvUtil.get('AWS_PROFILE') })();
42
42
  this.accessKeyId = creds.accessKeyId;
package/src/service.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { Readable } from 'stream';
2
+
1
3
  import * as s3 from '@aws-sdk/client-s3';
2
4
  import type { MetadataBearer } from '@aws-sdk/types';
3
5
 
@@ -16,10 +18,10 @@ import { ModelExpiryUtil } from '@travetto/model/src/internal/service/expiry';
16
18
  import { S3ModelConfig } from './config';
17
19
 
18
20
  function isMetadataBearer(o: unknown): o is MetadataBearer {
19
- return !!o && '$metadata' in (o as object);
21
+ return !!o && typeof o === 'object' && '$metadata' in o;
20
22
  }
21
23
 
22
- function hasContenttype<T>(o: T): o is T & { contenttype?: string } {
24
+ function hasContentType<T>(o: T): o is T & { contenttype?: string } {
23
25
  return o && 'contenttype' in o;
24
26
  }
25
27
 
@@ -35,7 +37,7 @@ export class S3ModelService implements ModelCrudSupport, ModelStreamSupport, Mod
35
37
 
36
38
  constructor(public readonly config: S3ModelConfig) { }
37
39
 
38
- #resolveKey(cls: Class | string, id?: string) {
40
+ #resolveKey(cls: Class | string, id?: string): string {
39
41
  let key: string;
40
42
  if (cls === STREAM_SPACE) { // If we are streaming, treat as primary use case
41
43
  key = id!; // Store it directly at root
@@ -52,14 +54,15 @@ export class S3ModelService implements ModelCrudSupport, ModelStreamSupport, Mod
52
54
  return key;
53
55
  }
54
56
 
55
- #q<U extends object>(cls: string | Class, id: string, extra: U = {} as U) {
57
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
58
+ #q<U extends object>(cls: string | Class, id: string, extra: U = {} as U): (U & { Key: string, Bucket: string }) {
56
59
  const key = this.#resolveKey(cls, id);
57
- return { Key: key, Bucket: this.config.bucket, ...extra } as (U & { Key: string, Bucket: string });
60
+ return { Key: key, Bucket: this.config.bucket, ...extra };
58
61
  }
59
62
 
60
- #getExpiryConfig<T extends ModelType>(cls: Class<T>, item: T) {
63
+ #getExpiryConfig<T extends ModelType>(cls: Class<T>, item: T): { Expires?: Date } {
61
64
  if (ModelRegistry.get(cls).expiresAt) {
62
- const { expiresAt } = ModelExpiryUtil.getExpiryState(cls, item as T);
65
+ const { expiresAt } = ModelExpiryUtil.getExpiryState(cls, item);
63
66
  if (expiresAt) {
64
67
  return { Expires: expiresAt };
65
68
  }
@@ -67,7 +70,7 @@ export class S3ModelService implements ModelCrudSupport, ModelStreamSupport, Mod
67
70
  return {};
68
71
  }
69
72
 
70
- async * #iterateBucket(cls?: string | Class) {
73
+ async * #iterateBucket(cls?: string | Class): AsyncIterable<{ Key: string, id: string }[]> {
71
74
  let Marker: string | undefined;
72
75
  for (; ;) {
73
76
  const obs = await this.client.listObjects({ Bucket: this.config.bucket, Prefix: cls ? this.#resolveKey(cls) : undefined, Marker });
@@ -85,7 +88,7 @@ export class S3ModelService implements ModelCrudSupport, ModelStreamSupport, Mod
85
88
  /**
86
89
  * Write multipart file upload, in chunks
87
90
  */
88
- async #writeMultipart(id: string, input: NodeJS.ReadableStream, meta: StreamMeta): Promise<void> {
91
+ async #writeMultipart(id: string, input: Readable, meta: StreamMeta): Promise<void> {
89
92
  const { UploadId } = await this.client.createMultipartUpload(this.#q(STREAM_SPACE, id, {
90
93
  ContentType: meta.contentType,
91
94
  ContentLength: meta.size,
@@ -95,7 +98,7 @@ export class S3ModelService implements ModelCrudSupport, ModelStreamSupport, Mod
95
98
  let buffers: Buffer[] = [];
96
99
  let total = 0;
97
100
  let n = 1;
98
- const flush = async () => {
101
+ const flush = async (): Promise<void> => {
99
102
  if (!total) { return; }
100
103
  const part = await this.client.uploadPart(this.#q(STREAM_SPACE, id, {
101
104
  Body: Buffer.concat(buffers),
@@ -121,13 +124,13 @@ export class S3ModelService implements ModelCrudSupport, ModelStreamSupport, Mod
121
124
  UploadId,
122
125
  MultipartUpload: { Parts: parts }
123
126
  }));
124
- } catch (e) {
127
+ } catch (err) {
125
128
  await this.client.abortMultipartUpload(this.#q(STREAM_SPACE, id, { UploadId }));
126
- throw e;
129
+ throw err;
127
130
  }
128
131
  }
129
132
 
130
- async #deleteKeys(items: { Key: string }[]) {
133
+ async #deleteKeys(items: { Key: string }[]): Promise<void> {
131
134
  if (this.config.endpoint.includes('localhost')) {
132
135
  await Promise.all(items.map(item => this.client.deleteObject({
133
136
  Bucket: this.config.bucket,
@@ -143,15 +146,15 @@ export class S3ModelService implements ModelCrudSupport, ModelStreamSupport, Mod
143
146
  }
144
147
  }
145
148
 
146
- uuid() {
149
+ uuid(): string {
147
150
  return Util.uuid(32);
148
151
  }
149
152
 
150
- async postConstruct() {
153
+ async postConstruct(): Promise<void> {
151
154
  this.client = new s3.S3(this.config.config);
152
155
  }
153
156
 
154
- async head<T extends ModelType>(cls: Class<T>, id: string) {
157
+ async head<T extends ModelType>(cls: Class<T>, id: string): Promise<boolean> {
155
158
  try {
156
159
  const res = await this.client.headObject(this.#q(cls, id));
157
160
  const { expiresAt } = ModelRegistry.get(cls);
@@ -159,17 +162,17 @@ export class S3ModelService implements ModelCrudSupport, ModelStreamSupport, Mod
159
162
  return false;
160
163
  }
161
164
  return true;
162
- } catch (e) {
163
- if (isMetadataBearer(e)) {
164
- if (e.$metadata.httpStatusCode === 404) {
165
+ } catch (err) {
166
+ if (isMetadataBearer(err)) {
167
+ if (err.$metadata.httpStatusCode === 404) {
165
168
  return false;
166
169
  }
167
170
  }
168
- throw e;
171
+ throw err;
169
172
  }
170
173
  }
171
174
 
172
- async get<T extends ModelType>(cls: Class<T>, id: string) {
175
+ async get<T extends ModelType>(cls: Class<T>, id: string): Promise<T> {
173
176
  try {
174
177
  const result = await this.client.getObject(this.#q(cls, id));
175
178
  if (result.Body) {
@@ -188,17 +191,18 @@ export class S3ModelService implements ModelCrudSupport, ModelStreamSupport, Mod
188
191
  }
189
192
  }
190
193
  throw new NotFoundError(cls, id);
191
- } catch (e) {
192
- if (isMetadataBearer(e)) {
193
- if (e.$metadata.httpStatusCode === 404) {
194
- e = new NotFoundError(cls, id);
194
+ } catch (err) {
195
+ if (isMetadataBearer(err)) {
196
+ if (err.$metadata.httpStatusCode === 404) {
197
+ err = new NotFoundError(cls, id);
195
198
  }
196
199
  }
197
- throw e;
200
+ throw err;
198
201
  }
199
202
  }
200
203
 
201
- async store<T extends ModelType>(cls: Class<T>, item: OptionalId<T>, preStore = true) {
204
+ async store<T extends ModelType>(cls: Class<T>, item: OptionalId<T>, preStore = true): Promise<T> {
205
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
202
206
  let prepped: T = item as T;
203
207
  if (preStore) {
204
208
  prepped = await ModelCrudUtil.preStore(cls, item, this);
@@ -211,7 +215,7 @@ export class S3ModelService implements ModelCrudSupport, ModelStreamSupport, Mod
211
215
  return prepped;
212
216
  }
213
217
 
214
- async create<T extends ModelType>(cls: Class<T>, item: OptionalId<T>) {
218
+ async create<T extends ModelType>(cls: Class<T>, item: OptionalId<T>): Promise<T> {
215
219
  if (item.id) {
216
220
  if (await this.head(cls, item.id)) {
217
221
  throw new ExistsError(cls, item.id);
@@ -220,7 +224,7 @@ export class S3ModelService implements ModelCrudSupport, ModelStreamSupport, Mod
220
224
  return this.store(cls, item);
221
225
  }
222
226
 
223
- async update<T extends ModelType>(cls: Class<T>, item: T) {
227
+ async update<T extends ModelType>(cls: Class<T>, item: T): Promise<T> {
224
228
  ModelCrudUtil.ensureNotSubType(cls);
225
229
  if (!(await this.head(cls, item.id))) {
226
230
  throw new NotFoundError(cls, item.id);
@@ -228,19 +232,19 @@ export class S3ModelService implements ModelCrudSupport, ModelStreamSupport, Mod
228
232
  return this.store(cls, item);
229
233
  }
230
234
 
231
- async upsert<T extends ModelType>(cls: Class<T>, item: OptionalId<T>) {
235
+ async upsert<T extends ModelType>(cls: Class<T>, item: OptionalId<T>): Promise<T> {
232
236
  ModelCrudUtil.ensureNotSubType(cls);
233
237
  return this.store(cls, item);
234
238
  }
235
239
 
236
- async updatePartial<T extends ModelType>(cls: Class<T>, item: Partial<T> & { id: string }, view?: string) {
240
+ async updatePartial<T extends ModelType>(cls: Class<T>, item: Partial<T> & { id: string }, view?: string): Promise<T> {
237
241
  ModelCrudUtil.ensureNotSubType(cls);
238
242
  const id = item.id;
239
- item = await ModelCrudUtil.naivePartialUpdate(cls, item, view, () => this.get(cls, id)) as T;
240
- return this.store<T>(cls, item as T, false);
243
+ const prepped = await ModelCrudUtil.naivePartialUpdate(cls, item, view, (): Promise<T> => this.get(cls, id));
244
+ return this.store<T>(cls, prepped, false);
241
245
  }
242
246
 
243
- async delete<T extends ModelType>(cls: Class<T>, id: string) {
247
+ async delete<T extends ModelType>(cls: Class<T>, id: string): Promise<void> {
244
248
  ModelCrudUtil.ensureNotSubType(cls);
245
249
  if (!(await this.head(cls, id))) {
246
250
  throw new NotFoundError(cls, id);
@@ -248,14 +252,14 @@ export class S3ModelService implements ModelCrudSupport, ModelStreamSupport, Mod
248
252
  await this.client.deleteObject(this.#q(cls, id));
249
253
  }
250
254
 
251
- async * list<T extends ModelType>(cls: Class<T>) {
255
+ async * list<T extends ModelType>(cls: Class<T>): AsyncIterable<T> {
252
256
  for await (const batch of this.#iterateBucket(cls)) {
253
257
  for (const { id } of batch) {
254
258
  try {
255
259
  yield await this.get(cls, id);
256
- } catch (e) {
257
- if (!(e instanceof NotFoundError)) {
258
- throw e;
260
+ } catch (err) {
261
+ if (!(err instanceof NotFoundError)) {
262
+ throw err;
259
263
  }
260
264
  }
261
265
  }
@@ -267,7 +271,7 @@ export class S3ModelService implements ModelCrudSupport, ModelStreamSupport, Mod
267
271
  return -1;
268
272
  }
269
273
 
270
- async upsertStream(location: string, input: NodeJS.ReadableStream, meta: StreamMeta) {
274
+ async upsertStream(location: string, input: Readable, meta: StreamMeta): Promise<void> {
271
275
  if (meta.size < this.config.chunkSize) { // If bigger than 5 mb
272
276
  // Upload to s3
273
277
  await this.client.putObject(this.#q(STREAM_SPACE, location, {
@@ -284,7 +288,7 @@ export class S3ModelService implements ModelCrudSupport, ModelStreamSupport, Mod
284
288
  }
285
289
  }
286
290
 
287
- async getStream(location: string) {
291
+ async getStream(location: string): Promise<Readable> {
288
292
  // Read from s3
289
293
  const res = await this.client.getObject(this.#q(STREAM_SPACE, location));
290
294
  if (res.Body instanceof Buffer || // Buffer
@@ -296,29 +300,30 @@ export class S3ModelService implements ModelCrudSupport, ModelStreamSupport, Mod
296
300
  throw new AppError(`Unable to read type: ${typeof res.Body}`);
297
301
  }
298
302
 
299
- async headStream(location: string) {
303
+ async headStream(location: string): Promise<{ Metadata?: Partial<StreamMeta>, ContentLength?: number }> {
300
304
  const query = this.#q(STREAM_SPACE, location);
301
305
  try {
302
- return await this.client.headObject(query);
303
- } catch (e) {
304
- if (isMetadataBearer(e)) {
305
- if (e.$metadata.httpStatusCode === 404) {
306
- e = new NotFoundError(STREAM_SPACE, location);
306
+ return (await this.client.headObject(query));
307
+ } catch (err) {
308
+ if (isMetadataBearer(err)) {
309
+ if (err.$metadata.httpStatusCode === 404) {
310
+ err = new NotFoundError(STREAM_SPACE, location);
307
311
  }
308
312
  }
309
- throw e;
313
+ throw err;
310
314
  }
311
315
  }
312
316
 
313
- async describeStream(location: string) {
317
+ async describeStream(location: string): Promise<StreamMeta> {
314
318
  const obj = await this.headStream(location);
315
319
 
316
320
  if (obj) {
317
- const ret = {
318
- ...obj.Metadata,
321
+ const ret: StreamMeta = {
322
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
323
+ ...obj.Metadata as StreamMeta,
319
324
  size: obj.ContentLength!,
320
- } as StreamMeta;
321
- if (hasContenttype(ret)) {
325
+ };
326
+ if (hasContentType(ret)) {
322
327
  ret['contentType'] = ret['contenttype']!;
323
328
  delete ret['contenttype'];
324
329
  }
@@ -328,25 +333,25 @@ export class S3ModelService implements ModelCrudSupport, ModelStreamSupport, Mod
328
333
  }
329
334
  }
330
335
 
331
- async truncateModel<T extends ModelType>(model: Class<T>) {
336
+ async truncateModel<T extends ModelType>(model: Class<T>): Promise<void> {
332
337
  for await (const items of this.#iterateBucket(model)) {
333
338
  await this.#deleteKeys(items);
334
339
  }
335
340
  }
336
341
 
337
- async deleteStream(location: string) {
342
+ async deleteStream(location: string): Promise<void> {
338
343
  await this.client.deleteObject(this.#q(STREAM_SPACE, location));
339
344
  }
340
345
 
341
- async createStorage() {
346
+ async createStorage(): Promise<void> {
342
347
  try {
343
348
  await this.client.headBucket({ Bucket: this.config.bucket });
344
- } catch (e) {
349
+ } catch {
345
350
  await this.client.createBucket({ Bucket: this.config.bucket });
346
351
  }
347
352
  }
348
353
 
349
- async deleteStorage() {
354
+ async deleteStorage(): Promise<void> {
350
355
  if (this.config.namespace) {
351
356
  for await (const items of this.#iterateBucket('')) {
352
357
  await this.#deleteKeys(items);