@eik/core 1.3.53 → 1.3.55

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.
Files changed (64) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/lib/classes/alias.js +48 -48
  3. package/lib/classes/asset.js +99 -92
  4. package/lib/classes/author.js +20 -20
  5. package/lib/classes/http-incoming.js +52 -52
  6. package/lib/classes/http-outgoing.js +84 -83
  7. package/lib/classes/meta.js +20 -23
  8. package/lib/classes/package.js +73 -70
  9. package/lib/classes/versions.js +62 -60
  10. package/lib/handlers/alias.delete.js +125 -120
  11. package/lib/handlers/alias.get.js +92 -87
  12. package/lib/handlers/alias.post.js +196 -203
  13. package/lib/handlers/alias.put.js +196 -202
  14. package/lib/handlers/auth.post.js +95 -93
  15. package/lib/handlers/map.get.js +110 -111
  16. package/lib/handlers/map.put.js +256 -231
  17. package/lib/handlers/pkg.get.js +120 -122
  18. package/lib/handlers/pkg.log.js +112 -110
  19. package/lib/handlers/pkg.put.js +223 -213
  20. package/lib/handlers/versions.get.js +92 -101
  21. package/lib/main.js +47 -47
  22. package/lib/multipart/form-field.js +20 -23
  23. package/lib/multipart/form-file.js +22 -24
  24. package/lib/multipart/parser.js +231 -217
  25. package/lib/sinks/mem-entry.js +26 -31
  26. package/lib/sinks/test.js +287 -273
  27. package/lib/utils/defaults.js +11 -11
  28. package/lib/utils/globals.js +5 -5
  29. package/lib/utils/healthcheck.js +131 -108
  30. package/lib/utils/path-builders-fs.js +61 -29
  31. package/lib/utils/path-builders-uri.js +26 -18
  32. package/lib/utils/utils.js +76 -79
  33. package/package.json +22 -17
  34. package/types/classes/alias.d.ts +28 -0
  35. package/types/classes/asset.d.ts +48 -0
  36. package/types/classes/author.d.ts +17 -0
  37. package/types/classes/http-incoming.d.ts +37 -0
  38. package/types/classes/http-outgoing.d.ts +20 -0
  39. package/types/classes/meta.d.ts +17 -0
  40. package/types/classes/package.d.ts +40 -0
  41. package/types/classes/versions.d.ts +28 -0
  42. package/types/handlers/alias.delete.d.ts +33 -0
  43. package/types/handlers/alias.get.d.ts +48 -0
  44. package/types/handlers/alias.post.d.ts +83 -0
  45. package/types/handlers/alias.put.d.ts +83 -0
  46. package/types/handlers/auth.post.d.ts +82 -0
  47. package/types/handlers/map.get.d.ts +51 -0
  48. package/types/handlers/map.put.d.ts +78 -0
  49. package/types/handlers/pkg.get.d.ts +51 -0
  50. package/types/handlers/pkg.log.d.ts +51 -0
  51. package/types/handlers/pkg.put.d.ts +107 -0
  52. package/types/handlers/versions.get.d.ts +48 -0
  53. package/types/main.d.ts +44 -0
  54. package/types/multipart/form-field.d.ts +17 -0
  55. package/types/multipart/form-file.d.ts +17 -0
  56. package/types/multipart/parser.d.ts +52 -0
  57. package/types/sinks/mem-entry.d.ts +15 -0
  58. package/types/sinks/test.d.ts +32 -0
  59. package/types/utils/defaults.d.ts +9 -0
  60. package/types/utils/globals.d.ts +8 -0
  61. package/types/utils/healthcheck.d.ts +24 -0
  62. package/types/utils/path-builders-fs.d.ts +41 -0
  63. package/types/utils/path-builders-uri.d.ts +26 -0
  64. package/types/utils/utils.d.ts +6 -0
