@kaito-http/core 3.0.1 → 4.0.0-beta.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.
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/cors/cors.ts
21
+ var cors_exports = {};
22
+ __export(cors_exports, {
23
+ experimental_createCORSTransform: () => experimental_createCORSTransform,
24
+ experimental_createOriginMatcher: () => experimental_createOriginMatcher
25
+ });
26
+ module.exports = __toCommonJS(cors_exports);
27
+ function experimental_createOriginMatcher(origins) {
28
+ if (origins.length === 0) {
29
+ return () => false;
30
+ }
31
+ const source = origins.map((origin) => {
32
+ if (origin.startsWith("*.")) {
33
+ const escapedDomain = origin.slice(2).replace(/[.+?^${}()|[\]\\]/g, "\\$&");
34
+ return `^(?:https?://)[^.]+\\.${escapedDomain}$`;
35
+ } else {
36
+ const escapedOrigin = origin.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
37
+ return `^${escapedOrigin}$`;
38
+ }
39
+ }).join("|");
40
+ const regex = new RegExp(source);
41
+ return (origin) => regex.test(origin);
42
+ }
43
+ function experimental_createCORSTransform(origins) {
44
+ const matcher = experimental_createOriginMatcher(origins);
45
+ return (request, response) => {
46
+ const origin = request.headers.get("Origin");
47
+ if (origin && matcher(origin)) {
48
+ response.headers.set("Access-Control-Allow-Origin", origin);
49
+ response.headers.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
50
+ response.headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization");
51
+ response.headers.set("Access-Control-Max-Age", "86400");
52
+ response.headers.set("Access-Control-Allow-Credentials", "true");
53
+ }
54
+ };
55
+ }
56
+ // Annotate the CommonJS export names for ESM import in node:
57
+ 0 && (module.exports = {
58
+ experimental_createCORSTransform,
59
+ experimental_createOriginMatcher
60
+ });
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Creates a function that matches origins against a predefined set of patterns, supporting wildcards.
3
+ * The matcher handles both exact matches and wildcard subdomain patterns (e.g., '*.example.com').
4
+ *
5
+ * **⚠️ This API is experimental and may change or even be removed in the future. ⚠️**
6
+ *
7
+ * @param origins Array of origin patterns to match against.
8
+ * Patterns can be exact origins (e.g., 'https://example.com') or wildcard patterns (e.g., '*.example.com') that match subdomains.
9
+ * @returns A function that tests if an origin matches any of the patterns
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * const allowedOrigins = [
14
+ * 'https://example.com',
15
+ * '*.trusted-domain.com' // Won't match https://evil-domain.com, only subdomains
16
+ * ];
17
+ *
18
+ * const matcher = createOriginMatcher(allowedOrigins);
19
+ *
20
+ * // Exact match
21
+ * console.log(matcher('https://example.com')); // true
22
+ * console.log(matcher('http://example.com')); // false
23
+ *
24
+ * // Wildcard subdomain matches
25
+ * console.log(matcher('https://app.trusted-domain.com')); // true
26
+ * console.log(matcher('https://staging.trusted-domain.com')); // true
27
+ * console.log(matcher('https://trusted-domain.com')); // false, because it's not a subdomain
28
+ * console.log(matcher('https://evil-domain.com')); // false
29
+ * ```
30
+ */
31
+ declare function experimental_createOriginMatcher(origins: string[]): (origin: string) => boolean;
32
+ /**
33
+ * Create a function to apply CORS headers with sane defaults for most apps.
34
+ *
35
+ * **⚠️ This API is experimental and may change or even be removed in the future. ⚠️**
36
+ *
37
+ * @param options Options object
38
+ * @returns A function that will mutate the Response object by applying the CORS headers
39
+ * @example
40
+ * ```ts
41
+ * const cors = createCORSHandler({
42
+ * origins: ['https://example.com', "*.allows-subdomains.com", "http://localhost:3000"],
43
+ * });
44
+ *
45
+ * const handler = createKaitoHandler({
46
+ * // ...
47
+ * transform: async (request, response) => {
48
+ * cors(request, response);
49
+ * }
50
+ * });
51
+ * ```
52
+ */
53
+ declare function experimental_createCORSTransform(origins: string[]): (request: Request, response: Response) => void;
54
+
55
+ export { experimental_createCORSTransform, experimental_createOriginMatcher };
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Creates a function that matches origins against a predefined set of patterns, supporting wildcards.
3
+ * The matcher handles both exact matches and wildcard subdomain patterns (e.g., '*.example.com').
4
+ *
5
+ * **⚠️ This API is experimental and may change or even be removed in the future. ⚠️**
6
+ *
7
+ * @param origins Array of origin patterns to match against.
8
+ * Patterns can be exact origins (e.g., 'https://example.com') or wildcard patterns (e.g., '*.example.com') that match subdomains.
9
+ * @returns A function that tests if an origin matches any of the patterns
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * const allowedOrigins = [
14
+ * 'https://example.com',
15
+ * '*.trusted-domain.com' // Won't match https://evil-domain.com, only subdomains
16
+ * ];
17
+ *
18
+ * const matcher = createOriginMatcher(allowedOrigins);
19
+ *
20
+ * // Exact match
21
+ * console.log(matcher('https://example.com')); // true
22
+ * console.log(matcher('http://example.com')); // false
23
+ *
24
+ * // Wildcard subdomain matches
25
+ * console.log(matcher('https://app.trusted-domain.com')); // true
26
+ * console.log(matcher('https://staging.trusted-domain.com')); // true
27
+ * console.log(matcher('https://trusted-domain.com')); // false, because it's not a subdomain
28
+ * console.log(matcher('https://evil-domain.com')); // false
29
+ * ```
30
+ */
31
+ declare function experimental_createOriginMatcher(origins: string[]): (origin: string) => boolean;
32
+ /**
33
+ * Create a function to apply CORS headers with sane defaults for most apps.
34
+ *
35
+ * **⚠️ This API is experimental and may change or even be removed in the future. ⚠️**
36
+ *
37
+ * @param options Options object
38
+ * @returns A function that will mutate the Response object by applying the CORS headers
39
+ * @example
40
+ * ```ts
41
+ * const cors = createCORSHandler({
42
+ * origins: ['https://example.com', "*.allows-subdomains.com", "http://localhost:3000"],
43
+ * });
44
+ *
45
+ * const handler = createKaitoHandler({
46
+ * // ...
47
+ * transform: async (request, response) => {
48
+ * cors(request, response);
49
+ * }
50
+ * });
51
+ * ```
52
+ */
53
+ declare function experimental_createCORSTransform(origins: string[]): (request: Request, response: Response) => void;
54
+
55
+ export { experimental_createCORSTransform, experimental_createOriginMatcher };
@@ -0,0 +1,34 @@
1
+ // src/cors/cors.ts
2
+ function experimental_createOriginMatcher(origins) {
3
+ if (origins.length === 0) {
4
+ return () => false;
5
+ }
6
+ const source = origins.map((origin) => {
7
+ if (origin.startsWith("*.")) {
8
+ const escapedDomain = origin.slice(2).replace(/[.+?^${}()|[\]\\]/g, "\\$&");
9
+ return `^(?:https?://)[^.]+\\.${escapedDomain}$`;
10
+ } else {
11
+ const escapedOrigin = origin.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
12
+ return `^${escapedOrigin}$`;
13
+ }
14
+ }).join("|");
15
+ const regex = new RegExp(source);
16
+ return (origin) => regex.test(origin);
17
+ }
18
+ function experimental_createCORSTransform(origins) {
19
+ const matcher = experimental_createOriginMatcher(origins);
20
+ return (request, response) => {
21
+ const origin = request.headers.get("Origin");
22
+ if (origin && matcher(origin)) {
23
+ response.headers.set("Access-Control-Allow-Origin", origin);
24
+ response.headers.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
25
+ response.headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization");
26
+ response.headers.set("Access-Control-Max-Age", "86400");
27
+ response.headers.set("Access-Control-Allow-Credentials", "true");
28
+ }
29
+ };
30
+ }
31
+ export {
32
+ experimental_createCORSTransform,
33
+ experimental_createOriginMatcher
34
+ };
package/dist/index.cjs CHANGED
@@ -24,19 +24,17 @@ __export(index_exports, {
24
24
  KaitoRequest: () => KaitoRequest,
25
25
  Router: () => Router,
26
26
  WrappedError: () => WrappedError,
27
- createKaitoHandler: () => createKaitoHandler,
28
- createUtilities: () => createUtilities,
29
- isNodeLikeDev: () => isNodeLikeDev,
30
- parsable: () => parsable
27
+ create: () => create,
28
+ isNodeLikeDev: () => isNodeLikeDev
31
29
  });
