@travetto/model-s3 4.1.4 → 5.0.0-rc.1

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.
Files changed (3) hide show
  1. package/LICENSE +1 -1
  2. package/package.json +6 -6
  3. package/src/service.ts +30 -26
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2020 ArcSine Technologies
3
+ Copyright (c) 2023 ArcSine Technologies
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/model-s3",
3
- "version": "4.1.4",
3
+ "version": "5.0.0-rc.1",
4
4
  "description": "S3 backing for the travetto model module.",
5
5
  "keywords": [
6
6
  "s3",
@@ -25,13 +25,13 @@
25
25
  "directory": "module/model-s3"
26
26
  },
27
27
  "dependencies": {
28
- "@aws-sdk/client-s3": "^3.600.0",
29
- "@aws-sdk/credential-provider-ini": "^3.592.0",
30
- "@travetto/config": "^4.1.2",
31
- "@travetto/model": "^4.1.3"
28
+ "@aws-sdk/client-s3": "^3.609.0",
29
+ "@aws-sdk/credential-provider-ini": "^3.609.0",
30
+ "@travetto/config": "^5.0.0-rc.1",
31
+ "@travetto/model": "^5.0.0-rc.1"
32
32
  },
33
33
  "peerDependencies": {
34
- "@travetto/command": "^4.1.1"
34
+ "@travetto/command": "^5.0.0-rc.1"
35
35
  },
36
36
  "peerDependenciesMeta": {
37
37
  "@travetto/command": {
package/src/service.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Readable } from 'node:stream';
2
+ import { buffer as toBuffer } from 'node:stream/consumers';
2
3
  import { Agent } from 'node:https';
3
4
 
4
5
  import { S3, CompletedPart, type CreateMultipartUploadRequest } from '@aws-sdk/client-s3';
@@ -7,10 +8,11 @@ import { NodeHttpHandler } from '@smithy/node-http-handler';
7
8
 
8
9
  import {
9
10
  ModelCrudSupport, ModelStreamSupport, ModelStorageSupport, StreamMeta,
10
- ModelType, ModelRegistry, ExistsError, NotFoundError, OptionalId, PartialStream
11
+ ModelType, ModelRegistry, ExistsError, NotFoundError, OptionalId,
12
+ StreamRange
11
13
  } from '@travetto/model';
12
14
  import { Injectable } from '@travetto/di';
13
- import { StreamUtil, Class, AppError } from '@travetto/base';
15
+ import { Class, AppError } from '@travetto/base';
14
16
 
15
17
  import { ModelCrudUtil } from '@travetto/model/src/internal/service/crud';
16
18
  import { ModelExpirySupport } from '@travetto/model/src/service/expiry';
@@ -18,6 +20,7 @@ import { ModelExpiryUtil } from '@travetto/model/src/internal/service/expiry';
18
20
  import { ModelStorageUtil } from '@travetto/model/src/internal/service/storage';
19
21
 
20
22
  import { S3ModelConfig } from './config';
23
+ import { enforceRange } from '@travetto/model/src/internal/service/stream';
21
24
 
22
25
  function isMetadataBearer(o: unknown): o is MetadataBearer {
23
26
  return !!o && typeof o === 'object' && '$metadata' in o;
@@ -201,7 +204,7 @@ export class S3ModelService implements ModelCrudSupport, ModelStreamSupport, Mod
201
204
  const result = await this.client.getObject(this.#q(cls, id));
202
205
  if (result.Body) {
203
206
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
204
- const body = (await StreamUtil.streamToBuffer(result.Body as Readable)).toString('utf8');
207
+ const body = (await toBuffer(result.Body as Readable)).toString('utf8');
205
208
  const output = await ModelCrudUtil.load(cls, body);
206
209
  if (output) {
207
210
  const { expiresAt } = ModelRegistry.get(cls);
@@ -300,7 +303,7 @@ export class S3ModelService implements ModelCrudSupport, ModelStreamSupport, Mod
300
303
  if (meta.size < this.config.chunkSize) { // If smaller than chunk size
301
304
  // Upload to s3
302
305
  await this.client.putObject(this.#q(STREAM_SPACE, location, {
303
- Body: await StreamUtil.toBuffer(input),
306
+ Body: await toBuffer(input),
304
307
  ContentLength: meta.size,
305
308
  ...this.#getMetaBase(meta),
306
309
  }));
@@ -309,34 +312,35 @@ export class S3ModelService implements ModelCrudSupport, ModelStreamSupport, Mod
309
312
  }
310
313
  }
311
314
 
312
- async getStream(location: string): Promise<Readable> {
315
+ async #getObject(location: string, range: Required<StreamRange>): Promise<Readable> {
313
316
  // Read from s3
314
- const res = await this.client.getObject(this.#q(STREAM_SPACE, location));
315
- if (res.Body instanceof Buffer || // Buffer
316
- typeof res.Body === 'string' || // string
317
- res.Body && ('pipe' in res.Body) // Stream
318
- ) {
319
- return StreamUtil.toStream(res.Body);
317
+ const res = await this.client.getObject(this.#q(STREAM_SPACE, location, range ? {
318
+ Range: `bytes=${range.start}-${range.end}`
319
+ } : {}));
320
+
321
+ if (!res.Body) {
322
+ throw new AppError('Unable to read type: undefined');
323
+ }
324
+
325
+ if (typeof res.Body === 'string') { // string
326
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
327
+ return Readable.from(res.Body, { encoding: (res.Body as string).endsWith('=') ? 'base64' : 'utf8' });
328
+ } else if (res.Body instanceof Buffer) { // Buffer
329
+ return Readable.from(res.Body);
330
+ } else if ('pipe' in res.Body) { // Stream
331
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
332
+ return res.Body as Readable;
320
333
  }
321
334
  throw new AppError(`Unable to read type: ${typeof res.Body}`);
322
335
  }
323
336
 
324
- async getStreamPartial(location: string, start: number, end?: number): Promise<PartialStream> {
325
- const meta = await this.describeStream(location);
326
-
327
- [start, end] = StreamUtil.enforceRange(start, end, meta.size);
328
-
329
- // Read from s3
330
- const res = await this.client.getObject(this.#q(STREAM_SPACE, location, {
331
- Range: `bytes=${start}-${end}`
332
- }));
333
- if (res.Body instanceof Buffer || // Buffer
334
- typeof res.Body === 'string' || // string
335
- res.Body && ('pipe' in res.Body) // Stream
336
- ) {
337
- return { stream: await StreamUtil.toStream(res.Body), range: [start, end] };
337
+ async getStream(location: string, range?: StreamRange): Promise<Readable> {
338
+ if (range) {
339
+ const meta = await this.describeStream(location);
340
+ range = enforceRange(range, meta.size);
338
341
  }
339
- throw new AppError(`Unable to read type: ${typeof res.Body}`);
342
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
343
+ return this.#getObject(location, range as Required<StreamRange>);
340
344
  }
341
345
 
342
346
  async headStream(location: string): Promise<{ Metadata?: Partial<StreamMeta>, ContentLength?: number }> {