@eik/core 2.2.3 → 2.4.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/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ # [2.4.0](https://github.com/eik-lib/core/compare/v2.3.0...v2.4.0) (2026-08-25)
2
+
3
+
4
+ ### Features
5
+
6
+ * replace pipeline-based reads and writes with buffer operations ([#649](https://github.com/eik-lib/core/issues/649)) ([ace642f](https://github.com/eik-lib/core/commit/ace642f66ac08abcbda86c51b181bf107df68c5c))
7
+
8
+ # [2.3.0](https://github.com/eik-lib/core/compare/v2.2.3...v2.3.0) (2026-08-24)
9
+
10
+
11
+ ### Features
12
+
13
+ * add traceId to upload logs and improve observability ([#647](https://github.com/eik-lib/core/issues/647)) ([32f8d38](https://github.com/eik-lib/core/commit/32f8d38290fab142cad9febc8379cd12f791cd70))
14
+
1
15
  ## [2.2.3](https://github.com/eik-lib/core/compare/v2.2.2...v2.2.3) (2026-08-22)
2
16
 
3
17
 
@@ -153,6 +153,7 @@ const AliasPost = class AliasPost {
153
153
  */
154
154
  async handler(req, user, type, name, alias) {
155
155
  const end = this._histogram.timer();
156
+ const traceId = req.traceId;
156
157
 
157
158
  const pAlias = decodeUriComponent(alias);
158
159
  const pName = decodeUriComponent(name);
@@ -163,7 +164,7 @@ const AliasPost = class AliasPost {
163
164
  validators.type(type);
164
165
  } catch (error) {
165
166
  this._log.info(
166
- `alias:post - Validation failed - ${error instanceof Error ? error.message : String(error)}`,
167
+ `alias:post - Validation failed - ${error instanceof Error ? error.message : String(error)} - TraceId: ${traceId}`,
167
168
  );
168
169
  const e = new HttpError.NotFound();
169
170
  end({ labels: { success: false, status: e.status } });
@@ -175,7 +176,7 @@ const AliasPost = class AliasPost {
175
176
 
176
177
  if (!org) {
177
178
  this._log.debug(
178
- `alias:post - Hostname does not match a configured organization - ${url.hostname}`,
179
+ `alias:post - Hostname does not match a configured organization - ${url.hostname} - TraceId: ${traceId}`,
179
180
  );
180
181
  const e = new HttpError.BadRequest();
181
182
  end({ labels: { success: false, status: e.status, type } });
@@ -195,14 +196,18 @@ const AliasPost = class AliasPost {
195
196
  const exist = await this._exist(incoming);
196
197
  if (!exist) {
197
198
  this._log.info(
198
- `alias:post - Alias does not exists - Org: ${org} - Type: ${type} - Name: ${pName} - Alias: ${pAlias}`,
199
+ `alias:post - Alias does not exists - Org: ${org} - Type: ${type} - Name: ${pName} - Alias: ${pAlias} - TraceId: ${traceId}`,
199
200
  );
200
201
  const e = new HttpError.NotFound();
201
202
  end({ labels: { success: false, status: e.status, type } });
202
203
  throw e;
203
204
  }
204
205
 
205
- await this._parser(incoming);
206
+ const resolvedAlias = await this._parser(incoming);
207
+
208
+ this._log.info(
209
+ `alias:post - Alias created - Org: ${org} - Type: ${type} - Name: ${pName} - Alias: ${pAlias} - Version: ${resolvedAlias.version} - TraceId: ${traceId}`,
210
+ );
206
211
 
207
212
  const outgoing = new HttpOutgoing();
208
213
  outgoing.cacheControl = this._cacheControl || "";
@@ -94,7 +94,7 @@ const AliasPut = class AliasPut {
94
94
  if (this._sink) await this._sink.read(path);
95
95
  } catch (error) {
96
96
  this._log.error(
97
- `alias:post - Unable to locate requested published package version - Version ${alias.name}@${alias.version}`,
97
+ `alias:put - Unable to locate requested published package version - Version ${alias.name}@${alias.version}`,
98
98
  );
99
99
  this._log.trace(error);
100
100
  throw new HttpError.NotFound();
@@ -153,6 +153,7 @@ const AliasPut = class AliasPut {
153
153
  */
154
154
  async handler(req, user, type, name, alias) {
155
155
  const end = this._histogram.timer();
156
+ const traceId = req.traceId;
156
157
 
157
158
  const pAlias = decodeUriComponent(alias);
158
159
  const pName = decodeUriComponent(name);
@@ -163,7 +164,7 @@ const AliasPut = class AliasPut {
163
164
  validators.type(type);
164
165
  } catch (error) {
165
166
  this._log.info(
166
- `alias:put - Validation failed - ${error instanceof Error ? error.message : String(error)}`,
167
+ `alias:put - Validation failed - ${error instanceof Error ? error.message : String(error)} - TraceId: ${traceId}`,
167
168
  );
168
169
  const e = new HttpError.BadRequest();
169
170
  end({ labels: { success: false, status: e.status } });
@@ -175,7 +176,7 @@ const AliasPut = class AliasPut {
175
176
 
176
177
  if (!org) {
177
178
  this._log.debug(
178
- `alias:put - Hostname does not match a configured organization - ${url.hostname}`,
179
+ `alias:put - Hostname does not match a configured organization - ${url.hostname} - TraceId: ${traceId}`,
179
180
  );
180
181
  const e = new HttpError.BadRequest();
181
182
  end({ labels: { success: false, status: e.status, type } });
@@ -195,14 +196,18 @@ const AliasPut = class AliasPut {
195
196
  const exist = await this._exist(incoming);
196
197
  if (exist) {
197
198
  this._log.info(
198
- `alias:put - Alias exists - Org: ${org} - Type: ${type} - Name: ${pName} - Alias: ${pAlias}`,
199
+ `alias:put - Alias exists - Org: ${org} - Type: ${type} - Name: ${pName} - Alias: ${pAlias} - TraceId: ${traceId}`,
199
200
  );
200
201
  const e = new HttpError.Conflict();
201
202
  end({ labels: { success: false, status: e.status, type } });
202
203
  throw e;
203
204
  }
204
205
 
205
- await this._parser(incoming);
206
+ const resolvedAlias = await this._parser(incoming);
207
+
208
+ this._log.info(
209
+ `alias:put - Alias updated - Org: ${org} - Type: ${type} - Name: ${pName} - Alias: ${pAlias} - Version: ${resolvedAlias.version} - TraceId: ${traceId}`,
210
+ );
206
211
 
207
212
  const outgoing = new HttpOutgoing();
208
213
  outgoing.cacheControl = this._cacheControl || "";
@@ -179,12 +179,14 @@ const MapPut = class MapPut {
179
179
 
180
180
  if (!this._sink) throw new Error("No sink configured");
181
181
 
182
+ const traceId = incoming.request?.traceId;
183
+
182
184
  try {
183
185
  await this._sink.exist(path);
184
186
  } catch {
185
187
  // File does not exist — this is a new import map.
186
188
  this._log.info(
187
- `map:put - Version meta file did not exist in sink - Create new - Pathname: ${path}`,
189
+ `map:put - Version meta file did not exist in sink - Create new - Pathname: ${path} - TraceId: ${traceId}`,
188
190
  );
189
191
  return new Versions(incoming);
190
192
  }
@@ -195,7 +197,7 @@ const MapPut = class MapPut {
195
197
  // entire version history.
196
198
  const obj = await readJSON(this._sink, path);
197
199
  this._log.info(
198
- `map:put - Successfully read version meta file from sink - Pathname: ${path}`,
200
+ `map:put - Successfully read version meta file from sink - Pathname: ${path} - TraceId: ${traceId}`,
199
201
  );
200
202
  return new Versions(obj);
201
203
  }
@@ -206,9 +208,10 @@ const MapPut = class MapPut {
206
208
  */
207
209
  async _writeVersions(incoming, versions) {
208
210
  const path = createFilePathToVersion(incoming);
211
+ const traceId = incoming.request?.traceId;
209
212
  await writeJSON(this._sink, path, versions, "application/json");
210
213
  this._log.info(
211
- `map:put - Successfully wrote version meta file to sink - Pathname: ${path}`,
214
+ `map:put - Successfully wrote version meta file to sink - Pathname: ${path} - TraceId: ${traceId}`,
212
215
  );
213
216
  }
214
217
 
@@ -220,6 +223,7 @@ const MapPut = class MapPut {
220
223
  */
221
224
  async handler(req, user, name, version) {
222
225
  const end = this._histogram.timer();
226
+ const traceId = req.traceId;
223
227
 
224
228
  const pVersion = decodeUriComponent(version);
225
229
  const pName = decodeUriComponent(name);
@@ -229,7 +233,7 @@ const MapPut = class MapPut {
229
233
  validators.name(pName);
230
234
  } catch (error) {
231
235
  this._log.info(
232
- `map:put - Validation failed - ${error instanceof Error ? error.message : String(error)}`,
236
+ `map:put - Validation failed - ${error instanceof Error ? error.message : String(error)} - TraceId: ${traceId}`,
233
237
  );
234
238
  const e = new HttpError.BadRequest();
235
239
  end({ labels: { success: false, status: e.status } });
@@ -241,7 +245,7 @@ const MapPut = class MapPut {
241
245
 
242
246
  if (!org) {
243
247
  this._log.debug(
244
- `map:put - Hostname does not match a configured organization - ${url.hostname}`,
248
+ `map:put - Hostname does not match a configured organization - ${url.hostname} - TraceId: ${traceId}`,
245
249
  );
246
250
  const e = new HttpError.BadRequest();
247
251
  end({ labels: { success: false, status: e.status, type: "map" } });
@@ -266,7 +270,7 @@ const MapPut = class MapPut {
266
270
 
267
271
  if (!versions.check(pVersion)) {
268
272
  this._log.info(
269
- `map:put - Semver version is lower than previous version of the package - Org: ${org} - Name: ${pName} - Version: ${pVersion}`,
273
+ `map:put - Semver version is lower than previous version of the package - Org: ${org} - Name: ${pName} - Version: ${pVersion} - TraceId: ${traceId}`,
270
274
  );
271
275
  const e = new HttpError.Conflict();
272
276
  end({ labels: { success: false, status: e.status, type: "map" } });
@@ -136,7 +136,7 @@ const PkgPut = class PkgPut {
136
136
  "application/json",
137
137
  );
138
138
  this._log.info(
139
- `pkg:put - Successfully wrote eik.json to sink - Pathname: ${eikJsonPath}`,
139
+ `pkg:put - Successfully wrote eik.json to sink - Pathname: ${eikJsonPath} - TraceId: ${incoming.request?.traceId}`,
140
140
  );
141
141
 
142
142
  const asset = new Asset({
@@ -167,7 +167,7 @@ const PkgPut = class PkgPut {
167
167
  } catch {
168
168
  // File does not exist — this is a new package.
169
169
  this._log.info(
170
- `pkg:put - Version meta file did not exist in sink - Create new - Pathname: ${path}`,
170
+ `pkg:put - Version meta file did not exist in sink - Create new - Pathname: ${path} - TraceId: ${incoming.request?.traceId}`,
171
171
  );
172
172
  return new Versions(incoming);
173
173
  }
@@ -178,7 +178,7 @@ const PkgPut = class PkgPut {
178
178
  // overwrite the index and erase the entire version history.
179
179
  const obj = await readJSON(this._sink, path);
180
180
  this._log.info(
181
- `pkg:put - Successfully read version meta file from sink - Pathname: ${path}`,
181
+ `pkg:put - Successfully read version meta file from sink - Pathname: ${path} - TraceId: ${incoming.request?.traceId}`,
182
182
  );
183
183
  return new Versions(obj);
184
184
  }
@@ -191,14 +191,14 @@ const PkgPut = class PkgPut {
191
191
  try {
192
192
  await readEikJson(this._sink, path);
193
193
  this._log.info(
194
- `pkg:put - Found version meta file from sink - Pathname: ${path}`,
194
+ `pkg:put - Found version meta file from sink - Pathname: ${path} - TraceId: ${incoming.request?.traceId}`,
195
195
  );
196
196
  return true;
197
197
  // eslint-disable-next-line no-unused-vars
198
198
  } catch (error) {
199
199
  // File does not exist, its probably a new package
200
200
  this._log.info(
201
- `pkg:put - Did not find meta file in sink - Create new - Pathname: ${path}`,
201
+ `pkg:put - Did not find meta file in sink - Create new - Pathname: ${path} - TraceId: ${incoming.request?.traceId}`,
202
202
  );
203
203
  return false;
204
204
  }
@@ -212,7 +212,7 @@ const PkgPut = class PkgPut {
212
212
  const path = createFilePathToVersion(incoming);
213
213
  await writeJSON(this._sink, path, versions, "application/json");
214
214
  this._log.info(
215
- `pkg:put - Successfully wrote version meta file to sink - Pathname: ${path}`,
215
+ `pkg:put - Successfully wrote version meta file to sink - Pathname: ${path} - TraceId: ${incoming.request?.traceId}`,
216
216
  );
217
217
  }
218
218
 
@@ -225,6 +225,7 @@ const PkgPut = class PkgPut {
225
225
  */
226
226
  async handler(req, user, type, name, version) {
227
227
  const end = this._histogram.timer();
228
+ const traceId = req.traceId;
228
229
 
229
230
  const pVersion = decodeUriComponent(version);
230
231
  const pName = decodeUriComponent(name);
@@ -235,7 +236,7 @@ const PkgPut = class PkgPut {
235
236
  validators.type(type);
236
237
  } catch (error) {
237
238
  this._log.info(
238
- `pkg:put - Validation failed - ${error instanceof Error ? error.message : String(error)}`,
239
+ `pkg:put - Validation failed - ${error instanceof Error ? error.message : String(error)} - TraceId: ${traceId}`,
239
240
  );
240
241
  const e = new HttpError.BadRequest();
241
242
  end({ labels: { success: false, status: e.status } });
@@ -247,7 +248,7 @@ const PkgPut = class PkgPut {
247
248
 
248
249
  if (!org) {
249
250
  this._log.debug(
250
- `pkg:put - Hostname does not match a configured organization - ${url.hostname}`,
251
+ `pkg:put - Hostname does not match a configured organization - ${url.hostname} - TraceId: ${traceId}`,
251
252
  );
252
253
  const e = new HttpError.BadRequest();
253
254
  end({ labels: { success: false, status: e.status, type } });
@@ -264,11 +265,15 @@ const PkgPut = class PkgPut {
264
265
  org,
265
266
  });
266
267
 
268
+ this._log.info(
269
+ `pkg:put - Received upload - Name: ${pName} - Version: ${pVersion} - TraceId: ${traceId}`,
270
+ );
271
+
267
272
  const versionExists = await this._readVersion(incoming);
268
273
 
269
274
  if (versionExists) {
270
275
  this._log.info(
271
- `pkg:put - Semver version already exists for the package - Org: ${org} - Name: ${pName} - Version: ${pVersion}`,
276
+ `pkg:put - Semver version already exists for the package - Org: ${org} - Name: ${pName} - Version: ${pVersion} - TraceId: ${traceId}`,
272
277
  );
273
278
  const e = new HttpError.Conflict();
274
279
  end({ labels: { success: false, status: e.status, type } });
@@ -276,6 +281,9 @@ const PkgPut = class PkgPut {
276
281
  }
277
282
 
278
283
  const versions = await this._readVersions(incoming).catch(() => {
284
+ this._log.error(
285
+ `pkg:put - Failed to read version meta from sink - Name: ${pName} - Version: ${pVersion} - TraceId: ${traceId}`,
286
+ );
279
287
  const e = new HttpError.BadGateway();
280
288
  end({ labels: { success: false, status: e.status, type } });
281
289
  throw e;
@@ -290,6 +298,9 @@ const PkgPut = class PkgPut {
290
298
  eikJsonAsset = await this._writeEikJson(incoming, eikJson);
291
299
  // eslint-disable-next-line no-unused-vars
292
300
  } catch (error) {
301
+ this._log.error(
302
+ `pkg:put - Failed to write eik.json to sink - Name: ${pName} - Version: ${pVersion} - TraceId: ${traceId}`,
303
+ );
293
304
  const e = new HttpError.BadGateway();
294
305
  end({ labels: { success: false, status: e.status, type } });
295
306
  throw e;
@@ -300,10 +311,13 @@ const PkgPut = class PkgPut {
300
311
  const pkgMetaPath = createFilePathToPackage(pkg);
301
312
  await writeJSON(this._sink, pkgMetaPath, pkg, "application/json");
302
313
  this._log.info(
303
- `pkg:put - Successfully wrote package meta file to sink - Pathname: ${pkgMetaPath}`,
314
+ `pkg:put - Successfully wrote package meta file to sink - Pathname: ${pkgMetaPath} - TraceId: ${traceId}`,
304
315
  );
305
316
  // eslint-disable-next-line no-unused-vars
306
317
  } catch (error) {
318
+ this._log.error(
319
+ `pkg:put - Failed to write package meta to sink - Name: ${pName} - Version: ${pVersion} - TraceId: ${traceId}`,
320
+ );
307
321
  const e = new HttpError.BadGateway();
308
322
  end({ labels: { success: false, status: e.status, type } });
309
323
  throw e;
@@ -315,6 +329,9 @@ const PkgPut = class PkgPut {
315
329
  await this._writeVersions(incoming, versions);
316
330
  // eslint-disable-next-line no-unused-vars
317
331
  } catch (error) {
332
+ this._log.error(
333
+ `pkg:put - Failed to write version meta to sink - Name: ${pName} - Version: ${pVersion} - TraceId: ${traceId}`,
334
+ );
318
335
  const e = new HttpError.BadGateway();
319
336
  end({ labels: { success: false, status: e.status, type } });
320
337
  throw e;
@@ -325,6 +342,10 @@ const PkgPut = class PkgPut {
325
342
  outgoing.statusCode = 303;
326
343
  outgoing.location = createURIPathToPkgLog(pkg);
327
344
 
345
+ this._log.info(
346
+ `pkg:put - Upload completed - Name: ${pName} - Version: ${pVersion} - TraceId: ${traceId}`,
347
+ );
348
+
328
349
  end({ labels: { status: outgoing.statusCode, type } });
329
350
 
330
351
  return outgoing;
@@ -148,8 +148,10 @@ const MultipartParser = class MultipartParser {
148
148
  */
149
149
  _handleFile({ fieldname, file, filename, incoming }) {
150
150
  return new Promise((resolve, reject) => {
151
+ const traceId = incoming.request?.traceId;
152
+
151
153
  this._log.info(
152
- `multipart - Start extracting package - Field: ${fieldname} - Filename: ${filename}`,
154
+ `multipart - Start extracting package - Field: ${fieldname} - Filename: ${filename} - Name: ${incoming.name} - Version: ${incoming.version} - TraceId: ${traceId}`,
153
155
  );
154
156
 
155
157
  const ac = new AbortController();
@@ -167,6 +169,10 @@ const MultipartParser = class MultipartParser {
167
169
  /** @type {import("tar").ReadEntry[]} */
168
170
  const pendingEntries = [];
169
171
 
172
+ let totalFiles = 0;
173
+ let writtenFiles = 0;
174
+ let failedFiles = 0;
175
+
170
176
  const drainPending = () => {
171
177
  while (
172
178
  runningWrites < MAX_CONCURRENT_WRITES &&
@@ -180,20 +186,34 @@ const MultipartParser = class MultipartParser {
180
186
  incoming,
181
187
  entry,
182
188
  signal: ac.signal,
183
- }).finally(() => {
189
+ });
190
+ // Track counts on the raw promise without altering its
191
+ // resolve/reject value so the queue still propagates
192
+ // errors to Promise.all and resolves with Asset objects.
193
+ persist
194
+ .then(() => {
195
+ writtenFiles++;
196
+ })
197
+ .catch(() => {
198
+ failedFiles++;
199
+ });
200
+ const tracked = persist.finally(() => {
184
201
  runningWrites--;
185
202
  drainPending();
186
203
  });
187
- persist.catch(() => {});
188
- queue.push(persist);
204
+ tracked.catch(() => {});
205
+ queue.push(tracked);
189
206
  }
190
207
  };
191
208
 
192
209
  file.once("limit", () => {
193
210
  this._log.warn(
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`,
211
+ `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 - TraceId: ${traceId}`,
195
212
  );
196
213
  ac.abort();
214
+ this._log.info(
215
+ `multipart - Upload aborted - Field: ${fieldname} - Running: ${runningWrites} - Pending: ${pendingEntries.length} - TraceId: ${traceId}`,
216
+ );
197
217
  file.emit("error", new HttpError.PayloadTooLarge());
198
218
  });
199
219
 
@@ -222,6 +242,9 @@ const MultipartParser = class MultipartParser {
222
242
  capturedEikJson = JSON.parse(
223
243
  Buffer.concat(chunks).toString("utf8"),
224
244
  );
245
+ this._log.debug(
246
+ `multipart - Found eik.json in tar - Field: ${fieldname} - TraceId: ${traceId}`,
247
+ );
225
248
  } catch {
226
249
  // Malformed eik.json in tar — proceed without it.
227
250
  }
@@ -235,6 +258,7 @@ const MultipartParser = class MultipartParser {
235
258
  // Holding an entry without piping it blocks the tar parser
236
259
  // at that position in the archive, creating natural
237
260
  // backpressure on the upstream upload stream.
261
+ totalFiles++;
238
262
  pendingEntries.push(entry);
239
263
  drainPending();
240
264
  },
@@ -243,7 +267,7 @@ const MultipartParser = class MultipartParser {
243
267
  pipeline(file, extract, (error) => {
244
268
  if (error) {
245
269
  this._log.info(
246
- `multipart - Uploaded package could not be extracted properly - Field: ${fieldname} - Filename: ${filename}`,
270
+ `multipart - Uploaded package could not be extracted properly - Field: ${fieldname} - Filename: ${filename} - Name: ${incoming.name} - Version: ${incoming.version} - Total: ${totalFiles} - Written: ${writtenFiles} - Failed: ${failedFiles} - TraceId: ${traceId}`,
247
271
  );
248
272
  this._log.trace(error);
249
273
 
@@ -272,8 +296,17 @@ const MultipartParser = class MultipartParser {
272
296
  return;
273
297
  }
274
298
 
299
+ if (!capturedEikJson) {
300
+ this._log.debug(
301
+ `multipart - No eik.json in tar, server will generate it - Field: ${fieldname} - TraceId: ${traceId}`,
302
+ );
303
+ }
304
+
275
305
  Promise.all(queue)
276
306
  .then((result) => {
307
+ this._log.info(
308
+ `multipart - Tar successfully unpacked - Field: ${fieldname} - Filename: ${filename} - Name: ${incoming.name} - Version: ${incoming.version} - Total: ${totalFiles} - Written: ${writtenFiles} - Failed: ${failedFiles} - TraceId: ${traceId}`,
309
+ );
277
310
  const formFile = new FormFile({
278
311
  name: fieldname,
279
312
  value: result,
@@ -292,6 +325,8 @@ const MultipartParser = class MultipartParser {
292
325
  * @param {{ incoming: any, entry: any, signal: AbortSignal }} options
293
326
  */
294
327
  async _persistFile({ incoming, entry, signal }) {
328
+ const traceId = incoming.request?.traceId;
329
+
295
330
  const asset = new Asset({
296
331
  pathname: entry.path,
297
332
  version: incoming.version,
@@ -301,60 +336,56 @@ const MultipartParser = class MultipartParser {
301
336
  });
302
337
  asset.size = entry.size;
303
338
 
304
- const path = createFilePathToAsset(asset);
339
+ const assetPath = createFilePathToAsset(asset);
305
340
 
306
- this._log.info(
307
- `multipart - Start writing asset to sink - Pathname: ${path}`,
341
+ this._log.debug(
342
+ `multipart - Start writing asset to sink - Pathname: ${assetPath} - TraceId: ${traceId}`,
308
343
  );
309
344
 
310
345
  if (!this._sink) {
311
346
  throw new Error("No sink configured");
312
347
  }
313
- const writer = await this._sink.write(path, asset.mimeType);
314
-
315
- const integrityStream = ssri.integrityStream({ single: true });
316
- let hash = "";
317
- integrityStream.once("integrity", (/** @type {any} */ integrity) => {
318
- hash = integrity;
319
- });
320
348
 
321
- return new Promise((resolve, reject) => {
322
- const onAbort = () => {
323
- entry.destroy();
324
- integrityStream.destroy();
325
- writer.destroy();
326
- };
349
+ if (signal.aborted) {
350
+ entry.resume();
351
+ throw new Error("Aborted");
352
+ }
327
353
 
354
+ // Buffer all chunks from the tar entry — no pipeline or stream Transform needed
355
+ const chunks = [];
356
+ for await (const chunk of entry) {
328
357
  if (signal.aborted) {
329
- onAbort();
330
- reject(new Error("Aborted"));
331
- return;
358
+ throw new Error("Aborted");
332
359
  }
360
+ chunks.push(chunk);
361
+ }
362
+ const buffer = Buffer.concat(chunks);
333
363
 
334
- signal.addEventListener("abort", onAbort, { once: true });
335
-
336
- pipeline(entry, integrityStream, writer, (error) => {
337
- signal.removeEventListener("abort", onAbort);
338
-
339
- if (error) {
340
- this._log.error(
341
- `multipart - Failed writing asset to sink - Pathname: ${path}`,
342
- );
343
- this._log.trace(error);
364
+ if (signal.aborted) {
365
+ throw new Error("Aborted");
366
+ }
344
367
 
345
- reject(error);
346
- return;
347
- }
368
+ // Compute integrity hash directly from the buffer
369
+ const integrity = ssri.fromData(buffer, { single: true });
370
+ asset.integrity = integrity.toString();
348
371
 
349
- asset.integrity = hash.toString();
372
+ try {
373
+ await this._sink.writeBuffer(assetPath, asset.mimeType, buffer, {
374
+ traceId,
375
+ });
376
+ } catch (error) {
377
+ this._log.error(
378
+ `multipart - Failed writing asset to sink - Pathname: ${assetPath} - TraceId: ${traceId}`,
379
+ );
380
+ this._log.trace(error);
381
+ throw error;
382
+ }
350
383
 
351
- this._log.info(
352
- `multipart - Successfully wrote asset to sink - Pathname: ${path}`,
353
- );
384
+ this._log.debug(
385
+ `multipart - Successfully wrote asset to sink - Pathname: ${assetPath} - TraceId: ${traceId}`,
386
+ );
354
387
 
355
- resolve(asset);
356
- });
357
- });
388
+ return asset;
358
389
  }
359
390
  };
360
391
  export default MultipartParser;
package/lib/sinks/test.js CHANGED
@@ -109,6 +109,63 @@ export default class SinkTest extends Sink {
109
109
 
110
110
  // Common SINK API
111
111
 
112
+ /**
113
+ * @param {string} filePath
114
+ * @param {string} contentType
115
+ * @param {Buffer} buffer
116
+ * @param {{ traceId?: string }} [options]
117
+ * @returns {Promise<void>}
118
+ */
119
+ // eslint-disable-next-line no-unused-vars
120
+ async writeBuffer(filePath, contentType, buffer, options = {}) {
121
+ const operation = "write";
122
+ try {
123
+ Sink.validateFilePath(filePath);
124
+ Sink.validateContentType(contentType);
125
+ } catch (error) {
126
+ this._counter.inc({ labels: { operation } });
127
+ throw error;
128
+ }
129
+ const pathname = toUrlPathname(path.join(this._rootPath, filePath));
130
+ if (pathname.indexOf(this._rootPath) !== 0) {
131
+ this._counter.inc({ labels: { operation } });
132
+ throw new Error(`Directory traversal - ${filePath}`);
133
+ }
134
+ const entry = new Entry({ mimeType: contentType, payload: [buffer] });
135
+ this._state.set(pathname, entry);
136
+ this._counter.inc({ labels: { success: true, access: true, operation } });
137
+ }
138
+
139
+ /**
140
+ * @param {string} filePath
141
+ * @returns {Promise<Buffer>}
142
+ */
143
+ async readBuffer(filePath) {
144
+ const operation = "read";
145
+ try {
146
+ Sink.validateFilePath(filePath);
147
+ } catch (error) {
148
+ this._counter.inc({ labels: { operation } });
149
+ throw error;
150
+ }
151
+ const pathname = toUrlPathname(path.join(this._rootPath, filePath));
152
+ if (pathname.indexOf(this._rootPath) !== 0) {
153
+ this._counter.inc({ labels: { operation } });
154
+ throw new Error(`Directory traversal - ${filePath}`);
155
+ }
156
+ const entry = this._state.get(pathname);
157
+ if (!entry) {
158
+ this._counter.inc({ labels: { access: true, operation } });
159
+ throw new Error(`${filePath} does not exist`);
160
+ }
161
+ this._counter.inc({ labels: { success: true, access: true, operation } });
162
+ return Buffer.concat(
163
+ (entry.payload || []).map((/** @type {any} */ item) =>
164
+ Buffer.isBuffer(item) ? item : Buffer.from(item),
165
+ ),
166
+ );
167
+ }
168
+
112
169
  /**
113
170
  * @param {string} filePath
114
171
  * @param {string} contentType
@@ -1,39 +1,14 @@
1
- import { Writable, Readable, pipeline } from "node:stream";
1
+ import { Writable, pipeline } from "node:stream";
2
2
 
3
3
  /**
4
4
  * @param {any} sink
5
5
  * @param {string} path
6
6
  */
7
- const readJSON = (sink, path) =>
8
- // eslint-disable-next-line no-async-promise-executor
9
- new Promise(async (resolve, reject) => {
10
- try {
11
- /** @type {any[]} */
12
- const buffer = [];
13
- const from = await sink.read(path);
14
-
15
- const to = new Writable({
16
- objectMode: false,
17
- write(chunk, encoding, callback) {
18
- buffer.push(chunk);
19
- callback();
20
- },
21
- });
7
+ const readJSON = async (sink, path) => {
8
+ const buffer = await sink.readBuffer(path);
9
+ return JSON.parse(buffer.toString("utf8"));
10
+ };
22
11
 
23
- pipeline(from.stream, to, (error) => {
24
- if (error) return reject(error);
25
- const str = Buffer.concat(buffer).toString("utf8");
26
- try {
27
- const obj = JSON.parse(str);
28
- return resolve(obj);
29
- } catch (err) {
30
- return reject(err);
31
- }
32
- });
33
- } catch (error) {
34
- reject(error);
35
- }
36
- });
37
12
  /**
38
13
  * @param {any} sink
39
14
  * @param {string} path
@@ -46,30 +21,12 @@ const readEikJson = (sink, path) => sink.exist(path);
46
21
  * @param {any} obj
47
22
  * @param {string} contentType
48
23
  */
49
- const writeJSON = (sink, path, obj, contentType) =>
50
- // eslint-disable-next-line no-async-promise-executor
51
- new Promise(async (resolve, reject) => {
52
- try {
53
- const buffer = Buffer.from(JSON.stringify(obj));
54
-
55
- const from = new Readable({
56
- objectMode: false,
57
- read() {
58
- this.push(buffer);
59
- this.push(null);
60
- },
61
- });
62
-
63
- const to = await sink.write(path, contentType);
24
+ const writeJSON = async (sink, path, obj, contentType) => {
25
+ const buffer = Buffer.from(JSON.stringify(obj));
26
+ await sink.writeBuffer(path, contentType, buffer);
27
+ return buffer;
28
+ };
64
29
 
65
- pipeline(from, to, (error) => {
66
- if (error) return reject(error);
67
- return resolve(buffer);
68
- });
69
- } catch (error) {
70
- reject(error);
71
- }
72
- });
73
30
  /**
74
31
  * @param {any} from
75
32
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eik/core",
3
- "version": "2.2.3",
3
+ "version": "2.4.0",
4
4
  "description": "Core server package",
5
5
  "main": "lib/main.js",
6
6
  "types": "./types/main.d.ts",
@@ -29,9 +29,9 @@
29
29
  "author": "",
30
30
  "license": "MIT",
31
31
  "dependencies": {
32
- "@eik/sink": "1.2.7",
33
- "@eik/sink-file-system": "2.2.0",
34
- "@eik/sink-memory": "2.2.2",
32
+ "@eik/sink": "1.4.0",
33
+ "@eik/sink-file-system": "2.4.0",
34
+ "@eik/sink-memory": "2.4.0",
35
35
  "@metrics/client": "2.5.5",
36
36
  "abslog": "2.4.4",
37
37
  "busboy": "1.6.0",
@@ -55,7 +55,36 @@ declare const AliasPost: {
55
55
  incoming: any;
56
56
  entry: any;
57
57
  signal: AbortSignal;
58
- }): Promise<any>;
58
+ }): Promise<{
59
+ _mimeType: string;
60
+ _type: string;
61
+ _size: number;
62
+ _integrity: string;
63
+ _pathname: string;
64
+ _version: string;
65
+ _name: string;
66
+ _org: string;
67
+ get integrity(): string;
68
+ set integrity(value: string);
69
+ get pathname(): string;
70
+ get mimeType(): string;
71
+ get version(): string;
72
+ get asset(): string;
73
+ get name(): string;
74
+ get type(): string;
75
+ set type(value: string);
76
+ get size(): number;
77
+ set size(value: number);
78
+ get org(): string;
79
+ toJSON(): {
80
+ integrity: string;
81
+ pathname: string;
82
+ mimeType: string;
83
+ type: string;
84
+ size: number;
85
+ };
86
+ get [Symbol.toStringTag](): string;
87
+ }>;
59
88
  get [Symbol.toStringTag](): string;
60
89
  };
61
90
  get metrics(): Metrics;
@@ -55,7 +55,36 @@ declare const AliasPut: {
55
55
  incoming: any;
56
56
  entry: any;
57
57
  signal: AbortSignal;
58
- }): Promise<any>;
58
+ }): Promise<{
59
+ _mimeType: string;
60
+ _type: string;
61
+ _size: number;
62
+ _integrity: string;
63
+ _pathname: string;
64
+ _version: string;
65
+ _name: string;
66
+ _org: string;
67
+ get integrity(): string;
68
+ set integrity(value: string);
69
+ get pathname(): string;
70
+ get mimeType(): string;
71
+ get version(): string;
72
+ get asset(): string;
73
+ get name(): string;
74
+ get type(): string;
75
+ set type(value: string);
76
+ get size(): number;
77
+ set size(value: number);
78
+ get org(): string;
79
+ toJSON(): {
80
+ integrity: string;
81
+ pathname: string;
82
+ mimeType: string;
83
+ type: string;
84
+ size: number;
85
+ };
86
+ get [Symbol.toStringTag](): string;
87
+ }>;
59
88
  get [Symbol.toStringTag](): string;
60
89
  };
61
90
  get metrics(): Metrics;
@@ -55,7 +55,36 @@ declare const AuthPost: {
55
55
  incoming: any;
56
56
  entry: any;
57
57
  signal: AbortSignal;
58
- }): Promise<any>;
58
+ }): Promise<{
59
+ _mimeType: string;
60
+ _type: string;
61
+ _size: number;
62
+ _integrity: string;
63
+ _pathname: string;
64
+ _version: string;
65
+ _name: string;
66
+ _org: string;
67
+ get integrity(): string;
68
+ set integrity(value: string);
69
+ get pathname(): string;
70
+ get mimeType(): string;
71
+ get version(): string;
72
+ get asset(): string;
73
+ get name(): string;
74
+ get type(): string;
75
+ set type(value: string);
76
+ get size(): number;
77
+ set size(value: number);
78
+ get org(): string;
79
+ toJSON(): {
80
+ integrity: string;
81
+ pathname: string;
82
+ mimeType: string;
83
+ type: string;
84
+ size: number;
85
+ };
86
+ get [Symbol.toStringTag](): string;
87
+ }>;
59
88
  get [Symbol.toStringTag](): string;
60
89
  };
61
90
  get metrics(): Metrics;
@@ -58,7 +58,36 @@ declare const PkgPut: {
58
58
  incoming: any;
59
59
  entry: any;
60
60
  signal: AbortSignal;
61
- }): Promise<any>;
61
+ }): Promise<{
62
+ _mimeType: string;
63
+ _type: string;
64
+ _size: number;
65
+ _integrity: string;
66
+ _pathname: string;
67
+ _version: string;
68
+ _name: string;
69
+ _org: string;
70
+ get integrity(): string;
71
+ set integrity(value: string);
72
+ get pathname(): string;
73
+ get mimeType(): string;
74
+ get version(): string;
75
+ get asset(): string;
76
+ get name(): string;
77
+ get type(): string;
78
+ set type(value: string);
79
+ get size(): number;
80
+ set size(value: number);
81
+ get org(): string;
82
+ toJSON(): {
83
+ integrity: string;
84
+ pathname: string;
85
+ mimeType: string;
86
+ type: string;
87
+ size: number;
88
+ };
89
+ get [Symbol.toStringTag](): string;
90
+ }>;
62
91
  get [Symbol.toStringTag](): string;
63
92
  };
64
93
  get metrics(): Metrics;
@@ -58,7 +58,36 @@ declare const MultipartParser: {
58
58
  incoming: any;
59
59
  entry: any;
60
60
  signal: AbortSignal;
61
- }): Promise<any>;
61
+ }): Promise<{
62
+ _mimeType: string;
63
+ _type: string;
64
+ _size: number;
65
+ _integrity: string;
66
+ _pathname: string;
67
+ _version: string;
68
+ _name: string;
69
+ _org: string;
70
+ get integrity(): string;
71
+ set integrity(value: string);
72
+ get pathname(): string;
73
+ get mimeType(): string;
74
+ get version(): string;
75
+ get asset(): string;
76
+ get name(): string;
77
+ get type(): string;
78
+ set type(value: string);
79
+ get size(): number;
80
+ set size(value: number);
81
+ get org(): string;
82
+ toJSON(): {
83
+ integrity: string;
84
+ pathname: string;
85
+ mimeType: string;
86
+ type: string;
87
+ size: number;
88
+ };
89
+ get [Symbol.toStringTag](): string;
90
+ }>;
62
91
  get [Symbol.toStringTag](): string;
63
92
  };
64
93
  };
@@ -9,7 +9,7 @@ export function readJSON(sink: any, path: string): Promise<any>;
9
9
  * @param {any} obj
10
10
  * @param {string} contentType
11
11
  */
12
- export function writeJSON(sink: any, path: string, obj: any, contentType: string): Promise<any>;
12
+ export function writeJSON(sink: any, path: string, obj: any, contentType: string): Promise<Buffer<ArrayBuffer>>;
13
13
  /**
14
14
  * @param {any} from
15
15
  */