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