32
30
  module.exports = __toCommonJS(index_exports);
33
31
 
32
+ // src/router/router.ts
33
+ var import_zod = require("zod");
34
+ var import_zod_openapi = require("zod-openapi");
35
+
34
36
  // src/error.ts
35
37
  var WrappedError = class _WrappedError extends Error {
36
- constructor(data) {
37
- super("Something was thrown, but it was not an instance of Error, so a WrappedError was created.");
38
- this.data = data;
39
- }
40
38
  static maybe(maybeError) {
41
39
  if (maybeError instanceof Error) {
42
40
  return maybeError;
@@ -46,36 +44,62 @@ var WrappedError = class _WrappedError extends Error {
46
44
  static from(data) {
47
45
  return new _WrappedError(data);
48
46
  }
47
+ data;
48
+ constructor(data) {
49
+ super("Something was thrown, but it was not an instance of Error, so a WrappedError was created.");
50
+ this.data = data;
51
+ }
49
52
  };
50
53
  var KaitoError = class extends Error {
54
+ status;
51
55
  constructor(status, message) {
52
56
  super(message);
53
57
  this.status = status;
54
58
  }
55
59
  };
56
60
 
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
- }
61
+ // src/head.ts
62
+ var KaitoHead = class {
63
+ #headers;
64
+ #status;
65
+ constructor() {
66
+ this.#headers = null;
67
+ this.#status = 200;
68
+ }
69
+ get headers() {
70
+ if (this.#headers === null) {
71
+ this.#headers = new Headers();
70
72
  }
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;
73
+ return this.#headers;
74
+ }
75
+ status(status) {
76
+ if (status === void 0) {
77
+ return this.#status;
75
78
  }
76
- return response;
77
- };
78
- }
79
+ this.#status = status;
80
+ return this;
81
+ }
82
+ /**
83
+ * Turn this KaitoHead instance into a Response instance
84
+ * @param body The Kaito JSON format to be sent as the response body
85
+ * @returns A Response instance, ready to be sent
86
+ */
87
+ toResponse(body) {
88
+ const init = {
89
+ status: this.#status
90
+ };
91
+ if (this.#headers) {
92
+ init.headers = this.#headers;
93
+ }
94
+ return Response.json(body, init);
95
+ }
96
+ /**
97
+ * Whether this KaitoHead instance has been touched/modified
98
+ */
99
+ get touched() {
100
+ return this.#status !== 200 || this.#headers !== null;
101
+ }
102
+ };
79
103
 
