@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,237 +1,262 @@
1
- import { validators } from '@eik/common';
2
- import originalUrl from 'original-url';
3
- import HttpError from 'http-errors';
4
- import Busboy from 'busboy';
5
- import crypto from 'node:crypto';
6
- import abslog from 'abslog';
7
- import Metrics from '@metrics/client';
1
+ import { validators } from "@eik/common";
2
+ import originalUrl from "original-url";
3
+ import HttpError from "http-errors";
4
+ import Busboy from "busboy";
5
+ import crypto from "node:crypto";
6
+ import abslog from "abslog";
7
+ import Metrics from "@metrics/client";
8
8
 
9
9
  import {
10
- createFilePathToImportMap,
11
- createFilePathToVersion,
12
- } from '../utils/path-builders-fs.js';
13
- import { createURIPathToImportMap } from '../utils/path-builders-uri.js';
14
- import HttpIncoming from '../classes/http-incoming.js';
15
- import HttpOutgoing from '../classes/http-outgoing.js';
16
- import Versions from '../classes/versions.js';
17
- import Author from '../classes/author.js';
18
- import config from '../utils/defaults.js';
19
- import { decodeUriComponent, streamCollector, writeJSON, readJSON, } from '../utils/utils.js';
10
+ createFilePathToImportMap,
11
+ createFilePathToVersion,
12
+ } from "../utils/path-builders-fs.js";
13
+ import { createURIPathToImportMap } from "../utils/path-builders-uri.js";
14
+ import HttpIncoming from "../classes/http-incoming.js";
15
+ import HttpOutgoing from "../classes/http-outgoing.js";
16
+ import Versions from "../classes/versions.js";
17
+ import Author from "../classes/author.js";
18
+ import config from "../utils/defaults.js";
19
+ import {
20
+ decodeUriComponent,
21
+ streamCollector,
22
+ writeJSON,
23
+ readJSON,
24
+ } from "../utils/utils.js";
25
+
26
+ /**
27
+ * @typedef {object} MapPutOptions
28
+ * @property {number} [mapMaxFileSize]
29
+ * @property {string} [cacheControl]
30
+ * @property {Array<[string, string]>} [organizations] List of key-value pairs [hostname, organization]
31
+ * @property {import("@eik/sink").default} [sink]
32
+ * @property {import("abslog").AbstractLoggerOptions} [logger]
33
+ */
20
34
 
