@constructive-io/s3-streamer 2.11.0 → 2.14.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/esm/streamer.js CHANGED
@@ -1,5 +1,6 @@
1
+ import { streamContentType } from '@constructive-io/content-type-stream';
1
2
  import getS3 from './s3';
2
- import { upload as streamUpload } from './utils';
3
+ import { upload as streamUpload, uploadWithContentType as streamUploadWithContentType, } from './utils';
3
4
  export class Streamer {
4
5
  s3;
5
6
  defaultBucket;
@@ -25,6 +26,22 @@ export class Streamer {
25
26
  bucket
26
27
  });
27
28
  }
29
+ async uploadWithContentType({ readStream, contentType, key, bucket = this.defaultBucket, magic, }) {
30
+ if (!bucket) {
31
+ throw new Error('Bucket is required');
32
+ }
33
+ return await streamUploadWithContentType({
34
+ client: this.s3,
35
+ readStream,
36
+ contentType,
37
+ key,
38
+ bucket,
39
+ magic,
40
+ });
41
+ }
42
+ async detectContentType({ readStream, filename, peekBytes, }) {
43
+ return streamContentType({ readStream, filename, peekBytes });
44
+ }
28
45
  destroy() {
29
46
  this.s3.destroy();
30
47
  }
package/esm/utils.js CHANGED
@@ -28,8 +28,25 @@ export const uploadFromStream = ({ client, key, contentType, bucket }) => {
28
28
  });
29
29
  return pass;
30
30
  };
