@kaito-http/core 3.0.1 → 3.0.3
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.cjs +40 -38
- package/dist/index.d.cts +11 -11
- package/dist/index.d.ts +11 -11
- package/dist/index.js +39 -38
- 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/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';
|
package/src/stream/stream.ts
DELETED
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
export class KaitoSSEResponse<_T> extends Response {
|
|
2
|
-
constructor(body: ReadableStream<string>, init?: ResponseInit) {
|
|
3
|
-
const headers = new Headers(init?.headers);
|
|
4
|
-
|
|
5
|
-
headers.set('Content-Type', 'text/event-stream');
|
|
6
|
-
headers.set('Cache-Control', 'no-cache');
|
|
7
|
-
headers.set('Connection', 'keep-alive');
|
|
8
|
-
|
|
9
|
-
super(body, {
|
|
10
|
-
...init,
|
|
11
|
-
headers,
|
|
12
|
-
});
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
async *[Symbol.asyncIterator]() {
|
|
16
|
-
for await (const chunk of this.body!) {
|
|
17
|
-
yield chunk;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export type SSEEvent<T, E extends string> = (
|
|
23
|
-
| {
|
|
24
|
-
data: T;
|
|
25
|
-
event?: E | undefined;
|
|
26
|
-
}
|
|
27
|
-
| {
|
|
28
|
-
data?: T | undefined;
|
|
29
|
-
event: E;
|
|
30
|
-
}
|
|
31
|
-
) & {
|
|
32
|
-
retry?: number;
|
|
33
|
-
id?: string;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Converts an SSE Event into a string, ready for sending to the client
|
|
38
|
-
* @param event The SSE Event
|
|
39
|
-
* @returns A stringified version
|
|
40
|
-
*/
|
|
41
|
-
export function sseEventToString(event: SSEEvent<unknown, string>): string {
|
|
42
|
-
let result = '';
|
|
43
|
-
|
|
44
|
-
if (event.event) {
|
|
45
|
-
result += `event:${event.event}\n`;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (event.id) {
|
|
49
|
-
result += `id:${event.id}\n`;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (event.retry) {
|
|
53
|
-
result += `retry:${event.retry}\n`;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (event.data !== undefined) {
|
|
57
|
-
result += `data:${JSON.stringify(event.data)}`;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return result;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export class SSEController<U, E extends string> implements Disposable {
|
|
64
|
-
private readonly controller: ReadableStreamDefaultController<string>;
|
|
65
|
-
|
|
66
|
-
public constructor(controller: ReadableStreamDefaultController<string>) {
|
|
67
|
-
this.controller = controller;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
public enqueue(event: SSEEvent<U, E>): void {
|
|
71
|
-
this.controller.enqueue(sseEventToString(event) + '\n\n');
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
public close(): void {
|
|
75
|
-
this.controller.close();
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
[Symbol.dispose](): void {
|
|
79
|
-
this.close();
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export interface SSESource<U, E extends string> {
|
|
84
|
-
cancel?: UnderlyingSourceCancelCallback;
|
|
85
|
-
start?(controller: SSEController<U, E>): Promise<void>;
|
|
86
|
-
pull?(controller: SSEController<U, E>): Promise<void>;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function sseFromSource<U, E extends string>(source: SSESource<U, E>) {
|
|
90
|
-
const start = source.start;
|
|
91
|
-
const pull = source.pull;
|
|
92
|
-
const cancel = source.cancel;
|
|
93
|
-
|
|
94
|
-
const readable = new ReadableStream<string>({
|
|
95
|
-
...(cancel ? {cancel} : {}),
|
|
96
|
-
|
|
97
|
-
...(start
|
|
98
|
-
? {
|
|
99
|
-
start: async controller => {
|
|
100
|
-
await start(new SSEController<U, E>(controller));
|
|
101
|
-
},
|
|
102
|
-
}
|
|
103
|
-
: {}),
|
|
104
|
-
|
|
105
|
-
...(pull
|
|
106
|
-
? {
|
|
107
|
-
pull: async controller => {
|
|
108
|
-
await pull(new SSEController<U, E>(controller));
|
|
109
|
-
},
|
|
110
|
-
}
|
|
111
|
-
: {}),
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
return new KaitoSSEResponse<SSEEvent<U, E>>(readable);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export function sse<U, E extends string, T extends SSEEvent<U, E>>(
|
|
118
|
-
source: SSESource<U, E> | AsyncGenerator<T, unknown, unknown> | (() => AsyncGenerator<T, unknown, unknown>),
|
|
119
|
-
): KaitoSSEResponse<T> {
|
|
120
|
-
const evaluated = typeof source === 'function' ? source() : source;
|
|
121
|
-
|
|
122
|
-
if ('next' in evaluated) {
|
|
123
|
-
const generator = evaluated;
|
|
124
|
-
return sseFromSource<U, E>({
|
|
125
|
-
async start(controller) {
|
|
126
|
-
// TODO: use `using` once Node.js supports it
|
|
127
|
-
// // ensures close is called on controller when we're done
|
|
128
|
-
// using c = controller;
|
|
129
|
-
try {
|
|
130
|
-
for await (const event of generator) {
|
|
131
|
-
controller.enqueue(event);
|
|
132
|
-
}
|
|
133
|
-
} finally {
|
|
134
|
-
controller.close();
|
|
135
|
-
}
|
|
136
|
-
},
|
|
137
|
-
});
|
|
138
|
-
} else {
|
|
139
|
-
// if the SSESource interface is used only strings are permitted.
|
|
140
|
-
// serialization / deserialization for objects is left to the user
|
|
141
|
-
return sseFromSource<U, E>(evaluated);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
export function sseFromAnyReadable<R, U, E extends string>(
|
|
146
|
-
stream: ReadableStream<R>,
|
|
147
|
-
transform: (chunk: R) => SSEEvent<U, E>,
|
|
148
|
-
): KaitoSSEResponse<SSEEvent<U, E>> {
|
|
149
|
-
const transformer = new TransformStream({
|
|
150
|
-
transform: (chunk, controller) => {
|
|
151
|
-
controller.enqueue(transform(chunk));
|
|
152
|
-
},
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
return sse(stream.pipeThrough(transformer));
|
|
156
|
-
}
|
package/src/util.ts
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import type {KaitoHead} from './head.ts';
|
|
2
|
-
import type {KaitoRequest} from './request.ts';
|
|
3
|
-
import {Router} from './router/router.ts';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* A helper to check if the environment is Node.js-like and the NODE_ENV is development
|
|
7
|
-
*/
|
|
8
|
-
export const isNodeLikeDev =
|
|
9
|
-
typeof process !== 'undefined' && typeof process.env !== 'undefined' && process.env.NODE_ENV === 'development';
|
|
10
|
-
|
|
11
|
-
export type ErroredAPIResponse = {success: false; data: null; message: string};
|
|
12
|
-
export type SuccessfulAPIResponse<T> = {success: true; data: T; message: 'OK'};
|
|
13
|
-
export type APIResponse<T> = ErroredAPIResponse | SuccessfulAPIResponse<T>;
|
|
14
|
-
export type AnyResponse = APIResponse<unknown>;
|
|
15
|
-
export type MakeOptional<T, K extends keyof T> = T extends T ? Omit<T, K> & Partial<Pick<T, K>> : never;
|
|
16
|
-
|
|
17
|
-
export type ExtractRouteParams<T extends string> = string extends T
|
|
18
|
-
? Record<string, string>
|
|
19
|
-
: T extends `${string}:${infer Param}/${infer Rest}`
|
|
20
|
-
? {[k in Param | keyof ExtractRouteParams<Rest>]: string}
|
|
21
|
-
: T extends `${string}:${infer Param}`
|
|
22
|
-
? {[k in Param]: string}
|
|
23
|
-
: {};
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* A function that is called to get the context for a request.
|
|
27
|
-
*
|
|
28
|
-
* This is useful for things like authentication, to pass in a database connection, etc.
|
|
29
|
-
*
|
|
30
|
-
* It's fine for this function to throw; if it does, the error is passed to the `onError` function.
|
|
31
|
-
*
|
|
32
|
-
* @param req - The kaito request object, which contains the request method, url, headers, etc
|
|
33
|
-
* @param head - The kaito head object, which contains getters and setters for headers and status
|
|
34
|
-
* @returns The context for your routes
|
|
35
|
-
*/
|
|
36
|
-
export type GetContext<Result> = (req: KaitoRequest, head: KaitoHead) => Promise<Result>;
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* A helper function to create typed necessary functions
|
|
40
|
-
*
|
|
41
|
-
* @example
|
|
42
|
-
* ```ts
|
|
43
|
-
* const {router, getContext} = createUtilities(async (req, res) => {
|
|
44
|
-
* // Return context here
|
|
45
|
-
* })
|
|
46
|
-
*
|
|
47
|
-
* const app = router().get('/', async () => "hello");
|
|
48
|
-
*
|
|
49
|
-
* const server = createKaitoHandler({
|
|
50
|
-
* router: app,
|
|
51
|
-
* getContext,
|
|
52
|
-
* // ...
|
|
53
|
-
* });
|
|
54
|
-
* ```
|
|
55
|
-
*/
|
|
56
|
-
export function createUtilities<Context>(getContext: GetContext<Context>): {
|
|
57
|
-
getContext: GetContext<Context>;
|
|
58
|
-
router: () => Router<Context, Context, never>;
|
|
59
|
-
} {
|
|
60
|
-
return {
|
|
61
|
-
getContext,
|
|
62
|
-
router: () => Router.create<Context>(),
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export interface Parsable<Output = any, Input = Output> {
|
|
67
|
-
_input: Input;
|
|
68
|
-
parse: (value: unknown) => Output;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export type InferParsable<T> =
|
|
72
|
-
T extends Parsable<infer Output, infer Input>
|
|
73
|
-
? {
|
|
74
|
-
input: Input;
|
|
75
|
-
output: Output;
|
|
76
|
-
}
|
|
77
|
-
: never;
|
|
78
|
-
|
|
79
|
-
export function parsable<T>(parse: (value: unknown) => T): Parsable<T, T> {
|
|
80
|
-
return {
|
|
81
|
-
parse,
|
|
82
|
-
} as Parsable<T, T>;
|
|
83
|
-
}
|