@balena/pinejs-webresource-s3 0.2.1 → 1.0.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/.versionbot/CHANGELOG.yml +12 -1
- package/CHANGELOG.md +5 -0
- package/build/index.js +20 -28
- package/build/index.js.map +1 -1
- package/package.json +3 -3
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
- commits:
|
|
2
|
+
- subject: Convert to ESM
|
|
3
|
+
hash: 9f55e86919d873e71a84d0e5359943f128aea15e
|
|
4
|
+
body: ""
|
|
5
|
+
footer:
|
|
6
|
+
Change-type: major
|
|
7
|
+
change-type: major
|
|
8
|
+
author: Otavio Jacobi
|
|
9
|
+
version: 1.0.0
|
|
10
|
+
title: ""
|
|
11
|
+
date: 2025-01-02T13:05:37.621Z
|
|
1
12
|
- commits:
|
|
2
13
|
- subject: Update dependencies
|
|
3
14
|
hash: 92d1e2bdcacc5ac98a755b3c7145a3940bfa413e
|
|
@@ -22,7 +33,7 @@
|
|
|
22
33
|
author: Otavio Jacobi
|
|
23
34
|
version: 0.2.1
|
|
24
35
|
title: ""
|
|
25
|
-
date: 2025-01-02T12:
|
|
36
|
+
date: 2025-01-02T12:58:51.221Z
|
|
26
37
|
- commits:
|
|
27
38
|
- subject: Add beginMultipartUpload/commitMultiPartUpload
|
|
28
39
|
hash: 8ac4f8e08b79392ed762d9732dd43bc5d94d6b71
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file
|
|
|
4
4
|
automatically by Versionist. DO NOT EDIT THIS FILE MANUALLY!
|
|
5
5
|
This project adheres to [Semantic Versioning](http://semver.org/).
|
|
6
6
|
|
|
7
|
+
# v1.0.0
|
|
8
|
+
## (2025-01-02)
|
|
9
|
+
|
|
10
|
+
* Convert to ESM [Otavio Jacobi]
|
|
11
|
+
|
|
7
12
|
# v0.2.1
|
|
8
13
|
## (2025-01-02)
|
|
9
14
|
|
package/build/index.js
CHANGED
|
@@ -1,26 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const client_s3_1 = require("@aws-sdk/client-s3");
|
|
8
|
-
const lib_storage_1 = require("@aws-sdk/lib-storage");
|
|
9
|
-
const s3_request_presigner_1 = require("@aws-sdk/s3-request-presigner");
|
|
10
|
-
const memoizee_1 = __importDefault(require("memoizee"));
|
|
11
|
-
const node_crypto_1 = require("node:crypto");
|
|
12
|
-
const typed_error_1 = require("typed-error");
|
|
1
|
+
import { DeleteObjectCommand, GetObjectCommand, S3Client, CreateMultipartUploadCommand, UploadPartCommand, CompleteMultipartUploadCommand, HeadObjectCommand, } from '@aws-sdk/client-s3';
|
|
2
|
+
import { Upload } from '@aws-sdk/lib-storage';
|
|
3
|
+
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
|
4
|
+
import memoize from 'memoizee';
|
|
5
|
+
import { randomUUID } from 'node:crypto';
|
|
6
|
+
import { TypedError } from 'typed-error';
|
|
13
7
|
const normalizeHref = (href) => {
|
|
14
8
|
return href.split('?')[0];
|
|
15
9
|
};
|
|
16
|
-
class FileSizeExceededError extends
|
|
10
|
+
export class FileSizeExceededError extends TypedError {
|
|
17
11
|
constructor(maxSize) {
|
|
18
12
|
super(`File size exceeded the limit of ${maxSize} bytes.`);
|
|
19
13
|
this.name = 'FileSizeExceededError';
|
|
20
14
|
}
|
|
21
15
|
}
|
|
22
|
-
|
|
23
|
-
class S3Handler {
|
|
16
|
+
export class S3Handler {
|
|
24
17
|
constructor(config) {
|
|
25
18
|
this.config = {
|
|
26
19
|
region: config.region,
|
|
@@ -37,8 +30,8 @@ class S3Handler {
|
|
|
37
30
|
config.signedUrlCacheExpireTimeSeconds ?? 82800;
|
|
38
31
|
this.maxFileSize = config.maxSize ?? 52428800;
|
|
39
32
|
this.bucket = config.bucket;
|
|
40
|
-
this.client = new
|
|
41
|
-
this.cachedGetSignedUrl = (
|
|
33
|
+
this.client = new S3Client(this.config);
|
|
34
|
+
this.cachedGetSignedUrl = memoize(this.s3SignUrl, {
|
|
42
35
|
maxAge: this.signedUrlCacheExpireTimeSeconds * 1000,
|
|
43
36
|
});
|
|
44
37
|
}
|
|
@@ -51,7 +44,7 @@ class S3Handler {
|
|
|
51
44
|
Body: resource.stream,
|
|
52
45
|
ContentType: resource.mimetype,
|
|
53
46
|
};
|
|
54
|
-
const upload = new
|
|
47
|
+
const upload = new Upload({ client: this.client, params });
|
|
55
48
|
upload.on('httpUploadProgress', async (ev) => {
|
|
56
49
|
size = ev.total ?? ev.loaded;
|
|
57
50
|
if (size > this.maxFileSize) {
|
|
@@ -73,7 +66,7 @@ class S3Handler {
|
|
|
73
66
|
}
|
|
74
67
|
async removeFile(href) {
|
|
75
68
|
const fileKey = this.getKeyFromHref(href);
|
|
76
|
-
const command = new
|
|
69
|
+
const command = new DeleteObjectCommand({
|
|
77
70
|
Bucket: this.bucket,
|
|
78
71
|
Key: fileKey,
|
|
79
72
|
});
|
|
@@ -88,7 +81,7 @@ class S3Handler {
|
|
|
88
81
|
}
|
|
89
82
|
async beginMultipartUpload(fieldName, payload) {
|
|
90
83
|
const fileKey = this.getFileKey(fieldName, payload.filename);
|
|
91
|
-
const createMultiPartResponse = await this.client.send(new
|
|
84
|
+
const createMultiPartResponse = await this.client.send(new CreateMultipartUploadCommand({
|
|
92
85
|
Bucket: this.bucket,
|
|
93
86
|
Key: fileKey,
|
|
94
87
|
ContentType: payload.content_type,
|
|
@@ -100,13 +93,13 @@ class S3Handler {
|
|
|
100
93
|
return { fileKey, uploadId: createMultiPartResponse.UploadId, uploadParts };
|
|
101
94
|
}
|
|
102
95
|
async commitMultipartUpload({ fileKey, uploadId, filename, providerCommitData, }) {
|
|
103
|
-
await this.client.send(new
|
|
96
|
+
await this.client.send(new CompleteMultipartUploadCommand({
|
|
104
97
|
Bucket: this.bucket,
|
|
105
98
|
Key: fileKey,
|
|
106
99
|
UploadId: uploadId,
|
|
107
100
|
MultipartUpload: providerCommitData,
|
|
108
101
|
}));
|
|
109
|
-
const headResult = await this.client.send(new
|
|
102
|
+
const headResult = await this.client.send(new HeadObjectCommand({
|
|
110
103
|
Bucket: this.bucket,
|
|
111
104
|
Key: fileKey,
|
|
112
105
|
}));
|
|
@@ -118,11 +111,11 @@ class S3Handler {
|
|
|
118
111
|
};
|
|
119
112
|
}
|
|
120
113
|
s3SignUrl(fileKey) {
|
|
121
|
-
const command = new
|
|
114
|
+
const command = new GetObjectCommand({
|
|
122
115
|
Bucket: this.bucket,
|
|
123
116
|
Key: fileKey,
|
|
124
117
|
});
|
|
125
|
-
return
|
|
118
|
+
return getSignedUrl(this.client, command, {
|
|
126
119
|
expiresIn: this.signedUrlExpireTimeSeconds,
|
|
127
120
|
});
|
|
128
121
|
}
|
|
@@ -134,7 +127,7 @@ class S3Handler {
|
|
|
134
127
|
return hrefWithoutParams.substring(hrefWithoutParams.lastIndexOf('/') + 1);
|
|
135
128
|
}
|
|
136
129
|
getFileKey(fieldName, fileName) {
|
|
137
|
-
return `${fieldName}_${
|
|
130
|
+
return `${fieldName}_${randomUUID()}_${fileName}`;
|
|
138
131
|
}
|
|
139
132
|
async getUploadParts(fileKey, uploadId, payload) {
|
|
140
133
|
const chunkSizesWithParts = this.getChunkSizesWithParts(payload.size, payload.chunk_size);
|
|
@@ -145,14 +138,14 @@ class S3Handler {
|
|
|
145
138
|
})));
|
|
146
139
|
}
|
|
147
140
|
async getPartUploadUrl(fileKey, uploadId, partNumber, partSize) {
|
|
148
|
-
const command = new
|
|
141
|
+
const command = new UploadPartCommand({
|
|
149
142
|
Bucket: this.bucket,
|
|
150
143
|
Key: fileKey,
|
|
151
144
|
UploadId: uploadId,
|
|
152
145
|
PartNumber: partNumber,
|
|
153
146
|
ContentLength: partSize,
|
|
154
147
|
});
|
|
155
|
-
return
|
|
148
|
+
return getSignedUrl(this.client, command, {
|
|
156
149
|
expiresIn: this.signedUrlExpireTimeSeconds,
|
|
157
150
|
});
|
|
158
151
|
}
|
|
@@ -169,5 +162,4 @@ class S3Handler {
|
|
|
169
162
|
return chunkSizesWithParts;
|
|
170
163
|
}
|
|
171
164
|
}
|
|
172
|
-
exports.S3Handler = S3Handler;
|
|
173
165
|
//# sourceMappingURL=index.js.map
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@balena/pinejs-webresource-s3",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "A PineJS WebResource handler for storing & serving files on S3",
|
|
5
|
-
"type": "
|
|
5
|
+
"type": "module",
|
|
6
6
|
"main": "build/index.js",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"build": "tsc",
|
|
@@ -29,6 +29,6 @@
|
|
|
29
29
|
"typed-error": "^3.2.2"
|
|
30
30
|
},
|
|
31
31
|
"versionist": {
|
|
32
|
-
"publishedAt": "2025-01-
|
|
32
|
+
"publishedAt": "2025-01-02T13:05:37.665Z"
|
|
33
33
|
}
|
|
34
34
|
}
|