@bepalo/router 1.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/LICENSE +21 -0
- package/README.md +557 -0
- package/dist/cjs/helpers.d.ts +290 -0
- package/dist/cjs/helpers.d.ts.map +1 -0
- package/dist/cjs/helpers.js +691 -0
- package/dist/cjs/helpers.js.map +1 -0
- package/dist/cjs/index.d.ts +5 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +21 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/list.d.ts +166 -0
- package/dist/cjs/list.d.ts.map +1 -0
- package/dist/cjs/list.js +483 -0
- package/dist/cjs/list.js.map +1 -0
- package/dist/cjs/middlewares.d.ts +251 -0
- package/dist/cjs/middlewares.d.ts.map +1 -0
- package/dist/cjs/middlewares.js +359 -0
- package/dist/cjs/middlewares.js.map +1 -0
- package/dist/cjs/router.d.ts +333 -0
- package/dist/cjs/router.d.ts.map +1 -0
- package/dist/cjs/router.js +659 -0
- package/dist/cjs/router.js.map +1 -0
- package/dist/cjs/tree.d.ts +18 -0
- package/dist/cjs/tree.d.ts.map +1 -0
- package/dist/cjs/tree.js +162 -0
- package/dist/cjs/tree.js.map +1 -0
- package/dist/cjs/types.d.ts +127 -0
- package/dist/cjs/types.d.ts.map +1 -0
- package/dist/cjs/types.js +3 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/cjs/upload-stream.d.ts +105 -0
- package/dist/cjs/upload-stream.d.ts.map +1 -0
- package/dist/cjs/upload-stream.js +417 -0
- package/dist/cjs/upload-stream.js.map +1 -0
- package/dist/helpers.d.ts +290 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +691 -0
- package/dist/helpers.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/list.d.ts +166 -0
- package/dist/list.d.ts.map +1 -0
- package/dist/list.js +483 -0
- package/dist/list.js.map +1 -0
- package/dist/middlewares.d.ts +251 -0
- package/dist/middlewares.d.ts.map +1 -0
- package/dist/middlewares.js +359 -0
- package/dist/middlewares.js.map +1 -0
- package/dist/router.d.ts +333 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +659 -0
- package/dist/router.js.map +1 -0
- package/dist/tree.d.ts +18 -0
- package/dist/tree.d.ts.map +1 -0
- package/dist/tree.js +162 -0
- package/dist/tree.js.map +1 -0
- package/dist/types.d.ts +127 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/upload-stream.d.ts +105 -0
- package/dist/upload-stream.d.ts.map +1 -0
- package/dist/upload-stream.js +417 -0
- package/dist/upload-stream.js.map +1 -0
- package/package.json +51 -0
|
@@ -0,0 +1,659 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @file A fast radix-trie based router for JavaScript runtimes.
|
|
4
|
+
* @module @bepalo/router
|
|
5
|
+
* @author Natnael Eshetu
|
|
6
|
+
* @exports Router
|
|
7
|
+
*/
|
|
8
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
9
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
10
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
11
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
12
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
13
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
14
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
18
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
19
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
20
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
21
|
+
};
|
|
22
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
23
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
24
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
25
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
26
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
27
|
+
};
|
|
28
|
+
var _Router_trees, _Router_enable, _Router_defaultHeaders, _Router_defaultCatcher, _Router_defaultFallback, _Router_setters;
|
|
29
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
+
exports.Router = exports.isValidHttpMethod = void 0;
|
|
31
|
+
const tree_js_1 = require("./tree.js");
|
|
32
|
+
/**
|
|
33
|
+
* Checks if a string is a valid HTTP method.
|
|
34
|
+
* @param {string} method - The method string to validate
|
|
35
|
+
* @returns {boolean} True if the method is valid, false otherwise
|
|
36
|
+
*/
|
|
37
|
+
const isValidHttpMethod = (method) => {
|
|
38
|
+
switch (method) {
|
|
39
|
+
case "HEAD":
|
|
40
|
+
case "OPTIONS":
|
|
41
|
+
case "GET":
|
|
42
|
+
case "POST":
|
|
43
|
+
case "PUT":
|
|
44
|
+
case "PATCH":
|
|
45
|
+
case "DELETE":
|
|
46
|
+
return true;
|
|
47
|
+
default:
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
exports.isValidHttpMethod = isValidHttpMethod;
|
|
52
|
+
/**
|
|
53
|
+
* Initializes method trees for all HTTP methods.
|
|
54
|
+
* @returns {Record<HttpMethod, Tree<RouteNode<Context>>>} Trees for each HTTP method
|
|
55
|
+
* @template Context
|
|
56
|
+
*/
|
|
57
|
+
function initMethodTrees() {
|
|
58
|
+
return {
|
|
59
|
+
HEAD: new tree_js_1.Tree(),
|
|
60
|
+
OPTIONS: new tree_js_1.Tree(),
|
|
61
|
+
GET: new tree_js_1.Tree(),
|
|
62
|
+
POST: new tree_js_1.Tree(),
|
|
63
|
+
PUT: new tree_js_1.Tree(),
|
|
64
|
+
PATCH: new tree_js_1.Tree(),
|
|
65
|
+
DELETE: new tree_js_1.Tree(),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/** @constant {Array} emptyArray - Empty array constant for optimization */
|
|
69
|
+
const emptyArray = [];
|
|
70
|
+
/**
|
|
71
|
+
* A fast radix-trie based router for JavaScript runtimes.
|
|
72
|
+
* Supports hooks, filters, handlers, fallbacks, catchers, and after handlers.
|
|
73
|
+
* @class
|
|
74
|
+
* @template Context
|
|
75
|
+
* @example
|
|
76
|
+
* const router = new Router();
|
|
77
|
+
*
|
|
78
|
+
* // Register a simple GET handler
|
|
79
|
+
* router.handle("GET /users/:id", async (req, ctx) => {
|
|
80
|
+
* const userId = ctx.params.id;
|
|
81
|
+
* return json({ userId });
|
|
82
|
+
* });
|
|
83
|
+
*
|
|
84
|
+
* // Register a hook that runs before all /api routes
|
|
85
|
+
* router.hook("* /api/**", (req, ctx) => {
|
|
86
|
+
* console.log(`API request: ${req.method} ${req.url}`);
|
|
87
|
+
* });
|
|
88
|
+
*
|
|
89
|
+
* // Register an error handler
|
|
90
|
+
* router.catch("* /**", (req, ctx) => {
|
|
91
|
+
* console.error(ctx.error);
|
|
92
|
+
* return json({ error: "Something went wrong" }, { status: 500 });
|
|
93
|
+
* });
|
|
94
|
+
*
|
|
95
|
+
* // Handle a request and get a response
|
|
96
|
+
* const response = await router.respond(new Request("http://localhost/"));
|
|
97
|
+
*
|
|
98
|
+
*/
|
|
99
|
+
class Router {
|
|
100
|
+
/**
|
|
101
|
+
* Gets the routing trees for all handler types.
|
|
102
|
+
* @returns {Record<HandlerType, Record<HttpMethod, Tree<RouteNode<Context>>>>}
|
|
103
|
+
*/
|
|
104
|
+
get trees() {
|
|
105
|
+
return __classPrivateFieldGet(this, _Router_trees, "f");
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Gets the enabled handler types configuration.
|
|
109
|
+
* @returns {HandlerEnable}
|
|
110
|
+
*/
|
|
111
|
+
get enabled() {
|
|
112
|
+
return __classPrivateFieldGet(this, _Router_enable, "f");
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Gets the default headers configuration.
|
|
116
|
+
* @returns {Array<[string, string]>}
|
|
117
|
+
*/
|
|
118
|
+
get defaultHeaders() {
|
|
119
|
+
return __classPrivateFieldGet(this, _Router_defaultHeaders, "f");
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Gets the default catcher handler.
|
|
123
|
+
* @returns {Handler<Context>|undefined}
|
|
124
|
+
*/
|
|
125
|
+
get defaultCatcher() {
|
|
126
|
+
return __classPrivateFieldGet(this, _Router_defaultCatcher, "f");
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Gets the default fallback handler.
|
|
130
|
+
* @returns {Handler<Context>|undefined}
|
|
131
|
+
*/
|
|
132
|
+
get defaultFallback() {
|
|
133
|
+
return __classPrivateFieldGet(this, _Router_defaultFallback, "f");
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Gets the route registration history.
|
|
137
|
+
* @returns {Set<HandlerSetter<Context>>}
|
|
138
|
+
*/
|
|
139
|
+
get setters() {
|
|
140
|
+
return __classPrivateFieldGet(this, _Router_setters, "f");
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Creates a new Router instance.
|
|
144
|
+
* @param {RouterConfig<Context>} [config] - Configuration options
|
|
145
|
+
*/
|
|
146
|
+
constructor(config) {
|
|
147
|
+
_Router_trees.set(this, {
|
|
148
|
+
filter: initMethodTrees(),
|
|
149
|
+
hook: initMethodTrees(),
|
|
150
|
+
handler: initMethodTrees(),
|
|
151
|
+
fallback: initMethodTrees(),
|
|
152
|
+
catcher: initMethodTrees(),
|
|
153
|
+
after: initMethodTrees(),
|
|
154
|
+
});
|
|
155
|
+
_Router_enable.set(this, {
|
|
156
|
+
hooks: true,
|
|
157
|
+
afters: true,
|
|
158
|
+
filters: true,
|
|
159
|
+
fallbacks: true,
|
|
160
|
+
catchers: true,
|
|
161
|
+
});
|
|
162
|
+
_Router_defaultHeaders.set(this, []);
|
|
163
|
+
_Router_defaultCatcher.set(this, void 0);
|
|
164
|
+
_Router_defaultFallback.set(this, void 0);
|
|
165
|
+
_Router_setters.set(this, new Set());
|
|
166
|
+
this.respond = this.respond.bind(this);
|
|
167
|
+
if (config === null || config === void 0 ? void 0 : config.defaultHeaders) {
|
|
168
|
+
__classPrivateFieldSet(this, _Router_defaultHeaders, config.defaultHeaders, "f");
|
|
169
|
+
}
|
|
170
|
+
if (config === null || config === void 0 ? void 0 : config.enable) {
|
|
171
|
+
__classPrivateFieldSet(this, _Router_enable, config.enable, "f");
|
|
172
|
+
}
|
|
173
|
+
if (config === null || config === void 0 ? void 0 : config.defaultCatcher) {
|
|
174
|
+
__classPrivateFieldSet(this, _Router_defaultCatcher, config.defaultCatcher, "f");
|
|
175
|
+
}
|
|
176
|
+
if (config === null || config === void 0 ? void 0 : config.defaultFallback) {
|
|
177
|
+
__classPrivateFieldSet(this, _Router_defaultFallback, config.defaultFallback, "f");
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Registers a hook handler that runs before other handlers.
|
|
182
|
+
* Hooks cannot modify the response directly but can modify context.
|
|
183
|
+
* Their responses are ignored.
|
|
184
|
+
* @param {"*"|MethodPath|Array<MethodPath>} urls - URL patterns to match
|
|
185
|
+
* @param {Handler<Context & XContext>|Pipeline<Context & XContext>} pipeline - Handler(s) to execute
|
|
186
|
+
* @param {HandlerOptions} [options] - Registration options
|
|
187
|
+
* @returns {Router<Context & XContext>} The router instance for chaining
|
|
188
|
+
* @template XContext
|
|
189
|
+
* @example
|
|
190
|
+
* router.hook("GET /api/**", (req, ctx) => {
|
|
191
|
+
* ctx.startTime = Date.now();
|
|
192
|
+
* });
|
|
193
|
+
*/
|
|
194
|
+
hook(urls, pipeline, options) {
|
|
195
|
+
return this.setRoutes("hook", urls, pipeline, options);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Registers an after handler that runs after the response is created.
|
|
199
|
+
* After handlers can inspect and modify the response from the context.
|
|
200
|
+
* Their responses are ignored.
|
|
201
|
+
* @param {"*"|MethodPath|Array<MethodPath>} urls - URL patterns to match
|
|
202
|
+
* @param {Handler<Context & XContext>|Pipeline<Context & XContext>} pipeline - Handler(s) to execute
|
|
203
|
+
* @param {HandlerOptions} [options] - Registration options
|
|
204
|
+
* @returns {Router<Context & XContext>} The router instance for chaining
|
|
205
|
+
* @template XContext
|
|
206
|
+
* @example
|
|
207
|
+
* router.after("GET /**", (req, ctx) => {
|
|
208
|
+
* console.log(`Request completed: ${req.method} ${req.url}`);
|
|
209
|
+
* });
|
|
210
|
+
*/
|
|
211
|
+
after(urls, pipeline, options) {
|
|
212
|
+
return this.setRoutes("after", urls, pipeline, options);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Registers a filter handler that can intercept and modify requests.
|
|
216
|
+
* Filters run after hooks but before handlers and can return a response.
|
|
217
|
+
* @param {"*"|MethodPath|Array<MethodPath>} urls - URL patterns to match
|
|
218
|
+
* @param {Handler<Context & XContext>|Pipeline<Context & XContext>} pipeline - Handler(s) to execute
|
|
219
|
+
* @param {HandlerOptions} [options] - Registration options
|
|
220
|
+
* @returns {Router<Context & XContext>} The router instance for chaining
|
|
221
|
+
* @template XContext
|
|
222
|
+
* @example
|
|
223
|
+
* router.filter("GET /admin/**", (req, ctx) => {
|
|
224
|
+
* if (!req.headers.get("x-admin-token")) {
|
|
225
|
+
* return json({ error: "Unauthorized" }, { status: 401 });
|
|
226
|
+
* }
|
|
227
|
+
* });
|
|
228
|
+
*/
|
|
229
|
+
filter(urls, pipeline, options) {
|
|
230
|
+
return this.setRoutes("filter", urls, pipeline, options);
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Registers a main request handler.
|
|
234
|
+
* Handlers are the primary way to respond to requests.
|
|
235
|
+
* @param {"*"|MethodPath|Array<MethodPath>} urls - URL patterns to match
|
|
236
|
+
* @param {Handler<Context & XContext>|Pipeline<Context & XContext>} pipeline - Handler(s) to execute
|
|
237
|
+
* @param {HandlerOptions} [options] - Registration options
|
|
238
|
+
* @returns {Router<Context & XContext>} The router instance for chaining
|
|
239
|
+
* @template XContext
|
|
240
|
+
* @example
|
|
241
|
+
* router.handle("GET /users", async (req, ctx) => {
|
|
242
|
+
* const users = await getUsers();
|
|
243
|
+
* return json({ users });
|
|
244
|
+
* });
|
|
245
|
+
*/
|
|
246
|
+
handle(urls, pipeline, options) {
|
|
247
|
+
return this.setRoutes("handler", urls, pipeline, options);
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Registers a fallback handler that runs when no main handler matches.
|
|
251
|
+
* Fallbacks are useful for custom 404 pages or default behaviors.
|
|
252
|
+
* @param {"*"|MethodPath|Array<MethodPath>} urls - URL patterns to match
|
|
253
|
+
* @param {Handler<Context & XContext>|Pipeline<Context & XContext>} pipeline - Handler(s) to execute
|
|
254
|
+
* @param {HandlerOptions} [options] - Registration options
|
|
255
|
+
* @returns {Router<Context & XContext>} The router instance for chaining
|
|
256
|
+
* @template XContext
|
|
257
|
+
* @example
|
|
258
|
+
* router.fallback("GET /**", (req, ctx) => {
|
|
259
|
+
* return json({ error: "Not found" }, { status: 404 });
|
|
260
|
+
* });
|
|
261
|
+
*/
|
|
262
|
+
fallback(urls, pipeline, options) {
|
|
263
|
+
return this.setRoutes("fallback", urls, pipeline, options);
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Registers an error handler for catching exceptions.
|
|
267
|
+
* Catchers receive the error in the context and can return a response.
|
|
268
|
+
* @param {"*"|MethodPath|Array<MethodPath>} urls - URL patterns to match
|
|
269
|
+
* @param {Handler<Context & XContext & { error: Error }>|Pipeline<Context & XContext & { error: Error }>} pipeline - Handler(s) to execute
|
|
270
|
+
* @param {HandlerOptions} [options] - Registration options
|
|
271
|
+
* @returns {Router<Context & XContext & { error: Error }>} The router instance for chaining
|
|
272
|
+
* @template XContext
|
|
273
|
+
* @example
|
|
274
|
+
* router.catch("GET /**", (req, ctx) => {
|
|
275
|
+
* console.error(ctx.error);
|
|
276
|
+
* return json({ error: "Internal server error" }, { status: 500 });
|
|
277
|
+
* });
|
|
278
|
+
*/
|
|
279
|
+
catch(urls, pipeline, options) {
|
|
280
|
+
return this.setRoutes("catcher", urls, pipeline, options);
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Appends routes from another router under a base URL.
|
|
284
|
+
* Useful for mounting sub-routers or organizing routes by prefix.
|
|
285
|
+
* @param {`/${string}`} baseUrl - The base URL to mount the router under
|
|
286
|
+
* @param {Router<Context>} router - The router to append
|
|
287
|
+
* @param {HandlerOptions} [options] - Registration options
|
|
288
|
+
* @returns {Router<Context>} The router instance for chaining
|
|
289
|
+
* @example
|
|
290
|
+
* const apiRouter = new Router();
|
|
291
|
+
* apiRouter.handle("GET /users", getUsersHandler);
|
|
292
|
+
*
|
|
293
|
+
* const mainRouter = new Router();
|
|
294
|
+
* mainRouter.append("/api", apiRouter);
|
|
295
|
+
* // Now GET /api/users routes to getUsersHandler
|
|
296
|
+
*/
|
|
297
|
+
append(baseUrl, router, options) {
|
|
298
|
+
baseUrl =
|
|
299
|
+
baseUrl.charAt(baseUrl.length - 1) === "/"
|
|
300
|
+
? baseUrl.slice(0, baseUrl.length - 1)
|
|
301
|
+
: baseUrl;
|
|
302
|
+
for (const elem of __classPrivateFieldGet(router, _Router_setters, "f")) {
|
|
303
|
+
let urls;
|
|
304
|
+
if (typeof elem.urls === "string") {
|
|
305
|
+
const [method, path] = elem.urls.split(" ", 2);
|
|
306
|
+
urls = `${method} ${baseUrl}${path}`;
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
urls = elem.urls.map((url) => {
|
|
310
|
+
const [method, path] = url.split(" ", 2);
|
|
311
|
+
return `${method} ${baseUrl}${path}`;
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
this.setRoutes(elem.handlerType, urls, elem.pipeline, Object.assign(Object.assign({}, elem.options), options));
|
|
315
|
+
}
|
|
316
|
+
return this;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Low-level method to register routes of any handler type.
|
|
320
|
+
* @param {HandlerType} handlerType - The type of handler to register
|
|
321
|
+
* @param {"*"|MethodPath|Array<MethodPath>} urls - URL patterns to match
|
|
322
|
+
* @param {Handler<Context>|Pipeline<Context>} pipeline_ - Handler(s) to execute
|
|
323
|
+
* @param {HandlerOptions} [options] - Registration options
|
|
324
|
+
* @returns {Router<Context>} The router instance for chaining
|
|
325
|
+
* @private
|
|
326
|
+
*/
|
|
327
|
+
setRoutes(handlerType, urls, pipeline_, options) {
|
|
328
|
+
const pipeline = Array.isArray(pipeline_)
|
|
329
|
+
? pipeline_
|
|
330
|
+
: [pipeline_];
|
|
331
|
+
const splitUrls = urls === "*" ? Router.ALL_METHOD_PATHS : splitUrl(urls);
|
|
332
|
+
for (const { method, nodes, params, pathname } of splitUrls) {
|
|
333
|
+
const treeNode = __classPrivateFieldGet(this, _Router_trees, "f")[handlerType][method];
|
|
334
|
+
const splitPaths = pathname.substring(1).split("/");
|
|
335
|
+
const splitPathsLength_1 = splitPaths.length - 1;
|
|
336
|
+
for (let i = 0; i < splitPathsLength_1; i++) {
|
|
337
|
+
switch (splitPaths[i]) {
|
|
338
|
+
case "**":
|
|
339
|
+
throw new Error(`Super-glob '**' in the middle of pathname '${pathname}'. Should only be at the end.`);
|
|
340
|
+
case ".**":
|
|
341
|
+
throw new Error(`Super-glob '.**' in the middle of pathname '${pathname}'. Should only be at the end.`);
|
|
342
|
+
case ".*":
|
|
343
|
+
throw new Error(`glob '.*' in the middle of pathname '${pathname}'. Should only be at the end.`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
if (!(options === null || options === void 0 ? void 0 : options.overwrite)) {
|
|
347
|
+
const node = treeNode.get(splitPaths);
|
|
348
|
+
if (node) {
|
|
349
|
+
const maxLen = Math.min(node.nodes.length, splitPaths.length);
|
|
350
|
+
let colliding = [];
|
|
351
|
+
for (let i = 0; i < maxLen; i++) {
|
|
352
|
+
if (node.nodes[i] === "*"
|
|
353
|
+
? splitPaths[i].startsWith(":") || splitPaths[i] === "*"
|
|
354
|
+
: node.nodes[i] === splitPaths[i]) {
|
|
355
|
+
colliding.unshift(i);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
if (colliding.length > 0 && colliding[0] === maxLen - 1) {
|
|
359
|
+
throw new Error(`Overriding route '${node.pathname}' with '${pathname}'`);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
treeNode.set(nodes, {
|
|
364
|
+
method,
|
|
365
|
+
nodes,
|
|
366
|
+
pipeline,
|
|
367
|
+
pathname,
|
|
368
|
+
params,
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
// add to setters for later use
|
|
372
|
+
__classPrivateFieldGet(this, _Router_setters, "f").add({
|
|
373
|
+
handlerType,
|
|
374
|
+
urls,
|
|
375
|
+
pipeline: pipeline_,
|
|
376
|
+
options,
|
|
377
|
+
});
|
|
378
|
+
return this;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Handles an incoming HTTP request and returns a response.
|
|
382
|
+
* This is the main entry point for request processing.
|
|
383
|
+
* Handlers are only called if they are not disabled.
|
|
384
|
+
* @param {Request} req - The incoming HTTP request
|
|
385
|
+
* @param {Partial<Context>} [context] - Initial context object
|
|
386
|
+
* @returns {Promise<Response>} The HTTP response
|
|
387
|
+
*/
|
|
388
|
+
respond(req, context) {
|
|
389
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
390
|
+
var _a, _b, _c, _d;
|
|
391
|
+
const method = req.method;
|
|
392
|
+
if (!(0, exports.isValidHttpMethod)(method)) {
|
|
393
|
+
return new Response("Method Not Allowed", {
|
|
394
|
+
status: 405,
|
|
395
|
+
statusText: "Method Not Allowed",
|
|
396
|
+
headers: (_a = context === null || context === void 0 ? void 0 : context.headers) !== null && _a !== void 0 ? _a : new Headers(__classPrivateFieldGet(this, _Router_defaultHeaders, "f")),
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
let response = undefined;
|
|
400
|
+
const url = new URL(req.url);
|
|
401
|
+
const key = url.pathname
|
|
402
|
+
.substring(1, url.pathname !== "/" && url.pathname.endsWith("/")
|
|
403
|
+
? url.pathname.length - 1
|
|
404
|
+
: url.pathname.length)
|
|
405
|
+
.split("/");
|
|
406
|
+
const hookNodes = this.enabled.hooks
|
|
407
|
+
? __classPrivateFieldGet(this, _Router_trees, "f").hook[method].getAll(key)
|
|
408
|
+
: emptyArray;
|
|
409
|
+
const afterNodes = this.enabled.afters
|
|
410
|
+
? __classPrivateFieldGet(this, _Router_trees, "f").after[method].getAll(key)
|
|
411
|
+
: emptyArray;
|
|
412
|
+
const filterNodes = this.enabled.filters
|
|
413
|
+
? __classPrivateFieldGet(this, _Router_trees, "f").filter[method].getAll(key)
|
|
414
|
+
: emptyArray;
|
|
415
|
+
const handlerNodes = __classPrivateFieldGet(this, _Router_trees, "f").handler[method].getAll(key);
|
|
416
|
+
const fallbackNodes = this.enabled.fallbacks
|
|
417
|
+
? __classPrivateFieldGet(this, _Router_trees, "f").fallback[method].getAll(key)
|
|
418
|
+
: emptyArray;
|
|
419
|
+
const catcherNodes = this.enabled.catchers
|
|
420
|
+
? __classPrivateFieldGet(this, _Router_trees, "f").catcher[method].getAll(key)
|
|
421
|
+
: emptyArray;
|
|
422
|
+
const found = {
|
|
423
|
+
hooks: hookNodes.length > 0,
|
|
424
|
+
afters: afterNodes.length > 0,
|
|
425
|
+
filters: filterNodes.length > 0,
|
|
426
|
+
handlers: handlerNodes.length > 0,
|
|
427
|
+
fallbacks: fallbackNodes.length > 0,
|
|
428
|
+
catchers: catcherNodes.length > 0,
|
|
429
|
+
};
|
|
430
|
+
const ctx = Object.assign({ params: (_b = context === null || context === void 0 ? void 0 : context.params) !== null && _b !== void 0 ? _b : {}, headers: (_c = context === null || context === void 0 ? void 0 : context.headers) !== null && _c !== void 0 ? _c : new Headers(__classPrivateFieldGet(this, _Router_defaultHeaders, "f")), found }, context);
|
|
431
|
+
try {
|
|
432
|
+
// hooks
|
|
433
|
+
if (found.hooks) {
|
|
434
|
+
const params = hookNodes[hookNodes.length - 1].params;
|
|
435
|
+
if (params) {
|
|
436
|
+
for (const [index, param] of params) {
|
|
437
|
+
ctx.params[param.name] = key[index];
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
let hookResponse = undefined;
|
|
441
|
+
for (const hookNode of hookNodes) {
|
|
442
|
+
for (const hook of hookNode.pipeline) {
|
|
443
|
+
hookResponse = yield hook(req, ctx);
|
|
444
|
+
if (hookResponse)
|
|
445
|
+
break;
|
|
446
|
+
}
|
|
447
|
+
if (hookResponse)
|
|
448
|
+
break;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
// filters
|
|
452
|
+
if (found.filters) {
|
|
453
|
+
const params = filterNodes[filterNodes.length - 1].params;
|
|
454
|
+
if (params) {
|
|
455
|
+
for (const [index, param] of params) {
|
|
456
|
+
ctx.params[param.name] = key[index];
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
for (const filterNode of filterNodes) {
|
|
460
|
+
for (const filter of filterNode.pipeline) {
|
|
461
|
+
response = yield filter(req, ctx);
|
|
462
|
+
if (response)
|
|
463
|
+
break;
|
|
464
|
+
}
|
|
465
|
+
if (response)
|
|
466
|
+
break;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
// handlers
|
|
470
|
+
if (found.handlers) {
|
|
471
|
+
const params = handlerNodes[handlerNodes.length - 1].params;
|
|
472
|
+
if (params) {
|
|
473
|
+
for (const [index, param] of params) {
|
|
474
|
+
ctx.params[param.name] = key[index];
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
if (!(response instanceof Response)) {
|
|
478
|
+
for (const handlerNode of handlerNodes) {
|
|
479
|
+
for (const handler of handlerNode.pipeline) {
|
|
480
|
+
response = yield handler(req, ctx);
|
|
481
|
+
if (response)
|
|
482
|
+
break;
|
|
483
|
+
}
|
|
484
|
+
if (response)
|
|
485
|
+
break;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
// fallbacks
|
|
490
|
+
if (!(response instanceof Response)) {
|
|
491
|
+
if (found.fallbacks) {
|
|
492
|
+
const params = fallbackNodes[fallbackNodes.length - 1].params;
|
|
493
|
+
if (params) {
|
|
494
|
+
for (const [index, param] of params) {
|
|
495
|
+
ctx.params[param.name] = key[index];
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
for (const fallbackNode of fallbackNodes) {
|
|
499
|
+
for (const fallback of fallbackNode.pipeline) {
|
|
500
|
+
response = yield fallback(req, ctx);
|
|
501
|
+
if (response)
|
|
502
|
+
break;
|
|
503
|
+
}
|
|
504
|
+
if (response)
|
|
505
|
+
break;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
if (!(response instanceof Response) && __classPrivateFieldGet(this, _Router_defaultFallback, "f")) {
|
|
510
|
+
response = yield __classPrivateFieldGet(this, _Router_defaultFallback, "f").call(this, req, ctx);
|
|
511
|
+
}
|
|
512
|
+
// append context headers to response
|
|
513
|
+
if (response instanceof Response) {
|
|
514
|
+
if (ctx.headers) {
|
|
515
|
+
for (const [key, value] of ctx.headers) {
|
|
516
|
+
response.headers.set(key, value);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
if (response instanceof Response)
|
|
521
|
+
ctx.response = response;
|
|
522
|
+
response =
|
|
523
|
+
(_d = (typeof response === "boolean" ? null : response)) !== null && _d !== void 0 ? _d : (found.handlers || found.fallbacks
|
|
524
|
+
? new Response(null, {
|
|
525
|
+
status: 204,
|
|
526
|
+
statusText: "No Content",
|
|
527
|
+
headers: ctx.headers,
|
|
528
|
+
})
|
|
529
|
+
: new Response("Not Found", {
|
|
530
|
+
status: 404,
|
|
531
|
+
statusText: "Not Found",
|
|
532
|
+
headers: ctx.headers,
|
|
533
|
+
}));
|
|
534
|
+
// after response handlers
|
|
535
|
+
if (found.afters) {
|
|
536
|
+
const params = afterNodes[afterNodes.length - 1].params;
|
|
537
|
+
if (params) {
|
|
538
|
+
for (const [index, param] of params) {
|
|
539
|
+
ctx.params[param.name] = key[index];
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
let afterResponse = undefined;
|
|
543
|
+
for (const afterNode of afterNodes) {
|
|
544
|
+
for (const after of afterNode.pipeline) {
|
|
545
|
+
afterResponse = yield after(req, ctx);
|
|
546
|
+
if (afterResponse)
|
|
547
|
+
break;
|
|
548
|
+
}
|
|
549
|
+
if (afterResponse)
|
|
550
|
+
break;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
catch (error) {
|
|
555
|
+
// error handlers
|
|
556
|
+
ctx.error = error instanceof Error ? error : new Error(String(error));
|
|
557
|
+
if (found.catchers) {
|
|
558
|
+
const params = catcherNodes[catcherNodes.length - 1].params;
|
|
559
|
+
if (params) {
|
|
560
|
+
for (const [index, param] of params) {
|
|
561
|
+
ctx.params[param.name] = key[index];
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
for (const catcherNode of catcherNodes) {
|
|
565
|
+
for (const catcher of catcherNode.pipeline) {
|
|
566
|
+
response = yield catcher(req, ctx);
|
|
567
|
+
if (response)
|
|
568
|
+
break;
|
|
569
|
+
}
|
|
570
|
+
if (response)
|
|
571
|
+
break;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
if (!(response instanceof Response) && __classPrivateFieldGet(this, _Router_defaultCatcher, "f")) {
|
|
575
|
+
response = yield __classPrivateFieldGet(this, _Router_defaultCatcher, "f").call(this, req, ctx);
|
|
576
|
+
}
|
|
577
|
+
if (!(response instanceof Response)) {
|
|
578
|
+
response = new Response("Internal Server Error", {
|
|
579
|
+
status: 500,
|
|
580
|
+
statusText: "Internal Server Error",
|
|
581
|
+
headers: ctx.headers,
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
return response;
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
exports.Router = Router;
|
|
590
|
+
_Router_trees = new WeakMap(), _Router_enable = new WeakMap(), _Router_defaultHeaders = new WeakMap(), _Router_defaultCatcher = new WeakMap(), _Router_defaultFallback = new WeakMap(), _Router_setters = new WeakMap();
|
|
591
|
+
/**
|
|
592
|
+
* Static property containing all HTTP methods with wildcard paths.
|
|
593
|
+
* @type {Array<SplitURL>}
|
|
594
|
+
* @readonly
|
|
595
|
+
*/
|
|
596
|
+
Router.ALL_METHOD_PATHS = splitUrl([
|
|
597
|
+
"HEAD /.**",
|
|
598
|
+
"OPTIONS /.**",
|
|
599
|
+
"GET /.**",
|
|
600
|
+
"POST /.**",
|
|
601
|
+
"PUT /.**",
|
|
602
|
+
"PATCH /.**",
|
|
603
|
+
"DELETE /.**",
|
|
604
|
+
]);
|
|
605
|
+
/**
|
|
606
|
+
* Splits URL patterns into their components for routing.
|
|
607
|
+
* Supports wildcards (*), super-globs (**), and parameters (:param).
|
|
608
|
+
* @param {MethodPath|Array<MethodPath>} urls - URL patterns to split
|
|
609
|
+
* @returns {Array<SplitURL>} Array of split URL components
|
|
610
|
+
* @private
|
|
611
|
+
* @example
|
|
612
|
+
* // Returns: [{ method: 'GET', pathname: '/users/:id', nodes: ['users', '*'], params: Map({1: {name: 'id', index: 1}}) }]
|
|
613
|
+
* splitUrl(["GET /users/:id"]);
|
|
614
|
+
*/
|
|
615
|
+
function splitUrl(urls) {
|
|
616
|
+
urls = (Array.isArray(urls) ? urls : [urls]);
|
|
617
|
+
let splitUrls = [];
|
|
618
|
+
for (const mp of urls) {
|
|
619
|
+
const [method, pathname] = mp
|
|
620
|
+
.split(" ", 2)
|
|
621
|
+
.map((mu) => mu.trim());
|
|
622
|
+
const params = new Map();
|
|
623
|
+
const nodes = [];
|
|
624
|
+
const pathNodes = pathname.substring(1).split("/");
|
|
625
|
+
const lastPathNode = pathNodes.length > 0 && pathNodes[pathNodes.length - 1];
|
|
626
|
+
// check the last path node to match '***'
|
|
627
|
+
if (lastPathNode === ".**") {
|
|
628
|
+
const curNodes = pathNodes.slice(0, pathNodes.length - 1);
|
|
629
|
+
splitUrls.push({ method, pathname, nodes: [...curNodes, ""], params });
|
|
630
|
+
splitUrls.push({ method, pathname, nodes: [...curNodes, "**"], params });
|
|
631
|
+
}
|
|
632
|
+
else if (lastPathNode === ".*") {
|
|
633
|
+
const curNodes = pathNodes.splice(0, pathNodes.length - 1);
|
|
634
|
+
splitUrls.push({ method, pathname, nodes: [...curNodes, ""], params });
|
|
635
|
+
splitUrls.push({ method, pathname, nodes: [...curNodes, "*"], params });
|
|
636
|
+
}
|
|
637
|
+
else {
|
|
638
|
+
// process the path nodes
|
|
639
|
+
for (let index = 0; index < pathNodes.length; index++) {
|
|
640
|
+
const pathNode = pathNodes[index];
|
|
641
|
+
if (pathNode === ".**" && index < pathNodes.length - 1) {
|
|
642
|
+
throw new Error("Super-Glob not at the end of pathname");
|
|
643
|
+
}
|
|
644
|
+
if (pathNode.startsWith(":")) {
|
|
645
|
+
const name = pathNode.substring(1);
|
|
646
|
+
params.set(index, { name, index });
|
|
647
|
+
nodes.push("*");
|
|
648
|
+
}
|
|
649
|
+
else {
|
|
650
|
+
nodes.push(pathNode);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
splitUrls.push({ method, pathname, nodes, params });
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
return splitUrls;
|
|
657
|
+
}
|
|
658
|
+
exports.default = Router;
|
|
659
|
+
//# sourceMappingURL=router.js.map
|