@kaito-http/core 3.0.0-beta.8 → 3.0.1

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.
package/dist/index.cjs CHANGED
@@ -1,9 +1,7 @@
1
1
  "use strict";
2
- var __create = Object.create;
3
2
  var __defProp = Object.defineProperty;
4
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
6
  var __export = (target, all) => {
9
7
  for (var name in all)
@@ -17,33 +15,21 @@ var __copyProps = (to, from, except, desc) => {
17
15
  }
18
16
  return to;
19
17
  };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
19
 
30
20
  // src/index.ts
31
- var src_exports = {};
32
- __export(src_exports, {
21
+ var index_exports = {};
22
+ __export(index_exports, {
33
23
  KaitoError: () => KaitoError,
34
24
  KaitoRequest: () => KaitoRequest,
35
- KaitoResponse: () => KaitoResponse,
36
25
  Router: () => Router,
37
26
  WrappedError: () => WrappedError,
38
- createFMWServer: () => createFMWServer,
39
- createGetContext: () => createGetContext,
40
- createServer: () => createServer2,
27
+ createKaitoHandler: () => createKaitoHandler,
41
28
  createUtilities: () => createUtilities,
42
- getBody: () => getBody,
43
- getLastEntryInMultiHeaderValue: () => getLastEntryInMultiHeaderValue,
29
+ isNodeLikeDev: () => isNodeLikeDev,
44
30
  parsable: () => parsable
45
31
  });
46
- module.exports = __toCommonJS(src_exports);
32
+ module.exports = __toCommonJS(index_exports);
47
33
 
48
34
  // src/error.ts
49
35
  var WrappedError = class _WrappedError extends Error {
@@ -68,205 +54,265 @@ var KaitoError = class extends Error {
68
54
  }
69
55
  };
70
56
 
71
- // src/req.ts
72
- var import_node_tls = require("tls");
73
-
74
- // src/util.ts
75
- var import_content_type = require("content-type");
76
- var import_node_stream = require("stream");
77
- var import_consumers = require("stream/consumers");
78
- var import_raw_body = __toESM(require("raw-body"), 1);
57
+ // src/handler.ts
58
+ function createKaitoHandler(config) {
59
+ const handle = config.router.freeze(config);
60
+ return async (request) => {
61
+ if (config.before) {
62
+ const result = await config.before(request);
63
+ if (result instanceof Response) {
64
+ if (config.transform) {
65
+ const result2 = await config.transform(request, result);
66
+ if (result2 instanceof Response) return result;
67
+ }
68
+ return result;
69
+ }
70
+ }
71
+ const response = await handle(request);
72
+ if (config.transform) {
73
+ const result = await config.transform(request, response);
74
+ if (result instanceof Response) return result;
75
+ }
76
+ return response;
77
+ };
78
+ }
79
79
 
80
- // src/router.ts
81
- var import_find_my_way = __toESM(require("find-my-way"), 1);
80
+ // src/request.ts
81
+ var KaitoRequest = class {
82
+ url;
83
+ _request;
84
+ constructor(url, request) {
85
+ this._request = request;
86
+ this.url = url;
87
+ }
88
+ get headers() {
89
+ return this._request.headers;
90
+ }
91
+ get method() {
92
+ return this._request.method;
93
+ }
94
+ async arrayBuffer() {
95
+ return this._request.arrayBuffer();
96
+ }
97
+ async blob() {
98
+ return this._request.blob();
99
+ }
100
+ async formData() {
101
+ return this._request.formData();
102
+ }
103
+ async bytes() {
104
+ const buffer = await this.arrayBuffer();
105
+ return new Uint8Array(buffer);
106
+ }
107
+ async json() {
108
+ return this._request.json();
109
+ }
110
+ async text() {
111
+ return this._request.text();
112
+ }
113
+ get request() {
114
+ return this._request;
115
+ }
116
+ };
82
117
 
83
- // src/res.ts
84
- var import_cookie = require("cookie");
85
- var KaitoResponse = class {
86
- constructor(raw) {
87
- this.raw = raw;
118
+ // src/head.ts
119
+ var KaitoHead = class {
120
+ _headers;
121
+ _status;
122
+ constructor() {
123
+ this._headers = null;
124
+ this._status = 200;
88
125
  }
89
- /**
90
- * Send a response
91
- * @param key The key of the header
92
- * @param value The value of the header
93
- * @returns The response object
94
- */
95
- header(key, value) {
96
- this.raw.setHeader(key, value);
97
- return this;
126
+ get headers() {
127
+ if (this._headers === null) {
128
+ this._headers = new Headers();
129
+ }
130
+ return this._headers;
98
131
  }
99
- /**
100
- * Set the status code of the response
101
- * @param code The status code
102
- * @returns The response object
103
- */
104
- status(code) {
105
- this.raw.statusCode = code;
132
+ status(status) {
133
+ if (status === void 0) {
134
+ return this._status;
135
+ }
136
+ this._status = status;
106
137
  return this;
107
138
  }
108
139
  /**
109
- * Set a cookie
110
- * @param name The name of the cookie
111
- * @param value The value of the cookie
112
- * @param options The options for the cookie
113
- * @returns The response object
140
+ * Turn this KaitoHead instance into a Response instance
141
+ * @param body The Kaito JSON format to be sent as the response body
142
+ * @returns A Response instance, ready to be sent
114
143
  */
115
- cookie(name, value, options) {
116
- this.raw.setHeader("Set-Cookie", (0, import_cookie.serialize)(name, value, options));
117
- return this;
144
+ toResponse(body) {
145
+ const init = {
146
+ status: this._status
147
+ };
148
+ if (this._headers) {
149
+ init.headers = this._headers;
150
+ }
151
+ return Response.json(body, init);
118
152
  }
119
153
  /**
120
- * Send a JSON APIResponse body
121
- * @param data The data to send
122
- * @returns The response object
154
+ * Whether this KaitoHead instance has been touched/modified
123
155
  */
124
- json(data) {
125
- const json2 = JSON.stringify(data);
126
- this.raw.setHeader("Content-Type", "application/json");
127
- this.raw.setHeader("Content-Length", Buffer.byteLength(json2));
128
- this.raw.end(json2);
129
- return this;
156
+ get touched() {
157
+ return this._status !== 200 || this._headers !== null;
130
158
  }
131
159
  };
132
160
 
133
- // src/router.ts
134
- var getSend = (res) => (status, response) => {
135
- if (res.raw.headersSent) {
136
- return;
137
- }
138
- res.status(status).json(response);
139
- };
161
+ // src/util.ts
162
+ var isNodeLikeDev = typeof process !== "undefined" && typeof process.env !== "undefined" && process.env.NODE_ENV === "development";
163
+ function createUtilities(getContext) {
164
+ return {
165
+ getContext,
166
+ router: () => Router.create()
167
+ };
168
+ }
169
+ function parsable(parse) {
170
+ return {
171
+ parse
172
+ };
173
+ }
174
+
175
+ // src/router/router.ts
140
176
  var Router = class _Router {
141
- routerOptions;
142
- routes;
143
- static create = () => new _Router([], {
144
- through: async (context) => context
177
+ state;
178
+ static create = () => new _Router({
179
+ through: async (context) => context,
180
+ routes: /* @__PURE__ */ new Set()
145
181
  });
146
182
  static parseQuery(schema, url) {
147
183
  if (!schema) {
148
184
  return {};
149
185
  }
150
186
  const result = {};
151
- for (const [key, parsable2] of Object.entries(schema)) {
187
+ for (const key in schema) {
188
+ if (!schema.hasOwnProperty(key)) continue;
152
189
  const value = url.searchParams.get(key);
153
- result[key] = parsable2.parse(value);
190
+ result[key] = schema[key].parse(value);
154
191
  }
155
192
  return result;
156
193
  }
157
- static async handle(server, route, options) {
158
- const send = getSend(options.res);
159
- try {
160
- const rootCtx = await server.getContext(options.req, options.res);
161
- const ctx = await route.through(rootCtx);
162
- const body = await route.body?.parse(await getBody(options.req)) ?? void 0;
163
- const query = _Router.parseQuery(route.query, options.req.url);
164
- const result = await route.run({
165
- ctx,
166
- body,
167
- query,
168
- params: options.params
169
- });
170
- if (options.res.raw.headersSent) {
171
- return {
172
- success: true,
173
- data: result
174
- };
175
- }
176
- send(200, {
177
- success: true,
178
- data: result,
179
- message: "OK"
180
- });
181
- return {
182
- success: true,
183
- data: result
184
- };
185
- } catch (e) {
186
- const error = WrappedError.maybe(e);
187
- if (error instanceof KaitoError) {
188
- send(error.status, {
189
- success: false,
190
- data: null,
191
- message: error.message
192
- });
193
- return;
194
- }
195
- const { status, message } = await server.onError({ error, req: options.req, res: options.res }).catch(() => ({ status: 500, message: "Internal Server Error" }));
196
- send(status, {
197
- success: false,
198
- data: null,
199
- message
200
- });
201
- return {
202
- success: false,
203
- data: { status, message }
204
- };
205
- }
194
+ constructor(options) {
195
+ this.state = options;
206
196
  }
207
- constructor(routes, options) {
208
- this.routerOptions = options;
209
- this.routes = new Set(routes);
197
+ get routes() {
198
+ return this.state.routes;
210
199
  }
211
- /**
212
- * Adds a new route to the router
213
- * @deprecated Use the method-specific methods instead
214
- */
215
200
  add = (method, path, route) => {
216
201
  const merged = {
202
+ // TODO: Ideally fix the typing here, but this will be replaced in Kaito v4 where all routes must return a Response (which we can type)
217
203
  ...typeof route === "object" ? route : { run: route },
218
204
  method,
219
205
  path,
220
- through: this.routerOptions.through
206
+ through: this.state.through
221
207
  };
222
- return new _Router([...this.routes, merged], this.routerOptions);
208
+ return new _Router({
209
+ ...this.state,
210
+ routes: /* @__PURE__ */ new Set([...this.state.routes, merged])
211
+ });
223
212
  };
224
213
  merge = (pathPrefix, other) => {
225
- const newRoutes = [...other.routes].map((route) => ({
214
+ const newRoutes = [...other.state.routes].map((route) => ({
226
215
  ...route,
227
216
  path: `${pathPrefix}${route.path}`
228
217
  }));
229
- return new _Router(
230
- [...this.routes, ...newRoutes],
231
- this.routerOptions
232
- );
218
+ return new _Router({
219
+ ...this.state,
220
+ routes: /* @__PURE__ */ new Set([...this.state.routes, ...newRoutes])
221
+ });
233
222
  };
234
- // Allow for any server context to be passed
235
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
236
223
  freeze = (server) => {
237
- const instance = (0, import_find_my_way.default)({
238
- ignoreTrailingSlash: true,
239
- async defaultRoute(req, serverResponse) {
240
- const res = new KaitoResponse(serverResponse);
241
- const message = `Cannot ${req.method} ${req.url ?? "/"}`;
242
- getSend(res)(404, {
224
+ const routes = /* @__PURE__ */ new Map();
225
+ for (const route of this.state.routes) {
226
+ if (!routes.has(route.path)) {
227
+ routes.set(route.path, /* @__PURE__ */ new Map());
228
+ }
229
+ routes.get(route.path).set(route.method, route);
230
+ }
231
+ const findRoute = (method, path) => {
232
+ const params = {};
233
+ const pathParts = path.split("/").filter(Boolean);
234
+ for (const [routePath, methodHandlers] of routes) {
235
+ const routeParts = routePath.split("/").filter(Boolean);
236
+ if (routeParts.length !== pathParts.length) continue;
237
+ let matches = true;
238
+ for (let i = 0; i < routeParts.length; i++) {
239
+ const routePart = routeParts[i];
240
+ const pathPart = pathParts[i];
241
+ if (routePart && pathPart && routePart.startsWith(":")) {
242
+ params[routePart.slice(1)] = pathPart;
243
+ } else if (routePart !== pathPart) {
244
+ matches = false;
245
+ break;
246
+ }
247
+ }
248
+ if (matches) {
249
+ const route = methodHandlers.get(method);
250
+ if (route) return { route, params };
251
+ }
252
+ }
253
+ return { params };
254
+ };
255
+ return async (req) => {
256
+ const url = new URL(req.url);
257
+ const method = req.method;
258
+ const { route, params } = findRoute(method, url.pathname);
259
+ if (!route) {
260
+ const body = {
243
261
  success: false,
244
262
  data: null,
245
- message
246
- });
247
- return {
248
- success: false,
249
- data: { status: 404, message }
263
+ message: `Cannot ${method} ${url.pathname}`
250
264
  };
265
+ return Response.json(body, { status: 404 });
251
266
  }
252
- });
253
- for (const route of this.routes) {
254
- const handler = async (incomingMessage, serverResponse, params) => {
255
- const req = new KaitoRequest(incomingMessage);
256
- const res = new KaitoResponse(serverResponse);
257
- return _Router.handle(server, route, {
258
- params,
259
- req,
260
- res
267
+ const request = new KaitoRequest(url, req);
268
+ const head = new KaitoHead();
269
+ try {
270
+ const body = route.body ? await route.body.parse(await req.json()) : void 0;
271
+ const query = _Router.parseQuery(route.query, url);
272
+ const rootCtx = await server.getContext(request, head);
273
+ const ctx = await route.through(rootCtx);
274
+ const result = await route.run({
275
+ ctx,
276
+ body,
277
+ query,
278
+ params
279
+ });
280
+ if (result instanceof Response) {
281
+ if (isNodeLikeDev) {
282
+ if (head.touched) {
283
+ const msg = [
284
+ "Kaito detected that you used the KaitoHead object to modify the headers or status, but then returned a Response in the route",
285
+ "This is usually a mistake, as your Response object will override any changes you made to the headers or status code.",
286
+ "",
287
+ "This warning was shown because `process.env.NODE_ENV=development`"
288
+ ].join("\n");
289
+ console.warn(msg);
290
+ }
291
+ }
292
+ return result;
293
+ }
294
+ return head.toResponse({
295
+ success: true,
296
+ data: result,
297
+ message: "OK"
298
+ });
299
+ } catch (e) {
300
+ const error = WrappedError.maybe(e);
301
+ if (error instanceof KaitoError) {
302
+ return head.status(error.status).toResponse({
303
+ success: false,
304
+ data: null,
305
+ message: error.message
306
+ });
307
+ }
308
+ const { status, message } = await server.onError({ error, req: request }).catch(() => ({ status: 500, message: "Internal Server Error" }));
309
+ return head.status(status).toResponse({
310
+ success: false,
311
+ data: null,
312
+ message
261
313
  });
262
- };
263
- if (route.method === "*") {
264
- instance.all(route.path, handler);
265
- continue;
266
314
  }
267
- instance.on(route.method, route.path, handler);
268
- }
269
- return instance;
315
+ };
270
316
  };
271
317
  method = (method) => (path, route) => {
272
318
  return this.add(method, path, route);
@@ -278,162 +324,21 @@ var Router = class _Router {
278
324
  delete = this.method("DELETE");
279
325
  head = this.method("HEAD");
280
326
  options = this.method("OPTIONS");
281
- through = (transform) => new _Router(this.routes, {
282
- through: async (context) => {
283
- const fromCurrentRouter = await this.routerOptions.through(context);
284
- return transform(fromCurrentRouter);
285
- }
286
- });
287
- };
288
-
289
- // src/util.ts
290
- function createGetContext(callback) {
291
- return callback;
292
- }
293
- function createUtilities(getContext) {
294
- return {
295
- getContext,
296
- router: () => Router.create()
297
- };
298
- }
299
- function getLastEntryInMultiHeaderValue(headerValue) {
300
- const normalized = Array.isArray(headerValue) ? headerValue.join(",") : headerValue;
301
- const lastIndex = normalized.lastIndexOf(",");
302
- return lastIndex === -1 ? normalized.trim() : normalized.slice(lastIndex + 1).trim();
303
- }
304
- function parsable(parse) {
305
- return {
306
- parse
327
+ through = (through) => {
328
+ return new _Router({
329
+ ...this.state,
330
+ through: async (context) => through(await this.state.through(context))
331
+ });
307
332
  };
308
- }
309
- async function getBody(req) {
310
- if (!req.headers["content-type"]) {
311
- return null;
312
- }
313
- const buffer = await (0, import_raw_body.default)(req.raw);
314
- const { type } = (0, import_content_type.parse)(req.headers["content-type"]);
315
- switch (type) {
316
- case "application/json": {
317
- return (0, import_consumers.json)(import_node_stream.Readable.from(buffer));
318
- }
319
- default: {
320
- if (process.env.NODE_ENV === "development") {
321
- console.warn("[kaito] Unsupported content type:", type);
322
- console.warn("[kaito] This message is only shown in development mode.");
323
- }
324
- return null;
325
- }
326
- }
327
- }
328
-
329
- // src/req.ts
330
- var KaitoRequest = class {
331
- constructor(raw) {
332
- this.raw = raw;
333
- }
334
- _url = null;
335
- /**
336
- * The full URL of the request, including the protocol, hostname, and path.
337
- * Note: does not include the query string or hash
338
- */
339
- get fullURL() {
340
- return `${this.protocol}://${this.hostname}${this.raw.url ?? ""}`;
341
- }
342
- /**
343
- * A new URL instance for the full URL of the request.
344
- */
345
- get url() {
346
- if (this._url) {
347
- return this._url;
348
- }
349
- this._url = new URL(this.fullURL);
350
- return this._url;
351
- }
352
- /**
353
- * The HTTP method of the request.
354
- */
355
- get method() {
356
- if (!this.raw.method) {
357
- throw new Error("Request method is not defined, somehow...");
358
- }
359
- return this.raw.method;
360
- }
361
- /**
362
- * The protocol of the request, either `http` or `https`.
363
- */
364
- get protocol() {
365
- if (this.raw.socket instanceof import_node_tls.TLSSocket) {
366
- return this.raw.socket.encrypted ? "https" : "http";
367
- }
368
- return "http";
369
- }
370
- /**
371
- * The request headers
372
- */
373
- get headers() {
374
- return this.raw.headers;
375
- }
376
- /**
377
- * The hostname of the request.
378
- */
379
- get hostname() {
380
- return this.raw.headers.host ?? getLastEntryInMultiHeaderValue(this.raw.headers[":authority"] ?? []);
381
- }
382
333
  };
383
-
384
- // src/server.ts
385
- var http = __toESM(require("http"), 1);
386
- function createFMWServer(config) {
387
- const router = config.router.freeze(config);
388
- const rawRoutes = config.rawRoutes ?? {};
389
- for (const method in rawRoutes) {
390
- if (!Object.prototype.hasOwnProperty.call(rawRoutes, method)) {
391
- continue;
392
- }
393
- const routes = rawRoutes[method];
394
- if (!routes || routes.length === 0) {
395
- continue;
396
- }
397
- for (const route of routes) {
398
- if (method === "*") {
399
- router.all(route.path, route.handler);
400
- continue;
401
- }
402
- router[method.toLowerCase()](route.path, route.handler);
403
- }
404
- }
405
- const server = http.createServer(async (req, res) => {
406
- let before;
407
- if (config.before) {
408
- before = await config.before(req, res);
409
- } else {
410
- before = void 0;
411
- }
412
- if (res.headersSent) {
413
- return;
414
- }
415
- const result = await router.lookup(req, res);
416
- if ("after" in config && config.after) {
417
- await config.after(before, result);
418
- }
419
- });
420
- return { server, fmw: router };
421
- }
422
- function createServer2(config) {
423
- return createFMWServer(config).server;
424
- }
425
334
  // Annotate the CommonJS export names for ESM import in node:
426
335
  0 && (module.exports = {
427
336
  KaitoError,
428
337
  KaitoRequest,
429
- KaitoResponse,
430
338
  Router,
431
339
  WrappedError,
432
- createFMWServer,
433
- createGetContext,
434
- createServer,
340
+ createKaitoHandler,
435
341
  createUtilities,
436
- getBody,
437
- getLastEntryInMultiHeaderValue,
342
+ isNodeLikeDev,
438
343
  parsable
439
344
  });