80
104
  // src/request.ts
81
105
  var KaitoRequest = class {
@@ -115,91 +139,25 @@ var KaitoRequest = class {
115
139
  }
116
140
  };
117
141
 
118
- // src/head.ts
119
- var KaitoHead = class {
120
- _headers;
121
- _status;
122
- constructor() {
123
- this._headers = null;
124
- this._status = 200;
125
- }
126
- get headers() {
127
- if (this._headers === null) {
128
- this._headers = new Headers();
129
- }
130
- return this._headers;
131
- }
132
- status(status) {
133
- if (status === void 0) {
134
- return this._status;
135
- }
136
- this._status = status;
137
- return this;
138
- }
139
- /**
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
143
- */
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);
152
- }
153
- /**
154
- * Whether this KaitoHead instance has been touched/modified
155
- */
156
- get touched() {
157
- return this._status !== 200 || this._headers !== null;
158
- }
159
- };
160
-
161
142
  // src/util.ts
162
143
  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
144
 
175
145
  // src/router/router.ts
176
146
  var Router = class _Router {
177
147
  state;
178
- static create = () => new _Router({
148
+ static create = (config) => new _Router({
179
149
  through: async (context) => context,
180
- routes: /* @__PURE__ */ new Set()
150
+ routes: /* @__PURE__ */ new Set(),
151
+ config
181
152
  });
182
- static parseQuery(schema, url) {
183
- if (!schema) {
184
- return {};
185
- }
186
- const result = {};
187
- for (const key in schema) {
188
- if (!schema.hasOwnProperty(key)) continue;
189
- const value = url.searchParams.get(key);
190
- result[key] = schema[key].parse(value);
191
- }
192
- return result;
193
- }
194
- constructor(options) {
195
- this.state = options;
153
+ constructor(state) {
154
+ this.state = state;
196
155
  }
197
156
  get routes() {
198
157
  return this.state.routes;
199
158
  }
200
159
  add = (method, path, route) => {
201
160
  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)
203
161
  ...typeof route === "object" ? route : { run: route },
204
162
  method,
205
163
  path,
@@ -220,39 +178,54 @@ var Router = class _Router {
220
178
  routes: /* @__PURE__ */ new Set([...this.state.routes, ...newRoutes])
221
179
  });
222
180
  };
223
- freeze = (server) => {
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());
181
+ static getFindRoute = (routes) => (method, path) => {
182
+ const params = {};
183
+ const pathParts = path.split("/").filter(Boolean);
184
+ const methodRoutes = routes.get(method);
185
+ if (!methodRoutes) return {};
186
+ for (const [routePath, route] of methodRoutes) {
187
+ const routeParts = routePath.split("/").filter(Boolean);
188
+ if (routeParts.length !== pathParts.length) {
189
+ continue;
228
190
  }
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 };
191
+ let matches = true;
192
+ for (let i = 0; i < routeParts.length; i++) {
193
+ const routePart = routeParts[i];
194
+ const pathPart = pathParts[i];
195
+ if (routePart && pathPart && routePart.startsWith(":")) {
196
+ params[routePart.slice(1)] = pathPart;
197
+ } else if (routePart !== pathPart) {
198
+ matches = false;
199
+ break;
251
200
  }
252
201
  }
253
- return { params };
254
- };
255
- return async (req) => {
202
+ if (matches) return { route, params };
203
+ }
204
+ return {};
205
+ };
206
+ static buildQuerySchema = (schema) => {
207
+ const keys = Object.keys(schema);
208
+ return import_zod.z.instanceof(URLSearchParams).transform((params) => {
209
+ const result = {};
210
+ for (const key of keys) {
211
+ result[key] = params.get(key);
212
+ }
213
+ return result;
214
+ }).pipe(import_zod.z.object(schema));
215
+ };
216
+ serve = () => {
217
+ const methodToRoutesMap = /* @__PURE__ */ new Map();
218
+ for (const route of this.state.routes) {
219
+ if (!methodToRoutesMap.has(route.method)) {
220
+ methodToRoutesMap.set(route.method, /* @__PURE__ */ new Map());
221
+ }
222
+ methodToRoutesMap.get(route.method).set(route.path, {
223
+ ...route,
224
+ fastQuerySchema: route.query ? _Router.buildQuerySchema(route.query) : void 0
225
+ });
226
+ }
227
+ const findRoute = _Router.getFindRoute(methodToRoutesMap);
228
+ const handle = async (req) => {
256
229
  const url = new URL(req.url);
257
230
  const method = req.method;
258
231
  const { route, params } = findRoute(method, url.pathname);
@@ -267,10 +240,9 @@ var Router = class _Router {
267
240
  const request = new KaitoRequest(url, req);
268
241
  const head = new KaitoHead();
269
242
  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);
243
+ const body = route.body ? await route.body.parseAsync(await req.json()) : void 0;
244
+ const query = route.fastQuerySchema ? await route.fastQuerySchema.parseAsync(url.searchParams) : {};
245
+ const ctx = await route.through(await this.state.config.getContext?.(request, head) ?? null);
274
246
  const result = await route.run({
275
247
  ctx,
276
248
  body,
@@ -293,8 +265,7 @@ var Router = class _Router {
293
265
  }
294
266
  return head.toResponse({
295
267
  success: true,
296
- data: result,
297
- message: "OK"
268
+ data: result
298
269
  });
299
270
  } catch (e) {
300
271
  const error = WrappedError.maybe(e);
@@ -305,15 +276,115 @@ var Router = class _Router {
305
276
  message: error.message
306
277
  });
307
278
  }
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
313
- });
279
+ if (!this.state.config.onError) {
280
+ return head.status(500).toResponse({
281
+ success: false,
282
+ data: null,
283
+ message: "Internal Server Error"
284
+ });
285
+ }
286
+ try {
287
+ const { status, message } = await this.state.config.onError(error, request);
288
+ return head.status(status).toResponse({
289
+ success: false,
290
+ data: null,
291
+ message
292
+ });
293
+ } catch (e2) {
294
+ console.error("KAITO - Failed to handle error inside `.onError()`, returning 500 and Internal Server Error");
295
+ console.error(e2);
296
+ return head.status(500).toResponse({
297
+ success: false,
298
+ data: null,
299
+ message: "Internal Server Error"
300
+ });
301
+ }
302
+ }
303
+ };
304
+ return async (request) => {
305
+ if (this.state.config.before) {
306
+ const result = await this.state.config.before(request);
307
+ if (result instanceof Response) {
308
+ if (this.state.config.transform) {
309
+ const transformed = await this.state.config.transform(request, result);
310
+ if (transformed instanceof Response) {
311
+ return result;
312
+ }
313
+ }
314
+ return result;
315
+ }
316
+ }
317
+ const response = await handle(request);
318
+ if (this.state.config.transform) {
319
+ const transformed = await this.state.config.transform(request, response);
320
+ if (transformed instanceof Response) {
321
+ return transformed;
322
+ }
314
323
  }
324
+ return response;
315
325
  };
