@kaito-http/core 2.0.1 → 2.2.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,4 +1,6 @@
1
- import fastify from 'fastify';
1
+ import http from 'http';
2
+ import { TLSSocket } from 'tls';
3
+ import getRawBody from 'raw-body';
2
4
 
3
5
  function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
4
6
  try {
@@ -89,35 +91,176 @@ function _objectSpread2(target) {
89
91
  return target;
90
92
  }
91
93
 
92
- var Method;
94
+ class WrappedError extends Error {
95
+ static maybe(maybeError) {
96
+ if (maybeError instanceof Error) {
97
+ return maybeError;
98
+ }
99
+
100
+ return WrappedError.from(maybeError);
101
+ }
102
+
103
+ static from(data) {
104
+ return new WrappedError(data);
105
+ }
106
+
107
+ constructor(data) {
108
+ super('Something was thrown, but it was not an instance of Error, so a WrappedError was created.');
109
+ this.data = data;
110
+ }
111
+
112
+ }
113
+
114
+ function getLastEntryInMultiHeaderValue(headerValue) {
115
+ var normalized = Array.isArray(headerValue) ? headerValue.join(',') : headerValue;
116
+ var lastIndex = normalized.lastIndexOf(',');
117
+ return lastIndex === -1 ? normalized.trim() : normalized.slice(lastIndex + 1).trim();
118
+ } // Type for import('http').METHODS
119
+
120
+ function getInput(_x) {
121
+ return _getInput.apply(this, arguments);
122
+ }
123
+
124
+ function _getInput() {
125
+ _getInput = _asyncToGenerator(function* (req) {
126
+ if (req.method === 'GET') {
127
+ var input = req.url.searchParams.get('input');
128
+
129
+ if (!input) {
130
+ return null;
131
+ }
132
+
133
+ return JSON.parse(input);
134
+ }
135
+
136
+ var buffer = yield getRawBody(req.raw);
137
+
138
+ switch (req.headers['content-type']) {
139
+ case 'application/json':
140
+ {
141
+ return JSON.parse(buffer.toString());
142
+ }
143
+
144
+ default:
145
+ {
146
+ return null;
147
+ }
148
+ }
149
+ });
150
+ return _getInput.apply(this, arguments);
151
+ }
152
+
153
+ class KaitoRequest {
154
+ constructor(raw) {
155
+ this.raw = raw;
156
+ }
157
+
158
+ get fullURL() {
159
+ var _this$raw$url;
160
+
161
+ return "".concat(this.protocol, "://").concat(this.hostname).concat((_this$raw$url = this.raw.url) !== null && _this$raw$url !== void 0 ? _this$raw$url : '');
162
+ }
163
+
164
+ get url() {
165
+ return new URL(this.fullURL);
166
+ }
167
+
168
+ get method() {
169
+ if (!this.raw.method) {
170
+ throw new Error('Request method is not defined, somehow...');
171
+ }
172
+
173
+ return this.raw.method;
174
+ }
175
+
176
+ get protocol() {
177
+ if (this.raw.socket instanceof TLSSocket) {
178
+ return this.raw.socket.encrypted ? 'https' : 'http';
179
+ }
180
+
181
+ return 'http';
182
+ }
183
+
184
+ get headers() {
185
+ return this.raw.headers;
186
+ }
187
+
188
+ get hostname() {
189
+ var _this$raw$headers$hos, _this$raw$headers$Au;
190
+
191
+ 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 : []);
192
+ }
193
+
194
+ }
195
+
196
+ class KaitoResponse {
197
+ constructor(raw) {
198
+ this.raw = raw;
199
+ }
200
+
201
+ header(key, value) {
202
+ this.raw.setHeader(key, value);
203
+ return this;
204
+ }
205
+
206
+ status(code) {
207
+ this.raw.statusCode = code;
208
+ return this;
209
+ }
93
210
 
94
- (function (Method) {
95
- Method["GET"] = "GET";
96
- Method["POST"] = "POST";
97
- Method["PATCH"] = "PATCH";
98
- Method["DELETE"] = "DELETE";
99
- })(Method || (Method = {}));
211
+ json(data) {
212
+ var json = JSON.stringify(data);
213
+ this.raw.setHeader('Content-Type', 'application/json');
214
+ this.raw.setHeader('Content-Length', Buffer.byteLength(json));
215
+ this.raw.end(json);
216
+ return this;
217
+ }
218
+
219
+ }
100
220
 
101
221
  function createGetContext(getContext) {
102
222
  return getContext;
103
223
  }
