@kaito-http/core 3.0.0-beta.7 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +344 -0
- package/dist/index.d.cts +255 -0
- package/dist/index.d.ts +166 -148
- package/dist/index.js +220 -301
- package/dist/stream/stream.cjs +134 -0
- package/dist/stream/stream.d.cts +36 -0
- package/dist/stream/stream.d.ts +36 -0
- package/dist/stream/stream.js +105 -0
- package/package.json +24 -15
- package/src/error.ts +26 -0
- package/src/handler.ts +96 -0
- package/src/head.ts +83 -0
- package/src/index.ts +7 -0
- package/src/request.ts +47 -0
- package/src/route.ts +52 -0
- package/src/router/router.test.ts +269 -0
- package/src/router/router.ts +264 -0
- package/src/router/types.ts +1 -0
- package/src/stream/stream.ts +156 -0
- package/src/util.ts +83 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
KaitoError: () => KaitoError,
|
|
24
|
+
KaitoRequest: () => KaitoRequest,
|
|
25
|
+
Router: () => Router,
|
|
26
|
+
WrappedError: () => WrappedError,
|
|
27
|
+
createKaitoHandler: () => createKaitoHandler,
|
|
28
|
+
createUtilities: () => createUtilities,
|
|
29
|
+
isNodeLikeDev: () => isNodeLikeDev,
|
|
30
|
+
parsable: () => parsable
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(index_exports);
|
|
33
|
+
|
|
34
|
+
// src/error.ts
|
|
35
|
+
var WrappedError = class _WrappedError extends Error {
|
|
36
|
+
constructor(data) {
|
|
37
|
+
super("Something was thrown, but it was not an instance of Error, so a WrappedError was created.");
|
|
38
|
+
this.data = data;
|
|
39
|
+
}
|
|
40
|
+
static maybe(maybeError) {
|
|
41
|
+
if (maybeError instanceof Error) {
|
|
42
|
+
return maybeError;
|
|
43
|
+
}
|
|
44
|
+
return _WrappedError.from(maybeError);
|
|
45
|
+
}
|
|
46
|
+
static from(data) {
|
|
47
|
+
return new _WrappedError(data);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
var KaitoError = class extends Error {
|
|
51
|
+
constructor(status, message) {
|
|
52
|
+
super(message);
|
|
53
|
+
this.status = status;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// src/handler.ts
|
|
58
|
+
function createKaitoHandler(config) {
|
|
59
|
+
const handle = config.router.freeze(config);
|
|
60
|
+
return async (request) => {
|
|
61
|
+
if (config.before) {
|
|
62
|
+
const result = await config.before(request);
|
|
63
|
+
if (result instanceof Response) {
|
|
64
|
+
if (config.transform) {
|
|
65
|
+
const result2 = await config.transform(request, result);
|
|
66
|
+
if (result2 instanceof Response) return result;
|
|
67
|
+
}
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const response = await handle(request);
|
|
72
|
+
if (config.transform) {
|
|
73
|
+
const result = await config.transform(request, response);
|
|
74
|
+
if (result instanceof Response) return result;
|
|
75
|
+
}
|
|
76
|
+
return response;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/request.ts
|
|
81
|
+
var KaitoRequest = class {
|
|
82
|
+
url;
|
|
83
|
+
_request;
|
|
84
|
+
constructor(url, request) {
|
|
85
|
+
this._request = request;
|
|
86
|
+
this.url = url;
|
|
87
|
+
}
|
|
88
|
+
get headers() {
|
|
89
|
+
return this._request.headers;
|
|
90
|
+
}
|
|
91
|
+
get method() {
|
|
92
|
+
return this._request.method;
|
|
93
|
+
}
|
|
94
|
+
async arrayBuffer() {
|
|
95
|
+
return this._request.arrayBuffer();
|
|
96
|
+
}
|
|
97
|
+
async blob() {
|
|
98
|
+
return this._request.blob();
|
|
99
|
+
}
|
|
100
|
+
async formData() {
|
|
101
|
+
return this._request.formData();
|
|
102
|
+
}
|
|
103
|
+
async bytes() {
|
|
104
|
+
const buffer = await this.arrayBuffer();
|
|
105
|
+
return new Uint8Array(buffer);
|
|
106
|
+
}
|
|
107
|
+
async json() {
|
|
108
|
+
return this._request.json();
|
|
109
|
+
}
|
|
110
|
+
async text() {
|
|
111
|
+
return this._request.text();
|
|
112
|
+
}
|
|
113
|
+
get request() {
|
|
114
|
+
return this._request;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// src/head.ts
|
|
119
|
+
var KaitoHead = class {
|
|
120
|
+
_headers;
|
|
121
|
+
_status;
|
|
122
|
+
constructor() {
|
|
123
|
+
this._headers = null;
|
|
124
|
+
this._status = 200;
|
|
125
|
+
}
|
|
126
|
+
get headers() {
|
|
127
|
+
if (this._headers === null) {
|
|
128
|
+
this._headers = new Headers();
|
|
129
|
+
}
|
|
130
|
+
return this._headers;
|
|
131
|
+
}
|
|
132
|
+
status(status) {
|
|
133
|
+
if (status === void 0) {
|
|
134
|
+
return this._status;
|
|
135
|
+
}
|
|
136
|
+
this._status = status;
|
|
137
|
+
return this;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Turn this KaitoHead instance into a Response instance
|
|
141
|
+
* @param body The Kaito JSON format to be sent as the response body
|
|
142
|
+
* @returns A Response instance, ready to be sent
|
|
143
|
+
*/
|
|
144
|
+
toResponse(body) {
|
|
145
|
+
const init = {
|
|
146
|
+
status: this._status
|
|
147
|
+
};
|
|
148
|
+
if (this._headers) {
|
|
149
|
+
init.headers = this._headers;
|
|
150
|
+
}
|
|
151
|
+
return Response.json(body, init);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Whether this KaitoHead instance has been touched/modified
|
|
155
|
+
*/
|
|
156
|
+
get touched() {
|
|
157
|
+
return this._status !== 200 || this._headers !== null;
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// src/util.ts
|
|
162
|
+
var isNodeLikeDev = typeof process !== "undefined" && typeof process.env !== "undefined" && process.env.NODE_ENV === "development";
|
|
163
|
+
function createUtilities(getContext) {
|
|
164
|
+
return {
|
|
165
|
+
getContext,
|
|
166
|
+
router: () => Router.create()
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function parsable(parse) {
|
|
170
|
+
return {
|
|
171
|
+
parse
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// src/router/router.ts
|
|
176
|
+
var Router = class _Router {
|
|
177
|
+
state;
|
|
178
|
+
static create = () => new _Router({
|
|
179
|
+
through: async (context) => context,
|
|
180
|
+
routes: /* @__PURE__ */ new Set()
|
|
181
|
+
});
|
|
182
|
+
static parseQuery(schema, url) {
|
|
183
|
+
if (!schema) {
|
|
184
|
+
return {};
|
|
185
|
+
}
|
|
186
|
+
const result = {};
|
|
187
|
+
for (const key in schema) {
|
|
188
|
+
if (!schema.hasOwnProperty(key)) continue;
|
|
189
|
+
const value = url.searchParams.get(key);
|
|
190
|
+
result[key] = schema[key].parse(value);
|
|
191
|
+
}
|
|
192
|
+
return result;
|
|
193
|
+
}
|
|
194
|
+
constructor(options) {
|
|
195
|
+
this.state = options;
|
|
196
|
+
}
|
|
197
|
+
get routes() {
|
|
198
|
+
return this.state.routes;
|
|
199
|
+
}
|
|
200
|
+
add = (method, path, route) => {
|
|
201
|
+
const merged = {
|
|
202
|
+
// TODO: Ideally fix the typing here, but this will be replaced in Kaito v4 where all routes must return a Response (which we can type)
|
|
203
|
+
...typeof route === "object" ? route : { run: route },
|
|
204
|
+
method,
|
|
205
|
+
path,
|
|
206
|
+
through: this.state.through
|
|
207
|
+
};
|
|
208
|
+
return new _Router({
|
|
209
|
+
...this.state,
|
|
210
|
+
routes: /* @__PURE__ */ new Set([...this.state.routes, merged])
|
|
211
|
+
});
|
|
212
|
+
};
|
|
213
|
+
merge = (pathPrefix, other) => {
|
|
214
|
+
const newRoutes = [...other.state.routes].map((route) => ({
|
|
215
|
+
...route,
|
|
216
|
+
path: `${pathPrefix}${route.path}`
|
|
217
|
+
}));
|
|
218
|
+
return new _Router({
|
|
219
|
+
...this.state,
|
|
220
|
+
routes: /* @__PURE__ */ new Set([...this.state.routes, ...newRoutes])
|
|
221
|
+
});
|
|
222
|
+
};
|
|
223
|
+
freeze = (server) => {
|
|
224
|
+
const routes = /* @__PURE__ */ new Map();
|
|
225
|
+
for (const route of this.state.routes) {
|
|
226
|
+
if (!routes.has(route.path)) {
|
|
227
|
+
routes.set(route.path, /* @__PURE__ */ new Map());
|
|
228
|
+
}
|
|
229
|
+
routes.get(route.path).set(route.method, route);
|
|
230
|
+
}
|
|
231
|
+
const findRoute = (method, path) => {
|
|
232
|
+
const params = {};
|
|
233
|
+
const pathParts = path.split("/").filter(Boolean);
|
|
234
|
+
for (const [routePath, methodHandlers] of routes) {
|
|
235
|
+
const routeParts = routePath.split("/").filter(Boolean);
|
|
236
|
+
if (routeParts.length !== pathParts.length) continue;
|
|
237
|
+
let matches = true;
|
|
238
|
+
for (let i = 0; i < routeParts.length; i++) {
|
|
239
|
+
const routePart = routeParts[i];
|
|
240
|
+
const pathPart = pathParts[i];
|
|
241
|
+
if (routePart && pathPart && routePart.startsWith(":")) {
|
|
242
|
+
params[routePart.slice(1)] = pathPart;
|
|
243
|
+
} else if (routePart !== pathPart) {
|
|
244
|
+
matches = false;
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (matches) {
|
|
249
|
+
const route = methodHandlers.get(method);
|
|
250
|
+
if (route) return { route, params };
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return { params };
|
|
254
|
+
};
|
|
255
|
+
return async (req) => {
|
|
256
|
+
const url = new URL(req.url);
|
|
257
|
+
const method = req.method;
|
|
258
|
+
const { route, params } = findRoute(method, url.pathname);
|
|
259
|
+
if (!route) {
|
|
260
|
+
const body = {
|
|
261
|
+
success: false,
|
|
262
|
+
data: null,
|
|
263
|
+
message: `Cannot ${method} ${url.pathname}`
|
|
264
|
+
};
|
|
265
|
+
return Response.json(body, { status: 404 });
|
|
266
|
+
}
|
|
267
|
+
const request = new KaitoRequest(url, req);
|
|
268
|
+
const head = new KaitoHead();
|
|
269
|
+
try {
|
|
270
|
+
const body = route.body ? await route.body.parse(await req.json()) : void 0;
|
|
271
|
+
const query = _Router.parseQuery(route.query, url);
|
|
272
|
+
const rootCtx = await server.getContext(request, head);
|
|
273
|
+
const ctx = await route.through(rootCtx);
|
|
274
|
+
const result = await route.run({
|
|
275
|
+
ctx,
|
|
276
|
+
body,
|
|
277
|
+
query,
|
|
278
|
+
params
|
|
279
|
+
});
|
|
280
|
+
if (result instanceof Response) {
|
|
281
|
+
if (isNodeLikeDev) {
|
|
282
|
+
if (head.touched) {
|
|
283
|
+
const msg = [
|
|
284
|
+
"Kaito detected that you used the KaitoHead object to modify the headers or status, but then returned a Response in the route",
|
|
285
|
+
"This is usually a mistake, as your Response object will override any changes you made to the headers or status code.",
|
|
286
|
+
"",
|
|
287
|
+
"This warning was shown because `process.env.NODE_ENV=development`"
|
|
288
|
+
].join("\n");
|
|
289
|
+
console.warn(msg);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return result;
|
|
293
|
+
}
|
|
294
|
+
return head.toResponse({
|
|
295
|
+
success: true,
|
|
296
|
+
data: result,
|
|
297
|
+
message: "OK"
|
|
298
|
+
});
|
|
299
|
+
} catch (e) {
|
|
300
|
+
const error = WrappedError.maybe(e);
|
|
301
|
+
if (error instanceof KaitoError) {
|
|
302
|
+
return head.status(error.status).toResponse({
|
|
303
|
+
success: false,
|
|
304
|
+
data: null,
|
|
305
|
+
message: error.message
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
const { status, message } = await server.onError({ error, req: request }).catch(() => ({ status: 500, message: "Internal Server Error" }));
|
|
309
|
+
return head.status(status).toResponse({
|
|
310
|
+
success: false,
|
|
311
|
+
data: null,
|
|
312
|
+
message
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
};
|
|
317
|
+
method = (method) => (path, route) => {
|
|
318
|
+
return this.add(method, path, route);
|
|
319
|
+
};
|
|
320
|
+
get = this.method("GET");
|
|
321
|
+
post = this.method("POST");
|
|
322
|
+
put = this.method("PUT");
|
|
323
|
+
patch = this.method("PATCH");
|
|
324
|
+
delete = this.method("DELETE");
|
|
325
|
+
head = this.method("HEAD");
|
|
326
|
+
options = this.method("OPTIONS");
|
|
327
|
+
through = (through) => {
|
|
328
|
+
return new _Router({
|
|
329
|
+
...this.state,
|
|
330
|
+
through: async (context) => through(await this.state.through(context))
|
|
331
|
+
});
|
|
332
|
+
};
|
|
333
|
+
};
|
|
334
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
335
|
+
0 && (module.exports = {
|
|
336
|
+
KaitoError,
|
|
337
|
+
KaitoRequest,
|
|
338
|
+
Router,
|
|
339
|
+
WrappedError,
|
|
340
|
+
createKaitoHandler,
|
|
341
|
+
createUtilities,
|
|
342
|
+
isNodeLikeDev,
|
|
343
|
+
parsable
|
|
344
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
declare class WrappedError<T> extends Error {
|
|
2
|
+
readonly data: T;
|
|
3
|
+
static maybe<T>(maybeError: T): (T & Error) | WrappedError<T>;
|
|
4
|
+
static from<T>(data: T): WrappedError<T>;
|
|
5
|
+
private constructor();
|
|
6
|
+
}
|
|
7
|
+
declare class KaitoError extends Error {
|
|
8
|
+
readonly status: number;
|
|
9
|
+
constructor(status: number, message: string);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
declare class KaitoRequest {
|
|
13
|
+
readonly url: URL;
|
|
14
|
+
private readonly _request;
|
|
15
|
+
constructor(url: URL, request: Request);
|
|
16
|
+
get headers(): Headers;
|
|
17
|
+
get method(): string;
|
|
18
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
19
|
+
blob(): Promise<Blob>;
|
|
20
|
+
formData(): Promise<FormData>;
|
|
21
|
+
bytes(): Promise<Uint8Array>;
|
|
22
|
+
json(): Promise<unknown>;
|
|
23
|
+
text(): Promise<string>;
|
|
24
|
+
get request(): Request;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
type KaitoMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS' | 'CONNECT' | 'TRACE';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* This class is merely a wrapper around a `Headers` object and a status code.
|
|
31
|
+
* It's used while the router is executing a route to store any mutations to the status
|
|
32
|
+
* code or headers that the developer may want to make.
|
|
33
|
+
*
|
|
34
|
+
* This exists because there's otherwise no way to indicate back to Kaito that
|
|
35
|
+
* the developer wants to change the status code or headers.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```ts
|
|
39
|
+
* const response = new KaitoHead();
|
|
40
|
+
*
|
|
41
|
+
* response.status(200);
|
|
42
|
+
* response.headers.set('Content-Type', 'application/json');
|
|
43
|
+
*
|
|
44
|
+
* console.log(response.headers); // Headers {'content-type': 'application/json'}
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
declare class KaitoHead {
|
|
48
|
+
private _headers;
|
|
49
|
+
private _status;
|
|
50
|
+
constructor();
|
|
51
|
+
get headers(): Headers;
|
|
52
|
+
/**
|
|
53
|
+
* Gets the status code of this KaitoHead instance
|
|
54
|
+
* @returns The status code
|
|
55
|
+
*/
|
|
56
|
+
status(): number;
|
|
57
|
+
/**
|
|
58
|
+
* Sets the status code of this KaitoHead instance
|
|
59
|
+
* @param status The status code to set
|
|
60
|
+
* @returns This KaitoHead instance
|
|
61
|
+
*/
|
|
62
|
+
status(status: number): this;
|
|
63
|
+
/**
|
|
64
|
+
* Turn this KaitoHead instance into a Response instance
|
|
65
|
+
* @param body The Kaito JSON format to be sent as the response body
|
|
66
|
+
* @returns A Response instance, ready to be sent
|
|
67
|
+
*/
|
|
68
|
+
toResponse<T>(body: APIResponse<T>): Response;
|
|
69
|
+
/**
|
|
70
|
+
* Whether this KaitoHead instance has been touched/modified
|
|
71
|
+
*/
|
|
72
|
+
get touched(): boolean;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* A helper to check if the environment is Node.js-like and the NODE_ENV is development
|
|
77
|
+
*/
|
|
78
|
+
declare const isNodeLikeDev: boolean;
|
|
79
|
+
type ErroredAPIResponse = {
|
|
80
|
+
success: false;
|
|
81
|
+
data: null;
|
|
82
|
+
message: string;
|
|
83
|
+
};
|
|
84
|
+
type SuccessfulAPIResponse<T> = {
|
|
85
|
+
success: true;
|
|
86
|
+
data: T;
|
|
87
|
+
message: 'OK';
|
|
88
|
+
};
|
|
89
|
+
type APIResponse<T> = ErroredAPIResponse | SuccessfulAPIResponse<T>;
|
|
90
|
+
type AnyResponse = APIResponse<unknown>;
|
|
91
|
+
type MakeOptional<T, K extends keyof T> = T extends T ? Omit<T, K> & Partial<Pick<T, K>> : never;
|
|
92
|
+
type ExtractRouteParams<T extends string> = string extends T ? Record<string, string> : T extends `${string}:${infer Param}/${infer Rest}` ? {
|
|
93
|
+
[k in Param | keyof ExtractRouteParams<Rest>]: string;
|
|
94
|
+
} : T extends `${string}:${infer Param}` ? {
|
|
95
|
+
[k in Param]: string;
|
|
96
|
+
} : {};
|
|
97
|
+
/**
|
|
98
|
+
* A function that is called to get the context for a request.
|
|
99
|
+
*
|
|
100
|
+
* This is useful for things like authentication, to pass in a database connection, etc.
|
|
101
|
+
*
|
|
102
|
+
* It's fine for this function to throw; if it does, the error is passed to the `onError` function.
|
|
103
|
+
*
|
|
104
|
+
* @param req - The kaito request object, which contains the request method, url, headers, etc
|
|
105
|
+
* @param head - The kaito head object, which contains getters and setters for headers and status
|
|
106
|
+
* @returns The context for your routes
|
|
107
|
+
*/
|
|
108
|
+
type GetContext<Result> = (req: KaitoRequest, head: KaitoHead) => Promise<Result>;
|
|
109
|
+
/**
|
|
110
|
+
* A helper function to create typed necessary functions
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```ts
|
|
114
|
+
* const {router, getContext} = createUtilities(async (req, res) => {
|
|
115
|
+
* // Return context here
|
|
116
|
+
* })
|
|
117
|
+
*
|
|
118
|
+
* const app = router().get('/', async () => "hello");
|
|
119
|
+
*
|
|
120
|
+
* const server = createKaitoHandler({
|
|
121
|
+
* router: app,
|
|
122
|
+
* getContext,
|
|
123
|
+
* // ...
|
|
124
|
+
* });
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
declare function createUtilities<Context>(getContext: GetContext<Context>): {
|
|
128
|
+
getContext: GetContext<Context>;
|
|
129
|
+
router: () => Router<Context, Context, never>;
|
|
130
|
+
};
|
|
131
|
+
interface Parsable<Output = any, Input = Output> {
|
|
132
|
+
_input: Input;
|
|
133
|
+
parse: (value: unknown) => Output;
|
|
134
|
+
}
|
|
135
|
+
type InferParsable<T> = T extends Parsable<infer Output, infer Input> ? {
|
|
136
|
+
input: Input;
|
|
137
|
+
output: Output;
|
|
138
|
+
} : never;
|
|
139
|
+
declare function parsable<T>(parse: (value: unknown) => T): Parsable<T, T>;
|
|
140
|
+
|
|
141
|
+
type RouteArgument<Path extends string, Context, QueryOutput, BodyOutput> = {
|
|
142
|
+
ctx: Context;
|
|
143
|
+
body: BodyOutput;
|
|
144
|
+
query: QueryOutput;
|
|
145
|
+
params: ExtractRouteParams<Path>;
|
|
146
|
+
};
|
|
147
|
+
type AnyQueryDefinition = Record<string, Parsable<any, string | undefined>>;
|
|
148
|
+
type Through<From, To> = (context: From) => Promise<To>;
|
|
149
|
+
type Route<ContextFrom, ContextTo, Result, Path extends string, Method extends KaitoMethod, Query extends AnyQueryDefinition, Body extends Parsable> = {
|
|
150
|
+
through: Through<ContextFrom, ContextTo>;
|
|
151
|
+
body?: Body;
|
|
152
|
+
query?: Query;
|
|
153
|
+
path: Path;
|
|
154
|
+
method: Method;
|
|
155
|
+
run(arg: RouteArgument<Path, ContextTo, {
|
|
156
|
+
[Key in keyof Query]: InferParsable<Query[Key]>['output'];
|
|
157
|
+
}, InferParsable<Body>['output']>): Promise<Result>;
|
|
158
|
+
};
|
|
159
|
+
type AnyRoute<ContextFrom = any, ContextTo = any> = Route<ContextFrom, ContextTo, any, any, any, AnyQueryDefinition, any>;
|
|
160
|
+
|
|
161
|
+
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;
|
|
162
|
+
type PrefixRoutesPath<Prefix extends `/${string}`, R extends AnyRoute> = R extends R ? PrefixRoutesPathInner<R, Prefix> : never;
|
|
163
|
+
type RouterState<Routes extends AnyRoute, ContextFrom, ContextTo> = {
|
|
164
|
+
routes: Set<Routes>;
|
|
165
|
+
through: (context: ContextFrom) => Promise<ContextTo>;
|
|
166
|
+
};
|
|
167
|
+
type InferRoutes<R extends Router<any, any, any>> = R extends Router<any, any, infer R> ? R : never;
|
|
168
|
+
declare class Router<ContextFrom, ContextTo, R extends AnyRoute> {
|
|
169
|
+
private readonly state;
|
|
170
|
+
static create: <Context>() => Router<Context, Context, never>;
|
|
171
|
+
private static parseQuery;
|
|
172
|
+
constructor(options: RouterState<R, ContextFrom, ContextTo>);
|
|
173
|
+
get routes(): Set<R>;
|
|
174
|
+
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>>;
|
|
175
|
+
readonly merge: <PathPrefix extends `/${string}`, OtherRoutes extends AnyRoute>(pathPrefix: PathPrefix, other: Router<ContextFrom, unknown, OtherRoutes>) => Router<ContextFrom, ContextTo, Extract<R | PrefixRoutesPath<PathPrefix, OtherRoutes>, AnyRoute>>;
|
|
176
|
+
freeze: (server: Omit<HandlerConfig<ContextFrom>, "router">) => (req: Request) => Promise<Response>;
|
|
177
|
+
private readonly method;
|
|
178
|
+
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>>;
|
|
179
|
+
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>>;
|
|
180
|
+
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>>;
|
|
181
|
+
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>>;
|
|
182
|
+
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>>;
|
|
183
|
+
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>>;
|
|
184
|
+
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>>;
|
|
185
|
+
through: <NextContext>(through: (context: ContextTo) => Promise<NextContext>) => Router<ContextFrom, NextContext, R>;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
type HandlerConfig<ContextFrom> = {
|
|
189
|
+
/**
|
|
190
|
+
* The root router to mount on this handler.
|
|
191
|
+
*/
|
|
192
|
+
router: Router<ContextFrom, unknown, any>;
|
|
193
|
+
/**
|
|
194
|
+
* A function that is called to get the context for a request.
|
|
195
|
+
*
|
|
196
|
+
* This is useful for things like authentication, to pass in a database connection, etc.
|
|
197
|
+
*
|
|
198
|
+
* It's fine for this function to throw; if it does, the error is passed to the `onError` function.
|
|
199
|
+
*/
|
|
200
|
+
getContext: GetContext<ContextFrom>;
|
|
201
|
+
/**
|
|
202
|
+
* A function that is called when an error occurs inside a route handler.
|
|
203
|
+
*
|
|
204
|
+
* The result of this function is used to determine the response status and message, and is
|
|
205
|
+
* always sent to the client. You could include logic to check for production vs development
|
|
206
|
+
* environments here, and this would also be a good place to include error tracking
|
|
207
|
+
* like Sentry or Rollbar.
|
|
208
|
+
*
|
|
209
|
+
* @param arg - The error thrown, and the KaitoRequest instance
|
|
210
|
+
* @returns A KaitoError or an object with a status and message
|
|
211
|
+
*/
|
|
212
|
+
onError: (arg: {
|
|
213
|
+
error: Error;
|
|
214
|
+
req: KaitoRequest;
|
|
215
|
+
}) => Promise<KaitoError | {
|
|
216
|
+
status: number;
|
|
217
|
+
message: string;
|
|
218
|
+
}>;
|
|
219
|
+
/**
|
|
220
|
+
* A function that is called before every request. Most useful for bailing out early in the case of an OPTIONS request.
|
|
221
|
+
*
|
|
222
|
+
* @example
|
|
223
|
+
* ```ts
|
|
224
|
+
* before: async req => {
|
|
225
|
+
* if (req.method === 'OPTIONS') {
|
|
226
|
+
* return new Response(null, {status: 204});
|
|
227
|
+
* }
|
|
228
|
+
* }
|
|
229
|
+
* ```
|
|
230
|
+
*/
|
|
231
|
+
before?: (req: Request) => Promise<Response | void | undefined>;
|
|
232
|
+
/**
|
|
233
|
+
* Transforms the response before it is sent to the client. Very useful for settings headers like CORS.
|
|
234
|
+
*
|
|
235
|
+
* You can also return a new response in this function, or just mutate the current one.
|
|
236
|
+
*
|
|
237
|
+
* This function WILL receive the result of `.before()` if you return a response from it. This means
|
|
238
|
+
* you only need to define headers in a single place.
|
|
239
|
+
*
|
|
240
|
+
* @example
|
|
241
|
+
* ```ts
|
|
242
|
+
* transform: async (req, res) => {
|
|
243
|
+
* res.headers.set('Access-Control-Allow-Origin', 'http://localhost:3000');
|
|
244
|
+
* res.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
245
|
+
* res.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
246
|
+
* res.headers.set('Access-Control-Max-Age', '86400');
|
|
247
|
+
* res.headers.set('Access-Control-Allow-Credentials', 'true');
|
|
248
|
+
* }
|
|
249
|
+
* ```
|
|
250
|
+
*/
|
|
251
|
+
transform?: (req: Request, res: Response) => Promise<Response | void | undefined>;
|
|
252
|
+
};
|
|
253
|
+
declare function createKaitoHandler<Context>(config: HandlerConfig<Context>): (request: Request) => Promise<Response>;
|
|
254
|
+
|
|
255
|
+
export { type APIResponse, type AnyQueryDefinition, type AnyResponse, type AnyRoute, type ErroredAPIResponse, type ExtractRouteParams, type GetContext, type HandlerConfig, type InferParsable, type InferRoutes, KaitoError, type KaitoMethod, KaitoRequest, type MakeOptional, type Parsable, type Route, type RouteArgument, Router, type RouterState, type SuccessfulAPIResponse, type Through, WrappedError, createKaitoHandler, createUtilities, isNodeLikeDev, parsable };
|