31
+ const UPLOAD_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
31
32
  export const asyncUpload = ({ client, key, contentType, readStream, magic, bucket }) => {
32
33
  return new Promise((resolve, reject) => {
34
+ let settled = false;
35
+ const settle = (fn) => {
36
+ if (settled)
37
+ return;
38
+ settled = true;
39
+ clearTimeout(timer);
40
+ fn();
41
+ };
42
+ const timer = setTimeout(() => {
43
+ settle(() => {
44
+ readStream.destroy();
45
+ uploadStream.destroy();
46
+ contentStream.destroy();
47
+ reject(new Error(`Upload timed out after ${UPLOAD_TIMEOUT_MS / 1000}s for key=${key}`));
48
+ });
49
+ }, UPLOAD_TIMEOUT_MS);
33
50
  // upload stream
34
51
  let upload;
35
52
  const uploadStream = uploadFromStream({
@@ -43,21 +60,34 @@ export const asyncUpload = ({ client, key, contentType, readStream, magic, bucke
43
60
  const contentStream = new ContentStream();
44
61
  const tryResolve = () => {
45
62
  if (contents && upload) {
46
- resolve({
47
- upload,
48
- magic,
49
- contentType,
50
- contents
63
+ settle(() => {
64
+ resolve({
65
+ upload: upload,
66
+ magic,
67
+ contentType,
68
+ contents
69
+ });
51
70
  });
52
71
  }
53
72
  };
73
+ readStream.on('error', (error) => {
74
+ settle(() => {
75
+ uploadStream.destroy();
76
+ contentStream.destroy();
77
+ reject(error);
78
+ });
79
+ });
54
80
  contentStream
55
81
  .on('contents', function (results) {
56
82
  contents = results;
57
83
  tryResolve();
58
84
  })
59
85
  .on('error', (error) => {
60
- reject(error);
86
+ settle(() => {
87
+ readStream.destroy();
88
+ uploadStream.destroy();
89
+ reject(error);
90
+ });
61
91
  });
62
92
  uploadStream
63
93
  .on('upload', (results) => {
@@ -65,7 +95,11 @@ export const asyncUpload = ({ client, key, contentType, readStream, magic, bucke
65
95
  tryResolve();
66
96
  })
67
97
  .on('error', (error) => {
68
- reject(error);
98
+ settle(() => {
99
+ readStream.destroy();
100
+ contentStream.destroy();
101
+ reject(error);
102
+ });
69
103
  });
70
104
  // Ensure proper cleanup on stream end
71
105
  uploadStream.on('finish', () => {
@@ -80,12 +114,22 @@ export const upload = async ({ client, readStream, filename, key, bucket }) => {
80
114
  readStream,
81
115
  filename
82
116
  });
83
- return await asyncUpload({
117
+ return await uploadWithContentType({
84
118
  client,
85
- key,
86
- contentType,
87
119
  readStream: newStream,
120
+ contentType,
88
121
  magic,
122
+ key,
89
123
  bucket
90
124
  });
91
125
  };
126
+ export const uploadWithContentType = async ({ client, readStream, contentType, key, bucket, magic }) => {
127
+ return await asyncUpload({
128
+ client,
129
+ readStream,
130
+ contentType,
131
+ magic: magic ?? { charset: 'binary' },
132
+ key,
133
+ bucket,
134
+ });
135
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constructive-io/s3-streamer",
3
- "version": "2.11.0",
3
+ "version": "2.14.0",
4
4
  "author": "Constructive <developers@constructive.io>",
5
5
  "description": "stream files to s3",
6
6
  "main": "index.js",
@@ -25,20 +25,20 @@
25
25
  "build": "makage build",
26
26
  "build:dev": "makage build --dev",
27
27
  "lint": "eslint . --fix",
28
- "test": "jest --passWithNoTests",
28
+ "test": "jest",
29
29
  "test:watch": "jest --watch"
30
30
  },
31
31
  "devDependencies": {
32
- "@constructive-io/s3-utils": "^2.6.0",
33
- "@pgpmjs/env": "^2.11.0",
32
+ "@constructive-io/s3-utils": "^2.7.0",
33
+ "@pgpmjs/env": "^2.13.0",
34
34
  "glob": "^13.0.0",
35
35
  "makage": "^0.1.12"
36
36
  },
37
37
  "dependencies": {
38
38
  "@aws-sdk/client-s3": "^3.971.0",
39
39
  "@aws-sdk/lib-storage": "^3.958.0",
40
- "@constructive-io/content-type-stream": "^2.6.0",
41
- "@pgpmjs/types": "^2.16.0"
40
+ "@constructive-io/content-type-stream": "^2.8.0",
41
+ "@pgpmjs/types": "^2.17.0"
42
42
  },
43
43
  "keywords": [
44
44
  "s3",
@@ -48,5 +48,5 @@
48
48
  "minio",
49
49
  "constructive"
50
50
  ],
51
- "gitHead": "048188f6b43ffaa6146e7694b2b0d35d34cb2945"
51
+ "gitHead": "b758178b808ce0bf451e86c0bd7e92079155db7c"
52
52
  }
package/streamer.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { ReadStream } from 'fs';
1
+ import { streamContentType } from '@constructive-io/content-type-stream';
2
2
  import type { BucketProvider } from '@pgpmjs/types';
3
+ import type { Readable } from 'stream';
3
4
  import { type AsyncUploadResult } from './utils';
4
5
  interface StreamerOptions {
5
6
  awsRegion: string;
@@ -10,16 +11,32 @@ interface StreamerOptions {
10
11
  defaultBucket: string;
11
12
  }
12
13
  interface UploadParams {
13
- readStream: ReadStream;
14
+ readStream: Readable;
14
15
  filename: string;
15
16
  key: string;
16
17
  bucket?: string;
17
18
  }
19
+ interface UploadWithContentTypeParams {
20
+ readStream: Readable;
21
+ contentType: string;
22
+ key: string;
23
+ bucket?: string;
24
+ magic?: {
25
+ charset: string;
26
+ type?: string;
27
+ };
28
+ }
18
29
  export declare class Streamer {
19
30
  private s3;
20
31
  private defaultBucket?;
21
32
  constructor({ awsRegion, awsSecretKey, awsAccessKey, minioEndpoint, provider, defaultBucket }: StreamerOptions);
22
33
  upload({ readStream, filename, key, bucket }: UploadParams): Promise<AsyncUploadResult>;
34
+ uploadWithContentType({ readStream, contentType, key, bucket, magic, }: UploadWithContentTypeParams): Promise<AsyncUploadResult>;
35
+ detectContentType({ readStream, filename, peekBytes, }: {
36
+ readStream: Readable;
37
+ filename: string;
38
+ peekBytes?: number;
39
+ }): ReturnType<typeof streamContentType>;
23
40
  destroy(): void;
24
41
  }
25
42
  export default Streamer;
package/streamer.js CHANGED
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.Streamer = void 0;
7
+ const content_type_stream_1 = require("@constructive-io/content-type-stream");
7
8
  const s3_1 = __importDefault(require("./s3"));
8
9
  const utils_1 = require("./utils");
9
10
  class Streamer {
@@ -31,6 +32,22 @@ class Streamer {
31
32
  bucket
32
33
  });
33
34
  }
35
+ async uploadWithContentType({ readStream, contentType, key, bucket = this.defaultBucket, magic, }) {
36
+ if (!bucket) {
37
+ throw new Error('Bucket is required');
38
+ }
39
+ return await (0, utils_1.uploadWithContentType)({
40
+ client: this.s3,
41
+ readStream,
42
+ contentType,
43
+ key,
44
+ bucket,
45
+ magic,
46
+ });
47
+ }
48
+ async detectContentType({ readStream, filename, peekBytes, }) {
49
+ return (0, content_type_stream_1.streamContentType)({ readStream, filename, peekBytes });
50
+ }
34
51
  destroy() {
35
52
  this.s3.destroy();
36
53
  }
package/utils.d.ts CHANGED
@@ -10,6 +10,7 @@ export interface AsyncUploadParams extends UploadParams {
10
10
  readStream: Readable;
11
11
  magic: {
12
12
  charset: string;
13
+ type?: string;
13
14
  };
14
15
  }
15
16
  export interface UploadWithFilenameParams {
@@ -19,6 +20,17 @@ export interface UploadWithFilenameParams {
19
20
  key: string;
20
21
  bucket: string;
21
22
  }
23
+ export interface UploadWithContentTypeParams {
24
+ client: S3Client;
25
+ readStream: Readable;
26
+ contentType: string;
27
+ key: string;
28
+ bucket: string;
29
+ magic?: {
30
+ charset: string;
31
+ type?: string;
32
+ };
33
+ }
22
34
  export interface UploadResult {
23
35
  Location: string;
24
36
  ETag?: string;
@@ -29,6 +41,7 @@ export interface AsyncUploadResult {
29
41
  upload: UploadResult;
30
42
  magic: {
31
43
  charset: string;
44
+ type?: string;
32
45
  };
33
46
  contentType: string;
34
47
  contents: unknown;
@@ -36,3 +49,4 @@ export interface AsyncUploadResult {
36
49
  export declare const uploadFromStream: ({ client, key, contentType, bucket }: UploadParams) => PassThrough;
37
50
  export declare const asyncUpload: ({ client, key, contentType, readStream, magic, bucket }: AsyncUploadParams) => Promise<AsyncUploadResult>;
38
51
  export declare const upload: ({ client, readStream, filename, key, bucket }: UploadWithFilenameParams) => Promise<AsyncUploadResult>;
52
+ export declare const uploadWithContentType: ({ client, readStream, contentType, key, bucket, magic }: UploadWithContentTypeParams) => Promise<AsyncUploadResult>;
package/utils.js CHANGED
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.upload = exports.asyncUpload = exports.uploadFromStream = void 0;
6
+ exports.uploadWithContentType = exports.upload = exports.asyncUpload = exports.uploadFromStream = void 0;
7
7
  const lib_storage_1 = require("@aws-sdk/lib-storage");
8
8
  const content_type_stream_1 = require("@constructive-io/content-type-stream");
9
9
  const stream_1 = __importDefault(require("stream"));
@@ -35,8 +35,25 @@ const uploadFromStream = ({ client, key, contentType, bucket }) => {
35
35
  return pass;
36
36
  };
37
37
  exports.uploadFromStream = uploadFromStream;
38
+ const UPLOAD_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
38
39
  const asyncUpload = ({ client, key, contentType, readStream, magic, bucket }) => {
39
40
  return new Promise((resolve, reject) => {
41
+ let settled = false;
42
+ const settle = (fn) => {
43
+ if (settled)
44
+ return;
45
+ settled = true;
46
+ clearTimeout(timer);
47
+ fn();
48
+ };
49
+ const timer = setTimeout(() => {
50
+ settle(() => {
51
+ readStream.destroy();
52
+ uploadStream.destroy();
53
+ contentStream.destroy();
54
+ reject(new Error(`Upload timed out after ${UPLOAD_TIMEOUT_MS / 1000}s for key=${key}`));
55
+ });
56
+ }, UPLOAD_TIMEOUT_MS);
40
57
  // upload stream
41
58
  let upload;
42
59
  const uploadStream = (0, exports.uploadFromStream)({
@@ -50,21 +67,34 @@ const asyncUpload = ({ client, key, contentType, readStream, magic, bucket }) =>
50
67
  const contentStream = new content_type_stream_1.ContentStream();
51
68
  const tryResolve = () => {
52
69
  if (contents && upload) {
53
- resolve({
54
- upload,
55
- magic,
56
- contentType,
57
- contents
70
+ settle(() => {
71
+ resolve({
72
+ upload: upload,
73
+ magic,
74
+ contentType,
75
+ contents
76
+ });
58
77
  });
59
78
  }
60
79
  };
80
+ readStream.on('error', (error) => {
81
+ settle(() => {
82
+ uploadStream.destroy();
83
+ contentStream.destroy();
84
+ reject(error);
85
+ });
86
+ });
61
87
  contentStream
62
88
  .on('contents', function (results) {
63
89
  contents = results;
64
90
  tryResolve();
65
91
  })
66
92
  .on('error', (error) => {
67
- reject(error);
93
+ settle(() => {
94
+ readStream.destroy();
95
+ uploadStream.destroy();
96
+ reject(error);
97
+ });
68
98
  });
69
99
  uploadStream
70
100
  .on('upload', (results) => {
@@ -72,7 +102,11 @@ const asyncUpload = ({ client, key, contentType, readStream, magic, bucket }) =>
72
102
  tryResolve();
73
103
  })
74
104
  .on('error', (error) => {
75
- reject(error);
105
+ settle(() => {
106
+ readStream.destroy();
107
+ contentStream.destroy();
108
+ reject(error);
109
+ });
76
110
  });
77
111
  // Ensure proper cleanup on stream end
78
112
  uploadStream.on('finish', () => {
@@ -88,13 +122,24 @@ const upload = async ({ client, readStream, filename, key, bucket }) => {
88
122
  readStream,
89
123
  filename
90
124
  });
91
- return await (0, exports.asyncUpload)({
125
+ return await (0, exports.uploadWithContentType)({
92
126
  client,
93
- key,
94
- contentType,
95
127
  readStream: newStream,
128
+ contentType,
96
129
  magic,
130
+ key,
97
131
  bucket
98
132
  });
99
133
  };
100
134
  exports.upload = upload;
135
+ const uploadWithContentType = async ({ client, readStream, contentType, key, bucket, magic }) => {
136
+ return await (0, exports.asyncUpload)({
137
+ client,
138
+ readStream,
139
+ contentType,
140
+ magic: magic ?? { charset: 'binary' },
141
+ key,
142
+ bucket,
143
+ });
144
+ };
145
+ exports.uploadWithContentType = uploadWithContentType;