104
224
  class Router {
225
+ /**
226
+ * Ensures that the path does not start or end with a slash.
227
+ * @param path
228
+ * @private
229
+ */
230
+ static stripSlashes(path) {
231
+ if (path.startsWith('/')) {
232
+ path = path.slice(1);
233
+ }
234
+
235
+ if (path.endsWith('/')) {
236
+ path = path.slice(-1);
237
+ }
238
+
239
+ return path;
240
+ }
241
+
105
242
  constructor(procs) {
106
- _defineProperty(this, "create", method => (name, proc) => {
107
- return new Router(_objectSpread2(_objectSpread2({}, this.procs), {}, {
108
- [name]: _objectSpread2(_objectSpread2({}, proc), {}, {
243
+ _defineProperty(this, "create", method => (path, proc) => {
244
+ var stripped = Router.stripSlashes(path);
245
+ var pattern = new RegExp("^/".concat(stripped, "/?$"), 'i');
246
+
247
+ var merged = _objectSpread2(_objectSpread2({}, this.procs), {}, {
248
+ [path]: _objectSpread2(_objectSpread2({}, proc), {}, {
109
249
  method,
110
- name
250
+ path,
251
+ pattern
111
252
  })
112
- }));
253
+ });
254
+
255
+ return new Router(merged);
113
256
  });
114
257
 
115
258
  _defineProperty(this, "merge", (prefix, router) => {
116
259
  var newProcs = Object.entries(router.getProcs()).reduce((all, entry) => {
117
- var [name, proc] = entry;
260
+ var [path, proc] = entry;
118
261
  return _objectSpread2(_objectSpread2({}, all), {}, {
119
- ["".concat(prefix).concat(name)]: _objectSpread2(_objectSpread2({}, proc), {}, {
120
- name: "".concat(prefix).concat(name)
262
+ ["".concat(prefix).concat(path)]: _objectSpread2(_objectSpread2({}, proc), {}, {
263
+ path: "".concat(prefix).concat(path)
121
264
  })
122
265
  });
123
266
  }, {});
@@ -127,26 +270,55 @@ class Router {
127
270
  return new Router(mergedProcs);
128
271
  });
129
272
 
130
- _defineProperty(this, "get", this.create(Method.GET));
273
+ _defineProperty(this, "get", this.create('GET'));
274
+
275
+ _defineProperty(this, "post", this.create('POST'));
131
276
 
132
- _defineProperty(this, "post", this.create(Method.POST));
277
+ _defineProperty(this, "put", this.create('PUT'));
133
278
 
134
- _defineProperty(this, "patch", this.create(Method.PATCH));
279
+ _defineProperty(this, "patch", this.create('PATCH'));
135
280
 
136
- _defineProperty(this, "delete", this.create(Method.DELETE));
281
+ _defineProperty(this, "delete", this.create('DELETE'));
282
+
283
+ _defineProperty(this, "head", this.create('HEAD'));
284
+
285
+ _defineProperty(this, "options", this.create('OPTIONS'));
286
+
287
+ _defineProperty(this, "connect", this.create('CONNECT'));
288
+
289
+ _defineProperty(this, "trace", this.create('TRACE'));
290
+
291
+ _defineProperty(this, "acl", this.create('ACL'));
292
+
293
+ _defineProperty(this, "bind", this.create('BIND'));
137
294
 
138
295
  this.procs = procs;
296
+ this._procsArray = Object.values(procs);
139
297
  }
140
298
 
141
299
  getProcs() {
142
300
  return this.procs;
143
301
  }
144
302
 
303
+ find(method, url) {
304
+ for (var proc of this._procsArray) {
305
+ if (proc.method !== method) {
306
+ continue;
307
+ }
308
+
309
+ if (proc.pattern.test(url)) {
310
+ return proc;
311
+ }
312
+ }
313
+
314
+ return null;
315
+ }
316
+
145
317
  }
146
318
  class KaitoError extends Error {
147
- constructor(code, message, cause) {
319
+ constructor(status, message, cause) {
148
320
  super(message);
149
- this.code = code;
321
+ this.status = status;
150
322
  this.cause = cause;
151
323
  }
152
324
 
@@ -155,79 +327,76 @@ function createRouter() {
155
327
  return new Router({});
156
328
  }
157
329
  function createServer(config) {
158
- var tree = config.router.getProcs();
159
- var app = fastify();
160
- app.setErrorHandler( /*#__PURE__*/function () {
161
- var _ref = _asyncToGenerator(function* (error, req, res) {
162
- if (error instanceof KaitoError) {
163
- yield res.status(error.code).send({
164
- success: false,
165
- data: null,
166
- message: error.message
167
- });
168
- return;
169
- }
170
-
171
- var {
172
- code,
173
- message
174
- } = yield config.onError({
175
- error,
176
- req,
177
- res
178
- }).catch(() => ({
179
- code: 500,
180
- message: 'Something went wrong'
181
- }));
182
- yield res.status(code).send({
183
- success: false,
184
- data: null,
185
- message
186
- });
187
- });
188
-
189
- return function (_x, _x2, _x3) {
190
- return _ref.apply(this, arguments);
191
- };
192
- }());
193
- app.all('*', /*#__PURE__*/function () {
194
- var _ref2 = _asyncToGenerator(function* (req, res) {
195
- var _handler$input$parse, _handler$input;
196
-
197
- var logMessage = "".concat(req.hostname, " ").concat(req.method, " ").concat(req.routerPath);
330
+ var log = message => {
331
+ if (config.log === undefined) {
332
+ console.log(message);
333
+ } else if (config.log) {
334
+ config.log(message);
335
+ }
336
+ };
198
337
 
199
- if (config.log === undefined) {
200
- console.log(logMessage);
201
- } else if (config.log) {
202
- config.log(logMessage);
203
- }
338
+ return http.createServer( /*#__PURE__*/function () {
339
+ var _ref = _asyncToGenerator(function* (incomingMessage, serverResponse) {
340
+ var start = Date.now();
341
+ var req = new KaitoRequest(incomingMessage);
342
+ var res = new KaitoResponse(serverResponse);
204
343
 
205
- var url = new URL("".concat(req.protocol, "://").concat(req.hostname).concat(req.url));
206
- var handler = tree[url.pathname];
344
+ try {
345
+ var _handler$input, _yield$getInput;
207
346
 
208
- if (!handler) {
209
- throw new KaitoError(404, "Cannot ".concat(req.method, " this route."));
210
- }
347
+ var handler = config.router.find(req.method, req.url.pathname);
211
348
 
212
- var context = yield config.getContext(req, res); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
349
+ if (!handler) {
350
+ throw new KaitoError(404, "Cannot ".concat(req.method, " this route."));
351
+ }
213
352
 
214
- var input = (_handler$input$parse = (_handler$input = handler.input) === null || _handler$input === void 0 ? void 0 : _handler$input.parse(req.method === 'GET' ? req.query : req.body)) !== null && _handler$input$parse !== void 0 ? _handler$input$parse : null;
215
- yield res.send({
216
- success: true,
217
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
218
- data: yield handler.run({
353
+ var input = (_handler$input = handler.input) === null || _handler$input === void 0 ? void 0 : _handler$input.parse((_yield$getInput = yield getInput(req)) !== null && _yield$getInput !== void 0 ? _yield$getInput : undefined);
354
+ var context = yield config.getContext(req, res);
355
+ var data = yield handler.run({
219
356
  ctx: context,
220
357
  input
221
- }),
222
- message: 'OK'
223
- });
358
+ });
359
+ res.json({
360
+ success: true,
361
+ data,
362
+ message: 'OK'
363
+ });
364
+ } catch (error) {
365
+ if (error instanceof KaitoError) {
366
+ res.status(error.status).json({
367
+ success: false,
368
+ data: null,
369
+ message: error.message
370
+ });
371
+ return;
372
+ }
373
+
374
+ var {
375
+ status: _status,
376
+ message: _message
377
+ } = yield config.onError({
378
+ error: WrappedError.maybe(error),
379
+ req,
380
+ res
381
+ }).catch(() => ({
382
+ status: 500,
383
+ message: 'Something went wrong'
384
+ }));
385
+ res.status(_status).json({
386
+ success: false,
387
+ data: null,
388
+ message: _message
389
+ });
390
+ } finally {
391
+ var finish = Date.now();
392
+ log("".concat(req.method, " ").concat(req.fullURL, " ").concat(res.raw.statusCode, " ").concat(finish - start, "ms"));
393
+ }
224
394
  });
225
395
 
226
- return function (_x4, _x5) {
227
- return _ref2.apply(this, arguments);
396
+ return function (_x, _x2) {
397
+ return _ref.apply(this, arguments);
228
398
  };
229
399
  }());
230
- return app;
231
400
  }
232
401
 
233
- export { KaitoError, Method, Router, createGetContext, createRouter, createServer };
402
+ export { KaitoError, Router, createGetContext, createRouter, createServer };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaito-http/core",
3
- "version": "2.0.1",
3
+ "version": "2.2.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,7 +11,6 @@
11
11
  "devDependencies": {
12
12
  "@types/body-parser": "^1.19.2",
13
13
  "@types/node": "^17.0.24",
14
- "@types/node-fetch": "^2.6.1",
15
14
  "typescript": "4.6",
16
15
  "zod": "^3.14.4"
17
16
  },
@@ -20,10 +19,6 @@
20
19
  "readme.md",
21
20
  "dist"
22
21
  ],
23
- "dependencies": {
24
- "colorette": "^2.0.16",
25
- "fastify": "^3.28.0"
26
- },
27
22
  "bugs": {
28
23
  "url": "https://github.com/kaito-http/kaito/issues"
29
24
  },
@@ -33,8 +28,10 @@
33
28
  },
34
29
  "keywords": [
35
30
  "typescript",
36
- "fastify",
37
31
  "http",
38
32
  "framework"
39
- ]
33
+ ],
34
+ "dependencies": {
35
+ "raw-body": "^2.5.1"
36
+ }
40
37
  }