@eik/core 2.3.0 → 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,10 @@
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
+
1
8
  # [2.3.0](https://github.com/eik-lib/core/compare/v2.2.3...v2.3.0) (2026-08-24)
2
9
 
3
10
 
@@ -345,53 +345,47 @@ const MultipartParser = class MultipartParser {
345
345
  if (!this._sink) {
346
346
  throw new Error("No sink configured");
347
347
  }
348
- const writer = await this._sink.write(assetPath, asset.mimeType, {
349
- traceId,
350
- });
351
-
352
- const integrityStream = ssri.integrityStream({ single: true });
353
- let hash = "";
354
- integrityStream.once("integrity", (/** @type {any} */ integrity) => {
355
- hash = integrity;
356
- });
357
348
 
358
- return new Promise((resolve, reject) => {
359
- const onAbort = () => {
360
- entry.destroy();
361
- integrityStream.destroy();
362
- writer.destroy();
363
- };
349
+ if (signal.aborted) {
350
+ entry.resume();
351
+ throw new Error("Aborted");
352
+ }
364
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) {
365
357
  if (signal.aborted) {
366
- onAbort();
367
- reject(new Error("Aborted"));
368
- return;
358
+ throw new Error("Aborted");
369
359
  }
360
+ chunks.push(chunk);
361
+ }
362
+ const buffer = Buffer.concat(chunks);
370
363
 
371
- signal.addEventListener("abort", onAbort, { once: true });
372
-
373
- pipeline(entry, integrityStream, writer, (error) => {
374
- signal.removeEventListener("abort", onAbort);
375
-
376
- if (error) {
377
- this._log.error(
378
- `multipart - Failed writing asset to sink - Pathname: ${assetPath} - TraceId: ${traceId}`,
379
- );
380
- this._log.trace(error);
364
+ if (signal.aborted) {
365
+ throw new Error("Aborted");
366
+ }
381
367
 
382
- reject(error);
383
- return;
384
- }
368
+ // Compute integrity hash directly from the buffer
369
+ const integrity = ssri.fromData(buffer, { single: true });
370
+ asset.integrity = integrity.toString();
385
371
 
386
- 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
+ }
387
383
 
388
- this._log.debug(
389
- `multipart - Successfully wrote asset to sink - Pathname: ${assetPath} - TraceId: ${traceId}`,
390
- );
384
+ this._log.debug(
385
+ `multipart - Successfully wrote asset to sink - Pathname: ${assetPath} - TraceId: ${traceId}`,
386
+ );
391
387
 
392
- resolve(asset);
393
- });
394
- });
388
+ return asset;
395
389
  }
396
390
  };
397
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.3.0",
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.3.0",
33
- "@eik/sink-file-system": "2.3.0",
34
- "@eik/sink-memory": "2.3.0",
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
  */