@@ -1,220 +1,234 @@
1
- import { pipeline } from 'node:stream';
2
- import HttpError from 'http-errors';
3
- import Busboy from 'busboy';
4
- import abslog from 'abslog';
5
- import ssri from 'ssri';
6
- import tar from 'tar';
7
-
8
- import { createFilePathToAsset } from '../utils/path-builders-fs.js';
9
- import FormField from './form-field.js';
10
- import FormFile from './form-file.js';
11
- import Asset from '../classes/asset.js';
1
+ import { pipeline } from "node:stream";
2
+ import HttpError from "http-errors";
3
+ import Busboy from "busboy";
4
+ import abslog from "abslog";
5
+ import ssri from "ssri";
6
+ import tar from "tar";
7
+
8
+ import { createFilePathToAsset } from "../utils/path-builders-fs.js";
9
+ import FormField from "./form-field.js";
10
+ import FormFile from "./form-file.js";
11
+ import Asset from "../classes/asset.js";
12
+
13
+ /**
14
+ * @typedef {object} MultipartParserOptions
15
+ * @property {number} [pkgMaxFileSize=10000000]
16
+ * @property {string[]} [legalFields]
17
+ * @property {string[]} [legalFiles]
18
+ * @property {import("@eik/sink").default} [sink]
19
+ * @property {import("abslog").AbstractLoggerOptions} [logger]
20
+ */
12
21
 
