@eik/core 2.1.73 → 2.1.75
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/CHANGELOG.md +14 -0
- package/lib/multipart/parser.js +52 -10
- package/lib/utils/utils.js +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [2.1.75](https://github.com/eik-lib/core/compare/v2.1.74...v2.1.75) (2026-08-14)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* cap concurrent sink write streams in _handleFile to 16 ([#634](https://github.com/eik-lib/core/issues/634)) ([c16c463](https://github.com/eik-lib/core/commit/c16c463f567b43fc617886e7f5d52acc1fd3d075))
|
|
7
|
+
|
|
8
|
+
## [2.1.74](https://github.com/eik-lib/core/compare/v2.1.73...v2.1.74) (2026-08-14)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* prevent process crash on second busboy error and fix UTF-8 chunk corruption ([#633](https://github.com/eik-lib/core/issues/633)) ([9f5e3ec](https://github.com/eik-lib/core/commit/9f5e3ecab5ea8ceed16a0826c0c9c1824e68beac)), closes [#9](https://github.com/eik-lib/core/issues/9) [#11](https://github.com/eik-lib/core/issues/11)
|
|
14
|
+
|
|
1
15
|
## [2.1.73](https://github.com/eik-lib/core/compare/v2.1.72...v2.1.73) (2026-08-14)
|
|
2
16
|
|
|
3
17
|
|
package/lib/multipart/parser.js
CHANGED
|
@@ -106,7 +106,13 @@ const MultipartParser = class MultipartParser {
|
|
|
106
106
|
},
|
|
107
107
|
);
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
// Use .on() instead of .once() so that a second error emitted
|
|
110
|
+
// by a concurrent _handleFile failure (after the first error has
|
|
111
|
+
// already removed a .once() listener) does not hit zero listeners
|
|
112
|
+
// and crash the process with an unhandled EventEmitter throw.
|
|
113
|
+
// Calling reject() a second time is a safe no-op on an already-
|
|
114
|
+
// settled Promise.
|
|
115
|
+
busboy.on("error", (/** @type {any} */ error) => {
|
|
110
116
|
reject(error);
|
|
111
117
|
});
|
|
112
118
|
|
|
@@ -152,6 +158,37 @@ const MultipartParser = class MultipartParser {
|
|
|
152
158
|
/** @type {Record<string,any>|null} */
|
|
153
159
|
let capturedEikJson = null;
|
|
154
160
|
|
|
161
|
+
// Limit how many sink write streams are open simultaneously.
|
|
162
|
+
// Without a cap, a tar with thousands of files would open an equal
|
|
163
|
+
// number of concurrent GCS resumable uploads or filesystem write
|
|
164
|
+
// streams, exhausting the connection pool or file descriptor limit.
|
|
165
|
+
const MAX_CONCURRENT_WRITES = 16;
|
|
166
|
+
let runningWrites = 0;
|
|
167
|
+
/** @type {import("tar").ReadEntry[]} */
|
|
168
|
+
const pendingEntries = [];
|
|
169
|
+
|
|
170
|
+
const drainPending = () => {
|
|
171
|
+
while (
|
|
172
|
+
runningWrites < MAX_CONCURRENT_WRITES &&
|
|
173
|
+
pendingEntries.length > 0
|
|
174
|
+
) {
|
|
175
|
+
const entry = /** @type {import("tar").ReadEntry} */ (
|
|
176
|
+
pendingEntries.shift()
|
|
177
|
+
);
|
|
178
|
+
runningWrites++;
|
|
179
|
+
const persist = this._persistFile({
|
|
180
|
+
incoming,
|
|
181
|
+
entry,
|
|
182
|
+
signal: ac.signal,
|
|
183
|
+
}).finally(() => {
|
|
184
|
+
runningWrites--;
|
|
185
|
+
drainPending();
|
|
186
|
+
});
|
|
187
|
+
persist.catch(() => {});
|
|
188
|
+
queue.push(persist);
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
|
|
155
192
|
file.once("limit", () => {
|
|
156
193
|
this._log.warn(
|
|
157
194
|
`multipart - Uploaded package exceeds the max allowed file size - Field: ${fieldname} - Filename: ${filename} - Name: ${incoming.name} - Version: ${incoming.version} - Type: ${incoming.type} - Limit: ${this._pkgMaxFileSize} bytes`,
|
|
@@ -193,15 +230,13 @@ const MultipartParser = class MultipartParser {
|
|
|
193
230
|
return;
|
|
194
231
|
}
|
|
195
232
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
persist.catch(() => {});
|
|
204
|
-
queue.push(persist);
|
|
233
|
+
// Queue the entry. drainPending() starts it immediately if
|
|
234
|
+
// a slot is free, otherwise holds it until one opens up.
|
|
235
|
+
// Holding an entry without piping it blocks the tar parser
|
|
236
|
+
// at that position in the archive, creating natural
|
|
237
|
+
// backpressure on the upstream upload stream.
|
|
238
|
+
pendingEntries.push(entry);
|
|
239
|
+
drainPending();
|
|
205
240
|
},
|
|
206
241
|
});
|
|
207
242
|
|
|
@@ -214,6 +249,13 @@ const MultipartParser = class MultipartParser {
|
|
|
214
249
|
|
|
215
250
|
ac.abort();
|
|
216
251
|
|
|
252
|
+
// Drain any entries still waiting in the queue so the tar
|
|
253
|
+
// parser can wind down and the pipeline callback can fire.
|
|
254
|
+
for (const pending of pendingEntries) {
|
|
255
|
+
pending.resume();
|
|
256
|
+
}
|
|
257
|
+
pendingEntries.length = 0;
|
|
258
|
+
|
|
217
259
|
switch (error.code) {
|
|
218
260
|
case "TAR_BAD_ARCHIVE":
|
|
219
261
|
case "TAR_ABORT":
|
package/lib/utils/utils.js
CHANGED
|
@@ -22,7 +22,7 @@ const readJSON = (sink, path) =>
|
|
|
22
22
|
|
|
23
23
|
pipeline(from.stream, to, (error) => {
|
|
24
24
|
if (error) return reject(error);
|
|
25
|
-
const str =
|
|
25
|
+
const str = Buffer.concat(buffer).toString("utf8");
|
|
26
26
|
try {
|
|
27
27
|
const obj = JSON.parse(str);
|
|
28
28
|
return resolve(obj);
|
|
@@ -86,7 +86,7 @@ const streamCollector = (from) =>
|
|
|
86
86
|
|
|
87
87
|
pipeline(from, to, (error) => {
|
|
88
88
|
if (error) return reject(error);
|
|
89
|
-
return resolve(
|
|
89
|
+
return resolve(Buffer.concat(buffer).toString("utf8"));
|
|
90
90
|
});
|
|
91
91
|
});
|
|
92
92
|
|