21
35
  const MapPut = class MapPut {
22
- constructor({
23
- mapMaxFileSize,
24
- organizations,
25
- cacheControl,
26
- logger,
27
- sink,
28
- } = {}) {
29
- this._mapMaxFileSize = mapMaxFileSize || config.mapMaxFileSize;
30
- this._organizations = organizations || config.organizations;
31
- this._cacheControl = cacheControl;
32
- this._sink = sink;
33
- this._log = abslog(logger);
34
- this._metrics = new Metrics();
35
- this._histogram = this._metrics.histogram({
36
- name: 'eik_core_map_put_handler',
37
- description:
38
- 'Histogram measuring time taken in @eik/core MapPut handler method',
39
- labels: {
40
- success: true,
41
- type: 'unknown'
42
- },
43
- buckets: [
44
- 0.005,
45
- 0.01,
46
- 0.06,
47
- 0.1,
48
- 0.6,
49
- 1.0,
50
- 2.0,
51
- 4.0,
52
- ],
53
- });
54
- this._orgRegistry = new Map(this._organizations);
55
- }
56
-
57
- get metrics() {
58
- return this._metrics;
59
- }
60
-
61
- _parser(incoming) {
62
- return new Promise((resolve, reject) => {
63
- const path = createFilePathToImportMap(incoming);
64
- const queue = [];
65
-
66
- const busboy = Busboy({
67
- headers: incoming.headers,
68
- limits: {
69
- fields: 0,
70
- files: 1,
71
- fileSize: this._mapMaxFileSize,
72
- },
73
- });
74
-
75
- busboy.on('file', (fieldname, file) => {
76
- queue.push(this._handleFile({
77
- fieldname,
78
- file,
79
- path,
80
- }));
81
- });
82
-
83
- busboy.on('close', () => {
84
- Promise.all(queue).then((items) => {
85
- // Resolve with only the first item in the array since
86
- // only one uploaded file is accepted
87
- resolve(items[0]);
88
- }).catch(error => {
89
- reject(error);
90
- });
91
- });
92
-
93
- busboy.on('error', error => {
94
- reject(error);
95
- });
96
-
97
- // If incoming.request is handeled by stream.pipeline, it will
98
- // close to early for the http framework to handle it. Let the
99
- // http framework handle closing incoming.request
100
- incoming.request.pipe(busboy);
101
- });
102
- }
103
-
104
- async _handleFile ({ fieldname, file, path }) {
105
- // We accept only one file on this given fieldname.
106
- // Throw if any other files is posted.
107
- if (fieldname !== 'map') {
108
- this._log.info(`map:put - Import map submitted on wrong field name - Field: ${fieldname}`);
109
- throw new HttpError.BadRequest();
110
- }
111
-
112
- const hasher = crypto.createHash('sha512');
113
-
114
- // Buffer up the incoming file and check if we can
115
- // parse it as JSON or not.
116
- let obj = {};
117
- try {
118
- const str = await streamCollector(file);
119
- hasher.update(str);
120
- obj = JSON.parse(str);
121
- } catch (error) {
122
- this._log.error(`map:put - Import map can not be parsed`);
123
- this._log.trace(error);
124
- throw new HttpError.UnsupportedMediaType();
125
- }
126
-
127
- // Write file to storage.
128
- try {
129
- this._log.info(`map:put - Start writing import map to sink - Pathname: ${path}`);
130
- await writeJSON(this._sink, path, obj, 'application/json');
131
- } catch (error) {
132
- this._log.error(`map:put - Failed writing import map to sink - Pathname: ${path}`);
133
- this._log.trace(error);
134
- throw new HttpError.BadGateway();
135
- }
136
-
137
- this._log.info(`map:put - Successfully wrote import map to sink - Pathname: ${path}`);
138
-
139
- return `sha512-${hasher.digest('base64')}`;
140
- }
141
-
142
- async _readVersions (incoming) {
143
- const path = createFilePathToVersion(incoming);
144
- let versions;
145
- try {
146
- const obj = await readJSON(this._sink, path);
147
- versions = new Versions(obj);
148
- this._log.info(
149
- `map:put - Successfully read version meta file from sink - Pathname: ${path}`,
150
- );
151
- } catch (error) {
152
- // File does not exist, its probably a new package
153
- versions = new Versions(incoming);
154
- this._log.info(
155
- `map:put - Version meta file did not exist in sink - Create new - Pathname: ${path}`,
156
- );
157
- }
158
- return versions;
159
- }
160
-
161
- async _writeVersions(incoming, versions) {
162
- const path = createFilePathToVersion(incoming);
163
- await writeJSON(this._sink, path, versions, 'application/json');
164
- this._log.info(
165
- `map:put - Successfully wrote version meta file to sink - Pathname: ${path}`,
166
- );
167
- }
168
-
169
- async handler(req, user, name, version) {
170
- const end = this._histogram.timer();
171
-
172
- const pVersion = decodeUriComponent(version);
173
- const pName = decodeUriComponent(name);
174
-
175
- try {
176
- validators.version(pVersion);
177
- validators.name(pName);
178
- } catch (error) {
179
- this._log.info(`map:put - Validation failed - ${error.message}`);
180
- const e = new HttpError.BadRequest();
181
- end({ labels: { success: false, status: e.status } });
182
- throw e;
183
- }
184
-
185
- const url = originalUrl(req);
186
- const org = this._orgRegistry.get(url.hostname);
187
-
188
- if (!org) {
189
- this._log.info(`map:put - Hostname does not match a configured organization - ${url.hostname}`);
190
- const e = new HttpError.BadRequest();
191
- end({ labels: { success: false, status: e.status, type: 'map' } });
192
- throw e;
193
- }
194
-
195
- const author = new Author(user);
196
-
197
- const incoming = new HttpIncoming(req, {
198
- type: 'map',
199
- version: pVersion,
200
- author,
201
- name: pName,
202
- org,
203
- });
204
-
205
- const versions = await this._readVersions(incoming);
206
-
207
- if (!versions.check(pVersion)) {
208
- this._log.info(
209
- `map:put - Semver version is lower than previous version of the package - Org: ${org} - Name: ${pName} - Version: ${pVersion}`,
210
- );
211
- const e = new HttpError.Conflict();
212
- end({ labels: { success: false, status: e.status, type: 'map' } });
213
- throw e;
214
- }
215
-
216
- const integrity = await this._parser(incoming);
217
- versions.setVersion(pVersion, integrity);
218
-
219
- try {
220
- await this._writeVersions(incoming, versions);
221
- } catch (error) {
222
- const e = new HttpError.BadGateway();
223
- end({ labels: { success: false, status: e.status, type: 'map' } });
224
- throw e;
225
- }
226
-
227
- const outgoing = new HttpOutgoing();
228
- outgoing.cacheControl = this._cacheControl;
229
- outgoing.statusCode = 303;
230
- outgoing.location = createURIPathToImportMap(incoming);
231
-
232
- end({ labels: { status: outgoing.statusCode, type: 'map' } });
233
-
234
- return outgoing;
235
- }
36
+ /**
37
+ *
38
+ * @param {MapPutOptions} options
39
+ */
40
+ constructor({
41
+ mapMaxFileSize,
42
+ organizations,
43
+ cacheControl,
44
+ logger,
45
+ sink,
46
+ } = {}) {
47
+ this._mapMaxFileSize = mapMaxFileSize || config.mapMaxFileSize;
48
+ this._organizations = organizations || config.organizations;
49
+ this._cacheControl = cacheControl;
50
+ this._sink = sink;
51
+ this._log = abslog(logger);
52
+ this._metrics = new Metrics();
53
+ this._histogram = this._metrics.histogram({
54
+ name: "eik_core_map_put_handler",
55
+ description:
56
+ "Histogram measuring time taken in @eik/core MapPut handler method",
57
+ labels: {
58
+ success: true,
59
+ type: "unknown",
60
+ },
61
+ buckets: [0.005, 0.01, 0.06, 0.1, 0.6, 1.0, 2.0, 4.0],
62
+ });
63
+ this._orgRegistry = new Map(this._organizations);
64
+ }
65
+
66
+ get metrics() {
67
+ return this._metrics;
68
+ }
69
+
70
+ _parser(incoming) {
71
+ return new Promise((resolve, reject) => {
72
+ const path = createFilePathToImportMap(incoming);
73
+ const queue = [];
74
+
75
+ const busboy = Busboy({
76
+ headers: incoming.headers,
77
+ limits: {
78
+ fields: 0,
79
+ files: 1,
80
+ fileSize: this._mapMaxFileSize,
81
+ },
82
+ });
83
+
84
+ busboy.on("file", (fieldname, file) => {
85
+ queue.push(
86
+ this._handleFile({
87
+ fieldname,
88
+ file,
89
+ path,
90
+ }),
91
+ );
92
+ });
93
+
94
+ busboy.on("close", () => {
95
+ Promise.all(queue)
96
+ .then((items) => {
97
+ // Resolve with only the first item in the array since
98
+ // only one uploaded file is accepted
99
+ resolve(items[0]);
100
+ })
101
+ .catch((error) => {
102
+ reject(error);
103
+ });
104
+ });
105
+
106
+ busboy.on("error", (error) => {
107
+ reject(error);
108
+ });
109
+
110
+ // If incoming.request is handeled by stream.pipeline, it will
111
+ // close to early for the http framework to handle it. Let the
112
+ // http framework handle closing incoming.request
113
+ incoming.request.pipe(busboy);
114
+ });
115
+ }
116
+
117
+ async _handleFile({ fieldname, file, path }) {
118
+ // We accept only one file on this given fieldname.
119
+ // Throw if any other files is posted.
120
+ if (fieldname !== "map") {
121
+ this._log.info(
122
+ `map:put - Import map submitted on wrong field name - Field: ${fieldname}`,
123
+ );
124
+ throw new HttpError.BadRequest();
125
+ }
126
+
127
+ const hasher = crypto.createHash("sha512");
128
+
129
+ // Buffer up the incoming file and check if we can
130
+ // parse it as JSON or not.
131
+ let obj = {};
132
+ try {
133
+ const str = await streamCollector(file);
134
+ hasher.update(str);
135
+ obj = JSON.parse(str);
136
+ } catch (error) {
137
+ this._log.error(`map:put - Import map can not be parsed`);
138
+ this._log.trace(error);
139
+ throw new HttpError.UnsupportedMediaType();
140
+ }
141
+
142
+ // Write file to storage.
143
+ try {
144
+ this._log.info(
145
+ `map:put - Start writing import map to sink - Pathname: ${path}`,
146
+ );
147
+ await writeJSON(this._sink, path, obj, "application/json");
148
+ } catch (error) {
149
+ this._log.error(
150
+ `map:put - Failed writing import map to sink - Pathname: ${path}`,
151
+ );
152
+ this._log.trace(error);
153
+ throw new HttpError.BadGateway();
154
+ }
155
+
156
+ this._log.info(
157
+ `map:put - Successfully wrote import map to sink - Pathname: ${path}`,
158
+ );
159
+
160
+ return `sha512-${hasher.digest("base64")}`;
161
+ }
162
+
163
+ async _readVersions(incoming) {
164
+ const path = createFilePathToVersion(incoming);
165
+ let versions;
166
+ try {
167
+ const obj = await readJSON(this._sink, path);
168
+ versions = new Versions(obj);
169
+ this._log.info(
170
+ `map:put - Successfully read version meta file from sink - Pathname: ${path}`,
171
+ );
172
+ // eslint-disable-next-line no-unused-vars
173
+ } catch (error) {
174
+ // File does not exist, its probably a new package
175
+ versions = new Versions(incoming);
176
+ this._log.info(
177
+ `map:put - Version meta file did not exist in sink - Create new - Pathname: ${path}`,
178
+ );
179
+ }
180
+ return versions;
181
+ }
182
+
183
+ async _writeVersions(incoming, versions) {
184
+ const path = createFilePathToVersion(incoming);
185
+ await writeJSON(this._sink, path, versions, "application/json");
186
+ this._log.info(
187
+ `map:put - Successfully wrote version meta file to sink - Pathname: ${path}`,
188
+ );
189
+ }
190
+
191
+ async handler(req, user, name, version) {
192
+ const end = this._histogram.timer();
193
+
194
+ const pVersion = decodeUriComponent(version);
195
+ const pName = decodeUriComponent(name);
196
+
197
+ try {
198
+ validators.version(pVersion);
199
+ validators.name(pName);
200
+ } catch (error) {
201
+ this._log.info(`map:put - Validation failed - ${error.message}`);
202
+ const e = new HttpError.BadRequest();
203
+ end({ labels: { success: false, status: e.status } });
204
+ throw e;
205
+ }
206
+
207
+ const url = originalUrl(req);
208
+ const org = this._orgRegistry.get(url.hostname);
209
+
210
+ if (!org) {
211
+ this._log.info(
212
+ `map:put - Hostname does not match a configured organization - ${url.hostname}`,
213
+ );
214
+ const e = new HttpError.BadRequest();
215
+ end({ labels: { success: false, status: e.status, type: "map" } });
216
+ throw e;
217
+ }
218
+
219
+ const author = new Author(user);
220
+
221
+ const incoming = new HttpIncoming(req, {
222
+ type: "map",
223
+ version: pVersion,
224
+ author,
225
+ name: pName,
226
+ org,
227
+ });
228
+
229
+ const versions = await this._readVersions(incoming);
230
+
231
+ if (!versions.check(pVersion)) {
232
+ this._log.info(
233
+ `map:put - Semver version is lower than previous version of the package - Org: ${org} - Name: ${pName} - Version: ${pVersion}`,
234
+ );
235
+ const e = new HttpError.Conflict();
236
+ end({ labels: { success: false, status: e.status, type: "map" } });
237
+ throw e;
238
+ }
239
+
240
+ const integrity = await this._parser(incoming);
241
+ versions.setVersion(pVersion, integrity);
242
+
243
+ try {
244
+ await this._writeVersions(incoming, versions);
245
+ // eslint-disable-next-line no-unused-vars
246
+ } catch (error) {
247
+ const e = new HttpError.BadGateway();
248
+ end({ labels: { success: false, status: e.status, type: "map" } });
249
+ throw e;
250
+ }
251
+
252
+ const outgoing = new HttpOutgoing();
253
+ outgoing.cacheControl = this._cacheControl;
254
+ outgoing.statusCode = 303;
255
+ outgoing.location = createURIPathToImportMap(incoming);
256
+
257
+ end({ labels: { status: outgoing.statusCode, type: "map" } });
258
+
259
+ return outgoing;
260
+ }
236
261
  };
237
262
  export default MapPut;