@kaito-http/core 4.0.0-beta.4 → 4.0.0-beta.6

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
@@ -33,6 +33,7 @@ module.exports = __toCommonJS(index_exports);
33
33
  // src/router/router.ts
34
34
  var import_zod = require("zod");
35
35
  var import_zod_openapi = require("zod-openapi");
36
+ var import_extend = require("zod-openapi/extend");
36
37
 
37
38
  // src/error.ts
38
39
  var WrappedError = class _WrappedError extends Error {
@@ -149,7 +150,8 @@ var Router = class _Router {
149
150
  static create = (config) => new _Router({
150
151
  through: async (context) => context,
151
152
  routes: /* @__PURE__ */ new Set(),
152
- config
153
+ config,
154
+ paramsSchema: null
153
155
  });
154
156
  constructor(state) {
155
157
  this.state = state;
@@ -162,14 +164,17 @@ var Router = class _Router {
162
164
  ...typeof route === "object" ? route : { run: route },
163
165
  method,
164
166
  path,
165
- through: this.state.through
167
+ router: this
166
168
  };
167
169
  return new _Router({
168
170
  ...this.state,
169
171
  routes: /* @__PURE__ */ new Set([...this.state.routes, merged])
170
172
  });
171
173
  };
172
- params = () => new _Router(this.state);
174
+ params = (spec) => new _Router({
175
+ ...this.state,
176
+ paramsSchema: import_zod.z.object(spec)
177
+ });
173
178
  merge = (pathPrefix, other) => {
174
179
  const newRoutes = [...other.state.routes].map((route) => ({
175
180
  ...route,
@@ -230,7 +235,7 @@ var Router = class _Router {
230
235
  const handle = async (req) => {
231
236
  const url = new URL(req.url);
232
237
  const method = req.method;
233
- const { route, params } = findRoute(method, url.pathname);
238
+ const { route, params: rawParams } = findRoute(method, url.pathname);
234
239
  if (!route) {
235
240
  const body = {
236
241
  success: false,
@@ -244,7 +249,11 @@ var Router = class _Router {
244
249
  try {
245
250
  const body = route.body ? await route.body.parseAsync(await req.json()) : void 0;
246
251
  const query = route.fastQuerySchema ? await route.fastQuerySchema.parseAsync(url.searchParams) : {};
247
- const ctx = await route.through(await this.state.config.getContext?.(request, head) ?? null);
252
+ const params = route.router.state.paramsSchema ? route.router.state.paramsSchema.parse(rawParams) : rawParams;
253
+ const ctx = await route.router.state.through(
254
+ await this.state.config.getContext?.(request, head) ?? null,
255
+ params
256
+ );
248
257
  const result = await route.run({
249
258
  ctx,
250
259
  body,
@@ -331,23 +340,35 @@ var Router = class _Router {
331
340
  const paths = {};
332
341
  for (const route of this.state.routes) {
333
342
  const path = route.path;
343
+ if (!route.openapi) {
344
+ continue;
345
+ }
334
346
  const pathWithColonParamsReplaceWithCurlyBraces = path.replace(/:(\w+)/g, "{$1}");
335
347
  if (!paths[pathWithColonParamsReplaceWithCurlyBraces]) {
336
348
  paths[pathWithColonParamsReplaceWithCurlyBraces] = {};
337
349
  }
350
+ const content = route.openapi.body.type === "json" ? {
351
+ "application/json": {
352
+ schema: import_zod.z.object({
353
+ success: import_zod.z.literal(true).openapi({
354
+ type: "boolean",
355
+ enum: [true]
356
+ // Need this as zod-openapi doesn't properly work with literals
357
+ }),
358
+ data: route.openapi.body.schema
359
+ })
360
+ }
361
+ } : {
362
+ "text/event-stream": {
363
+ schema: route.openapi.body.schema
364
+ }
365
+ };
338
366
  const item = {
339
367
  description: route.openapi?.description ?? "Successful response",
340
368
  responses: {
341
- 200: {
369
+ default: {
342
370
  description: route.openapi?.description ?? "Successful response",
343
- ...route.openapi ? {
344
- content: {
345
- [{
346
- json: "application/json",
347
- sse: "text/event-stream"
348
- }[route.openapi.body.type]]: { schema: route.openapi?.body.schema }
349
- }
350
- } : {}
371
+ content
351
372
  }
352
373
  }
353
374
  };
@@ -385,7 +406,7 @@ var Router = class _Router {
385
406
  };
386
407
  })
387
408
  });
388
- return this.add("GET", "/openapi.json", async () => Response.json(doc));
409
+ return this.get("/openapi.json", () => Response.json(doc));
389
410
  };
390
411
  method = (method) => {
391
412
  return (path, route) => this.add(method, path, route);
@@ -400,7 +421,7 @@ var Router = class _Router {
400
421
  through = (through) => {
401
422
  return new _Router({
402
423
  ...this.state,
403
- through: async (context) => await through(await this.state.through(context))
424
+ through: async (context, params) => await through(await this.state.through(context, params), params)
404
425
  });
405
426
  };
406
427
  };
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { z } from 'zod';
1
+ import { z, ZodTypeDef } from 'zod';
2
2
  import { KaitoSSEResponse } from './stream/stream.cjs';
3
3
 
4
4
  declare class WrappedError<T> extends Error {
@@ -27,8 +27,6 @@ declare class KaitoRequest {
27
27
  get request(): Request;
28
28
  }
29
29
 
30
- type KaitoMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
31
-
32
30
  /**
33
31
  * This class is merely a wrapper around a `Headers` object and a status code.
34
32
  * It's used while the router is executing a route to store any mutations to the status
@@ -91,6 +89,10 @@ type APIResponse<T> = ErroredAPIResponse | SuccessfulAPIResponse<T>;
91
89
  type AnyResponse = APIResponse<unknown>;
92
90
  type MakeOptional<T, K extends keyof T> = T extends T ? Omit<T, K> & Partial<Pick<T, K>> : never;
93
91
  type MaybePromise<T> = T | Promise<T>;
92
+ type KaitoMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
93
+ type NotReadonly<T> = {
94
+ -readonly [K in keyof T]: T[K];
95
+ };
94
96
  type ExtractRouteParams<T extends string> = string extends T ? Record<string, string> : T extends `${string}:${infer Param}/${infer Rest}` ? {
95
97
  [k in Param | keyof ExtractRouteParams<Rest>]: string;
96
98
  } : T extends `${string}:${infer Param}` ? {
@@ -118,7 +120,7 @@ type RouteRunData<Params, Context, QueryOutput, BodyOutput> = {
118
120
  type AnyQuery = {
119
121
  [key in string]: any;
120
122
  };
121
- type Through<From, To> = (context: From) => Promise<To>;
123
+ type Through<From, To, RequiredParams extends Record<string, unknown>> = (context: From, params: RequiredParams) => Promise<To>;
122
124
  type SSEOutputSpec<Result> = {
123
125
  type: 'sse';
124
126
  schema: z.Schema<Result>;
@@ -133,8 +135,7 @@ type OutputSpec<Result> = {
133
135
  description?: string;
134
136
  body: NoInfer<Result extends KaitoSSEResponse<infer R> ? SSEOutputSpec<R> : JSONOutputSpec<Result>>;
135
137
  };
136
- type Route<ContextTo, Result, Path extends string, AdditionalParams extends Record<string, string>, Method extends KaitoMethod, Query, Body> = {
137
- through: Through<unknown, ContextTo>;
138
+ type Route<ContextTo, Result, Path extends string, AdditionalParams extends Record<string, unknown>, Method extends KaitoMethod, Query, Body> = {
138
139
  body?: z.Schema<Body>;
139
140
  query?: {
140
141
  [Key in keyof Query]: z.Schema<Query[Key]>;
@@ -142,36 +143,40 @@ type Route<ContextTo, Result, Path extends string, AdditionalParams extends Reco
142
143
  path: Path;
143
144
  method: Method;
144
145
  openapi?: OutputSpec<NoInfer<Result>>;
146
+ router: Router<unknown, ContextTo, AdditionalParams, AnyRoute>;
145
147
  run(data: RouteRunData<ExtractRouteParams<Path> & AdditionalParams, ContextTo, Query, Body>): Promise<Result> | Result;
146
148
  };
147
149
  type AnyRoute = Route<any, any, any, any, any, any, any>;
148
150
 
149
151
  type PrefixRoutesPathInner<R extends AnyRoute, Prefix extends `/${string}`> = R extends Route<infer ContextTo, infer Result, infer Path, infer AdditionalParams, infer Method, infer Query, infer BodyOutput> ? Route<ContextTo, Result, `${Prefix}${Path}`, AdditionalParams, Method, Query, BodyOutput> : never;
150
152
  type PrefixRoutesPath<Prefix extends `/${string}`, R extends AnyRoute> = R extends R ? PrefixRoutesPathInner<R, Prefix> : never;
151
- type RouterState<ContextFrom, ContextTo, Routes extends AnyRoute> = {
153
+ type RouterState<ContextFrom, ContextTo, RequiredParams extends Record<string, unknown>, Routes extends AnyRoute> = {
152
154
  routes: Set<Routes>;
153
- through: (context: unknown) => Promise<ContextTo>;
155
+ through: (context: unknown, params: RequiredParams) => Promise<ContextTo>;
154
156
  config: KaitoConfig<ContextFrom>;
157
+ paramsSchema: z.Schema<RequiredParams> | null;
155
158
  };
156
159
  /**
157
160
  * Accepts a router instance, and returns a union of all the routes in the router
158
161
  *
159
162
  * @example
160
163
  * ```ts
161
- * const app = router().get('/', () => 'Hello, world!');
164
+ * const app = router.get('/', () => 'Hello, world!');
162
165
  *
163
166
  * type Routes = InferRoutes<typeof app>;
164
167
  * ```
165
168
  */
166
- type InferRoutes<R extends Router<any, any, any, any>> = R extends Router<any, any, infer R extends AnyRoute, any> ? R : never;
167
- declare class Router<ContextFrom, ContextTo, R extends AnyRoute, RequiredParams extends Record<string, string>> {
169
+ type InferRoutes<R extends Router<any, any, any, any>> = R extends Router<any, any, any, infer R extends AnyRoute> ? R : never;
170
+ declare class Router<ContextFrom, ContextTo, RequiredParams extends Record<string, unknown>, R extends AnyRoute> {
168
171
  private readonly state;
169
- static create: <Context>(config: KaitoConfig<Context>) => Router<Context, Context, never, {}>;
170
- private constructor();
172
+ static create: <Context>(config: KaitoConfig<Context>) => Router<Context, Context, {}, never>;
173
+ protected constructor(state: RouterState<ContextFrom, ContextTo, RequiredParams, R>);
171
174
  get routes(): Set<R>;
172
175
  private add;
173
- params: this extends Router<infer ContextFrom, infer ContextTo, infer R extends AnyRoute, infer Params extends Record<string, string>> ? [keyof Params] extends [never] ? <NextParams extends Record<string, string> = {}>() => Router<ContextFrom, ContextTo, R, NextParams> : 'You cannot define params() on a router that has already had params defined, as routes that already consume params can break.' : never;
174
- readonly merge: <PathPrefix extends `/${string}`, OtherRoutes extends AnyRoute, NextRequiredParams extends Record<string, string>>(pathPrefix: keyof NextRequiredParams extends keyof ExtractRouteParams<PathPrefix> | keyof RequiredParams ? PathPrefix : `${string}:/${Exclude<Extract<keyof NextRequiredParams, string>, keyof RequiredParams>}${string}`, other: Router<ContextFrom, unknown, OtherRoutes, NextRequiredParams>) => Router<ContextFrom, ContextTo, Extract<R | PrefixRoutesPath<PathPrefix, Extract<OtherRoutes, AnyRoute>>, AnyRoute>, RequiredParams>;
176
+ params: this extends Router<infer ContextFrom, infer ContextTo, infer Params extends Record<string, unknown>, infer R extends AnyRoute> ? [keyof Params] extends [never] ? <NextParams extends Record<string, unknown> = {}>(spec: {
177
+ [Key in keyof NextParams]: z.ZodType<NextParams[Key], ZodTypeDef, string>;
178
+ }) => Router<ContextFrom, ContextTo, NextParams, R> : 'You cannot define params() on a router that has already had params defined, as routes that already consume params can break.' : never;
179
+ readonly merge: <PathPrefix extends `/${string}`, NextRequiredParams extends Record<string, unknown>, OtherRoutes extends AnyRoute>(pathPrefix: keyof NextRequiredParams extends keyof ExtractRouteParams<PathPrefix> | keyof RequiredParams ? PathPrefix : `Missing ${Exclude<Extract<keyof NextRequiredParams, string>, keyof RequiredParams>}${string}`, other: Router<ContextFrom, unknown, NextRequiredParams, OtherRoutes>) => Router<ContextFrom, ContextTo, RequiredParams, Extract<R | PrefixRoutesPath<PathPrefix, Extract<OtherRoutes, AnyRoute>>, AnyRoute>>;
175
180
  protected static getFindRoute: <R_1>(routes: Map<KaitoMethod, Map<string, R_1>>) => (method: KaitoMethod, path: string) => {
176
181
  route?: never;
177
182
  params?: never;
@@ -188,16 +193,16 @@ declare class Router<ContextFrom, ContextTo, R extends AnyRoute, RequiredParams
188
193
  description?: string;
189
194
  };
190
195
  servers?: Partial<Record<(`https://` | `http://`) | ({} & string), string>>;
191
- }) => Router<ContextFrom, ContextTo, R | Route<ContextTo, Response, "/openapi.json", RequiredParams, "GET", AnyQuery, unknown>, RequiredParams>;
196
+ }) => Router<ContextFrom, ContextTo, RequiredParams, R | Route<ContextTo, Response, "/openapi.json", RequiredParams, "GET", {}, never>>;
192
197
  private readonly method;
193
- get: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "GET", Query, Body>, "body" | "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextTo, Result, Path, RequiredParams, "GET", Query, Body>, RequiredParams>;
194
- post: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "POST", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextTo, Result, Path, RequiredParams, "POST", Query, Body>, RequiredParams>;
195
- put: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "PUT", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextTo, Result, Path, RequiredParams, "PUT", Query, Body>, RequiredParams>;
196
- patch: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "PATCH", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextTo, Result, Path, RequiredParams, "PATCH", Query, Body>, RequiredParams>;
197
- delete: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "DELETE", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextTo, Result, Path, RequiredParams, "DELETE", Query, Body>, RequiredParams>;
198
- head: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "HEAD", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextTo, Result, Path, RequiredParams, "HEAD", Query, Body>, RequiredParams>;
199
- options: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "OPTIONS", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextTo, Result, Path, RequiredParams, "OPTIONS", Query, Body>, RequiredParams>;
200
- through: <NextContext>(through: (context: ContextTo) => MaybePromise<NextContext>) => Router<ContextFrom, NextContext, R, RequiredParams>;
198
+ get: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "GET", Query, Body>, "body" | "path" | "method" | "router">) => Router<ContextFrom, ContextTo, RequiredParams, R | Route<ContextTo, Result, Path, RequiredParams, "GET", Query, Body>>;
199
+ post: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "POST", Query, Body>, "path" | "method" | "router">) => Router<ContextFrom, ContextTo, RequiredParams, R | Route<ContextTo, Result, Path, RequiredParams, "POST", Query, Body>>;
200
+ put: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "PUT", Query, Body>, "path" | "method" | "router">) => Router<ContextFrom, ContextTo, RequiredParams, R | Route<ContextTo, Result, Path, RequiredParams, "PUT", Query, Body>>;
201
+ patch: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "PATCH", Query, Body>, "path" | "method" | "router">) => Router<ContextFrom, ContextTo, RequiredParams, R | Route<ContextTo, Result, Path, RequiredParams, "PATCH", Query, Body>>;
202
+ delete: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "DELETE", Query, Body>, "path" | "method" | "router">) => Router<ContextFrom, ContextTo, RequiredParams, R | Route<ContextTo, Result, Path, RequiredParams, "DELETE", Query, Body>>;
203
+ head: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "HEAD", Query, Body>, "path" | "method" | "router">) => Router<ContextFrom, ContextTo, RequiredParams, R | Route<ContextTo, Result, Path, RequiredParams, "HEAD", Query, Body>>;
204
+ options: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "OPTIONS", Query, Body>, "path" | "method" | "router">) => Router<ContextFrom, ContextTo, RequiredParams, R | Route<ContextTo, Result, Path, RequiredParams, "OPTIONS", Query, Body>>;
205
+ through: <NextContext>(through: (context: ContextTo, params: RequiredParams) => MaybePromise<NextContext>) => Router<ContextFrom, NextContext, RequiredParams, R>;
201
206
  }
202
207
 
203
208
  type KaitoConfig<ContextFrom> = {
@@ -266,6 +271,6 @@ type KaitoConfig<ContextFrom> = {
266
271
  * @param config - The configuration for the router
267
272
  * @returns A new Kaito router
268
273
  */
269
- declare function create<Context = null>(config?: KaitoConfig<Context>): Router<Context, Context, never, {}>;
274
+ declare function create<Context = null>(config?: KaitoConfig<Context>): Router<Context, Context, {}, never>;
270
275
 
271
- export { type APIResponse, type AnyQuery, type AnyResponse, type AnyRoute, type ErroredAPIResponse, type ExtractRouteParams, type GetContext, type InferRoutes, type JSONOutputSpec, type KaitoConfig, KaitoError, KaitoHead, type KaitoMethod, KaitoRequest, type MakeOptional, type MaybePromise, type OutputSpec, type Route, type RouteRunData, Router, type RouterState, type SSEOutputSpec, type SuccessfulAPIResponse, type Through, WrappedError, create, isNodeLikeDev };
276
+ export { type APIResponse, type AnyQuery, type AnyResponse, type AnyRoute, type ErroredAPIResponse, type ExtractRouteParams, type GetContext, type InferRoutes, type JSONOutputSpec, type KaitoConfig, KaitoError, KaitoHead, type KaitoMethod, KaitoRequest, type MakeOptional, type MaybePromise, type NotReadonly, type OutputSpec, type Route, type RouteRunData, Router, type RouterState, type SSEOutputSpec, type SuccessfulAPIResponse, type Through, WrappedError, create, isNodeLikeDev };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { z } from 'zod';
1
+ import { z, ZodTypeDef } from 'zod';
2
2
  import { KaitoSSEResponse } from './stream/stream.js';
3
3
 
4
4
  declare class WrappedError<T> extends Error {
@@ -27,8 +27,6 @@ declare class KaitoRequest {
27
27
  get request(): Request;
28
28
  }
29
29
 
30
- type KaitoMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
31
-
32
30
  /**
33
31
  * This class is merely a wrapper around a `Headers` object and a status code.
34
32
  * It's used while the router is executing a route to store any mutations to the status
@@ -91,6 +89,10 @@ type APIResponse<T> = ErroredAPIResponse | SuccessfulAPIResponse<T>;
91
89
  type AnyResponse = APIResponse<unknown>;
92
90
  type MakeOptional<T, K extends keyof T> = T extends T ? Omit<T, K> & Partial<Pick<T, K>> : never;
93
91
  type MaybePromise<T> = T | Promise<T>;
92
+ type KaitoMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
93
+ type NotReadonly<T> = {
94
+ -readonly [K in keyof T]: T[K];
95
+ };
94
96
  type ExtractRouteParams<T extends string> = string extends T ? Record<string, string> : T extends `${string}:${infer Param}/${infer Rest}` ? {
95
97
  [k in Param | keyof ExtractRouteParams<Rest>]: string;
96
98
  } : T extends `${string}:${infer Param}` ? {
@@ -118,7 +120,7 @@ type RouteRunData<Params, Context, QueryOutput, BodyOutput> = {
118
120
  type AnyQuery = {
119
121
  [key in string]: any;
120
122
  };
121
- type Through<From, To> = (context: From) => Promise<To>;
123
+ type Through<From, To, RequiredParams extends Record<string, unknown>> = (context: From, params: RequiredParams) => Promise<To>;
122
124
  type SSEOutputSpec<Result> = {
123
125
  type: 'sse';
124
126
  schema: z.Schema<Result>;
@@ -133,8 +135,7 @@ type OutputSpec<Result> = {
133
135
  description?: string;
134
136
  body: NoInfer<Result extends KaitoSSEResponse<infer R> ? SSEOutputSpec<R> : JSONOutputSpec<Result>>;
135
137
  };
136
- type Route<ContextTo, Result, Path extends string, AdditionalParams extends Record<string, string>, Method extends KaitoMethod, Query, Body> = {
137
- through: Through<unknown, ContextTo>;
138
+ type Route<ContextTo, Result, Path extends string, AdditionalParams extends Record<string, unknown>, Method extends KaitoMethod, Query, Body> = {
138
139
  body?: z.Schema<Body>;
139
140
  query?: {
140
141
  [Key in keyof Query]: z.Schema<Query[Key]>;
@@ -142,36 +143,40 @@ type Route<ContextTo, Result, Path extends string, AdditionalParams extends Reco
142
143
  path: Path;
143
144
  method: Method;
144
145
  openapi?: OutputSpec<NoInfer<Result>>;
146
+ router: Router<unknown, ContextTo, AdditionalParams, AnyRoute>;
145
147
  run(data: RouteRunData<ExtractRouteParams<Path> & AdditionalParams, ContextTo, Query, Body>): Promise<Result> | Result;
146
148
  };
147
149
  type AnyRoute = Route<any, any, any, any, any, any, any>;
148
150
 
149
151
  type PrefixRoutesPathInner<R extends AnyRoute, Prefix extends `/${string}`> = R extends Route<infer ContextTo, infer Result, infer Path, infer AdditionalParams, infer Method, infer Query, infer BodyOutput> ? Route<ContextTo, Result, `${Prefix}${Path}`, AdditionalParams, Method, Query, BodyOutput> : never;
150
152
  type PrefixRoutesPath<Prefix extends `/${string}`, R extends AnyRoute> = R extends R ? PrefixRoutesPathInner<R, Prefix> : never;
151
- type RouterState<ContextFrom, ContextTo, Routes extends AnyRoute> = {
153
+ type RouterState<ContextFrom, ContextTo, RequiredParams extends Record<string, unknown>, Routes extends AnyRoute> = {
152
154
  routes: Set<Routes>;
153
- through: (context: unknown) => Promise<ContextTo>;
155
+ through: (context: unknown, params: RequiredParams) => Promise<ContextTo>;
154
156
  config: KaitoConfig<ContextFrom>;
157
+ paramsSchema: z.Schema<RequiredParams> | null;
155
158
  };
156
159
  /**
157
160
  * Accepts a router instance, and returns a union of all the routes in the router
158
161
  *
159
162
  * @example
160
163
  * ```ts
161
- * const app = router().get('/', () => 'Hello, world!');
164
+ * const app = router.get('/', () => 'Hello, world!');
162
165
  *
163
166
  * type Routes = InferRoutes<typeof app>;
164
167
  * ```
165
168
  */
166
- type InferRoutes<R extends Router<any, any, any, any>> = R extends Router<any, any, infer R extends AnyRoute, any> ? R : never;
167
- declare class Router<ContextFrom, ContextTo, R extends AnyRoute, RequiredParams extends Record<string, string>> {
169
+ type InferRoutes<R extends Router<any, any, any, any>> = R extends Router<any, any, any, infer R extends AnyRoute> ? R : never;
170
+ declare class Router<ContextFrom, ContextTo, RequiredParams extends Record<string, unknown>, R extends AnyRoute> {
168
171
  private readonly state;
169
- static create: <Context>(config: KaitoConfig<Context>) => Router<Context, Context, never, {}>;
170
- private constructor();
172
+ static create: <Context>(config: KaitoConfig<Context>) => Router<Context, Context, {}, never>;
173
+ protected constructor(state: RouterState<ContextFrom, ContextTo, RequiredParams, R>);
171
174
  get routes(): Set<R>;
172
175
  private add;
173
- params: this extends Router<infer ContextFrom, infer ContextTo, infer R extends AnyRoute, infer Params extends Record<string, string>> ? [keyof Params] extends [never] ? <NextParams extends Record<string, string> = {}>() => Router<ContextFrom, ContextTo, R, NextParams> : 'You cannot define params() on a router that has already had params defined, as routes that already consume params can break.' : never;
174
- readonly merge: <PathPrefix extends `/${string}`, OtherRoutes extends AnyRoute, NextRequiredParams extends Record<string, string>>(pathPrefix: keyof NextRequiredParams extends keyof ExtractRouteParams<PathPrefix> | keyof RequiredParams ? PathPrefix : `${string}:/${Exclude<Extract<keyof NextRequiredParams, string>, keyof RequiredParams>}${string}`, other: Router<ContextFrom, unknown, OtherRoutes, NextRequiredParams>) => Router<ContextFrom, ContextTo, Extract<R | PrefixRoutesPath<PathPrefix, Extract<OtherRoutes, AnyRoute>>, AnyRoute>, RequiredParams>;
176
+ params: this extends Router<infer ContextFrom, infer ContextTo, infer Params extends Record<string, unknown>, infer R extends AnyRoute> ? [keyof Params] extends [never] ? <NextParams extends Record<string, unknown> = {}>(spec: {
177
+ [Key in keyof NextParams]: z.ZodType<NextParams[Key], ZodTypeDef, string>;
178
+ }) => Router<ContextFrom, ContextTo, NextParams, R> : 'You cannot define params() on a router that has already had params defined, as routes that already consume params can break.' : never;
179
+ readonly merge: <PathPrefix extends `/${string}`, NextRequiredParams extends Record<string, unknown>, OtherRoutes extends AnyRoute>(pathPrefix: keyof NextRequiredParams extends keyof ExtractRouteParams<PathPrefix> | keyof RequiredParams ? PathPrefix : `Missing ${Exclude<Extract<keyof NextRequiredParams, string>, keyof RequiredParams>}${string}`, other: Router<ContextFrom, unknown, NextRequiredParams, OtherRoutes>) => Router<ContextFrom, ContextTo, RequiredParams, Extract<R | PrefixRoutesPath<PathPrefix, Extract<OtherRoutes, AnyRoute>>, AnyRoute>>;
175
180
  protected static getFindRoute: <R_1>(routes: Map<KaitoMethod, Map<string, R_1>>) => (method: KaitoMethod, path: string) => {
176
181
  route?: never;
177
182
  params?: never;
@@ -188,16 +193,16 @@ declare class Router<ContextFrom, ContextTo, R extends AnyRoute, RequiredParams
188
193
  description?: string;
189
194
  };
190
195
  servers?: Partial<Record<(`https://` | `http://`) | ({} & string), string>>;
191
- }) => Router<ContextFrom, ContextTo, R | Route<ContextTo, Response, "/openapi.json", RequiredParams, "GET", AnyQuery, unknown>, RequiredParams>;
196
+ }) => Router<ContextFrom, ContextTo, RequiredParams, R | Route<ContextTo, Response, "/openapi.json", RequiredParams, "GET", {}, never>>;
192
197
  private readonly method;
193
- get: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "GET", Query, Body>, "body" | "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextTo, Result, Path, RequiredParams, "GET", Query, Body>, RequiredParams>;
194
- post: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "POST", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextTo, Result, Path, RequiredParams, "POST", Query, Body>, RequiredParams>;
195
- put: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "PUT", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextTo, Result, Path, RequiredParams, "PUT", Query, Body>, RequiredParams>;
196
- patch: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "PATCH", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextTo, Result, Path, RequiredParams, "PATCH", Query, Body>, RequiredParams>;
197
- delete: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "DELETE", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextTo, Result, Path, RequiredParams, "DELETE", Query, Body>, RequiredParams>;
198
- head: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "HEAD", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextTo, Result, Path, RequiredParams, "HEAD", Query, Body>, RequiredParams>;
199
- options: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "OPTIONS", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextTo, Result, Path, RequiredParams, "OPTIONS", Query, Body>, RequiredParams>;
200
- through: <NextContext>(through: (context: ContextTo) => MaybePromise<NextContext>) => Router<ContextFrom, NextContext, R, RequiredParams>;
198
+ get: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "GET", Query, Body>, "body" | "path" | "method" | "router">) => Router<ContextFrom, ContextTo, RequiredParams, R | Route<ContextTo, Result, Path, RequiredParams, "GET", Query, Body>>;
199
+ post: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "POST", Query, Body>, "path" | "method" | "router">) => Router<ContextFrom, ContextTo, RequiredParams, R | Route<ContextTo, Result, Path, RequiredParams, "POST", Query, Body>>;
200
+ put: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "PUT", Query, Body>, "path" | "method" | "router">) => Router<ContextFrom, ContextTo, RequiredParams, R | Route<ContextTo, Result, Path, RequiredParams, "PUT", Query, Body>>;
201
+ patch: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "PATCH", Query, Body>, "path" | "method" | "router">) => Router<ContextFrom, ContextTo, RequiredParams, R | Route<ContextTo, Result, Path, RequiredParams, "PATCH", Query, Body>>;
202
+ delete: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "DELETE", Query, Body>, "path" | "method" | "router">) => Router<ContextFrom, ContextTo, RequiredParams, R | Route<ContextTo, Result, Path, RequiredParams, "DELETE", Query, Body>>;
203
+ head: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "HEAD", Query, Body>, "path" | "method" | "router">) => Router<ContextFrom, ContextTo, RequiredParams, R | Route<ContextTo, Result, Path, RequiredParams, "HEAD", Query, Body>>;
204
+ options: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "OPTIONS", Query, Body>, "path" | "method" | "router">) => Router<ContextFrom, ContextTo, RequiredParams, R | Route<ContextTo, Result, Path, RequiredParams, "OPTIONS", Query, Body>>;
205
+ through: <NextContext>(through: (context: ContextTo, params: RequiredParams) => MaybePromise<NextContext>) => Router<ContextFrom, NextContext, RequiredParams, R>;
201
206
  }
