@buenojs/bueno 0.8.0
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/.env.example +109 -0
- package/.github/workflows/ci.yml +31 -0
- package/LICENSE +21 -0
- package/README.md +892 -0
- package/architecture.md +652 -0
- package/bun.lock +70 -0
- package/dist/cli/index.js +3233 -0
- package/dist/index.js +9014 -0
- package/package.json +77 -0
- package/src/cache/index.ts +795 -0
- package/src/cli/ARCHITECTURE.md +837 -0
- package/src/cli/bin.ts +10 -0
- package/src/cli/commands/build.ts +425 -0
- package/src/cli/commands/dev.ts +248 -0
- package/src/cli/commands/generate.ts +541 -0
- package/src/cli/commands/help.ts +55 -0
- package/src/cli/commands/index.ts +112 -0
- package/src/cli/commands/migration.ts +355 -0
- package/src/cli/commands/new.ts +804 -0
- package/src/cli/commands/start.ts +208 -0
- package/src/cli/core/args.ts +283 -0
- package/src/cli/core/console.ts +349 -0
- package/src/cli/core/index.ts +60 -0
- package/src/cli/core/prompt.ts +424 -0
- package/src/cli/core/spinner.ts +265 -0
- package/src/cli/index.ts +135 -0
- package/src/cli/templates/deploy.ts +295 -0
- package/src/cli/templates/docker.ts +307 -0
- package/src/cli/templates/index.ts +24 -0
- package/src/cli/utils/fs.ts +428 -0
- package/src/cli/utils/index.ts +8 -0
- package/src/cli/utils/strings.ts +197 -0
- package/src/config/env.ts +408 -0
- package/src/config/index.ts +506 -0
- package/src/config/loader.ts +329 -0
- package/src/config/merge.ts +285 -0
- package/src/config/types.ts +320 -0
- package/src/config/validation.ts +441 -0
- package/src/container/forward-ref.ts +143 -0
- package/src/container/index.ts +386 -0
- package/src/context/index.ts +360 -0
- package/src/database/index.ts +1142 -0
- package/src/database/migrations/index.ts +371 -0
- package/src/database/schema/index.ts +619 -0
- package/src/frontend/api-routes.ts +640 -0
- package/src/frontend/bundler.ts +643 -0
- package/src/frontend/console-client.ts +419 -0
- package/src/frontend/console-stream.ts +587 -0
- package/src/frontend/dev-server.ts +846 -0
- package/src/frontend/file-router.ts +611 -0
- package/src/frontend/frameworks/index.ts +106 -0
- package/src/frontend/frameworks/react.ts +85 -0
- package/src/frontend/frameworks/solid.ts +104 -0
- package/src/frontend/frameworks/svelte.ts +110 -0
- package/src/frontend/frameworks/vue.ts +92 -0
- package/src/frontend/hmr-client.ts +663 -0
- package/src/frontend/hmr.ts +728 -0
- package/src/frontend/index.ts +342 -0
- package/src/frontend/islands.ts +552 -0
- package/src/frontend/isr.ts +555 -0
- package/src/frontend/layout.ts +475 -0
- package/src/frontend/ssr/react.ts +446 -0
- package/src/frontend/ssr/solid.ts +523 -0
- package/src/frontend/ssr/svelte.ts +546 -0
- package/src/frontend/ssr/vue.ts +504 -0
- package/src/frontend/ssr.ts +699 -0
- package/src/frontend/types.ts +2274 -0
- package/src/health/index.ts +604 -0
- package/src/index.ts +410 -0
- package/src/lock/index.ts +587 -0
- package/src/logger/index.ts +444 -0
- package/src/logger/transports/index.ts +969 -0
- package/src/metrics/index.ts +494 -0
- package/src/middleware/built-in.ts +360 -0
- package/src/middleware/index.ts +94 -0
- package/src/modules/filters.ts +458 -0
- package/src/modules/guards.ts +405 -0
- package/src/modules/index.ts +1256 -0
- package/src/modules/interceptors.ts +574 -0
- package/src/modules/lazy.ts +418 -0
- package/src/modules/lifecycle.ts +478 -0
- package/src/modules/metadata.ts +90 -0
- package/src/modules/pipes.ts +626 -0
- package/src/router/index.ts +339 -0
- package/src/router/linear.ts +371 -0
- package/src/router/regex.ts +292 -0
- package/src/router/tree.ts +562 -0
- package/src/rpc/index.ts +1263 -0
- package/src/security/index.ts +436 -0
- package/src/ssg/index.ts +631 -0
- package/src/storage/index.ts +456 -0
- package/src/telemetry/index.ts +1097 -0
- package/src/testing/index.ts +1586 -0
- package/src/types/index.ts +236 -0
- package/src/types/optional-deps.d.ts +219 -0
- package/src/validation/index.ts +276 -0
- package/src/websocket/index.ts +1004 -0
- package/tests/integration/cli.test.ts +1016 -0
- package/tests/integration/fullstack.test.ts +234 -0
- package/tests/unit/cache.test.ts +174 -0
- package/tests/unit/cli-commands.test.ts +892 -0
- package/tests/unit/cli.test.ts +1258 -0
- package/tests/unit/container.test.ts +279 -0
- package/tests/unit/context.test.ts +221 -0
- package/tests/unit/database.test.ts +183 -0
- package/tests/unit/linear-router.test.ts +280 -0
- package/tests/unit/lock.test.ts +336 -0
- package/tests/unit/middleware.test.ts +184 -0
- package/tests/unit/modules.test.ts +142 -0
- package/tests/unit/pubsub.test.ts +257 -0
- package/tests/unit/regex-router.test.ts +265 -0
- package/tests/unit/router.test.ts +373 -0
- package/tests/unit/rpc.test.ts +1248 -0
- package/tests/unit/security.test.ts +174 -0
- package/tests/unit/telemetry.test.ts +371 -0
- package/tests/unit/test-cache.test.ts +110 -0
- package/tests/unit/test-database.test.ts +282 -0
- package/tests/unit/tree-router.test.ts +325 -0
- package/tests/unit/validation.test.ts +794 -0
- package/tsconfig.json +27 -0
|
@@ -0,0 +1,562 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TreeRouter (Radix Tree Router)
|
|
3
|
+
*
|
|
4
|
+
* Optimized for applications with 100+ routes.
|
|
5
|
+
* - O(log n) lookups via radix tree traversal
|
|
6
|
+
* - Efficient prefix compression
|
|
7
|
+
* - Supports parameters, wildcards, and regex constraints
|
|
8
|
+
*
|
|
9
|
+
* Best for: Large APIs, enterprise applications, complex routing
|
|
10
|
+
*
|
|
11
|
+
* Tree Structure Example:
|
|
12
|
+
*
|
|
13
|
+
* root
|
|
14
|
+
* |
|
|
15
|
+
* /api
|
|
16
|
+
* / \
|
|
17
|
+
* /users /posts
|
|
18
|
+
* / \ \
|
|
19
|
+
* /:id /list /:id
|
|
20
|
+
* | | |
|
|
21
|
+
* GET GET GET
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import type {
|
|
25
|
+
HTTPMethod,
|
|
26
|
+
MiddlewareHandler,
|
|
27
|
+
PathParams,
|
|
28
|
+
RouteHandler,
|
|
29
|
+
} from "../types";
|
|
30
|
+
|
|
31
|
+
// ============= Types =============
|
|
32
|
+
|
|
33
|
+
export interface RouteMatch {
|
|
34
|
+
handler: RouteHandler;
|
|
35
|
+
params: PathParams;
|
|
36
|
+
middleware?: MiddlewareHandler[];
|
|
37
|
+
name?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface RouteOptions {
|
|
41
|
+
name?: string;
|
|
42
|
+
middleware?: MiddlewareHandler | MiddlewareHandler[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface RouteHandlerEntry {
|
|
46
|
+
handler: RouteHandler;
|
|
47
|
+
middleware?: MiddlewareHandler[];
|
|
48
|
+
name?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface TreeNode {
|
|
52
|
+
/** Path segment (e.g., "users", ":id", "") */
|
|
53
|
+
path: string;
|
|
54
|
+
|
|
55
|
+
/** Children indexed by path prefix */
|
|
56
|
+
children: Map<string, TreeNode>;
|
|
57
|
+
|
|
58
|
+
/** Parameter child (for :param patterns) */
|
|
59
|
+
paramChild?: TreeNode;
|
|
60
|
+
|
|
61
|
+
/** Parameter name if this is a param node */
|
|
62
|
+
paramName?: string;
|
|
63
|
+
|
|
64
|
+
/** Regex constraint for parameter */
|
|
65
|
+
paramRegex?: RegExp;
|
|
66
|
+
|
|
67
|
+
/** Wildcard child */
|
|
68
|
+
wildcardChild?: TreeNode;
|
|
69
|
+
|
|
70
|
+
/** Handlers indexed by HTTP method */
|
|
71
|
+
handlers: Map<HTTPMethod | "ALL", RouteHandlerEntry>;
|
|
72
|
+
|
|
73
|
+
/** Is this a wildcard node? */
|
|
74
|
+
isWildcard: boolean;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ============= TreeRouter Class =============
|
|
78
|
+
|
|
79
|
+
export class TreeRouter {
|
|
80
|
+
private root: TreeNode;
|
|
81
|
+
private groupPrefix = "";
|
|
82
|
+
private groupMiddleware: MiddlewareHandler[] = [];
|
|
83
|
+
private routeCount = 0;
|
|
84
|
+
|
|
85
|
+
constructor() {
|
|
86
|
+
this.root = this.createNode("");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private createNode(
|
|
90
|
+
path: string,
|
|
91
|
+
paramName?: string,
|
|
92
|
+
paramRegex?: RegExp,
|
|
93
|
+
isWildcard = false,
|
|
94
|
+
): TreeNode {
|
|
95
|
+
return {
|
|
96
|
+
path,
|
|
97
|
+
children: new Map(),
|
|
98
|
+
handlers: new Map(),
|
|
99
|
+
paramName,
|
|
100
|
+
paramRegex,
|
|
101
|
+
isWildcard,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Register a route with specific HTTP method
|
|
107
|
+
*/
|
|
108
|
+
private addRoute(
|
|
109
|
+
method: HTTPMethod | "ALL",
|
|
110
|
+
pattern: string,
|
|
111
|
+
handler: RouteHandler,
|
|
112
|
+
options?: RouteOptions,
|
|
113
|
+
): void {
|
|
114
|
+
const fullPattern = this.groupPrefix + pattern;
|
|
115
|
+
|
|
116
|
+
const optsMiddleware = options?.middleware;
|
|
117
|
+
const routeMiddleware: MiddlewareHandler[] = optsMiddleware
|
|
118
|
+
? Array.isArray(optsMiddleware)
|
|
119
|
+
? optsMiddleware
|
|
120
|
+
: [optsMiddleware]
|
|
121
|
+
: [];
|
|
122
|
+
|
|
123
|
+
const middleware = [...this.groupMiddleware, ...routeMiddleware];
|
|
124
|
+
|
|
125
|
+
// Parse pattern into segments
|
|
126
|
+
const segments = this.parsePattern(fullPattern);
|
|
127
|
+
|
|
128
|
+
// Insert into tree
|
|
129
|
+
const node = this.insertSegments(this.root, segments, 0);
|
|
130
|
+
|
|
131
|
+
// Store handler
|
|
132
|
+
node.handlers.set(method, {
|
|
133
|
+
handler,
|
|
134
|
+
middleware,
|
|
135
|
+
name: options?.name,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
this.routeCount++;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Parse a pattern into segments
|
|
143
|
+
*
|
|
144
|
+
* Examples:
|
|
145
|
+
* - "/users/:id" -> [{ type: 'static', value: 'users' }, { type: 'param', name: 'id' }]
|
|
146
|
+
* - "/files/*" -> [{ type: 'static', value: 'files' }, { type: 'wildcard' }]
|
|
147
|
+
*/
|
|
148
|
+
private parsePattern(pattern: string): Array<{
|
|
149
|
+
type: "static" | "param" | "wildcard";
|
|
150
|
+
value?: string;
|
|
151
|
+
name?: string;
|
|
152
|
+
regex?: RegExp;
|
|
153
|
+
optional?: boolean;
|
|
154
|
+
}> {
|
|
155
|
+
const segments: Array<{
|
|
156
|
+
type: "static" | "param" | "wildcard";
|
|
157
|
+
value?: string;
|
|
158
|
+
name?: string;
|
|
159
|
+
regex?: RegExp;
|
|
160
|
+
optional?: boolean;
|
|
161
|
+
}> = [];
|
|
162
|
+
|
|
163
|
+
// Normalize pattern
|
|
164
|
+
let normalized = pattern;
|
|
165
|
+
if (!normalized.startsWith("/")) {
|
|
166
|
+
normalized = `/${normalized}`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Remove trailing slash (except for root)
|
|
170
|
+
if (normalized.length > 1 && normalized.endsWith("/")) {
|
|
171
|
+
normalized = normalized.slice(0, -1);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (normalized === "/") {
|
|
175
|
+
return [{ type: "static", value: "" }];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const parts = normalized.split("/").filter(Boolean);
|
|
179
|
+
|
|
180
|
+
for (const part of parts) {
|
|
181
|
+
if (part === "*") {
|
|
182
|
+
segments.push({ type: "wildcard" });
|
|
183
|
+
} else if (part.startsWith(":")) {
|
|
184
|
+
let name = part.slice(1);
|
|
185
|
+
let regex: RegExp | undefined;
|
|
186
|
+
let optional = false;
|
|
187
|
+
|
|
188
|
+
// Check for optional marker
|
|
189
|
+
if (name.endsWith("?")) {
|
|
190
|
+
optional = true;
|
|
191
|
+
name = name.slice(0, -1);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Check for custom regex
|
|
195
|
+
const regexMatch = name.match(/^(\w+)<(.+)>$/);
|
|
196
|
+
if (regexMatch) {
|
|
197
|
+
name = regexMatch[1];
|
|
198
|
+
regex = new RegExp(`^(${regexMatch[2]})$`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
segments.push({ type: "param", name, regex, optional });
|
|
202
|
+
} else {
|
|
203
|
+
segments.push({ type: "static", value: part.toLowerCase() });
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return segments;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Insert segments into the tree, creating nodes as needed
|
|
212
|
+
*/
|
|
213
|
+
private insertSegments(
|
|
214
|
+
node: TreeNode,
|
|
215
|
+
segments: Array<{
|
|
216
|
+
type: "static" | "param" | "wildcard";
|
|
217
|
+
value?: string;
|
|
218
|
+
name?: string;
|
|
219
|
+
regex?: RegExp;
|
|
220
|
+
optional?: boolean;
|
|
221
|
+
}>,
|
|
222
|
+
index: number,
|
|
223
|
+
): TreeNode {
|
|
224
|
+
if (index >= segments.length) {
|
|
225
|
+
return node;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const segment = segments[index];
|
|
229
|
+
|
|
230
|
+
if (segment.type === "wildcard") {
|
|
231
|
+
// Create or get wildcard child
|
|
232
|
+
if (!node.wildcardChild) {
|
|
233
|
+
node.wildcardChild = this.createNode("*", "*", undefined, true);
|
|
234
|
+
}
|
|
235
|
+
return node.wildcardChild;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (segment.type === "param") {
|
|
239
|
+
// Create or get parameter child
|
|
240
|
+
if (!node.paramChild) {
|
|
241
|
+
node.paramChild = this.createNode(
|
|
242
|
+
`:${segment.name}`,
|
|
243
|
+
segment.name,
|
|
244
|
+
segment.regex,
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Continue with next segment
|
|
249
|
+
return this.insertSegments(node.paramChild, segments, index + 1);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Static segment
|
|
253
|
+
const value = segment.value!;
|
|
254
|
+
|
|
255
|
+
// Look for existing child with common prefix
|
|
256
|
+
let child = node.children.get(value);
|
|
257
|
+
|
|
258
|
+
if (!child) {
|
|
259
|
+
// Create new child
|
|
260
|
+
child = this.createNode(value);
|
|
261
|
+
node.children.set(value, child);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Continue with next segment
|
|
265
|
+
return this.insertSegments(child, segments, index + 1);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Match a route by method and pathname
|
|
270
|
+
*
|
|
271
|
+
* Performance: O(log n) average case for tree traversal
|
|
272
|
+
*/
|
|
273
|
+
match(method: HTTPMethod | "ALL", pathname: string): RouteMatch | undefined {
|
|
274
|
+
const params: PathParams = {};
|
|
275
|
+
|
|
276
|
+
// Normalize pathname
|
|
277
|
+
let normalized = pathname.toLowerCase();
|
|
278
|
+
if (normalized.length > 1 && normalized.endsWith("/")) {
|
|
279
|
+
normalized = normalized.slice(0, -1);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Split into parts
|
|
283
|
+
const parts =
|
|
284
|
+
normalized === "/" ? [""] : normalized.split("/").filter(Boolean);
|
|
285
|
+
|
|
286
|
+
// Search tree
|
|
287
|
+
const result = this.searchTree(this.root, parts, 0, method, params);
|
|
288
|
+
|
|
289
|
+
return result;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Search the tree for a matching route
|
|
294
|
+
*/
|
|
295
|
+
private searchTree(
|
|
296
|
+
node: TreeNode,
|
|
297
|
+
parts: string[],
|
|
298
|
+
partIndex: number,
|
|
299
|
+
method: HTTPMethod | "ALL",
|
|
300
|
+
params: PathParams,
|
|
301
|
+
): RouteMatch | undefined {
|
|
302
|
+
// Check if we've consumed all parts
|
|
303
|
+
if (partIndex >= parts.length) {
|
|
304
|
+
// Check for handler
|
|
305
|
+
const handlerEntry =
|
|
306
|
+
node.handlers.get(method) || node.handlers.get("ALL");
|
|
307
|
+
if (handlerEntry) {
|
|
308
|
+
return {
|
|
309
|
+
handler: handlerEntry.handler,
|
|
310
|
+
params: { ...params },
|
|
311
|
+
middleware: handlerEntry.middleware,
|
|
312
|
+
name: handlerEntry.name,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
return undefined;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const part = parts[partIndex];
|
|
319
|
+
|
|
320
|
+
// 1. Try exact match (static child) - highest priority
|
|
321
|
+
const staticChild = node.children.get(part);
|
|
322
|
+
if (staticChild) {
|
|
323
|
+
const result = this.searchTree(
|
|
324
|
+
staticChild,
|
|
325
|
+
parts,
|
|
326
|
+
partIndex + 1,
|
|
327
|
+
method,
|
|
328
|
+
params,
|
|
329
|
+
);
|
|
330
|
+
if (result) return result;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// 2. Try parameter child
|
|
334
|
+
if (node.paramChild) {
|
|
335
|
+
const paramNode = node.paramChild;
|
|
336
|
+
|
|
337
|
+
// Check regex constraint
|
|
338
|
+
if (paramNode.paramRegex) {
|
|
339
|
+
if (paramNode.paramRegex.test(part)) {
|
|
340
|
+
params[paramNode.paramName!] = part;
|
|
341
|
+
const result = this.searchTree(
|
|
342
|
+
paramNode,
|
|
343
|
+
parts,
|
|
344
|
+
partIndex + 1,
|
|
345
|
+
method,
|
|
346
|
+
params,
|
|
347
|
+
);
|
|
348
|
+
if (result) return result;
|
|
349
|
+
delete params[paramNode.paramName!];
|
|
350
|
+
}
|
|
351
|
+
} else {
|
|
352
|
+
params[paramNode.paramName!] = part;
|
|
353
|
+
const result = this.searchTree(
|
|
354
|
+
paramNode,
|
|
355
|
+
parts,
|
|
356
|
+
partIndex + 1,
|
|
357
|
+
method,
|
|
358
|
+
params,
|
|
359
|
+
);
|
|
360
|
+
if (result) return result;
|
|
361
|
+
delete params[paramNode.paramName!];
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// 3. Try wildcard child - lowest priority
|
|
366
|
+
if (node.wildcardChild) {
|
|
367
|
+
const wildcardNode = node.wildcardChild;
|
|
368
|
+
// Capture remaining path
|
|
369
|
+
params["*"] = parts.slice(partIndex).join("/");
|
|
370
|
+
|
|
371
|
+
const handlerEntry =
|
|
372
|
+
wildcardNode.handlers.get(method) || wildcardNode.handlers.get("ALL");
|
|
373
|
+
if (handlerEntry) {
|
|
374
|
+
return {
|
|
375
|
+
handler: handlerEntry.handler,
|
|
376
|
+
params: { ...params },
|
|
377
|
+
middleware: handlerEntry.middleware,
|
|
378
|
+
name: handlerEntry.name,
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return undefined;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Create a route group with prefix and optional middleware
|
|
388
|
+
*/
|
|
389
|
+
group(
|
|
390
|
+
prefix: string,
|
|
391
|
+
options?: { middleware?: MiddlewareHandler | MiddlewareHandler[] },
|
|
392
|
+
): TreeRouter {
|
|
393
|
+
const childRouter = new TreeRouter();
|
|
394
|
+
|
|
395
|
+
// Share root node
|
|
396
|
+
childRouter.root = this.root;
|
|
397
|
+
childRouter.groupPrefix = this.groupPrefix + prefix;
|
|
398
|
+
|
|
399
|
+
const optsMiddleware = options?.middleware;
|
|
400
|
+
const middlewareArray: MiddlewareHandler[] = optsMiddleware
|
|
401
|
+
? Array.isArray(optsMiddleware)
|
|
402
|
+
? optsMiddleware
|
|
403
|
+
: [optsMiddleware]
|
|
404
|
+
: [];
|
|
405
|
+
|
|
406
|
+
childRouter.groupMiddleware = [...this.groupMiddleware, ...middlewareArray];
|
|
407
|
+
childRouter.routeCount = this.routeCount;
|
|
408
|
+
|
|
409
|
+
return childRouter;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Get all registered routes (traverses tree)
|
|
414
|
+
*/
|
|
415
|
+
getRoutes(): Array<{
|
|
416
|
+
method: HTTPMethod | "ALL";
|
|
417
|
+
pattern: string;
|
|
418
|
+
name?: string;
|
|
419
|
+
}> {
|
|
420
|
+
const routes: Array<{
|
|
421
|
+
method: HTTPMethod | "ALL";
|
|
422
|
+
pattern: string;
|
|
423
|
+
name?: string;
|
|
424
|
+
}> = [];
|
|
425
|
+
|
|
426
|
+
this.traverseTree(this.root, "", routes);
|
|
427
|
+
|
|
428
|
+
return routes;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Traverse tree to collect all routes
|
|
433
|
+
*/
|
|
434
|
+
private traverseTree(
|
|
435
|
+
node: TreeNode,
|
|
436
|
+
currentPath: string,
|
|
437
|
+
routes: Array<{
|
|
438
|
+
method: HTTPMethod | "ALL";
|
|
439
|
+
pattern: string;
|
|
440
|
+
name?: string;
|
|
441
|
+
}>,
|
|
442
|
+
): void {
|
|
443
|
+
// Add handlers at this node
|
|
444
|
+
for (const [method, entry] of node.handlers) {
|
|
445
|
+
routes.push({
|
|
446
|
+
method,
|
|
447
|
+
pattern: currentPath || "/",
|
|
448
|
+
name: entry.name,
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Traverse static children
|
|
453
|
+
for (const [path, child] of node.children) {
|
|
454
|
+
const childPath = `${currentPath}/${path}`;
|
|
455
|
+
this.traverseTree(child, childPath, routes);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Traverse parameter child
|
|
459
|
+
if (node.paramChild) {
|
|
460
|
+
const childPath = `${currentPath}/:${node.paramChild.paramName}`;
|
|
461
|
+
this.traverseTree(node.paramChild, childPath, routes);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Traverse wildcard child
|
|
465
|
+
if (node.wildcardChild) {
|
|
466
|
+
const childPath = `${currentPath}/*`;
|
|
467
|
+
this.traverseTree(node.wildcardChild, childPath, routes);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Get router type for debugging
|
|
473
|
+
*/
|
|
474
|
+
getRouterType(): "tree" {
|
|
475
|
+
return "tree";
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Get route count
|
|
480
|
+
*/
|
|
481
|
+
getRouteCount(): number {
|
|
482
|
+
return this.routeCount;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Get tree statistics
|
|
487
|
+
*/
|
|
488
|
+
getTreeStats(): { nodes: number; depth: number; routes: number } {
|
|
489
|
+
return {
|
|
490
|
+
nodes: this.countNodes(this.root),
|
|
491
|
+
depth: this.getTreeDepth(this.root),
|
|
492
|
+
routes: this.routeCount,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
private countNodes(node: TreeNode): number {
|
|
497
|
+
let count = 1;
|
|
498
|
+
|
|
499
|
+
for (const child of node.children.values()) {
|
|
500
|
+
count += this.countNodes(child);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (node.paramChild) {
|
|
504
|
+
count += this.countNodes(node.paramChild);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (node.wildcardChild) {
|
|
508
|
+
count += this.countNodes(node.wildcardChild);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return count;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
private getTreeDepth(node: TreeNode): number {
|
|
515
|
+
let maxDepth = 0;
|
|
516
|
+
|
|
517
|
+
for (const child of node.children.values()) {
|
|
518
|
+
maxDepth = Math.max(maxDepth, this.getTreeDepth(child));
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
if (node.paramChild) {
|
|
522
|
+
maxDepth = Math.max(maxDepth, this.getTreeDepth(node.paramChild));
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
if (node.wildcardChild) {
|
|
526
|
+
maxDepth = Math.max(maxDepth, this.getTreeDepth(node.wildcardChild));
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return maxDepth + 1;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// ============= HTTP Method Helpers =============
|
|
533
|
+
|
|
534
|
+
get(pattern: string, handler: RouteHandler, options?: RouteOptions): void {
|
|
535
|
+
this.addRoute("GET", pattern, handler, options);
|
|
536
|
+
}
|
|
537
|
+
post(pattern: string, handler: RouteHandler, options?: RouteOptions): void {
|
|
538
|
+
this.addRoute("POST", pattern, handler, options);
|
|
539
|
+
}
|
|
540
|
+
put(pattern: string, handler: RouteHandler, options?: RouteOptions): void {
|
|
541
|
+
this.addRoute("PUT", pattern, handler, options);
|
|
542
|
+
}
|
|
543
|
+
patch(pattern: string, handler: RouteHandler, options?: RouteOptions): void {
|
|
544
|
+
this.addRoute("PATCH", pattern, handler, options);
|
|
545
|
+
}
|
|
546
|
+
delete(pattern: string, handler: RouteHandler, options?: RouteOptions): void {
|
|
547
|
+
this.addRoute("DELETE", pattern, handler, options);
|
|
548
|
+
}
|
|
549
|
+
head(pattern: string, handler: RouteHandler, options?: RouteOptions): void {
|
|
550
|
+
this.addRoute("HEAD", pattern, handler, options);
|
|
551
|
+
}
|
|
552
|
+
options(
|
|
553
|
+
pattern: string,
|
|
554
|
+
handler: RouteHandler,
|
|
555
|
+
options?: RouteOptions,
|
|
556
|
+
): void {
|
|
557
|
+
this.addRoute("OPTIONS", pattern, handler, options);
|
|
558
|
+
}
|
|
559
|
+
all(pattern: string, handler: RouteHandler, options?: RouteOptions): void {
|
|
560
|
+
this.addRoute("ALL", pattern, handler, options);
|
|
561
|
+
}
|
|
562
|
+
}
|