@constructive-io/s3-streamer 2.12.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 +18 -1
- package/esm/utils.js +54 -10
- package/package.json +6 -6
- package/streamer.d.ts +19 -2
- package/streamer.js +17 -0
- package/utils.d.ts +14 -0
- package/utils.js +56 -11
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
3
|
+
"version": "2.14.0",
|
|
4
4
|
"author": "Constructive <developers@constructive.io>",
|
|
5
5
|
"description": "stream files to s3",
|
|
6
6
|
"main": "index.js",
|
|
@@ -29,16 +29,16 @@
|
|
|
29
29
|
"test:watch": "jest --watch"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
|
-
"@constructive-io/s3-utils": "^2.
|
|
33
|
-
"@pgpmjs/env": "^2.
|
|
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.
|
|
41
|
-
"@pgpmjs/types": "^2.
|
|
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": "
|
|
51
|
+
"gitHead": "b758178b808ce0bf451e86c0bd7e92079155db7c"
|
|
52
52
|
}
|
package/streamer.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
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:
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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;
|