13
22
  const MultipartParser = class MultipartParser {
14
- constructor({
15
- pkgMaxFileSize,
16
- legalFields,
17
- legalFiles,
18
- logger,
19
- sink
20
- } = {}) {
21
- this._pkgMaxFileSize = pkgMaxFileSize;
22
- this._legalFields = legalFields || [];
23
- this._legalFiles = legalFiles || [];
24
- this._sink = sink;
25
- this._log = abslog(logger);
26
- }
27
-
28
- get [Symbol.toStringTag]() {
29
- return 'MultipartParser';
30
- }
31
-
32
- parse(incoming) {
33
- return new Promise((resolve, reject) => {
34
- const queue = [];
35
-
36
- const busboy = Busboy({
37
- headers: incoming.headers,
38
- limits: {
39
- fileSize: this._pkgMaxFileSize,
40
- fields: this._legalFields.length,
41
- files: this._legalFiles.length,
42
- },
43
- });
44
-
45
- busboy.on('field', (name, value) => {
46
- if (!this._legalFields.includes(name.toLowerCase())) {
47
- busboy.emit('error', new HttpError.BadRequest());
48
- return;
49
- }
50
-
51
- queue.push(this._handleField({
52
- value,
53
- name,
54
- }));
55
- });
56
-
57
- busboy.on('file', (fieldname, file, filename) => {
58
- if (!this._legalFiles.includes(fieldname.toLowerCase())) {
59
- busboy.emit('error', new HttpError.BadRequest());
60
- return;
61
- }
62
-
63
- queue.push(new Promise((done) => {
64
- this._handleFile({
65
- fieldname,
66
- file,
67
- filename,
68
- incoming,
69
- }).then((item) => {
70
- done(item);
71
- }).catch((error) => {
72
- // Emit an error on busboy instead of rejecting
73
- // This will break and terminate the stream stright away
74
- busboy.emit('error', error);
75
- });
76
- }));
77
- });
78
-
79
- busboy.once('error', (error) => {
80
- reject(error);
81
- });
82
-
83
- busboy.once('close', () => {
84
- Promise.all(queue).then((items) => {
85
- resolve(items);
86
- }).catch((error) => {
87
- reject(error);
88
- });
89
- });
90
-
91
- // If incoming.request is handeled by pipeline, it will close
92
- // to early for the http framework to handle it. Let the
93
- // http framework handle closing incoming.request
94
- incoming.request.pipe(busboy);
95
- })
96
- }
97
-
98
- _handleField({ name, value }) {
99
- this._log.info(
100
- `multipart - Input field added - Name: ${name} - Value: ${value}`,
101
- );
102
- return new FormField({ name, value });
103
- }
104
-
105
- _handleFile({ fieldname, file, filename, incoming }) {
106
- return new Promise((resolve, reject) => {
107
- this._log.info(
108
- `multipart - Start extracting package - Field: ${fieldname} - Filename: ${filename}`,
109
- );
110
-
111
- const queue = [];
112
-
113
- file.once('limit', () => {
114
- this._log.info(
115
- `multipart - Uploaded package exceeds legal file size limit - Field: ${fieldname} - Filename: ${filename}`,
116
- );
117
- file.emit('error', new HttpError.PayloadTooLarge());
118
- });
119
-
120
- const extract = new tar.Parse({
121
- strict: true,
122
- onentry: (entry) => {
123
- // Entries not supported must be thrown
124
- // away for extraction to continue
125
- if (entry.type.toLowerCase() !== 'file') {
126
- entry.resume();
127
- return;
128
- }
129
- queue.push(this._persistFile({ incoming, entry }));
130
- },
131
- });
132
-
133
-
134
- pipeline(file, extract, (error) => {
135
- if (error) {
136
- this._log.info(`multipart - Uploaded package could not be extracted properly - Field: ${fieldname} - Filename: ${filename}`)
137
- this._log.trace(error);
138
-
139
- switch (error.code) {
140
- case 'TAR_BAD_ARCHIVE':
141
- case 'TAR_ABORT':
142
- reject(new HttpError.UnsupportedMediaType());
143
- break;
144
- case 'TAR_ENTRY_UNSUPPORTED':
145
- case 'TAR_ENTRY_INVALID':
146
- case 'TAR_ENTRY_ERROR':
147
- reject(new HttpError.UnprocessableEntity());
148
- break;
149
- default:
150
- reject(error);
151
- }
152
- return;
153
- }
154
-
155
- Promise.all(queue).then((result) => {
156
- const formFile = new FormFile({ name: fieldname, value: result })
157
- resolve(formFile);
158
- }).catch((err) => {
159
- reject(err);
160
- });
161
- });
162
- });
163
- }
164
-
165
- _persistFile({ incoming, entry }) {
166
- // eslint-disable-next-line no-async-promise-executor
167
- return new Promise(async (resolve, reject) => {
168
- const asset = new Asset({
169
- pathname: entry.path,
170
- version: incoming.version,
171
- name: incoming.name,
172
- type: incoming.type,
173
- org: incoming.org,
174
- });
175
- asset.size = entry.size;
176
-
177
- const path = createFilePathToAsset(asset);
178
-
179
- this._log.info(
180
- `multipart - Start writing asset to sink - Pathname: ${path}`,
181
- );
182
-
183
- try {
184
- const writer = await this._sink.write(
185
- path,
186
- asset.mimeType,
187
- );
188
-
189
- const integrityStream = ssri.integrityStream({ single: true });
190
- let hash = '';
191
- integrityStream.once('integrity', (integrity) => {
192
- hash = integrity;
193
- });
194
-
195
- pipeline(entry, integrityStream, writer, error => {
196
- if (error) {
197
- this._log.error(
198
- `multipart - Failed writing asset to sink - Pathname: ${path}`,
199
- );
200
- this._log.trace(error);
201
-
202
- reject(error);
203
- return;
204
- }
205
-
206
- asset.integrity = hash.toString();
207
-
208
- this._log.info(
209
- `multipart - Successfully wrote asset to sink - Pathname: ${path}`,
210
- );
211
-
212
- resolve(asset);
213
- });
214
- } catch(error) {
215
- reject(error);
216
- }
217
- });
218
- }
219
- }
23
+ /**
24
+ * @param {MultipartParserOptions} options
25
+ */
26
+ constructor({ pkgMaxFileSize, legalFields, legalFiles, logger, sink } = {}) {
27
+ this._pkgMaxFileSize = pkgMaxFileSize;
28
+ this._legalFields = legalFields || [];
29
+ this._legalFiles = legalFiles || [];
30
+ this._sink = sink;
31
+ this._log = abslog(logger);
32
+ }
33
+
34
+ get [Symbol.toStringTag]() {
35
+ return "MultipartParser";
36
+ }
37
+
38
+ parse(incoming) {
39
+ return new Promise((resolve, reject) => {
40
+ const queue = [];
41
+
42
+ const busboy = Busboy({
43
+ headers: incoming.headers,
44
+ limits: {
45
+ fileSize: this._pkgMaxFileSize,
46
+ fields: this._legalFields.length,
47
+ files: this._legalFiles.length,
48
+ },
49
+ });
50
+
51
+ busboy.on("field", (name, value) => {
52
+ if (!this._legalFields.includes(name.toLowerCase())) {
53
+ busboy.emit("error", new HttpError.BadRequest());
54
+ return;
55
+ }
56
+
57
+ queue.push(
58
+ this._handleField({
59
+ value,
60
+ name,
61
+ }),
62
+ );
63
+ });
64
+
65
+ busboy.on("file", (fieldname, file, filename) => {
66
+ if (!this._legalFiles.includes(fieldname.toLowerCase())) {
67
+ busboy.emit("error", new HttpError.BadRequest());
68
+ return;
69
+ }
70
+
71
+ queue.push(
72
+ new Promise((done) => {
73
+ this._handleFile({
74
+ fieldname,
75
+ file,
76
+ filename,
77
+ incoming,
78
+ })
79
+ .then((item) => {
80
+ done(item);
81
+ })
82
+ .catch((error) => {
83
+ // Emit an error on busboy instead of rejecting
84
+ // This will break and terminate the stream stright away
85
+ busboy.emit("error", error);
86
+ });
87
+ }),
88
+ );
89
+ });
90
+
91
+ busboy.once("error", (error) => {
92
+ reject(error);
93
+ });
94
+
95
+ busboy.once("close", () => {
96
+ Promise.all(queue)
97
+ .then((items) => {
98
+ resolve(items);
99
+ })
100
+ .catch((error) => {
101
+ reject(error);
102
+ });
103
+ });
104
+
105
+ // If incoming.request is handeled by pipeline, it will close
106
+ // to early for the http framework to handle it. Let the
107
+ // http framework handle closing incoming.request
108
+ incoming.request.pipe(busboy);
109
+ });
110
+ }
111
+
112
+ _handleField({ name, value }) {
113
+ this._log.info(
114
+ `multipart - Input field added - Name: ${name} - Value: ${value}`,
115
+ );
116
+ return new FormField({ name, value });
117
+ }
118
+
119
+ _handleFile({ fieldname, file, filename, incoming }) {
120
+ return new Promise((resolve, reject) => {
121
+ this._log.info(
122
+ `multipart - Start extracting package - Field: ${fieldname} - Filename: ${filename}`,
123
+ );
124
+
125
+ const queue = [];
126
+
127
+ file.once("limit", () => {
128
+ this._log.info(
129
+ `multipart - Uploaded package exceeds legal file size limit - Field: ${fieldname} - Filename: ${filename}`,
130
+ );
131
+ file.emit("error", new HttpError.PayloadTooLarge());
132
+ });
133
+
134
+ const extract = new tar.Parse({
135
+ strict: true,
136
+ onentry: (entry) => {
137
+ // Entries not supported must be thrown
138
+ // away for extraction to continue
139
+ if (entry.type.toLowerCase() !== "file") {
140
+ entry.resume();
141
+ return;
142
+ }
143
+ queue.push(this._persistFile({ incoming, entry }));
144
+ },
145
+ });
146
+
147
+ pipeline(file, extract, (error) => {
148
+ if (error) {
149
+ this._log.info(
150
+ `multipart - Uploaded package could not be extracted properly - Field: ${fieldname} - Filename: ${filename}`,
151
+ );
152
+ this._log.trace(error);
153
+
154
+ switch (error.code) {
155
+ case "TAR_BAD_ARCHIVE":
156
+ case "TAR_ABORT":
157
+ reject(new HttpError.UnsupportedMediaType());
158
+ break;
159
+ case "TAR_ENTRY_UNSUPPORTED":
160
+ case "TAR_ENTRY_INVALID":
161
+ case "TAR_ENTRY_ERROR":
162
+ reject(new HttpError.UnprocessableEntity());
163
+ break;
164
+ default:
165
+ reject(error);
166
+ }
167
+ return;
168
+ }
169
+
170
+ Promise.all(queue)
171
+ .then((result) => {
172
+ const formFile = new FormFile({ name: fieldname, value: result });
173
+ resolve(formFile);
174
+ })
175
+ .catch((err) => {
176
+ reject(err);
177
+ });
178
+ });
179
+ });
180
+ }
181
+
182
+ _persistFile({ incoming, entry }) {
183
+ // eslint-disable-next-line no-async-promise-executor
184
+ return new Promise(async (resolve, reject) => {
185
+ const asset = new Asset({
186
+ pathname: entry.path,
187
+ version: incoming.version,
188
+ name: incoming.name,
189
+ type: incoming.type,
190
+ org: incoming.org,
191
+ });
192
+ asset.size = entry.size;
193
+
194
+ const path = createFilePathToAsset(asset);
195
+
196
+ this._log.info(
197
+ `multipart - Start writing asset to sink - Pathname: ${path}`,
198
+ );
199
+
200
+ try {
201
+ const writer = await this._sink.write(path, asset.mimeType);
202
+
203
+ const integrityStream = ssri.integrityStream({ single: true });
204
+ let hash = "";
205
+ integrityStream.once("integrity", (integrity) => {
206
+ hash = integrity;
207
+ });
208
+
209
+ pipeline(entry, integrityStream, writer, (error) => {
210
+ if (error) {
211
+ this._log.error(
212
+ `multipart - Failed writing asset to sink - Pathname: ${path}`,
213
+ );
214
+ this._log.trace(error);
215
+
216
+ reject(error);
217
+ return;
218
+ }
219
+
220
+ asset.integrity = hash.toString();
221
+
222
+ this._log.info(
223
+ `multipart - Successfully wrote asset to sink - Pathname: ${path}`,
224
+ );
225
+
226
+ resolve(asset);
227
+ });
228
+ } catch (error) {
229
+ reject(error);
230
+ }
231
+ });
232
+ }
233
+ };
220
234
  export default MultipartParser;
