@kaito-http/core 2.5.0 → 2.7.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.
@@ -1,8 +1,11 @@
1
1
  import { TLSSocket } from 'node:tls';
2
2
  import { parse } from 'content-type';
3
+ import { Readable } from 'node:stream';
4
+ import { json } from 'node:stream/consumers';
3
5
  import getRawBody from 'raw-body';
4
6
  import { serialize } from 'cookie';
5
7
  import fmw from 'find-my-way';
8
+ import { z } from 'zod';
6
9
  import * as http from 'node:http';
7
10
 
8
11
  class WrappedError extends Error {
@@ -10,26 +13,21 @@ class WrappedError extends Error {
10
13
  if (maybeError instanceof Error) {
11
14
  return maybeError;
12
15
  }
13
-
14
16
  return WrappedError.from(maybeError);
15
17
  }
16
-
17
18
  static from(data) {
18
19
  return new WrappedError(data);
19
20
  }
20
-
21
21
  constructor(data) {
22
22
  super('Something was thrown, but it was not an instance of Error, so a WrappedError was created.');
23
23
  this.data = data;
24
24
  }
25
-
26
25
  }
27
26
  class KaitoError extends Error {
28
27
  constructor(status, message) {
29
28
  super(message);
30
29
  this.status = status;
31
30
  }
32
-
33
31
  }
34
32
 
35
33
  function _defineProperty(obj, key, value) {
@@ -43,7 +41,6 @@ function _defineProperty(obj, key, value) {
43
41
  } else {
44
42
  obj[key] = value;
45
43
  }
46
-
47
44
  return obj;
48
45
  }
49
46
 
@@ -55,29 +52,24 @@ function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
55
52
  reject(error);
56
53
  return;
57
54
  }
58
-
59
55
  if (info.done) {
60
56
  resolve(value);
61
57
  } else {
62
58
  Promise.resolve(value).then(_next, _throw);
63
59
  }
64
60
  }
