@kaito-http/core 3.0.0-beta.16 → 3.0.0-beta.21
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 +27 -26
- package/dist/index.d.cts +80 -25
- package/dist/index.d.ts +80 -25
- package/dist/index.js +27 -26
- package/dist/stream/stream.cjs +142 -0
- package/dist/stream/stream.d.cts +39 -0
- package/dist/stream/stream.d.ts +39 -0
- package/dist/stream/stream.js +111 -0
- package/package.json +18 -4
- package/src/error.ts +26 -0
- package/src/index.ts +7 -0
- package/src/request.ts +47 -0
- package/src/response.ts +72 -0
- package/src/route.ts +53 -0
- package/src/router/router.test.ts +269 -0
- package/src/router/router.ts +254 -0
- package/src/router/types.ts +1 -0
- package/src/server.ts +87 -0
- package/src/stream/stream.ts +159 -0
- package/src/util.ts +66 -0
package/dist/index.cjs
CHANGED
|
@@ -106,6 +106,9 @@ var KaitoResponse = class {
|
|
|
106
106
|
return this._headers;
|
|
107
107
|
}
|
|
108
108
|
status(status) {
|
|
109
|
+
if (status === void 0) {
|
|
110
|
+
return this._status;
|
|
111
|
+
}
|
|
109
112
|
this._status = status;
|
|
110
113
|
return this;
|
|
111
114
|
}
|
|
@@ -125,19 +128,6 @@ var KaitoResponse = class {
|
|
|
125
128
|
}
|
|
126
129
|
};
|
|
127
130
|
|
|
128
|
-
// src/util.ts
|
|
129
|
-
function createUtilities(getContext) {
|
|
130
|
-
return {
|
|
131
|
-
getContext,
|
|
132
|
-
router: () => Router.create()
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
function parsable(parse) {
|
|
136
|
-
return {
|
|
137
|
-
parse
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
131
|
// src/router/router.ts
|
|
142
132
|
var Router = class _Router {
|
|
143
133
|
state;
|
|
@@ -150,9 +140,10 @@ var Router = class _Router {
|
|
|
150
140
|
return {};
|
|
151
141
|
}
|
|
152
142
|
const result = {};
|
|
153
|
-
for (const
|
|
143
|
+
for (const key in schema) {
|
|
144
|
+
if (!schema.hasOwnProperty(key)) continue;
|
|
154
145
|
const value = url.searchParams.get(key);
|
|
155
|
-
result[key] =
|
|
146
|
+
result[key] = schema[key].parse(value);
|
|
156
147
|
}
|
|
157
148
|
return result;
|
|
158
149
|
}
|
|
@@ -232,10 +223,10 @@ var Router = class _Router {
|
|
|
232
223
|
const request = new KaitoRequest(url, req);
|
|
233
224
|
const response = new KaitoResponse();
|
|
234
225
|
try {
|
|
235
|
-
const rootCtx = await server.getContext(request, response);
|
|
236
|
-
const ctx = await route.through(rootCtx);
|
|
237
226
|
const body = route.body ? await route.body.parse(await req.json()) : void 0;
|
|
238
227
|
const query = _Router.parseQuery(route.query, url);
|
|
228
|
+
const rootCtx = await server.getContext(request, response);
|
|
229
|
+
const ctx = await route.through(rootCtx);
|
|
239
230
|
const result = await route.run({
|
|
240
231
|
ctx,
|
|
241
232
|
body,
|
|
@@ -289,23 +280,33 @@ var Router = class _Router {
|
|
|
289
280
|
|
|
290
281
|
// src/server.ts
|
|
291
282
|
function createKaitoHandler(config) {
|
|
292
|
-
const
|
|
283
|
+
const handle = config.router.freeze(config);
|
|
293
284
|
return async (request) => {
|
|
294
|
-
let before = void 0;
|
|
295
285
|
if (config.before) {
|
|
296
286
|
const result = await config.before(request);
|
|
297
|
-
if (result instanceof Response)
|
|
298
|
-
return result;
|
|
299
|
-
}
|
|
300
|
-
before = result;
|
|
287
|
+
if (result instanceof Response) return result;
|
|
301
288
|
}
|
|
302
|
-
const response = await
|
|
303
|
-
if (
|
|
304
|
-
await config.
|
|
289
|
+
const response = await handle(request);
|
|
290
|
+
if (config.transform) {
|
|
291
|
+
const result = await config.transform(request, response);
|
|
292
|
+
if (result instanceof Response) return result;
|
|
305
293
|
}
|
|
306
294
|
return response;
|
|
307
295
|
};
|
|
308
296
|
}
|
|
297
|
+
|
|
298
|
+
// src/util.ts
|
|
299
|
+
function createUtilities(getContext) {
|
|
300
|
+
return {
|
|
301
|
+
getContext,
|
|
302
|
+
router: () => Router.create()
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
function parsable(parse) {
|
|
306
|
+
return {
|
|
307
|
+
parse
|
|
308
|
+
};
|
|
309
|
+
}
|
|
309
310
|
// Annotate the CommonJS export names for ESM import in node:
|
|
310
311
|
0 && (module.exports = {
|
|
311
312
|
KaitoError,
|
package/dist/index.d.cts
CHANGED
|
@@ -34,10 +34,10 @@ type KaitoMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIO
|
|
|
34
34
|
* ```ts
|
|
35
35
|
* const response = new KaitoResponse();
|
|
36
36
|
*
|
|
37
|
-
* response.status
|
|
38
|
-
* response.
|
|
37
|
+
* response.status(200);
|
|
38
|
+
* response.headers.set('Content-Type', 'application/json');
|
|
39
39
|
*
|
|
40
|
-
* console.log(response.headers); // Headers {
|
|
40
|
+
* console.log(response.headers); // Headers {'content-type': 'application/json'}
|
|
41
41
|
* ```
|
|
42
42
|
*/
|
|
43
43
|
declare class KaitoResponse {
|
|
@@ -45,6 +45,16 @@ declare class KaitoResponse {
|
|
|
45
45
|
private _status;
|
|
46
46
|
constructor();
|
|
47
47
|
get headers(): Headers;
|
|
48
|
+
/**
|
|
49
|
+
* Gets the status code of this KaitoResponse instance
|
|
50
|
+
* @returns The status code
|
|
51
|
+
*/
|
|
52
|
+
status(): number;
|
|
53
|
+
/**
|
|
54
|
+
* Sets the status code of this KaitoResponse instance
|
|
55
|
+
* @param status The status code to set
|
|
56
|
+
* @returns This KaitoResponse instance
|
|
57
|
+
*/
|
|
48
58
|
status(status: number): this;
|
|
49
59
|
/**
|
|
50
60
|
* Turn this KaitoResponse instance into a Response instance
|
|
@@ -54,26 +64,70 @@ declare class KaitoResponse {
|
|
|
54
64
|
toResponse<T>(body: APIResponse<T>): Response;
|
|
55
65
|
}
|
|
56
66
|
|
|
57
|
-
type Before
|
|
58
|
-
type
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
} | {
|
|
63
|
-
before?: undefined;
|
|
64
|
-
};
|
|
65
|
-
type ServerConfig<ContextFrom, BeforeAfterContext> = ServerConfigWithBefore<BeforeAfterContext> & {
|
|
67
|
+
type Before = (req: Request) => Promise<Response | void | undefined>;
|
|
68
|
+
type ServerConfig<ContextFrom> = {
|
|
69
|
+
/**
|
|
70
|
+
* The root router to mount on this server.
|
|
71
|
+
*/
|
|
66
72
|
router: Router<ContextFrom, unknown, any>;
|
|
73
|
+
/**
|
|
74
|
+
* A function that is called to get the context for a request.
|
|
75
|
+
*
|
|
76
|
+
* This is useful for things like authentication, to pass in a database connection, etc.
|
|
77
|
+
*
|
|
78
|
+
* It's fine for this function to throw; if it does, the error is passed to the `onError` function.
|
|
79
|
+
*/
|
|
67
80
|
getContext: GetContext<ContextFrom>;
|
|
68
|
-
|
|
81
|
+
/**
|
|
82
|
+
* A function that is called when an error occurs inside a route handler.
|
|
83
|
+
*
|
|
84
|
+
* The result of this function is used to determine the response status and message, and is
|
|
85
|
+
* always sent to the client. You could include logic to check for production vs development
|
|
86
|
+
* environments here, and this would also be a good place to include error tracking
|
|
87
|
+
* like Sentry or Rollbar.
|
|
88
|
+
*
|
|
89
|
+
* @param arg - The error and the request
|
|
90
|
+
* @returns A KaitoError or an object with a status and message
|
|
91
|
+
*/
|
|
92
|
+
onError: (arg: {
|
|
69
93
|
error: Error;
|
|
70
94
|
req: KaitoRequest;
|
|
71
|
-
})
|
|
95
|
+
}) => Promise<KaitoError | {
|
|
72
96
|
status: number;
|
|
73
97
|
message: string;
|
|
74
98
|
}>;
|
|
99
|
+
/**
|
|
100
|
+
* A function that is called before every request. Most useful for bailing out early in the case of an OPTIONS request.
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```ts
|
|
104
|
+
* before: async req => {
|
|
105
|
+
* if (req.method === 'OPTIONS') {
|
|
106
|
+
* return new Response(null, {status: 204});
|
|
107
|
+
* }
|
|
108
|
+
* }
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
before?: Before;
|
|
112
|
+
/**
|
|
113
|
+
* Transforms the response before it is sent to the client. Very useful for settings headers like CORS.
|
|
114
|
+
*
|
|
115
|
+
* You can also return a new response in this function, or just mutate the current one.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```ts
|
|
119
|
+
* transform: async (req, res) => {
|
|
120
|
+
* res.headers.set('Access-Control-Allow-Origin', 'http://localhost:3000');
|
|
121
|
+
* res.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
122
|
+
* res.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
123
|
+
* res.headers.set('Access-Control-Max-Age', '86400');
|
|
124
|
+
* res.headers.set('Access-Control-Allow-Credentials', 'true');
|
|
125
|
+
* }
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
transform?: (req: Request, res: Response) => Promise<Response | void | undefined>;
|
|
75
129
|
};
|
|
76
|
-
declare function createKaitoHandler<Context
|
|
130
|
+
declare function createKaitoHandler<Context>(config: ServerConfig<Context>): (request: Request) => Promise<Response>;
|
|
77
131
|
|
|
78
132
|
type PrefixRoutesPathInner<R extends AnyRoute, Prefix extends `/${string}`> = R extends Route<infer ContextFrom, infer ContextTo, infer Result, infer Path, infer Method, infer Query, infer BodyOutput> ? Route<ContextFrom, ContextTo, Result, `${Prefix}${Path}`, Method, Query, BodyOutput> : never;
|
|
79
133
|
type PrefixRoutesPath<Prefix extends `/${string}`, R extends AnyRoute> = R extends R ? PrefixRoutesPathInner<R, Prefix> : never;
|
|
@@ -88,17 +142,17 @@ declare class Router<ContextFrom, ContextTo, R extends AnyRoute> {
|
|
|
88
142
|
private static parseQuery;
|
|
89
143
|
constructor(options: RouterState<R, ContextFrom, ContextTo>);
|
|
90
144
|
get routes(): Set<R>;
|
|
91
|
-
add: <Result, Path extends string, Method extends KaitoMethod, Query extends AnyQueryDefinition = {}, Body extends Parsable = never>(method: Method, path: Path, route: (Method extends "GET" ? Omit<Route<ContextFrom, ContextTo, Result, Path, Method, Query, Body>, "body" | "path" | "method" | "through"> : Omit<Route<ContextFrom, ContextTo, Result, Path, Method, Query, Body>, "path" | "method" | "through">) | Route<ContextFrom, ContextTo, Result, Path, Method, Query, Body>["run"]) => Router<ContextFrom, ContextTo, R | Route<ContextFrom, ContextTo, Result
|
|
145
|
+
add: <Result, Path extends string, Method extends KaitoMethod, Query extends AnyQueryDefinition = {}, Body extends Parsable = never>(method: Method, path: Path, route: (Method extends "GET" ? Omit<Route<ContextFrom, ContextTo, Result, Path, Method, Query, Body>, "body" | "path" | "method" | "through"> : Omit<Route<ContextFrom, ContextTo, Result, Path, Method, Query, Body>, "path" | "method" | "through">) | Route<ContextFrom, ContextTo, Result, Path, Method, Query, Body>["run"]) => Router<ContextFrom, ContextTo, R | Route<ContextFrom, ContextTo, Result, Path, Method, Query, Body>>;
|
|
92
146
|
readonly merge: <PathPrefix extends `/${string}`, OtherRoutes extends AnyRoute>(pathPrefix: PathPrefix, other: Router<ContextFrom, unknown, OtherRoutes>) => Router<ContextFrom, ContextTo, Extract<R | PrefixRoutesPath<PathPrefix, OtherRoutes>, AnyRoute>>;
|
|
93
|
-
freeze: (server: Omit<ServerConfig<ContextFrom
|
|
147
|
+
freeze: (server: Omit<ServerConfig<ContextFrom>, "router">) => (req: Request) => Promise<Response>;
|
|
94
148
|
private readonly method;
|
|
95
|
-
get: <Result, Path extends string, Query extends AnyQueryDefinition = {}, Body extends Parsable = never>(path: Path, route: ((arg: RouteArgument<Path, ContextTo, { [Key in keyof Query]: InferParsable<Query[Key]>["output"]; }, InferParsable<Body>["output"]>) => Promise<Result>) | Omit<Route<ContextFrom, ContextTo, Result, Path, "GET", Query, Body>, "body" | "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextFrom, ContextTo, Result
|
|
96
|
-
post: <Result, Path extends string, Query extends AnyQueryDefinition = {}, Body extends Parsable = never>(path: Path, route: ((arg: RouteArgument<Path, ContextTo, { [Key in keyof Query]: InferParsable<Query[Key]>["output"]; }, InferParsable<Body>["output"]>) => Promise<Result>) | Omit<Route<ContextFrom, ContextTo, Result, Path, "POST", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextFrom, ContextTo, Result
|
|
97
|
-
put: <Result, Path extends string, Query extends AnyQueryDefinition = {}, Body extends Parsable = never>(path: Path, route: ((arg: RouteArgument<Path, ContextTo, { [Key in keyof Query]: InferParsable<Query[Key]>["output"]; }, InferParsable<Body>["output"]>) => Promise<Result>) | Omit<Route<ContextFrom, ContextTo, Result, Path, "PUT", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextFrom, ContextTo, Result
|
|
98
|
-
patch: <Result, Path extends string, Query extends AnyQueryDefinition = {}, Body extends Parsable = never>(path: Path, route: ((arg: RouteArgument<Path, ContextTo, { [Key in keyof Query]: InferParsable<Query[Key]>["output"]; }, InferParsable<Body>["output"]>) => Promise<Result>) | Omit<Route<ContextFrom, ContextTo, Result, Path, "PATCH", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextFrom, ContextTo, Result
|
|
99
|
-
delete: <Result, Path extends string, Query extends AnyQueryDefinition = {}, Body extends Parsable = never>(path: Path, route: ((arg: RouteArgument<Path, ContextTo, { [Key in keyof Query]: InferParsable<Query[Key]>["output"]; }, InferParsable<Body>["output"]>) => Promise<Result>) | Omit<Route<ContextFrom, ContextTo, Result, Path, "DELETE", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextFrom, ContextTo, Result
|
|
100
|
-
head: <Result, Path extends string, Query extends AnyQueryDefinition = {}, Body extends Parsable = never>(path: Path, route: ((arg: RouteArgument<Path, ContextTo, { [Key in keyof Query]: InferParsable<Query[Key]>["output"]; }, InferParsable<Body>["output"]>) => Promise<Result>) | Omit<Route<ContextFrom, ContextTo, Result, Path, "HEAD", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextFrom, ContextTo, Result
|
|
101
|
-
options: <Result, Path extends string, Query extends AnyQueryDefinition = {}, Body extends Parsable = never>(path: Path, route: ((arg: RouteArgument<Path, ContextTo, { [Key in keyof Query]: InferParsable<Query[Key]>["output"]; }, InferParsable<Body>["output"]>) => Promise<Result>) | Omit<Route<ContextFrom, ContextTo, Result, Path, "OPTIONS", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextFrom, ContextTo, Result
|
|
149
|
+
get: <Result, Path extends string, Query extends AnyQueryDefinition = {}, Body extends Parsable = never>(path: Path, route: ((arg: RouteArgument<Path, ContextTo, { [Key in keyof Query]: InferParsable<Query[Key]>["output"]; }, InferParsable<Body>["output"]>) => Promise<Result>) | Omit<Route<ContextFrom, ContextTo, Result, Path, "GET", Query, Body>, "body" | "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextFrom, ContextTo, Result, Path, "GET", Query, Body>>;
|
|
150
|
+
post: <Result, Path extends string, Query extends AnyQueryDefinition = {}, Body extends Parsable = never>(path: Path, route: ((arg: RouteArgument<Path, ContextTo, { [Key in keyof Query]: InferParsable<Query[Key]>["output"]; }, InferParsable<Body>["output"]>) => Promise<Result>) | Omit<Route<ContextFrom, ContextTo, Result, Path, "POST", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextFrom, ContextTo, Result, Path, "POST", Query, Body>>;
|
|
151
|
+
put: <Result, Path extends string, Query extends AnyQueryDefinition = {}, Body extends Parsable = never>(path: Path, route: ((arg: RouteArgument<Path, ContextTo, { [Key in keyof Query]: InferParsable<Query[Key]>["output"]; }, InferParsable<Body>["output"]>) => Promise<Result>) | Omit<Route<ContextFrom, ContextTo, Result, Path, "PUT", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextFrom, ContextTo, Result, Path, "PUT", Query, Body>>;
|
|
152
|
+
patch: <Result, Path extends string, Query extends AnyQueryDefinition = {}, Body extends Parsable = never>(path: Path, route: ((arg: RouteArgument<Path, ContextTo, { [Key in keyof Query]: InferParsable<Query[Key]>["output"]; }, InferParsable<Body>["output"]>) => Promise<Result>) | Omit<Route<ContextFrom, ContextTo, Result, Path, "PATCH", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextFrom, ContextTo, Result, Path, "PATCH", Query, Body>>;
|
|
153
|
+
delete: <Result, Path extends string, Query extends AnyQueryDefinition = {}, Body extends Parsable = never>(path: Path, route: ((arg: RouteArgument<Path, ContextTo, { [Key in keyof Query]: InferParsable<Query[Key]>["output"]; }, InferParsable<Body>["output"]>) => Promise<Result>) | Omit<Route<ContextFrom, ContextTo, Result, Path, "DELETE", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextFrom, ContextTo, Result, Path, "DELETE", Query, Body>>;
|
|
154
|
+
head: <Result, Path extends string, Query extends AnyQueryDefinition = {}, Body extends Parsable = never>(path: Path, route: ((arg: RouteArgument<Path, ContextTo, { [Key in keyof Query]: InferParsable<Query[Key]>["output"]; }, InferParsable<Body>["output"]>) => Promise<Result>) | Omit<Route<ContextFrom, ContextTo, Result, Path, "HEAD", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextFrom, ContextTo, Result, Path, "HEAD", Query, Body>>;
|
|
155
|
+
options: <Result, Path extends string, Query extends AnyQueryDefinition = {}, Body extends Parsable = never>(path: Path, route: ((arg: RouteArgument<Path, ContextTo, { [Key in keyof Query]: InferParsable<Query[Key]>["output"]; }, InferParsable<Body>["output"]>) => Promise<Result>) | Omit<Route<ContextFrom, ContextTo, Result, Path, "OPTIONS", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextFrom, ContextTo, Result, Path, "OPTIONS", Query, Body>>;
|
|
102
156
|
through: <NextContext>(transform: (context: ContextTo) => Promise<NextContext>) => Router<ContextFrom, NextContext, R>;
|
|
103
157
|
}
|
|
104
158
|
|
|
@@ -114,6 +168,7 @@ type SuccessfulAPIResponse<T> = {
|
|
|
114
168
|
};
|
|
115
169
|
type APIResponse<T> = ErroredAPIResponse | SuccessfulAPIResponse<T>;
|
|
116
170
|
type AnyResponse = APIResponse<unknown>;
|
|
171
|
+
type MakeOptional<T, K extends keyof T> = T extends T ? Omit<T, K> & Partial<Pick<T, K>> : never;
|
|
117
172
|
type ExtractRouteParams<T extends string> = string extends T ? Record<string, string> : T extends `${string}:${infer Param}/${infer Rest}` ? {
|
|
118
173
|
[k in Param | keyof ExtractRouteParams<Rest>]: string;
|
|
119
174
|
} : T extends `${string}:${infer Param}` ? {
|
|
@@ -172,4 +227,4 @@ type Route<ContextFrom, ContextTo, Result, Path extends string, Method extends K
|
|
|
172
227
|
};
|
|
173
228
|
type AnyRoute<ContextFrom = any, ContextTo = any> = Route<ContextFrom, ContextTo, any, any, any, AnyQueryDefinition, any>;
|
|
174
229
|
|
|
175
|
-
export { type APIResponse, type
|
|
230
|
+
export { type APIResponse, type AnyQueryDefinition, type AnyResponse, type AnyRoute, type Before, type ErroredAPIResponse, type ExtractRouteParams, type GetContext, type InferParsable, type InferRoutes, KaitoError, type KaitoMethod, KaitoRequest, type MakeOptional, type Parsable, type Route, type RouteArgument, Router, type RouterState, type ServerConfig, type SuccessfulAPIResponse, type Through, WrappedError, createKaitoHandler, createUtilities, parsable };
|
package/dist/index.d.ts
CHANGED
|
@@ -34,10 +34,10 @@ type KaitoMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIO
|
|
|
34
34
|
* ```ts
|
|
35
35
|
* const response = new KaitoResponse();
|
|
36
36
|
*
|
|
37
|
-
* response.status
|
|
38
|
-
* response.
|
|
37
|
+
* response.status(200);
|
|
38
|
+
* response.headers.set('Content-Type', 'application/json');
|
|
39
39
|
*
|
|
40
|
-
* console.log(response.headers); // Headers {
|
|
40
|
+
* console.log(response.headers); // Headers {'content-type': 'application/json'}
|
|
41
41
|
* ```
|
|
42
42
|
*/
|
|
43
43
|
declare class KaitoResponse {
|
|
@@ -45,6 +45,16 @@ declare class KaitoResponse {
|
|
|
45
45
|
private _status;
|
|
46
46
|
constructor();
|
|
47
47
|
get headers(): Headers;
|
|
48
|
+
/**
|
|
49
|
+
* Gets the status code of this KaitoResponse instance
|
|
50
|
+
* @returns The status code
|
|
51
|
+
*/
|
|
52
|
+
status(): number;
|
|
53
|
+
/**
|
|
54
|
+
* Sets the status code of this KaitoResponse instance
|
|
55
|
+
* @param status The status code to set
|
|
56
|
+
* @returns This KaitoResponse instance
|
|
57
|
+
*/
|
|
48
58
|
status(status: number): this;
|
|
49
59
|
/**
|
|
50
60
|
* Turn this KaitoResponse instance into a Response instance
|
|
@@ -54,26 +64,70 @@ declare class KaitoResponse {
|
|
|
54
64
|
toResponse<T>(body: APIResponse<T>): Response;
|
|
55
65
|
}
|
|
56
66
|
|
|
57
|
-
type Before
|
|
58
|
-
type
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
} | {
|
|
63
|
-
before?: undefined;
|
|
64
|
-
};
|
|
65
|
-
type ServerConfig<ContextFrom, BeforeAfterContext> = ServerConfigWithBefore<BeforeAfterContext> & {
|
|
67
|
+
type Before = (req: Request) => Promise<Response | void | undefined>;
|
|
68
|
+
type ServerConfig<ContextFrom> = {
|
|
69
|
+
/**
|
|
70
|
+
* The root router to mount on this server.
|
|
71
|
+
*/
|
|
66
72
|
router: Router<ContextFrom, unknown, any>;
|
|
73
|
+
/**
|
|
74
|
+
* A function that is called to get the context for a request.
|
|
75
|
+
*
|
|
76
|
+
* This is useful for things like authentication, to pass in a database connection, etc.
|
|
77
|
+
*
|
|
78
|
+
* It's fine for this function to throw; if it does, the error is passed to the `onError` function.
|
|
79
|
+
*/
|
|
67
80
|
getContext: GetContext<ContextFrom>;
|
|
68
|
-
|
|
81
|
+
/**
|
|
82
|
+
* A function that is called when an error occurs inside a route handler.
|
|
83
|
+
*
|
|
84
|
+
* The result of this function is used to determine the response status and message, and is
|
|
85
|
+
* always sent to the client. You could include logic to check for production vs development
|
|
86
|
+
* environments here, and this would also be a good place to include error tracking
|
|
87
|
+
* like Sentry or Rollbar.
|
|
88
|
+
*
|
|
89
|
+
* @param arg - The error and the request
|
|
90
|
+
* @returns A KaitoError or an object with a status and message
|
|
91
|
+
*/
|
|
92
|
+
onError: (arg: {
|
|
69
93
|
error: Error;
|
|
70
94
|
req: KaitoRequest;
|
|
71
|
-
})
|
|
95
|
+
}) => Promise<KaitoError | {
|
|
72
96
|
status: number;
|
|
73
97
|
message: string;
|
|
74
98
|
}>;
|
|
99
|
+
/**
|
|
100
|
+
* A function that is called before every request. Most useful for bailing out early in the case of an OPTIONS request.
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```ts
|
|
104
|
+
* before: async req => {
|
|
105
|
+
* if (req.method === 'OPTIONS') {
|
|
106
|
+
* return new Response(null, {status: 204});
|
|
107
|
+
* }
|
|
108
|
+
* }
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
before?: Before;
|
|
112
|
+
/**
|
|
113
|
+
* Transforms the response before it is sent to the client. Very useful for settings headers like CORS.
|
|
114
|
+
*
|
|
115
|
+
* You can also return a new response in this function, or just mutate the current one.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```ts
|
|
119
|
+
* transform: async (req, res) => {
|
|
120
|
+
* res.headers.set('Access-Control-Allow-Origin', 'http://localhost:3000');
|
|
121
|
+
* res.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
122
|
+
* res.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
123
|
+
* res.headers.set('Access-Control-Max-Age', '86400');
|
|
124
|
+
* res.headers.set('Access-Control-Allow-Credentials', 'true');
|
|
125
|
+
* }
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
transform?: (req: Request, res: Response) => Promise<Response | void | undefined>;
|
|
75
129
|
};
|
|
76
|
-
declare function createKaitoHandler<Context
|
|
130
|
+
declare function createKaitoHandler<Context>(config: ServerConfig<Context>): (request: Request) => Promise<Response>;
|
|
77
131
|
|
|
78
132
|
type PrefixRoutesPathInner<R extends AnyRoute, Prefix extends `/${string}`> = R extends Route<infer ContextFrom, infer ContextTo, infer Result, infer Path, infer Method, infer Query, infer BodyOutput> ? Route<ContextFrom, ContextTo, Result, `${Prefix}${Path}`, Method, Query, BodyOutput> : never;
|
|
79
133
|
type PrefixRoutesPath<Prefix extends `/${string}`, R extends AnyRoute> = R extends R ? PrefixRoutesPathInner<R, Prefix> : never;
|
|
@@ -88,17 +142,17 @@ declare class Router<ContextFrom, ContextTo, R extends AnyRoute> {
|
|
|
88
142
|
private static parseQuery;
|
|
89
143
|
constructor(options: RouterState<R, ContextFrom, ContextTo>);
|
|
90
144
|
get routes(): Set<R>;
|
|
91
|
-
add: <Result, Path extends string, Method extends KaitoMethod, Query extends AnyQueryDefinition = {}, Body extends Parsable = never>(method: Method, path: Path, route: (Method extends "GET" ? Omit<Route<ContextFrom, ContextTo, Result, Path, Method, Query, Body>, "body" | "path" | "method" | "through"> : Omit<Route<ContextFrom, ContextTo, Result, Path, Method, Query, Body>, "path" | "method" | "through">) | Route<ContextFrom, ContextTo, Result, Path, Method, Query, Body>["run"]) => Router<ContextFrom, ContextTo, R | Route<ContextFrom, ContextTo, Result
|
|
145
|
+
add: <Result, Path extends string, Method extends KaitoMethod, Query extends AnyQueryDefinition = {}, Body extends Parsable = never>(method: Method, path: Path, route: (Method extends "GET" ? Omit<Route<ContextFrom, ContextTo, Result, Path, Method, Query, Body>, "body" | "path" | "method" | "through"> : Omit<Route<ContextFrom, ContextTo, Result, Path, Method, Query, Body>, "path" | "method" | "through">) | Route<ContextFrom, ContextTo, Result, Path, Method, Query, Body>["run"]) => Router<ContextFrom, ContextTo, R | Route<ContextFrom, ContextTo, Result, Path, Method, Query, Body>>;
|
|
92
146
|
readonly merge: <PathPrefix extends `/${string}`, OtherRoutes extends AnyRoute>(pathPrefix: PathPrefix, other: Router<ContextFrom, unknown, OtherRoutes>) => Router<ContextFrom, ContextTo, Extract<R | PrefixRoutesPath<PathPrefix, OtherRoutes>, AnyRoute>>;
|
|
93
|
-
freeze: (server: Omit<ServerConfig<ContextFrom
|
|
147
|
+
freeze: (server: Omit<ServerConfig<ContextFrom>, "router">) => (req: Request) => Promise<Response>;
|
|
94
148
|
private readonly method;
|
|
95
|
-
get: <Result, Path extends string, Query extends AnyQueryDefinition = {}, Body extends Parsable = never>(path: Path, route: ((arg: RouteArgument<Path, ContextTo, { [Key in keyof Query]: InferParsable<Query[Key]>["output"]; }, InferParsable<Body>["output"]>) => Promise<Result>) | Omit<Route<ContextFrom, ContextTo, Result, Path, "GET", Query, Body>, "body" | "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextFrom, ContextTo, Result
|
|
96
|
-
post: <Result, Path extends string, Query extends AnyQueryDefinition = {}, Body extends Parsable = never>(path: Path, route: ((arg: RouteArgument<Path, ContextTo, { [Key in keyof Query]: InferParsable<Query[Key]>["output"]; }, InferParsable<Body>["output"]>) => Promise<Result>) | Omit<Route<ContextFrom, ContextTo, Result, Path, "POST", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextFrom, ContextTo, Result
|
|
97
|
-
put: <Result, Path extends string, Query extends AnyQueryDefinition = {}, Body extends Parsable = never>(path: Path, route: ((arg: RouteArgument<Path, ContextTo, { [Key in keyof Query]: InferParsable<Query[Key]>["output"]; }, InferParsable<Body>["output"]>) => Promise<Result>) | Omit<Route<ContextFrom, ContextTo, Result, Path, "PUT", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextFrom, ContextTo, Result
|
|
98
|
-
patch: <Result, Path extends string, Query extends AnyQueryDefinition = {}, Body extends Parsable = never>(path: Path, route: ((arg: RouteArgument<Path, ContextTo, { [Key in keyof Query]: InferParsable<Query[Key]>["output"]; }, InferParsable<Body>["output"]>) => Promise<Result>) | Omit<Route<ContextFrom, ContextTo, Result, Path, "PATCH", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextFrom, ContextTo, Result
|
|
99
|
-
delete: <Result, Path extends string, Query extends AnyQueryDefinition = {}, Body extends Parsable = never>(path: Path, route: ((arg: RouteArgument<Path, ContextTo, { [Key in keyof Query]: InferParsable<Query[Key]>["output"]; }, InferParsable<Body>["output"]>) => Promise<Result>) | Omit<Route<ContextFrom, ContextTo, Result, Path, "DELETE", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextFrom, ContextTo, Result
|
|
100
|
-
head: <Result, Path extends string, Query extends AnyQueryDefinition = {}, Body extends Parsable = never>(path: Path, route: ((arg: RouteArgument<Path, ContextTo, { [Key in keyof Query]: InferParsable<Query[Key]>["output"]; }, InferParsable<Body>["output"]>) => Promise<Result>) | Omit<Route<ContextFrom, ContextTo, Result, Path, "HEAD", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextFrom, ContextTo, Result
|
|
101
|
-
options: <Result, Path extends string, Query extends AnyQueryDefinition = {}, Body extends Parsable = never>(path: Path, route: ((arg: RouteArgument<Path, ContextTo, { [Key in keyof Query]: InferParsable<Query[Key]>["output"]; }, InferParsable<Body>["output"]>) => Promise<Result>) | Omit<Route<ContextFrom, ContextTo, Result, Path, "OPTIONS", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextFrom, ContextTo, Result
|
|
149
|
+
get: <Result, Path extends string, Query extends AnyQueryDefinition = {}, Body extends Parsable = never>(path: Path, route: ((arg: RouteArgument<Path, ContextTo, { [Key in keyof Query]: InferParsable<Query[Key]>["output"]; }, InferParsable<Body>["output"]>) => Promise<Result>) | Omit<Route<ContextFrom, ContextTo, Result, Path, "GET", Query, Body>, "body" | "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextFrom, ContextTo, Result, Path, "GET", Query, Body>>;
|
|
150
|
+
post: <Result, Path extends string, Query extends AnyQueryDefinition = {}, Body extends Parsable = never>(path: Path, route: ((arg: RouteArgument<Path, ContextTo, { [Key in keyof Query]: InferParsable<Query[Key]>["output"]; }, InferParsable<Body>["output"]>) => Promise<Result>) | Omit<Route<ContextFrom, ContextTo, Result, Path, "POST", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextFrom, ContextTo, Result, Path, "POST", Query, Body>>;
|
|
151
|
+
put: <Result, Path extends string, Query extends AnyQueryDefinition = {}, Body extends Parsable = never>(path: Path, route: ((arg: RouteArgument<Path, ContextTo, { [Key in keyof Query]: InferParsable<Query[Key]>["output"]; }, InferParsable<Body>["output"]>) => Promise<Result>) | Omit<Route<ContextFrom, ContextTo, Result, Path, "PUT", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextFrom, ContextTo, Result, Path, "PUT", Query, Body>>;
|
|
152
|
+
patch: <Result, Path extends string, Query extends AnyQueryDefinition = {}, Body extends Parsable = never>(path: Path, route: ((arg: RouteArgument<Path, ContextTo, { [Key in keyof Query]: InferParsable<Query[Key]>["output"]; }, InferParsable<Body>["output"]>) => Promise<Result>) | Omit<Route<ContextFrom, ContextTo, Result, Path, "PATCH", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextFrom, ContextTo, Result, Path, "PATCH", Query, Body>>;
|
|
153
|
+
delete: <Result, Path extends string, Query extends AnyQueryDefinition = {}, Body extends Parsable = never>(path: Path, route: ((arg: RouteArgument<Path, ContextTo, { [Key in keyof Query]: InferParsable<Query[Key]>["output"]; }, InferParsable<Body>["output"]>) => Promise<Result>) | Omit<Route<ContextFrom, ContextTo, Result, Path, "DELETE", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextFrom, ContextTo, Result, Path, "DELETE", Query, Body>>;
|
|
154
|
+
head: <Result, Path extends string, Query extends AnyQueryDefinition = {}, Body extends Parsable = never>(path: Path, route: ((arg: RouteArgument<Path, ContextTo, { [Key in keyof Query]: InferParsable<Query[Key]>["output"]; }, InferParsable<Body>["output"]>) => Promise<Result>) | Omit<Route<ContextFrom, ContextTo, Result, Path, "HEAD", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextFrom, ContextTo, Result, Path, "HEAD", Query, Body>>;
|
|
155
|
+
options: <Result, Path extends string, Query extends AnyQueryDefinition = {}, Body extends Parsable = never>(path: Path, route: ((arg: RouteArgument<Path, ContextTo, { [Key in keyof Query]: InferParsable<Query[Key]>["output"]; }, InferParsable<Body>["output"]>) => Promise<Result>) | Omit<Route<ContextFrom, ContextTo, Result, Path, "OPTIONS", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextFrom, ContextTo, Result, Path, "OPTIONS", Query, Body>>;
|
|
102
156
|
through: <NextContext>(transform: (context: ContextTo) => Promise<NextContext>) => Router<ContextFrom, NextContext, R>;
|
|
103
157
|
}
|
|
104
158
|
|
|
@@ -114,6 +168,7 @@ type SuccessfulAPIResponse<T> = {
|
|
|
114
168
|
};
|
|
115
169
|
type APIResponse<T> = ErroredAPIResponse | SuccessfulAPIResponse<T>;
|
|
116
170
|
type AnyResponse = APIResponse<unknown>;
|
|
171
|
+
type MakeOptional<T, K extends keyof T> = T extends T ? Omit<T, K> & Partial<Pick<T, K>> : never;
|
|
117
172
|
type ExtractRouteParams<T extends string> = string extends T ? Record<string, string> : T extends `${string}:${infer Param}/${infer Rest}` ? {
|
|
118
173
|
[k in Param | keyof ExtractRouteParams<Rest>]: string;
|
|
119
174
|
} : T extends `${string}:${infer Param}` ? {
|
|
@@ -172,4 +227,4 @@ type Route<ContextFrom, ContextTo, Result, Path extends string, Method extends K
|
|
|
172
227
|
};
|
|
173
228
|
type AnyRoute<ContextFrom = any, ContextTo = any> = Route<ContextFrom, ContextTo, any, any, any, AnyQueryDefinition, any>;
|
|
174
229
|
|
|
175
|
-
export { type APIResponse, type
|
|
230
|
+
export { type APIResponse, type AnyQueryDefinition, type AnyResponse, type AnyRoute, type Before, type ErroredAPIResponse, type ExtractRouteParams, type GetContext, type InferParsable, type InferRoutes, KaitoError, type KaitoMethod, KaitoRequest, type MakeOptional, type Parsable, type Route, type RouteArgument, Router, type RouterState, type ServerConfig, type SuccessfulAPIResponse, type Through, WrappedError, createKaitoHandler, createUtilities, parsable };
|
package/dist/index.js
CHANGED
|
@@ -74,6 +74,9 @@ var KaitoResponse = class {
|
|
|
74
74
|
return this._headers;
|
|
75
75
|
}
|
|
76
76
|
status(status) {
|
|
77
|
+
if (status === void 0) {
|
|
78
|
+
return this._status;
|
|
79
|
+
}
|
|
77
80
|
this._status = status;
|
|
78
81
|
return this;
|
|
79
82
|
}
|
|
@@ -93,19 +96,6 @@ var KaitoResponse = class {
|
|
|
93
96
|
}
|
|
94
97
|
};
|
|
95
98
|
|
|
96
|
-
// src/util.ts
|
|
97
|
-
function createUtilities(getContext) {
|
|
98
|
-
return {
|
|
99
|
-
getContext,
|
|
100
|
-
router: () => Router.create()
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
function parsable(parse) {
|
|
104
|
-
return {
|
|
105
|
-
parse
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
99
|
// src/router/router.ts
|
|
110
100
|
var Router = class _Router {
|
|
111
101
|
state;
|
|
@@ -118,9 +108,10 @@ var Router = class _Router {
|
|
|
118
108
|
return {};
|
|
119
109
|
}
|
|
120
110
|
const result = {};
|
|
121
|
-
for (const
|
|
111
|
+
for (const key in schema) {
|
|
112
|
+
if (!schema.hasOwnProperty(key)) continue;
|
|
122
113
|
const value = url.searchParams.get(key);
|
|
123
|
-
result[key] =
|
|
114
|
+
result[key] = schema[key].parse(value);
|
|
124
115
|
}
|
|
125
116
|
return result;
|
|
126
117
|
}
|
|
@@ -200,10 +191,10 @@ var Router = class _Router {
|
|
|
200
191
|
const request = new KaitoRequest(url, req);
|
|
201
192
|
const response = new KaitoResponse();
|
|
202
193
|
try {
|
|
203
|
-
const rootCtx = await server.getContext(request, response);
|
|
204
|
-
const ctx = await route.through(rootCtx);
|
|
205
194
|
const body = route.body ? await route.body.parse(await req.json()) : void 0;
|
|
206
195
|
const query = _Router.parseQuery(route.query, url);
|
|
196
|
+
const rootCtx = await server.getContext(request, response);
|
|
197
|
+
const ctx = await route.through(rootCtx);
|
|
207
198
|
const result = await route.run({
|
|
208
199
|
ctx,
|
|
209
200
|
body,
|
|
@@ -257,23 +248,33 @@ var Router = class _Router {
|
|
|
257
248
|
|
|
258
249
|
// src/server.ts
|
|
259
250
|
function createKaitoHandler(config) {
|
|
260
|
-
const
|
|
251
|
+
const handle = config.router.freeze(config);
|
|
261
252
|
return async (request) => {
|
|
262
|
-
let before = void 0;
|
|
263
253
|
if (config.before) {
|
|
264
254
|
const result = await config.before(request);
|
|
265
|
-
if (result instanceof Response)
|
|
266
|
-
return result;
|
|
267
|
-
}
|
|
268
|
-
before = result;
|
|
255
|
+
if (result instanceof Response) return result;
|
|
269
256
|
}
|
|
270
|
-
const response = await
|
|
271
|
-
if (
|
|
272
|
-
await config.
|
|
257
|
+
const response = await handle(request);
|
|
258
|
+
if (config.transform) {
|
|
259
|
+
const result = await config.transform(request, response);
|
|
260
|
+
if (result instanceof Response) return result;
|
|
273
261
|
}
|
|
274
262
|
return response;
|
|
275
263
|
};
|
|
276
264
|
}
|
|
265
|
+
|
|
266
|
+
// src/util.ts
|
|
267
|
+
function createUtilities(getContext) {
|
|
268
|
+
return {
|
|
269
|
+
getContext,
|
|
270
|
+
router: () => Router.create()
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
function parsable(parse) {
|
|
274
|
+
return {
|
|
275
|
+
parse
|
|
276
|
+
};
|
|
277
|
+
}
|
|
277
278
|
export {
|
|
278
279
|
KaitoError,
|
|
279
280
|
KaitoRequest,
|