crux.land

Api
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
// Copyright 2022 denosaurs. All rights reserved. MIT license.
import type { ConnInfo } from "https://deno.land/std@0.128.0/http/server.ts";
type HandlerContext<T = unknown> = T & ConnInfo;
export type Handler<T = unknown> = (
req: Request,
ctx: HandlerContext<T>,
) => Response | Promise<Response>;
/**
* A handler type for anytime the `MatchHandler` or `other` parameter handler
* fails
*/
export type ErrorHandler<T = unknown> = (
req: Request,
ctx: HandlerContext<T>,
err: unknown,
) => Response | Promise<Response>;
/**
* A handler type for anytime a method is received that is not defined
*/
export type UnknownMethodHandler<T = unknown> = (
req: Request,
ctx: HandlerContext<T>,
knownMethods: string[],
) => Response | Promise<Response>;
/**
* A handler type for a router path match which gets passed the matched values
*/
export type MatchHandler<T = unknown> = (
req: Request,
ctx: HandlerContext<T>,
match: Record<string, string>,
) => Response | Promise<Response>;
/**
* A record of route paths and `MatchHandler`s which are called when a match is
* found along with it's values.
*
* The route paths follow the path-to-regexp format with the addition of being able
* to prefix a route with a method name and the `@` sign. For example a route only
* accepting `GET` requests would look like: `GET@/`.
*/
// deno-lint-ignore ban-types
export type Routes<T = {}> = Record<string, MatchHandler<T>>;
/**
* The default other handler for the router
*/
export function defaultOtherHandler(_req: Request): Response {
return new Response(null, {
status: 404,
});
}
/**
* The default error handler for the router
*/
export function defaultErrorHandler(
_req: Request,
_ctx: HandlerContext,
err: unknown,
): Response {
console.error(err);
return new Response(null, {
status: 500,
});
}
/**
* The default unknown method handler for the router
*/
export function defaultUnknownMethodHandler(
_req: Request,
_ctx: HandlerContext,
knownMethods: string[],
): Response {
return new Response(null, {
status: 405,
headers: {
Accept: knownMethods.join(", "),
},
});
}
export const METHODS = [
"GET",
"HEAD",
"POST",
"PUT",
"DELETE",
"OPTIONS",
"PATCH",
] as const;
const methodRegex = new RegExp(`(?<=^(?:${METHODS.join("|")}))@`);
/**
* A simple and tiny router for deno
*
* ```
* import { serve } from "https://deno.land/std/http/server.ts";
* import { router } from "https://crux.land/router@0.0.9";
*
* await serve(
* router({
* "/": (_req) => new Response("Hello world!", { status: 200 }),
* }),
* );
* ```
*
* @param routes A record of all routes and their corresponding handler functions
* @param other An optional parameter which contains a handler for anything that
* doesn't match the `routes` parameter
* @param error An optional parameter which contains a handler for any time it
* fails to run the default request handling code
* @param unknownMethod An optional parameter which contains a handler for any time a method
* that is not defined is used
* @returns A deno std compatible request handler
*/
export function router<T = unknown>(
routes: Routes<T>,
other: Handler<T> = defaultOtherHandler,
error: ErrorHandler<T> = defaultErrorHandler,
unknownMethod: UnknownMethodHandler<T> = defaultUnknownMethodHandler,
): Handler<T> {
const internalRoutes: Record<string, { pattern: URLPattern, methods: Record<string, MatchHandler<T>> }> = {};
for (const [route, handler] of Object.entries(routes)) {
let [methodOrPath, path] = route.split(methodRegex);
let method = methodOrPath;
if (!path) {
path = methodOrPath;
method = "any";
}
const r = internalRoutes[path] ?? {
pattern: new URLPattern({ pathname: path }),
methods: {}
};
r.methods[method] = handler;
internalRoutes[path] = r;
}
return async (req, ctx) => {
try {
for (const { pattern, methods } of Object.values(internalRoutes)) {
const res = pattern.exec(req.url);
if (res !== null) {
for (const [method, handler] of Object.entries(methods)) {
if (req.method === method) {
return await handler(
req,
ctx,
res.pathname.groups,
);
}
}
if (methods["any"]) {
return await methods["any"](
req,
ctx,
res.pathname.groups,
);
} else {
return await unknownMethod(
req,
ctx,
Object.keys(methods),
);
}
}
}
return await other(req, ctx);
} catch (err) {
return error(req, ctx, err);
}
};
}
© 2020-2021 Denosaurs
GitHubDiscordTwitter