65
-
66
61
  function _asyncToGenerator(fn) {
67
62
  return function () {
68
63
  var self = this,
69
- args = arguments;
64
+ args = arguments;
70
65
  return new Promise(function (resolve, reject) {
71
66
  var gen = fn.apply(self, args);
72
-
73
67
  function _next(value) {
74
68
  asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
75
69
  }
76
-
77
70
  function _throw(err) {
78
71
  asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
79
72
  }
80
-
81
73
  _next(undefined);
82
74
  });
83
75
  };
@@ -91,154 +83,124 @@ function getLastEntryInMultiHeaderValue(headerValue) {
91
83
  var lastIndex = normalized.lastIndexOf(',');
92
84
  return lastIndex === -1 ? normalized.trim() : normalized.slice(lastIndex + 1).trim();
93
85
  }
94
- function getInput(_x) {
95
- return _getInput.apply(this, arguments);
86
+ function getBody(_x) {
87
+ return _getBody.apply(this, arguments);
96
88
  }
97
-
98
- function _getInput() {
99
- _getInput = _asyncToGenerator(function* (req) {
100
- if (req.method === 'GET') {
101
- var input = req.url.searchParams.get('input');
102
-
103
- if (!input) {
104
- return null;
105
- }
106
-
107
- return JSON.parse(input);
108
- }
109
-
110
- var buffer = yield getRawBody(req.raw);
111
-
89
+ function _getBody() {
90
+ _getBody = _asyncToGenerator(function* (req) {
112
91
  if (!req.headers['content-type']) {
113
92
  return null;
114
93
  }
115
-
94
+ var buffer = yield getRawBody(req.raw);
116
95
  var {
117
96
  type
118
97
  } = parse(req.headers['content-type']);
119
-
120
98
  switch (type) {
121
99
  case 'application/json':
122
100
  {
123
- return JSON.parse(buffer.toString());
101
+ return json(Readable.from(buffer));
124
102
  }
125
-
126
103
  default:
127
104
  {
128
105
  if (process.env.NODE_ENV === 'development') {
129
106
  console.warn('[kaito] Unsupported content type:', type);
130
107
  console.warn('[kaito] This message is only shown in development mode.');
131
108
  }
132
-
133
109
  return null;
134
110
  }
135
111
  }
136
112
  });
137
- return _getInput.apply(this, arguments);
113
+ return _getBody.apply(this, arguments);
138
114
  }
139
115
 
140
116
  class KaitoRequest {
141
117
  constructor(raw) {
142
118
  _defineProperty(this, "_url", null);
143
-
144
119
  this.raw = raw;
145
120
  }
121
+
146
122
  /**
147
123
  * The full URL of the request, including the protocol, hostname, and path.
148
124
  * Note: does not include the query string or hash
149
125
  */
150
-
151
-
152
126
  get fullURL() {
153
127
  var _this$raw$url;
154
-
155
128
  return "".concat(this.protocol, "://").concat(this.hostname).concat((_this$raw$url = this.raw.url) !== null && _this$raw$url !== void 0 ? _this$raw$url : '');
156
129
  }
130
+
157
131
  /**
158
132
  * A new URL instance for the full URL of the request.
159
133
  */
160
-
161
-
162
134
  get url() {
163
135
  if (this._url) {
164
136
  return this._url;
165
137
  }
166
-
167
138
  this._url = new URL(this.fullURL);
168
139
  return this._url;
169
140
  }
141
+
170
142
  /**
171
143
  * The HTTP method of the request.
172
144
  */
173
-
174
-
175
145
  get method() {
176
146
  if (!this.raw.method) {
177
147
  throw new Error('Request method is not defined, somehow...');
178
148
  }
179
-
180
149
  return this.raw.method;
181
150
  }
151
+
182
152
  /**
183
153
  * The protocol of the request, either `http` or `https`.
184
154
  */
185
-
186
-
187
155
  get protocol() {
188
156
  if (this.raw.socket instanceof TLSSocket) {
189
157
  return this.raw.socket.encrypted ? 'https' : 'http';
190
158
  }
191
-
192
159
  return 'http';
193
160
  }
161
+
194
162
  /**
195
163
  * The request headers
196
164
  */
197
-
198
-
199
165
  get headers() {
200
166
  return this.raw.headers;
201
167
  }
168
+
202
169
  /**
203
170
  * The hostname of the request.
204
171
  */
205
-
206
-
207
172
  get hostname() {
208
173
  var _this$raw$headers$hos, _this$raw$headers$Au;
209
-
210
174
  return (_this$raw$headers$hos = this.raw.headers.host) !== null && _this$raw$headers$hos !== void 0 ? _this$raw$headers$hos : getLastEntryInMultiHeaderValue((_this$raw$headers$Au = this.raw.headers[':authority']) !== null && _this$raw$headers$Au !== void 0 ? _this$raw$headers$Au : []);
211
175
  }
212
-
213
176
  }
214
177
 
215
178
  class KaitoResponse {
216
179
  constructor(raw) {
217
180
  this.raw = raw;
218
181
  }
182
+
219
183
  /**
220
184
  * Send a response
221
185
  * @param key The key of the header
222
186
  * @param value The value of the header
223
187
  * @returns The response object
224
188
  */
225
-
226
-
227
189
  header(key, value) {
228
190
  this.raw.setHeader(key, value);
229
191
  return this;
230
192
  }
193
+
231
194
  /**
232
195
  * Set the status code of the response
233
196
  * @param code The status code
234
197
  * @returns The response object
235
198
  */
236
-
237
-
238
199
  status(code) {
239
200
  this.raw.statusCode = code;
240
201
  return this;
241
202
  }
203
+
242
204
  /**
243
205
  * Set a cookie
244
206
  * @param name The name of the cookie
@@ -246,19 +208,16 @@ class KaitoResponse {
246
208
  * @param options The options for the cookie
247
209
  * @returns The response object
248
210
  */
249
-
250
-
251
211
  cookie(name, value, options) {
252
212
  this.raw.setHeader('Set-Cookie', serialize(name, value, options));
253
213
  return this;
254
214
  }
215
+
255
216
  /**
256
217
  * Send a JSON APIResponse body
257
218
  * @param data The data to send
258
219
  * @returns The response object
259
220
  */
260
-
261
-
262
221
  json(data) {
263
222
  var json = JSON.stringify(data);
264
223
  this.raw.setHeader('Content-Type', 'application/json');
@@ -266,66 +225,112 @@ class KaitoResponse {
266
225
  this.raw.end(json);
267
226
  return this;
268
227
  }
269
-
270
228
  }
271
229
 
272
230
  function ownKeys(object, enumerableOnly) {
273
231
  var keys = Object.keys(object);
274
-
275
232
  if (Object.getOwnPropertySymbols) {
276
233
  var symbols = Object.getOwnPropertySymbols(object);
277
-
278
- if (enumerableOnly) {
279
- symbols = symbols.filter(function (sym) {
280
- return Object.getOwnPropertyDescriptor(object, sym).enumerable;
281
- });
282
- }
283
-
284
- keys.push.apply(keys, symbols);
234
+ enumerableOnly && (symbols = symbols.filter(function (sym) {
235
+ return Object.getOwnPropertyDescriptor(object, sym).enumerable;
236
+ })), keys.push.apply(keys, symbols);
285
237
  }
286
-
287
238
  return keys;
288
239
  }
289
-
290
240
  function _objectSpread2(target) {
291
241
  for (var i = 1; i < arguments.length; i++) {
292
- var source = arguments[i] != null ? arguments[i] : {};
293
-
294
- if (i % 2) {
295
- ownKeys(Object(source), true).forEach(function (key) {
296
- _defineProperty(target, key, source[key]);
297
- });
298
- } else if (Object.getOwnPropertyDescriptors) {
299
- Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
300
- } else {
301
- ownKeys(Object(source)).forEach(function (key) {
302
- Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
303
- });
304
- }
242
+ var source = null != arguments[i] ? arguments[i] : {};
243
+ i % 2 ? ownKeys(Object(source), !0).forEach(function (key) {
244
+ _defineProperty(target, key, source[key]);
245
+ }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) {
246
+ Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
247
+ });
305
248
  }
306
-
307
249
  return target;
308
250
  }
309
251
 
310
252
  class Router {
253
+ static handle(server, route, options) {
254
+ return _asyncToGenerator(function* () {
255
+ try {
256
+ var _yield$route$body$par, _route$body;
257
+ var ctx = yield server.getContext(options.req, options.res);
258
+ var body = (_yield$route$body$par = yield (_route$body = route.body) === null || _route$body === void 0 ? void 0 : _route$body.parse(yield getBody(options.req))) !== null && _yield$route$body$par !== void 0 ? _yield$route$body$par : undefined;
259
+ var query = route.query ? z.object(route.query).parse(Object.fromEntries(options.req.url.searchParams.entries())) : {};
260
+ var result = yield route.run({
261
+ ctx,
262
+ body,
263
+ query,
264
+ params: options.params
265
+ });
266
+ options.res.status(200).json({
267
+ success: true,
268
+ data: result,
269
+ message: 'OK'
270
+ });
271
+ return {
272
+ success: true,
273
+ data: result
274
+ };
275
+ } catch (e) {
276
+ var error = WrappedError.maybe(e);
277
+ if (error instanceof KaitoError) {
278
+ options.res.status(error.status).json({
279
+ success: false,
280
+ data: null,
281
+ message: error.message
282
+ });
283
+ return;
284
+ }
285
+ var {
286
+ status,
287
+ message
288
+ } = yield server.onError({
289
+ error,
290
+ req: options.req,
291
+ res: options.res
292
+ }).catch(() => ({
293
+ status: 500,
294
+ message: 'Internal Server Error'
295
+ }));
296
+ options.res.status(status).json({
297
+ success: false,
298
+ data: null,
299
+ message
300
+ });
301
+ return {
302
+ success: false,
303
+ data: {
304
+ status,
305
+ message
306
+ }
307
+ };
308
+ }
309
+ })();
310
+ }
311
311
  constructor(routes) {
312
- _defineProperty(this, "add", route => new Router([...this.routes, route]));
313
-
312
+ _defineProperty(this, "old_add", route => new Router([...this.routes, route]));
313
+ _defineProperty(this, "add", (method, path, route) => {
314
+ var merged = _objectSpread2(_objectSpread2({}, typeof route === 'object' ? route : {
315
+ run: route
316
+ }), {}, {
317
+ method,
318
+ path
319
+ });
320
+ return new Router([...this.routes, merged]);
321
+ });
314
322
  _defineProperty(this, "merge", (pathPrefix, other) => {
315
323
  var newRoutes = other.routes.map(route => _objectSpread2(_objectSpread2({}, route), {}, {
316
324
  path: "".concat(pathPrefix).concat(route.path)
317
325
  }));
318
326
  return new Router([...this.routes, ...newRoutes]);
319
327
  });
320
-
321
328
  _defineProperty(this, "toFindMyWay", server => {
322
329
  var instance = fmw({
323
330
  ignoreTrailingSlash: true,
324
-
325
331
  defaultRoute(req, serverResponse) {
326
332
  return _asyncToGenerator(function* () {
327
333
  var _req$url;
328
-
329
334
  var res = new KaitoResponse(serverResponse);
330
335
  var message = "Cannot ".concat(req.method, " ").concat((_req$url = req.url) !== null && _req$url !== void 0 ? _req$url : '/');
331
336
  res.status(404).json({
@@ -342,9 +347,7 @@ class Router {
342
347
  };
343
348
  })();
344
349
  }
345
-
346
350
  });
347
-
348
351
  var _loop = function _loop(route) {
349
352
  var handler = /*#__PURE__*/function () {
350
353
  var _ref = _asyncToGenerator(function* (incomingMessage, serverResponse, params) {
@@ -356,146 +359,72 @@ class Router {
356
359
  res
357
360
  });
358
361
  });
359
-
360
362
  return function handler(_x, _x2, _x3) {
361
363
  return _ref.apply(this, arguments);
362
364
  };
363
365
  }();
364
-
365
366
  if (route.method === '*') {
366
367
  instance.all(route.path, handler);
367
368
  return "continue";
368
369
  }
369
-
370
370
  instance.on(route.method, route.path, handler);
371
371
  };
372
-
373
372
  for (var route of this.routes) {
374
373
  var _ret = _loop(route);
375
-
376
374
  if (_ret === "continue") continue;
377
375
  }
378
-
379
376
  return instance;
380
377
  });
381
-
382
378
  this.routes = routes;
383
379
  }
384
380
 
385
- static handle(server, route, options) {
386
- return _asyncToGenerator(function* () {
387
- try {
388
- var _route$input$parse, _route$input;
389
-
390
- var ctx = yield server.getContext(options.req, options.res);
391
- var body = yield getInput(options.req);
392
- var input = (_route$input$parse = (_route$input = route.input) === null || _route$input === void 0 ? void 0 : _route$input.parse(body)) !== null && _route$input$parse !== void 0 ? _route$input$parse : undefined;
393
- var result = yield route.run({
394
- ctx,
395
- input,
396
- params: options.params
397
- });
398
- options.res.status(200).json({
399
- success: true,
400
- data: result,
401
- message: 'OK'
402
- });
403
- return {
404
- success: true,
405
- data: result
406
- };
407
- } catch (e) {
408
- var error = WrappedError.maybe(e);
409
-
410
- if (error instanceof KaitoError) {
411
- options.res.status(error.status).json({
412
- success: false,
413
- data: null,
414
- message: error.message
415
- });
416
- return;
417
- }
418
-
419
- var {
420
- status,
421
- message
422
- } = yield server.onError({
423
- error,
424
- req: options.req,
425
- res: options.res
426
- }).catch(() => ({
427
- status: 500,
428
- message: 'Internal Server Error'
429
- }));
430
- options.res.status(status).json({
431
- success: false,
432
- data: null,
433
- message
434
- });
435
- return {
436
- success: false,
437
- data: {
438
- status,
439
- message
440
- }
441
- };
442
- }
443
- })();
444
- }
445
-
381
+ /**
382
+ * Adds a new route to the router
383
+ * @param route The route specification to add to this router
384
+ * @returns A new router with this route added
385
+ * @deprecated Use `Router#add` instead
386
+ */
446
387
  }
447
-
448
388
  _defineProperty(Router, "create", () => new Router([]));
449
389
 
450
390
  function createFMWServer(config) {
451
391
  var _config$rawRoutes;
452
-
453
392
  var fmw = config.router.toFindMyWay(config);
454
393
  var rawRoutes = (_config$rawRoutes = config.rawRoutes) !== null && _config$rawRoutes !== void 0 ? _config$rawRoutes : {};
455
-
456
394
  for (var method in rawRoutes) {
457
395
  if (!Object.prototype.hasOwnProperty.call(rawRoutes, method)) {
458
396
  continue;
459
397
  }
460
-
461
398
  var routes = rawRoutes[method];
462
-
463
399
  if (!routes || routes.length === 0) {
464
400
  continue;
465
401
  }
466
-
467
402
  for (var route of routes) {
468
403
  if (method === '*') {
469
404
  fmw.all(route.path, route.handler);
470
405
  continue;
471
406
  }
472
-
473
407
  fmw[method.toLowerCase()](route.path, route.handler);
474
408
  }
475
409
  }
476
-
477
410
  var server = http.createServer( /*#__PURE__*/function () {
478
411
  var _ref = _asyncToGenerator(function* (req, res) {
479
412
  var before;
480
-
481
413
  if (config.before) {
482
414
  before = yield config.before(req, res);
483
415
  } else {
484
- before = null;
485
- } // If the user has sent a response (e.g. replying to CORS), we don't want to do anything else.
486
-
416
+ before = undefined;
417
+ }
487
418
 
419
+ // If the user has sent a response (e.g. replying to CORS), we don't want to do anything else.
488
420
  if (res.headersSent) {
489
421
  return;
490
422
  }
491
-
492
423
  var result = yield fmw.lookup(req, res);
493
-
494
424
  if ('after' in config && config.after) {
495
425
  yield config.after(before, result);
496
426
  }
497
427
  });
498
-
499
428
  return function (_x, _x2) {
500
429
  return _ref.apply(this, arguments);
501
430
  };
@@ -509,4 +438,4 @@ function createServer(config) {
509
438
  return createFMWServer(config).server;
510
439
  }
511
440
 
512
- export { KaitoError, KaitoRequest, KaitoResponse, Router, WrappedError, createFMWServer, createGetContext, createServer, getInput, getLastEntryInMultiHeaderValue };
441
+ export { KaitoError, KaitoRequest, KaitoResponse, Router, WrappedError, createFMWServer, createGetContext, createServer, getBody, getLastEntryInMultiHeaderValue };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaito-http/core",
3
- "version": "2.5.0",
3
+ "version": "2.7.0",
4
4
  "description": "Functional HTTP Framework for TypeScript",
5
5
  "repository": "https://github.com/kaito-http/kaito",
6
6
  "author": "Alistair Smith <hi@alistair.sh>",
@@ -11,8 +11,8 @@
11
11
  "devDependencies": {
12
12
  "@types/content-type": "^1.1.5",
13
13
  "@types/cookie": "^0.5.1",
14
- "@types/node": "^18.7.18",
15
- "typescript": "4.8",
14
+ "@types/node": "^18.11.9",
15
+ "typescript": "4.9",
16
16
  "zod": "^3.19.1"
17
17
  },
18
18
  "files": [
@@ -35,7 +35,7 @@
35
35
  "dependencies": {
36
36
  "content-type": "^1.0.4",
37
37
  "cookie": "^0.5.0",
38
- "find-my-way": "^7.1.0",
38
+ "find-my-way": "^7.3.1",
39
39
  "raw-body": "^2.5.1"
40
40
  }
41
41
  }