@@ -1,40 +1,35 @@
1
- import crypto from 'node:crypto';
1
+ import crypto from "node:crypto";
2
2
 
3
3
  const Entry = class Entry {
4
- constructor({
5
- mimeType = 'application/octet-stream',
6
- payload = [],
7
- } = {}) {
8
- this._mimeType = mimeType;
9
- this._payload = payload;
10
- this._hash = '';
4
+ constructor({ mimeType = "application/octet-stream", payload = [] } = {}) {
5
+ this._mimeType = mimeType;
6
+ this._payload = payload;
7
+ this._hash = "";
11
8
 
12
- if (Array.isArray(payload)) {
13
- const hash = crypto.createHash('sha512');
14
- payload.forEach(buffer => {
15
- hash.update(buffer.toString());
16
- });
17
- this._hash = `sha512-${hash.digest(
18
- 'base64',
19
- )}`;
20
- }
21
- }
9
+ if (Array.isArray(payload)) {
10
+ const hash = crypto.createHash("sha512");
11
+ payload.forEach((buffer) => {
12
+ hash.update(buffer.toString());
13
+ });
14
+ this._hash = `sha512-${hash.digest("base64")}`;
15
+ }
16
+ }
22
17
 
23
- get mimeType() {
24
- return this._mimeType;
25
- }
18
+ get mimeType() {
19
+ return this._mimeType;
20
+ }
26
21
 
27
- get payload() {
28
- return this._payload;
29
- }
22
+ get payload() {
23
+ return this._payload;
24
+ }
30
25
 
31
- get hash() {
32
- return this._hash;
33
- }
26
+ get hash() {
27
+ return this._hash;
28
+ }
34
29
 
35
- get [Symbol.toStringTag]() {
36
- return 'Entry';
37
- }
38
- }
30
+ get [Symbol.toStringTag]() {
31
+ return "Entry";
32
+ }
33
+ };
39
34
 
40
35
  export default Entry;