@kaito-http/core 3.0.1 → 3.0.2
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/cors/cors.cjs +60 -0
- package/dist/cors/cors.d.cts +55 -0
- package/dist/cors/cors.d.ts +55 -0
- package/dist/cors/cors.js +34 -0
- package/dist/index.d.cts +10 -10
- package/dist/index.d.ts +10 -10
- package/package.json +8 -11
- package/src/error.ts +0 -26
- package/src/handler.ts +0 -96
- package/src/head.ts +0 -83
- package/src/index.ts +0 -7
- package/src/request.ts +0 -47
- package/src/route.ts +0 -52
- package/src/router/router.test.ts +0 -269
- package/src/router/router.ts +0 -264
- package/src/router/types.ts +0 -1
- package/src/stream/stream.ts +0 -156
- package/src/util.ts +0 -83
package/src/route.ts
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import type {KaitoMethod} from './router/types.ts';
|
|
2
|
-
import type {ExtractRouteParams, InferParsable, Parsable} from './util.ts';
|
|
3
|
-
|
|
4
|
-
export type RouteArgument<Path extends string, Context, QueryOutput, BodyOutput> = {
|
|
5
|
-
ctx: Context;
|
|
6
|
-
body: BodyOutput;
|
|
7
|
-
query: QueryOutput;
|
|
8
|
-
params: ExtractRouteParams<Path>;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export type AnyQueryDefinition = Record<string, Parsable<any, string | undefined>>;
|
|
12
|
-
|
|
13
|
-
export type Through<From, To> = (context: From) => Promise<To>;
|
|
14
|
-
|
|
15
|
-
export type Route<
|
|
16
|
-
// Router context
|
|
17
|
-
ContextFrom,
|
|
18
|
-
ContextTo,
|
|
19
|
-
// Route information
|
|
20
|
-
Result,
|
|
21
|
-
Path extends string,
|
|
22
|
-
Method extends KaitoMethod,
|
|
23
|
-
// Schemas
|
|
24
|
-
Query extends AnyQueryDefinition,
|
|
25
|
-
Body extends Parsable,
|
|
26
|
-
> = {
|
|
27
|
-
through: Through<ContextFrom, ContextTo>;
|
|
28
|
-
body?: Body;
|
|
29
|
-
query?: Query;
|
|
30
|
-
path: Path;
|
|
31
|
-
method: Method;
|
|
32
|
-
run(
|
|
33
|
-
arg: RouteArgument<
|
|
34
|
-
Path,
|
|
35
|
-
ContextTo,
|
|
36
|
-
{
|
|
37
|
-
[Key in keyof Query]: InferParsable<Query[Key]>['output'];
|
|
38
|
-
},
|
|
39
|
-
InferParsable<Body>['output']
|
|
40
|
-
>,
|
|
41
|
-
): Promise<Result>;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
export type AnyRoute<ContextFrom = any, ContextTo = any> = Route<
|
|
45
|
-
ContextFrom,
|
|
46
|
-
ContextTo,
|
|
47
|
-
any,
|
|
48
|
-
any,
|
|
49
|
-
any,
|
|
50
|
-
AnyQueryDefinition,
|
|
51
|
-
any
|
|
52
|
-
>;
|
|
@@ -1,269 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert';
|
|
2
|
-
import {describe, it} from 'node:test';
|
|
3
|
-
import {z} from 'zod';
|
|
4
|
-
import {KaitoError} from '../error.ts';
|
|
5
|
-
import {Router} from './router.ts';
|
|
6
|
-
|
|
7
|
-
type Context = {
|
|
8
|
-
userId: string;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
type AuthContext = Context & {
|
|
12
|
-
isAdmin: boolean;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
describe('Router', () => {
|
|
16
|
-
describe('create', () => {
|
|
17
|
-
it('should create an empty router', () => {
|
|
18
|
-
const router = Router.create<Context>();
|
|
19
|
-
assert.strictEqual(router.routes.size, 0);
|
|
20
|
-
});
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
describe('route handling', () => {
|
|
24
|
-
it('should handle GET requests', async () => {
|
|
25
|
-
const router = Router.create<Context>().get('/users', {
|
|
26
|
-
run: async () => ({users: []}),
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
const handler = router.freeze({
|
|
30
|
-
getContext: async () => ({userId: '123'}),
|
|
31
|
-
onError: async () => ({status: 500, message: 'Internal Server Error'}),
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
const response = await handler(new Request('http://localhost/users', {method: 'GET'}));
|
|
35
|
-
const data = await response.json();
|
|
36
|
-
|
|
37
|
-
assert.strictEqual(response.status, 200);
|
|
38
|
-
assert.deepStrictEqual(data, {
|
|
39
|
-
success: true,
|
|
40
|
-
data: {users: []},
|
|
41
|
-
message: 'OK',
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('should handle POST requests with body parsing', async () => {
|
|
46
|
-
const router = Router.create<Context>().post('/users', {
|
|
47
|
-
body: z.object({name: z.string()}),
|
|
48
|
-
run: async ({body}) => ({id: '1', name: body.name}),
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
const handler = router.freeze({
|
|
52
|
-
getContext: async () => ({userId: '123'}),
|
|
53
|
-
onError: async () => ({status: 500, message: 'Internal Server Error'}),
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
const response = await handler(
|
|
57
|
-
new Request('http://localhost/users', {
|
|
58
|
-
method: 'POST',
|
|
59
|
-
headers: {'Content-Type': 'application/json'},
|
|
60
|
-
body: JSON.stringify({name: 'John'}),
|
|
61
|
-
}),
|
|
62
|
-
);
|
|
63
|
-
const data = await response.json();
|
|
64
|
-
|
|
65
|
-
assert.strictEqual(response.status, 200);
|
|
66
|
-
assert.deepStrictEqual(data, {
|
|
67
|
-
success: true,
|
|
68
|
-
data: {id: '1', name: 'John'},
|
|
69
|
-
message: 'OK',
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('should handle URL parameters', async () => {
|
|
74
|
-
const router = Router.create<Context>().get('/users/:id', {
|
|
75
|
-
run: async ({params}) => ({id: params.id}),
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
const handler = router.freeze({
|
|
79
|
-
getContext: async () => ({userId: '123'}),
|
|
80
|
-
onError: async () => ({status: 500, message: 'Internal Server Error'}),
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
const response = await handler(new Request('http://localhost/users/456', {method: 'GET'}));
|
|
84
|
-
const data = await response.json();
|
|
85
|
-
|
|
86
|
-
assert.strictEqual(response.status, 200);
|
|
87
|
-
assert.deepStrictEqual(data, {
|
|
88
|
-
success: true,
|
|
89
|
-
data: {id: '456'},
|
|
90
|
-
message: 'OK',
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it('should handle query parameters', async () => {
|
|
95
|
-
const router = Router.create<Context>().get('/search', {
|
|
96
|
-
query: {
|
|
97
|
-
q: z.string(),
|
|
98
|
-
limit: z
|
|
99
|
-
.string()
|
|
100
|
-
.transform(value => Number(value))
|
|
101
|
-
.pipe(z.number()),
|
|
102
|
-
},
|
|
103
|
-
run: async ({query}) => ({
|
|
104
|
-
query: query.q,
|
|
105
|
-
limit: query.limit,
|
|
106
|
-
}),
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
const handler = router.freeze({
|
|
110
|
-
getContext: async () => ({userId: '123'}),
|
|
111
|
-
onError: async () => ({status: 500, message: 'Internal Server Error'}),
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
const response = await handler(new Request('http://localhost/search?q=test&limit=10', {method: 'GET'}));
|
|
115
|
-
const data = await response.json();
|
|
116
|
-
|
|
117
|
-
assert.strictEqual(response.status, 200);
|
|
118
|
-
assert.deepStrictEqual(data, {
|
|
119
|
-
success: true,
|
|
120
|
-
data: {query: 'test', limit: 10},
|
|
121
|
-
message: 'OK',
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
describe('middleware and context', () => {
|
|
127
|
-
it('should transform context through middleware', async () => {
|
|
128
|
-
const router = Router.create<Context>()
|
|
129
|
-
.through(async ctx => ({
|
|
130
|
-
...ctx,
|
|
131
|
-
isAdmin: ctx.userId === 'admin',
|
|
132
|
-
}))
|
|
133
|
-
.get('/admin', {
|
|
134
|
-
run: async ({ctx}) => ({
|
|
135
|
-
isAdmin: (ctx as AuthContext).isAdmin,
|
|
136
|
-
}),
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
const handler = router.freeze({
|
|
140
|
-
getContext: async () => ({userId: 'admin'}),
|
|
141
|
-
onError: async () => ({status: 500, message: 'Internal Server Error'}),
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
const response = await handler(new Request('http://localhost/admin', {method: 'GET'}));
|
|
145
|
-
const data = await response.json();
|
|
146
|
-
|
|
147
|
-
assert.strictEqual(response.status, 200);
|
|
148
|
-
assert.deepStrictEqual(data, {
|
|
149
|
-
success: true,
|
|
150
|
-
data: {isAdmin: true},
|
|
151
|
-
message: 'OK',
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
describe('error handling', () => {
|
|
157
|
-
it('should handle KaitoError with custom status', async () => {
|
|
158
|
-
const router = Router.create<Context>().get('/error', {
|
|
159
|
-
run: async () => {
|
|
160
|
-
throw new KaitoError(403, 'Forbidden');
|
|
161
|
-
},
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
const handler = router.freeze({
|
|
165
|
-
getContext: async () => ({userId: '123'}),
|
|
166
|
-
onError: async () => ({status: 500, message: 'Internal Server Error'}),
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
const response = await handler(new Request('http://localhost/error', {method: 'GET'}));
|
|
170
|
-
const data = await response.json();
|
|
171
|
-
|
|
172
|
-
assert.strictEqual(response.status, 403);
|
|
173
|
-
assert.deepStrictEqual(data, {
|
|
174
|
-
success: false,
|
|
175
|
-
data: null,
|
|
176
|
-
message: 'Forbidden',
|
|
177
|
-
});
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
it('should handle generic errors with server error handler', async () => {
|
|
181
|
-
const router = Router.create<Context>().get('/error', {
|
|
182
|
-
run: async () => {
|
|
183
|
-
throw new Error('Something went wrong');
|
|
184
|
-
},
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
const handler = router.freeze({
|
|
188
|
-
getContext: async () => ({userId: '123'}),
|
|
189
|
-
onError: async () => ({status: 500, message: 'Custom Error Message'}),
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
const response = await handler(new Request('http://localhost/error', {method: 'GET'}));
|
|
193
|
-
const data = await response.json();
|
|
194
|
-
|
|
195
|
-
assert.strictEqual(response.status, 500);
|
|
196
|
-
assert.deepStrictEqual(data, {
|
|
197
|
-
success: false,
|
|
198
|
-
data: null,
|
|
199
|
-
message: 'Custom Error Message',
|
|
200
|
-
});
|
|
201
|
-
});
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
describe('router merging', () => {
|
|
205
|
-
it('should merge routers with prefix', async () => {
|
|
206
|
-
const userRouter = Router.create<Context>().get('/me', {
|
|
207
|
-
run: async ({ctx}) => ({id: ctx.userId}),
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
const mainRouter = Router.create<Context>().merge('/api', userRouter);
|
|
211
|
-
|
|
212
|
-
const handler = mainRouter.freeze({
|
|
213
|
-
getContext: async () => ({userId: '123'}),
|
|
214
|
-
onError: async () => ({status: 500, message: 'Internal Server Error'}),
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
const response = await handler(new Request('http://localhost/api/me', {method: 'GET'}));
|
|
218
|
-
const data = await response.json();
|
|
219
|
-
|
|
220
|
-
assert.strictEqual(response.status, 200);
|
|
221
|
-
assert.deepStrictEqual(data, {
|
|
222
|
-
success: true,
|
|
223
|
-
data: {id: '123'},
|
|
224
|
-
message: 'OK',
|
|
225
|
-
});
|
|
226
|
-
});
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
describe('404 handling', () => {
|
|
230
|
-
it('should return 404 for non-existent routes', async () => {
|
|
231
|
-
const router = Router.create<Context>();
|
|
232
|
-
const handler = router.freeze({
|
|
233
|
-
getContext: async () => ({userId: '123'}),
|
|
234
|
-
onError: async () => ({status: 500, message: 'Internal Server Error'}),
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
const response = await handler(new Request('http://localhost/not-found', {method: 'GET'}));
|
|
238
|
-
const data = await response.json();
|
|
239
|
-
|
|
240
|
-
assert.strictEqual(response.status, 404);
|
|
241
|
-
assert.deepStrictEqual(data, {
|
|
242
|
-
success: false,
|
|
243
|
-
data: null,
|
|
244
|
-
message: 'Cannot GET /not-found',
|
|
245
|
-
});
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
it('should return 404 for wrong method on existing path', async () => {
|
|
249
|
-
const router = Router.create<Context>().get('/users', {
|
|
250
|
-
run: async () => ({users: []}),
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
const handler = router.freeze({
|
|
254
|
-
getContext: async () => ({userId: '123'}),
|
|
255
|
-
onError: async () => ({status: 500, message: 'Internal Server Error'}),
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
const response = await handler(new Request('http://localhost/users', {method: 'POST'}));
|
|
259
|
-
const data = await response.json();
|
|
260
|
-
|
|
261
|
-
assert.strictEqual(response.status, 404);
|
|
262
|
-
assert.deepStrictEqual(data, {
|
|
263
|
-
success: false,
|
|
264
|
-
data: null,
|
|
265
|
-
message: 'Cannot POST /users',
|
|
266
|
-
});
|
|
267
|
-
});
|
|
268
|
-
});
|
|
269
|
-
});
|
package/src/router/router.ts
DELETED
|
@@ -1,264 +0,0 @@
|
|
|
1
|
-
import {KaitoError, WrappedError} from '../error.ts';
|
|
2
|
-
import type {HandlerConfig} from '../handler.ts';
|
|
3
|
-
import {KaitoHead} from '../head.ts';
|
|
4
|
-
import {KaitoRequest} from '../request.ts';
|
|
5
|
-
import type {AnyQueryDefinition, AnyRoute, Route} from '../route.ts';
|
|
6
|
-
import {isNodeLikeDev, type ErroredAPIResponse, type Parsable} from '../util.ts';
|
|
7
|
-
import type {KaitoMethod} from './types.ts';
|
|
8
|
-
|
|
9
|
-
type PrefixRoutesPathInner<R extends AnyRoute, Prefix extends `/${string}`> =
|
|
10
|
-
R extends Route<
|
|
11
|
-
infer ContextFrom,
|
|
12
|
-
infer ContextTo,
|
|
13
|
-
infer Result,
|
|
14
|
-
infer Path,
|
|
15
|
-
infer Method,
|
|
16
|
-
infer Query,
|
|
17
|
-
infer BodyOutput
|
|
18
|
-
>
|
|
19
|
-
? Route<ContextFrom, ContextTo, Result, `${Prefix}${Path}`, Method, Query, BodyOutput>
|
|
20
|
-
: never;
|
|
21
|
-
|
|
22
|
-
type PrefixRoutesPath<Prefix extends `/${string}`, R extends AnyRoute> = R extends R
|
|
23
|
-
? PrefixRoutesPathInner<R, Prefix>
|
|
24
|
-
: never;
|
|
25
|
-
|
|
26
|
-
export type RouterState<Routes extends AnyRoute, ContextFrom, ContextTo> = {
|
|
27
|
-
routes: Set<Routes>;
|
|
28
|
-
through: (context: ContextFrom) => Promise<ContextTo>;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export type InferRoutes<R extends Router<any, any, any>> = R extends Router<any, any, infer R> ? R : never;
|
|
32
|
-
|
|
33
|
-
export class Router<ContextFrom, ContextTo, R extends AnyRoute> {
|
|
34
|
-
private readonly state: RouterState<R, ContextFrom, ContextTo>;
|
|
35
|
-
|
|
36
|
-
public static create = <Context>(): Router<Context, Context, never> =>
|
|
37
|
-
new Router<Context, Context, never>({
|
|
38
|
-
through: async context => context,
|
|
39
|
-
routes: new Set(),
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
private static parseQuery<T extends AnyQueryDefinition>(schema: T | undefined, url: URL) {
|
|
43
|
-
if (!schema) {
|
|
44
|
-
return {};
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const result: Record<PropertyKey, unknown> = {};
|
|
48
|
-
for (const key in schema) {
|
|
49
|
-
if (!schema.hasOwnProperty(key)) continue;
|
|
50
|
-
const value = url.searchParams.get(key);
|
|
51
|
-
result[key] = (schema[key] as Parsable).parse(value);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return result as {
|
|
55
|
-
[Key in keyof T]: ReturnType<T[Key]['parse']>;
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
public constructor(options: RouterState<R, ContextFrom, ContextTo>) {
|
|
60
|
-
this.state = options;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
public get routes() {
|
|
64
|
-
return this.state.routes;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
public add = <
|
|
68
|
-
Result,
|
|
69
|
-
Path extends string,
|
|
70
|
-
Method extends KaitoMethod,
|
|
71
|
-
Query extends AnyQueryDefinition = {},
|
|
72
|
-
Body extends Parsable = never,
|
|
73
|
-
>(
|
|
74
|
-
method: Method,
|
|
75
|
-
path: Path,
|
|
76
|
-
route:
|
|
77
|
-
| (Method extends 'GET'
|
|
78
|
-
? Omit<
|
|
79
|
-
Route<ContextFrom, ContextTo, Result, Path, Method, Query, Body>,
|
|
80
|
-
'body' | 'path' | 'method' | 'through'
|
|
81
|
-
>
|
|
82
|
-
: Omit<Route<ContextFrom, ContextTo, Result, Path, Method, Query, Body>, 'path' | 'method' | 'through'>)
|
|
83
|
-
| Route<ContextFrom, ContextTo, Result, Path, Method, Query, Body>['run'],
|
|
84
|
-
): Router<ContextFrom, ContextTo, R | Route<ContextFrom, ContextTo, Result, Path, Method, Query, Body>> => {
|
|
85
|
-
const merged: Route<ContextFrom, ContextTo, Result, Path, Method, Query, Body> = {
|
|
86
|
-
// 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)
|
|
87
|
-
...((typeof route === 'object' ? route : {run: route}) as {run: never}),
|
|
88
|
-
method,
|
|
89
|
-
path,
|
|
90
|
-
through: this.state.through,
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
return new Router({
|
|
94
|
-
...this.state,
|
|
95
|
-
routes: new Set([...this.state.routes, merged]),
|
|
96
|
-
});
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
public readonly merge = <PathPrefix extends `/${string}`, OtherRoutes extends AnyRoute>(
|
|
100
|
-
pathPrefix: PathPrefix,
|
|
101
|
-
other: Router<ContextFrom, unknown, OtherRoutes>,
|
|
102
|
-
): Router<ContextFrom, ContextTo, Extract<R | PrefixRoutesPath<PathPrefix, OtherRoutes>, AnyRoute>> => {
|
|
103
|
-
const newRoutes = [...other.state.routes].map(route => ({
|
|
104
|
-
...route,
|
|
105
|
-
path: `${pathPrefix}${route.path as string}`,
|
|
106
|
-
}));
|
|
107
|
-
|
|
108
|
-
return new Router<ContextFrom, ContextTo, Extract<R | PrefixRoutesPath<PathPrefix, OtherRoutes>, AnyRoute>>({
|
|
109
|
-
...this.state,
|
|
110
|
-
routes: new Set([...this.state.routes, ...newRoutes] as never),
|
|
111
|
-
});
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
public freeze = (server: Omit<HandlerConfig<ContextFrom>, 'router'>) => {
|
|
115
|
-
const routes = new Map<string, Map<KaitoMethod, AnyRoute>>();
|
|
116
|
-
|
|
117
|
-
for (const route of this.state.routes) {
|
|
118
|
-
if (!routes.has(route.path)) {
|
|
119
|
-
routes.set(route.path, new Map());
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
routes.get(route.path)!.set(route.method, route);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const findRoute = (method: KaitoMethod, path: string): {route?: AnyRoute; params: Record<string, string>} => {
|
|
126
|
-
const params: Record<string, string> = {};
|
|
127
|
-
const pathParts = path.split('/').filter(Boolean);
|
|
128
|
-
|
|
129
|
-
for (const [routePath, methodHandlers] of routes) {
|
|
130
|
-
const routeParts = routePath.split('/').filter(Boolean);
|
|
131
|
-
|
|
132
|
-
if (routeParts.length !== pathParts.length) continue;
|
|
133
|
-
|
|
134
|
-
let matches = true;
|
|
135
|
-
for (let i = 0; i < routeParts.length; i++) {
|
|
136
|
-
const routePart = routeParts[i];
|
|
137
|
-
const pathPart = pathParts[i];
|
|
138
|
-
|
|
139
|
-
if (routePart && pathPart && routePart.startsWith(':')) {
|
|
140
|
-
params[routePart.slice(1)] = pathPart;
|
|
141
|
-
} else if (routePart !== pathPart) {
|
|
142
|
-
matches = false;
|
|
143
|
-
break;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (matches) {
|
|
148
|
-
const route = methodHandlers.get(method);
|
|
149
|
-
if (route) return {route, params};
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return {params};
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
return async (req: Request): Promise<Response> => {
|
|
157
|
-
const url = new URL(req.url);
|
|
158
|
-
const method = req.method as KaitoMethod;
|
|
159
|
-
|
|
160
|
-
const {route, params} = findRoute(method, url.pathname);
|
|
161
|
-
|
|
162
|
-
if (!route) {
|
|
163
|
-
const body: ErroredAPIResponse = {
|
|
164
|
-
success: false,
|
|
165
|
-
data: null,
|
|
166
|
-
message: `Cannot ${method} ${url.pathname}`,
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
return Response.json(body, {status: 404});
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const request = new KaitoRequest(url, req);
|
|
173
|
-
const head = new KaitoHead();
|
|
174
|
-
|
|
175
|
-
try {
|
|
176
|
-
const body = route.body ? await route.body.parse(await req.json()) : undefined;
|
|
177
|
-
const query = Router.parseQuery(route.query, url);
|
|
178
|
-
|
|
179
|
-
const rootCtx = await server.getContext(request, head);
|
|
180
|
-
const ctx = await route.through(rootCtx);
|
|
181
|
-
|
|
182
|
-
const result = await route.run({
|
|
183
|
-
ctx,
|
|
184
|
-
body,
|
|
185
|
-
query,
|
|
186
|
-
params,
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
if (result instanceof Response) {
|
|
190
|
-
if (isNodeLikeDev) {
|
|
191
|
-
if (head.touched) {
|
|
192
|
-
const msg = [
|
|
193
|
-
'Kaito detected that you used the KaitoHead object to modify the headers or status, but then returned a Response in the route',
|
|
194
|
-
'This is usually a mistake, as your Response object will override any changes you made to the headers or status code.',
|
|
195
|
-
'',
|
|
196
|
-
'This warning was shown because `process.env.NODE_ENV=development`',
|
|
197
|
-
].join('\n');
|
|
198
|
-
|
|
199
|
-
console.warn(msg);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
return result;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return head.toResponse({
|
|
207
|
-
success: true,
|
|
208
|
-
data: result,
|
|
209
|
-
message: 'OK',
|
|
210
|
-
});
|
|
211
|
-
} catch (e) {
|
|
212
|
-
const error = WrappedError.maybe(e);
|
|
213
|
-
|
|
214
|
-
if (error instanceof KaitoError) {
|
|
215
|
-
return head.status(error.status).toResponse({
|
|
216
|
-
success: false,
|
|
217
|
-
data: null,
|
|
218
|
-
message: error.message,
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const {status, message} = await server
|
|
223
|
-
.onError({error, req: request})
|
|
224
|
-
.catch(() => ({status: 500, message: 'Internal Server Error'}));
|
|
225
|
-
|
|
226
|
-
return head.status(status).toResponse({
|
|
227
|
-
success: false,
|
|
228
|
-
data: null,
|
|
229
|
-
message,
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
};
|
|
233
|
-
};
|
|
234
|
-
|
|
235
|
-
private readonly method =
|
|
236
|
-
<M extends KaitoMethod>(method: M) =>
|
|
237
|
-
<Result, Path extends string, Query extends AnyQueryDefinition = {}, Body extends Parsable = never>(
|
|
238
|
-
path: Path,
|
|
239
|
-
route:
|
|
240
|
-
| (M extends 'GET'
|
|
241
|
-
? Omit<Route<ContextFrom, ContextTo, Result, Path, M, Query, Body>, 'body' | 'path' | 'method' | 'through'>
|
|
242
|
-
: Omit<Route<ContextFrom, ContextTo, Result, Path, M, Query, Body>, 'path' | 'method' | 'through'>)
|
|
243
|
-
| Route<ContextFrom, ContextTo, Result, Path, M, Query, Body>['run'],
|
|
244
|
-
) => {
|
|
245
|
-
return this.add<Result, Path, M, Query, Body>(method, path, route);
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
public get = this.method('GET');
|
|
249
|
-
public post = this.method('POST');
|
|
250
|
-
public put = this.method('PUT');
|
|
251
|
-
public patch = this.method('PATCH');
|
|
252
|
-
public delete = this.method('DELETE');
|
|
253
|
-
public head = this.method('HEAD');
|
|
254
|
-
public options = this.method('OPTIONS');
|
|
255
|
-
|
|
256
|
-
public through = <NextContext>(
|
|
257
|
-
through: (context: ContextTo) => Promise<NextContext>,
|
|
258
|
-
): Router<ContextFrom, NextContext, R> => {
|
|
259
|
-
return new Router<ContextFrom, NextContext, R>({
|
|
260
|
-
...this.state,
|
|
261
|
-
through: async context => through(await this.state.through(context)),
|
|
262
|
-
});
|
|
263
|
-
};
|
|
264
|
-
}
|
package/src/router/types.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export type KaitoMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS' | 'CONNECT' | 'TRACE';
|