202
207
 
203
208
  type KaitoConfig<ContextFrom> = {
@@ -266,6 +271,6 @@ type KaitoConfig<ContextFrom> = {
266
271
  * @param config - The configuration for the router
267
272
  * @returns A new Kaito router
268
273
  */
269
- declare function create<Context = null>(config?: KaitoConfig<Context>): Router<Context, Context, never, {}>;
274
+ declare function create<Context = null>(config?: KaitoConfig<Context>): Router<Context, Context, {}, never>;
270
275
 
271
- export { type APIResponse, type AnyQuery, type AnyResponse, type AnyRoute, type ErroredAPIResponse, type ExtractRouteParams, type GetContext, type InferRoutes, type JSONOutputSpec, type KaitoConfig, KaitoError, KaitoHead, type KaitoMethod, KaitoRequest, type MakeOptional, type MaybePromise, type OutputSpec, type Route, type RouteRunData, Router, type RouterState, type SSEOutputSpec, type SuccessfulAPIResponse, type Through, WrappedError, create, isNodeLikeDev };
276
+ export { type APIResponse, type AnyQuery, type AnyResponse, type AnyRoute, type ErroredAPIResponse, type ExtractRouteParams, type GetContext, type InferRoutes, type JSONOutputSpec, type KaitoConfig, KaitoError, KaitoHead, type KaitoMethod, KaitoRequest, type MakeOptional, type MaybePromise, type NotReadonly, type OutputSpec, type Route, type RouteRunData, Router, type RouterState, type SSEOutputSpec, type SuccessfulAPIResponse, type Through, WrappedError, create, isNodeLikeDev };
package/dist/index.js CHANGED
@@ -1,6 +1,9 @@
1
1
  // src/router/router.ts
2
2
  import { z } from "zod";
3
- import { createDocument } from "zod-openapi";
3
+ import {
4
+ createDocument
5
+ } from "zod-openapi";
6
+ import "zod-openapi/extend";
4
7
 
5
8
  // src/error.ts
6
9
  var WrappedError = class _WrappedError extends Error {
@@ -117,7 +120,8 @@ var Router = class _Router {
117
120
  static create = (config) => new _Router({
118
121
  through: async (context) => context,
119
122
  routes: /* @__PURE__ */ new Set(),
120
- config
123
+ config,
124
+ paramsSchema: null
121
125
  });
122
126
  constructor(state) {
123
127
  this.state = state;
@@ -130,14 +134,17 @@ var Router = class _Router {
130
134
  ...typeof route === "object" ? route : { run: route },
131
135
  method,
132
136
  path,
133
- through: this.state.through
137
+ router: this
134
138
  };
135
139
  return new _Router({
136
140
  ...this.state,
137
141
  routes: /* @__PURE__ */ new Set([...this.state.routes, merged])
138
142
  });
139
143
  };
140
- params = () => new _Router(this.state);
144
+ params = (spec) => new _Router({
145
+ ...this.state,
146
+ paramsSchema: z.object(spec)
147
+ });
141
148
  merge = (pathPrefix, other) => {
142
149
  const newRoutes = [...other.state.routes].map((route) => ({
143
150
  ...route,
@@ -198,7 +205,7 @@ var Router = class _Router {
198
205
  const handle = async (req) => {
199
206
  const url = new URL(req.url);
200
207
  const method = req.method;
201
- const { route, params } = findRoute(method, url.pathname);
208
+ const { route, params: rawParams } = findRoute(method, url.pathname);
202
209
  if (!route) {
203
210
  const body = {
204
211
  success: false,
@@ -212,7 +219,11 @@ var Router = class _Router {
212
219
  try {
213
220
  const body = route.body ? await route.body.parseAsync(await req.json()) : void 0;
214
221
  const query = route.fastQuerySchema ? await route.fastQuerySchema.parseAsync(url.searchParams) : {};
215
- const ctx = await route.through(await this.state.config.getContext?.(request, head) ?? null);
222
+ const params = route.router.state.paramsSchema ? route.router.state.paramsSchema.parse(rawParams) : rawParams;
223
+ const ctx = await route.router.state.through(
224
+ await this.state.config.getContext?.(request, head) ?? null,
225
+ params
226
+ );
216
227
  const result = await route.run({
217
228
  ctx,
218
229
  body,
@@ -299,23 +310,35 @@ var Router = class _Router {
299
310
  const paths = {};
300
311
  for (const route of this.state.routes) {
301
312
  const path = route.path;
313
+ if (!route.openapi) {
314
+ continue;
315
+ }
302
316
  const pathWithColonParamsReplaceWithCurlyBraces = path.replace(/:(\w+)/g, "{$1}");
303
317
  if (!paths[pathWithColonParamsReplaceWithCurlyBraces]) {
304
318
  paths[pathWithColonParamsReplaceWithCurlyBraces] = {};
305
319
  }
320
+ const content = route.openapi.body.type === "json" ? {
321
+ "application/json": {
322
+ schema: z.object({
323
+ success: z.literal(true).openapi({
324
+ type: "boolean",
325
+ enum: [true]
326
+ // Need this as zod-openapi doesn't properly work with literals
327
+ }),
328
+ data: route.openapi.body.schema
329
+ })
330
+ }
331
+ } : {
332
+ "text/event-stream": {
333
+ schema: route.openapi.body.schema
334
+ }
335
+ };
306
336
  const item = {
307
337
  description: route.openapi?.description ?? "Successful response",
308
338
  responses: {
309
- 200: {
339
+ default: {
310
340
  description: route.openapi?.description ?? "Successful response",
311
- ...route.openapi ? {
312
- content: {
313
- [{
314
- json: "application/json",
315
- sse: "text/event-stream"
316
- }[route.openapi.body.type]]: { schema: route.openapi?.body.schema }
317
- }
318
- } : {}
341
+ content
319
342
  }
320
343
  }
321
344
  };
@@ -353,7 +376,7 @@ var Router = class _Router {
353
376
  };
354
377
  })
355
378
  });
356
- return this.add("GET", "/openapi.json", async () => Response.json(doc));
379
+ return this.get("/openapi.json", () => Response.json(doc));
357
380
  };
358
381
  method = (method) => {
359
382
  return (path, route) => this.add(method, path, route);
@@ -368,7 +391,7 @@ var Router = class _Router {
368
391
  through = (through) => {
369
392
  return new _Router({
370
393
  ...this.state,
371
- through: async (context) => await through(await this.state.through(context))
394
+ through: async (context, params) => await through(await this.state.through(context, params), params)
372
395
  });
373
396
  };
374
397
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaito-http/core",
3
- "version": "4.0.0-beta.4",
3
+ "version": "4.0.0-beta.6",
4
4
  "author": "Alistair Smith <hi@alistair.sh>",
5
5
  "repository": "https://github.com/kaito-http/kaito",
6
6
  "devDependencies": {