316
326
  };
327
+ openapi = (highLevelSpec) => {
328
+ const OPENAPI_VERSION = "3.0.0";
329
+ const paths = {};
330
+ for (const route of this.state.routes) {
331
+ const path = route.path;
332
+ const pathWithColonParamsReplaceWithCurlyBraces = path.replace(/:(\w+)/g, "{$1}");
333
+ if (!paths[pathWithColonParamsReplaceWithCurlyBraces]) {
334
+ paths[pathWithColonParamsReplaceWithCurlyBraces] = {};
335
+ }
336
+ const item = {
337
+ description: route.openapi?.description ?? "Successful response",
338
+ responses: {
339
+ 200: {
340
+ description: route.openapi?.description ?? "Successful response",
341
+ ...route.openapi ? {
342
+ content: {
343
+ [{
344
+ json: "application/json",
345
+ sse: "text/event-stream"
346
+ }[route.openapi.body.type]]: { schema: route.openapi?.body.schema }
347
+ }
348
+ } : {}
349
+ }
350
+ }
351
+ };
352
+ if (route.body) {
353
+ item.requestBody = {
354
+ content: {
355
+ "application/json": { schema: route.body }
356
+ }
357
+ };
358
+ }
359
+ const params = {};
360
+ if (route.query) {
361
+ params.query = import_zod.z.object(route.query);
362
+ }
363
+ const urlParams = path.match(/:(\w+)/g);
364
+ if (urlParams) {
365
+ const pathParams = {};
366
+ for (const param of urlParams) {
367
+ pathParams[param.slice(1)] = import_zod.z.string();
368
+ }
369
+ params.path = import_zod.z.object(pathParams);
370
+ }
371
+ item.requestParams = params;
372
+ paths[pathWithColonParamsReplaceWithCurlyBraces][route.method.toLowerCase()] = item;
373
+ }
374
+ const doc = (0, import_zod_openapi.createDocument)({
375
+ openapi: OPENAPI_VERSION,
376
+ paths,
377
+ ...highLevelSpec,
378
+ servers: Object.entries(highLevelSpec.servers ?? {}).map((entry) => {
379
+ const [url, description] = entry;
380
+ return {
381
+ url,
382
+ description
383
+ };
384
+ })
385
+ });
386
+ return this.add("GET", "/openapi.json", async () => Response.json(doc));
387
+ };
317
388
  method = (method) => (path, route) => {
318
389
  return this.add(method, path, route);
319
390
  };
@@ -327,18 +398,21 @@ var Router = class _Router {
327
398
  through = (through) => {
328
399
  return new _Router({
329
400
  ...this.state,
330
- through: async (context) => through(await this.state.through(context))
401
+ through: async (context) => await through(await this.state.through(context))
331
402
  });
332
403
  };
333
404
  };
405
+
406
+ // src/create.ts
407
+ function create(config = {}) {
408
+ return () => Router.create(config);
409
+ }
334
410
  // Annotate the CommonJS export names for ESM import in node:
335
411
  0 && (module.exports = {
336
412
  KaitoError,
337
413
  KaitoRequest,
338
414
  Router,
339
415
  WrappedError,
340
- createKaitoHandler,
341
- createUtilities,
342
- isNodeLikeDev,
343
- parsable
416
+ create,
417
+ isNodeLikeDev
344
418
  });