astro-routify 1.2.2 → 1.5.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/README.md +367 -28
- package/dist/core/RouteTrie.d.ts +4 -3
- package/dist/core/RouteTrie.js +132 -35
- package/dist/core/RouterBuilder.d.ts +96 -13
- package/dist/core/RouterBuilder.js +243 -21
- package/dist/core/decorators.d.ts +29 -0
- package/dist/core/decorators.js +49 -0
- package/dist/core/defineGroup.d.ts +33 -17
- package/dist/core/defineGroup.js +66 -23
- package/dist/core/defineHandler.d.ts +31 -13
- package/dist/core/defineHandler.js +1 -23
- package/dist/core/defineRoute.d.ts +32 -3
- package/dist/core/defineRoute.js +36 -9
- package/dist/core/defineRouter.d.ts +11 -1
- package/dist/core/defineRouter.js +79 -16
- package/dist/core/internal/createJsonStreamRoute.d.ts +1 -1
- package/dist/core/internal/createJsonStreamRoute.js +12 -4
- package/dist/core/middlewares.d.ts +47 -0
- package/dist/core/middlewares.js +110 -0
- package/dist/core/openapi.d.ts +17 -0
- package/dist/core/openapi.js +84 -0
- package/dist/core/registry.d.ts +15 -0
- package/dist/core/registry.js +26 -0
- package/dist/core/responseHelpers.d.ts +22 -5
- package/dist/core/responseHelpers.js +121 -20
- package/dist/core/stream.d.ts +3 -3
- package/dist/core/stream.js +16 -9
- package/dist/core/streamJsonArray.d.ts +1 -1
- package/dist/core/streamJsonArray.js +1 -1
- package/dist/index.d.ts +338 -57
- package/dist/index.js +5 -1
- package/package.json +5 -3
package/dist/core/RouteTrie.js
CHANGED
|
@@ -1,56 +1,153 @@
|
|
|
1
1
|
export class RouteTrie {
|
|
2
2
|
constructor() {
|
|
3
|
-
this.root = { children: new Map(),
|
|
3
|
+
this.root = { children: new Map(), routes: new Map() };
|
|
4
4
|
}
|
|
5
|
-
insert(
|
|
6
|
-
|
|
5
|
+
insert(route) {
|
|
6
|
+
if (!route || typeof route.path !== 'string') {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
const segments = this.segmentize(route.path, true);
|
|
7
10
|
let node = this.root;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
const paramNames = {};
|
|
12
|
+
for (let i = 0; i < segments.length; i++) {
|
|
13
|
+
const segment = segments[i];
|
|
14
|
+
if (segment === '**') {
|
|
15
|
+
if (!node.catchAllChild) {
|
|
16
|
+
node.catchAllChild = { children: new Map(), routes: new Map() };
|
|
17
|
+
}
|
|
18
|
+
node = node.catchAllChild;
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
else if (segment === '*') {
|
|
22
|
+
if (!node.wildcardChild) {
|
|
23
|
+
node.wildcardChild = { children: new Map(), routes: new Map() };
|
|
24
|
+
}
|
|
25
|
+
node = node.wildcardChild;
|
|
26
|
+
}
|
|
27
|
+
else if (segment.startsWith(':')) {
|
|
28
|
+
const regexMatch = segment.match(/^:([a-zA-Z0-9_]+)\((.*)\)$/);
|
|
29
|
+
if (regexMatch) {
|
|
30
|
+
const paramName = regexMatch[1];
|
|
31
|
+
const regexStr = regexMatch[2];
|
|
32
|
+
if (!node.regexChildren)
|
|
33
|
+
node.regexChildren = [];
|
|
34
|
+
let regexNode = node.regexChildren.find((rc) => rc.regex.source === regexStr && rc.paramName === paramName)?.node;
|
|
35
|
+
if (!regexNode) {
|
|
36
|
+
regexNode = { children: new Map(), routes: new Map() };
|
|
37
|
+
node.regexChildren.push({
|
|
38
|
+
regex: new RegExp(regexStr),
|
|
39
|
+
paramName,
|
|
40
|
+
node: regexNode,
|
|
41
|
+
});
|
|
42
|
+
// Sort regex by specificity (longer source first)
|
|
43
|
+
node.regexChildren.sort((a, b) => b.regex.source.length - a.regex.source.length);
|
|
44
|
+
}
|
|
45
|
+
node = regexNode;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
const paramName = segment.slice(1);
|
|
49
|
+
paramNames[i] = paramName;
|
|
50
|
+
if (!node.paramChild) {
|
|
51
|
+
node.paramChild = { children: new Map(), routes: new Map() };
|
|
52
|
+
}
|
|
53
|
+
node = node.paramChild;
|
|
16
54
|
}
|
|
17
|
-
node = node.paramChild;
|
|
18
55
|
}
|
|
19
56
|
else {
|
|
20
57
|
if (!node.children.has(segment)) {
|
|
21
|
-
node.children.set(segment, { children: new Map(),
|
|
58
|
+
node.children.set(segment, { children: new Map(), routes: new Map() });
|
|
22
59
|
}
|
|
23
60
|
node = node.children.get(segment);
|
|
24
61
|
}
|
|
25
62
|
}
|
|
26
|
-
node.
|
|
63
|
+
node.routes.set(route.method, { route, paramNames });
|
|
27
64
|
}
|
|
28
65
|
find(path, method) {
|
|
29
|
-
const segments = this.segmentize(path);
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
66
|
+
const segments = this.segmentize(path, true);
|
|
67
|
+
return this.matchNode(this.root, segments, 0, method, {});
|
|
68
|
+
}
|
|
69
|
+
matchNode(node, segments, index, method, capturedValues) {
|
|
70
|
+
if (index === segments.length) {
|
|
71
|
+
let info = node.routes.get(method) ?? null;
|
|
72
|
+
let allowed = info ? undefined : [...node.routes.keys()];
|
|
73
|
+
// If no route here, check if there's a catch-all that matches "empty" remaining
|
|
74
|
+
if (!info && node.catchAllChild) {
|
|
75
|
+
info = node.catchAllChild.routes.get(method) ?? null;
|
|
76
|
+
allowed = info ? undefined : [...node.catchAllChild.routes.keys()];
|
|
77
|
+
if (info) {
|
|
78
|
+
return { route: info.route, allowed, params: { '*': '' } };
|
|
79
|
+
}
|
|
35
80
|
}
|
|
36
|
-
|
|
37
|
-
params
|
|
38
|
-
|
|
81
|
+
if (info) {
|
|
82
|
+
const params = {};
|
|
83
|
+
for (const [depth, name] of Object.entries(info.paramNames)) {
|
|
84
|
+
params[name] = capturedValues[Number(depth)];
|
|
85
|
+
}
|
|
86
|
+
return { route: info.route, params };
|
|
39
87
|
}
|
|
40
|
-
|
|
41
|
-
|
|
88
|
+
return { route: null, allowed, params: {} };
|
|
89
|
+
}
|
|
90
|
+
const segment = segments[index];
|
|
91
|
+
// 1. Static Match
|
|
92
|
+
const staticChild = node.children.get(segment);
|
|
93
|
+
if (staticChild) {
|
|
94
|
+
const match = this.matchNode(staticChild, segments, index + 1, method, capturedValues);
|
|
95
|
+
if (match.route || (match.allowed && match.allowed.length > 0))
|
|
96
|
+
return match;
|
|
97
|
+
}
|
|
98
|
+
// 2. Regex Match
|
|
99
|
+
if (node.regexChildren) {
|
|
100
|
+
for (const rc of node.regexChildren) {
|
|
101
|
+
if (rc.regex.test(segment)) {
|
|
102
|
+
const match = this.matchNode(rc.node, segments, index + 1, method, capturedValues);
|
|
103
|
+
if (match.route || (match.allowed && match.allowed.length > 0)) {
|
|
104
|
+
match.params[rc.paramName] = segment;
|
|
105
|
+
return match;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// 3. Param Match
|
|
111
|
+
if (node.paramChild) {
|
|
112
|
+
capturedValues[index] = segment;
|
|
113
|
+
const match = this.matchNode(node.paramChild, segments, index + 1, method, capturedValues);
|
|
114
|
+
if (match.route || (match.allowed && match.allowed.length > 0)) {
|
|
115
|
+
return match;
|
|
42
116
|
}
|
|
117
|
+
delete capturedValues[index];
|
|
118
|
+
}
|
|
119
|
+
// 4. Wildcard Match (*)
|
|
120
|
+
if (node.wildcardChild) {
|
|
121
|
+
const match = this.matchNode(node.wildcardChild, segments, index + 1, method, capturedValues);
|
|
122
|
+
if (match.route || (match.allowed && match.allowed.length > 0))
|
|
123
|
+
return match;
|
|
124
|
+
}
|
|
125
|
+
// 5. Catch-all Match (**)
|
|
126
|
+
if (node.catchAllChild) {
|
|
127
|
+
const info = node.catchAllChild.routes.get(method) ?? null;
|
|
128
|
+
const params = {};
|
|
129
|
+
// Capture the rest of the path
|
|
130
|
+
const remainder = segments.slice(index).join('/');
|
|
131
|
+
params['*'] = remainder;
|
|
132
|
+
return {
|
|
133
|
+
route: info ? info.route : null,
|
|
134
|
+
allowed: info ? undefined : [...node.catchAllChild.routes.keys()],
|
|
135
|
+
params,
|
|
136
|
+
};
|
|
43
137
|
}
|
|
44
|
-
|
|
45
|
-
return { handler: null, params };
|
|
46
|
-
const handler = node.handlers.get(method) ?? null;
|
|
47
|
-
return {
|
|
48
|
-
handler,
|
|
49
|
-
allowed: handler ? undefined : [...node.handlers.keys()],
|
|
50
|
-
params,
|
|
51
|
-
};
|
|
138
|
+
return { route: null, params: {} };
|
|
52
139
|
}
|
|
53
|
-
segmentize(path) {
|
|
54
|
-
|
|
140
|
+
segmentize(path, decode = false) {
|
|
141
|
+
if (typeof path !== 'string')
|
|
142
|
+
return [];
|
|
143
|
+
const segments = path.split('/').filter(Boolean);
|
|
144
|
+
return decode ? segments.map(s => {
|
|
145
|
+
try {
|
|
146
|
+
return decodeURIComponent(s);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return s;
|
|
150
|
+
}
|
|
151
|
+
}) : segments;
|
|
55
152
|
}
|
|
56
153
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import type { APIRoute } from 'astro';
|
|
1
2
|
import { type Route } from './defineRoute';
|
|
2
3
|
import { type RouterOptions } from './defineRouter';
|
|
3
4
|
import { RouteGroup } from './defineGroup';
|
|
4
|
-
import { type
|
|
5
|
+
import { type Middleware } from './defineHandler';
|
|
5
6
|
/**
|
|
6
7
|
* A fluent builder for creating and composing API routes in Astro.
|
|
7
8
|
*
|
|
@@ -27,12 +28,72 @@ import { type Handler } from './defineHandler';
|
|
|
27
28
|
*
|
|
28
29
|
* export const GET = router.build();
|
|
29
30
|
* ```
|
|
31
|
+
*
|
|
32
|
+
* @example Auto-discovering routes via Vite glob:
|
|
33
|
+
* ```ts
|
|
34
|
+
* const router = new RouterBuilder()
|
|
35
|
+
* .addModules(import.meta.glob('./**\/*.routes.ts', { eager: true }));
|
|
36
|
+
*
|
|
37
|
+
* export const ALL = router.build();
|
|
38
|
+
* ```
|
|
30
39
|
*/
|
|
31
40
|
export declare class RouterBuilder {
|
|
32
41
|
private _options;
|
|
33
42
|
private _routes;
|
|
43
|
+
private _groups;
|
|
44
|
+
private _middlewares;
|
|
45
|
+
private _shouldLog;
|
|
34
46
|
private static _registerWarned;
|
|
47
|
+
/**
|
|
48
|
+
* A global RouterBuilder instance for easy, centralized route registration.
|
|
49
|
+
*/
|
|
50
|
+
static readonly global: RouterBuilder;
|
|
35
51
|
constructor(options?: RouterOptions);
|
|
52
|
+
/**
|
|
53
|
+
* Adds a global middleware to all routes registered in this builder.
|
|
54
|
+
*
|
|
55
|
+
* @param middleware - The middleware function.
|
|
56
|
+
* @returns The current builder instance.
|
|
57
|
+
*/
|
|
58
|
+
use(middleware: Middleware): this;
|
|
59
|
+
/**
|
|
60
|
+
* Creates and adds a new route group to the builder.
|
|
61
|
+
*
|
|
62
|
+
* @param basePath - The base path for the group.
|
|
63
|
+
* @param configure - Optional callback to configure the group.
|
|
64
|
+
* @returns The created RouteGroup instance.
|
|
65
|
+
*/
|
|
66
|
+
group(basePath: string, configure?: (group: RouteGroup) => void): RouteGroup;
|
|
67
|
+
/**
|
|
68
|
+
* Adds all routes and groups that have been auto-registered via `defineRoute(..., true)`
|
|
69
|
+
* or `defineGroup(..., true)`.
|
|
70
|
+
*
|
|
71
|
+
* @returns The current builder instance.
|
|
72
|
+
*/
|
|
73
|
+
addRegistered(): this;
|
|
74
|
+
/**
|
|
75
|
+
* Bulk registers routes and groups from a module collection.
|
|
76
|
+
* Ideal for use with Vite's `import.meta.glob` (with `{ eager: true }`).
|
|
77
|
+
*
|
|
78
|
+
* It will search for both default and named exports that are either `Route` or `RouteGroup`.
|
|
79
|
+
*
|
|
80
|
+
* @param modules A record of modules (e.g. from `import.meta.glob`).
|
|
81
|
+
* @returns The current builder instance.
|
|
82
|
+
*/
|
|
83
|
+
addModules(modules: Record<string, any>): this;
|
|
84
|
+
/**
|
|
85
|
+
* Prints all registered routes to the console.
|
|
86
|
+
* Useful for debugging during development.
|
|
87
|
+
*
|
|
88
|
+
* @returns The current builder instance.
|
|
89
|
+
*/
|
|
90
|
+
logRoutes(): this;
|
|
91
|
+
/**
|
|
92
|
+
* Disables the automatic logging of routes that happens in development mode.
|
|
93
|
+
*
|
|
94
|
+
* @returns The current builder instance.
|
|
95
|
+
*/
|
|
96
|
+
disableLogging(): this;
|
|
36
97
|
/**
|
|
37
98
|
* @deprecated Prefer `addGroup()` or `addRoute()` for structured routing.
|
|
38
99
|
*
|
|
@@ -71,39 +132,39 @@ export declare class RouterBuilder {
|
|
|
71
132
|
/**
|
|
72
133
|
* Adds a GET route.
|
|
73
134
|
* @param path Route path (e.g., `/items/:id`)
|
|
74
|
-
* @param
|
|
135
|
+
* @param handlers Middleware(s) followed by a request handler function.
|
|
75
136
|
*/
|
|
76
|
-
addGet(path: string,
|
|
137
|
+
addGet(path: string, ...handlers: any[]): this;
|
|
77
138
|
/**
|
|
78
139
|
* Adds a POST route.
|
|
79
140
|
* @param path Route path.
|
|
80
|
-
* @param
|
|
141
|
+
* @param handlers Middleware(s) followed by a request handler function.
|
|
81
142
|
*/
|
|
82
|
-
addPost(path: string,
|
|
143
|
+
addPost(path: string, ...handlers: any[]): this;
|
|
83
144
|
/**
|
|
84
145
|
* Adds a PUT route.
|
|
85
146
|
* @param path Route path.
|
|
86
|
-
* @param
|
|
147
|
+
* @param handlers Middleware(s) followed by a request handler function.
|
|
87
148
|
*/
|
|
88
|
-
addPut(path: string,
|
|
149
|
+
addPut(path: string, ...handlers: any[]): this;
|
|
89
150
|
/**
|
|
90
151
|
* Adds a DELETE route.
|
|
91
152
|
* @param path Route path.
|
|
92
|
-
* @param
|
|
153
|
+
* @param handlers Middleware(s) followed by a request handler function.
|
|
93
154
|
*/
|
|
94
|
-
addDelete(path: string,
|
|
155
|
+
addDelete(path: string, ...handlers: any[]): this;
|
|
95
156
|
/**
|
|
96
157
|
* Adds a PATCH route.
|
|
97
158
|
* @param path Route path.
|
|
98
|
-
* @param
|
|
159
|
+
* @param handlers Middleware(s) followed by a request handler function.
|
|
99
160
|
*/
|
|
100
|
-
addPatch(path: string,
|
|
161
|
+
addPatch(path: string, ...handlers: any[]): this;
|
|
101
162
|
/**
|
|
102
163
|
* Internal helper to add a route with any HTTP method.
|
|
103
164
|
*
|
|
104
165
|
* @param method The HTTP method.
|
|
105
166
|
* @param subPath Path segment (can be relative or absolute).
|
|
106
|
-
* @param
|
|
167
|
+
* @param args Middleware(s) and handler.
|
|
107
168
|
* @returns The current builder instance (for chaining).
|
|
108
169
|
*/
|
|
109
170
|
private add;
|
|
@@ -115,5 +176,27 @@ export declare class RouterBuilder {
|
|
|
115
176
|
*
|
|
116
177
|
* @returns A fully resolved Astro route handler.
|
|
117
178
|
*/
|
|
118
|
-
build():
|
|
179
|
+
build(): APIRoute;
|
|
119
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* A convenience helper to create a router.
|
|
183
|
+
*
|
|
184
|
+
* If modules are provided (e.g. from Vite's `import.meta.glob`), they will be registered.
|
|
185
|
+
* If no modules are provided, it will automatically include all routes that were
|
|
186
|
+
* registered via the auto-registration flags (`defineRoute(..., true)`).
|
|
187
|
+
*
|
|
188
|
+
* @example Auto-discovery via glob:
|
|
189
|
+
* ```ts
|
|
190
|
+
* export const ALL = createRouter(import.meta.glob('./**\/*.ts', { eager: true }));
|
|
191
|
+
* ```
|
|
192
|
+
*
|
|
193
|
+
* @example Auto-registration via global registry:
|
|
194
|
+
* ```ts
|
|
195
|
+
* export const ALL = createRouter();
|
|
196
|
+
* ```
|
|
197
|
+
*
|
|
198
|
+
* @param modulesOrOptions Either modules to register or router options.
|
|
199
|
+
* @param options Router options (if first arg is modules).
|
|
200
|
+
* @returns An Astro-compatible route handler.
|
|
201
|
+
*/
|
|
202
|
+
export declare function createRouter(modulesOrOptions?: Record<string, any> | RouterOptions, options?: RouterOptions): APIRoute;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { defineRoute } from './defineRoute';
|
|
1
|
+
import { defineRoute, isRoute } from './defineRoute';
|
|
2
2
|
import { defineRouter } from './defineRouter';
|
|
3
|
+
import { defineGroup } from './defineGroup';
|
|
3
4
|
import { HttpMethod } from './HttpMethod';
|
|
5
|
+
import { globalRegistry } from './registry';
|
|
4
6
|
/**
|
|
5
7
|
* A fluent builder for creating and composing API routes in Astro.
|
|
6
8
|
*
|
|
@@ -26,14 +28,156 @@ import { HttpMethod } from './HttpMethod';
|
|
|
26
28
|
*
|
|
27
29
|
* export const GET = router.build();
|
|
28
30
|
* ```
|
|
31
|
+
*
|
|
32
|
+
* @example Auto-discovering routes via Vite glob:
|
|
33
|
+
* ```ts
|
|
34
|
+
* const router = new RouterBuilder()
|
|
35
|
+
* .addModules(import.meta.glob('./**\/*.routes.ts', { eager: true }));
|
|
36
|
+
*
|
|
37
|
+
* export const ALL = router.build();
|
|
38
|
+
* ```
|
|
29
39
|
*/
|
|
30
40
|
export class RouterBuilder {
|
|
31
41
|
constructor(options) {
|
|
32
42
|
this._routes = [];
|
|
43
|
+
this._groups = [];
|
|
44
|
+
this._middlewares = [];
|
|
45
|
+
this._shouldLog = false;
|
|
33
46
|
this._options = {
|
|
34
47
|
basePath: 'api',
|
|
35
48
|
...options,
|
|
36
49
|
};
|
|
50
|
+
if ((typeof process !== 'undefined' && process.env?.NODE_ENV === 'development') ||
|
|
51
|
+
import.meta.env?.DEV) {
|
|
52
|
+
this._shouldLog = true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Adds a global middleware to all routes registered in this builder.
|
|
57
|
+
*
|
|
58
|
+
* @param middleware - The middleware function.
|
|
59
|
+
* @returns The current builder instance.
|
|
60
|
+
*/
|
|
61
|
+
use(middleware) {
|
|
62
|
+
this._middlewares.push(middleware);
|
|
63
|
+
return this;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Creates and adds a new route group to the builder.
|
|
67
|
+
*
|
|
68
|
+
* @param basePath - The base path for the group.
|
|
69
|
+
* @param configure - Optional callback to configure the group.
|
|
70
|
+
* @returns The created RouteGroup instance.
|
|
71
|
+
*/
|
|
72
|
+
group(basePath, configure) {
|
|
73
|
+
const group = defineGroup(basePath, configure);
|
|
74
|
+
this.addGroup(group);
|
|
75
|
+
return group;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Adds all routes and groups that have been auto-registered via `defineRoute(..., true)`
|
|
79
|
+
* or `defineGroup(..., true)`.
|
|
80
|
+
*
|
|
81
|
+
* @returns The current builder instance.
|
|
82
|
+
*/
|
|
83
|
+
addRegistered() {
|
|
84
|
+
const items = globalRegistry.getItems();
|
|
85
|
+
const lastRouteIndex = new Map();
|
|
86
|
+
const routesByKey = new Map();
|
|
87
|
+
const lastGroupIndex = new Map();
|
|
88
|
+
const groupsByKey = new Map();
|
|
89
|
+
items.forEach((item, index) => {
|
|
90
|
+
if (item && typeof item === 'object' && item._routifyType === 'group') {
|
|
91
|
+
const key = item.getBasePath();
|
|
92
|
+
lastGroupIndex.set(key, index);
|
|
93
|
+
groupsByKey.set(key, item);
|
|
94
|
+
}
|
|
95
|
+
else if (isRoute(item)) {
|
|
96
|
+
const key = `${item.method}:${item.path}`;
|
|
97
|
+
lastRouteIndex.set(key, index);
|
|
98
|
+
routesByKey.set(key, item);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
items.forEach((item, index) => {
|
|
102
|
+
if (item && typeof item === 'object' && item._routifyType === 'group') {
|
|
103
|
+
const key = item.getBasePath();
|
|
104
|
+
if (lastGroupIndex.get(key) === index)
|
|
105
|
+
this.addGroup(groupsByKey.get(key));
|
|
106
|
+
}
|
|
107
|
+
else if (isRoute(item)) {
|
|
108
|
+
const key = `${item.method}:${item.path}`;
|
|
109
|
+
if (lastRouteIndex.get(key) === index)
|
|
110
|
+
this.addRoute(routesByKey.get(key));
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
return this;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Bulk registers routes and groups from a module collection.
|
|
117
|
+
* Ideal for use with Vite's `import.meta.glob` (with `{ eager: true }`).
|
|
118
|
+
*
|
|
119
|
+
* It will search for both default and named exports that are either `Route` or `RouteGroup`.
|
|
120
|
+
*
|
|
121
|
+
* @param modules A record of modules (e.g. from `import.meta.glob`).
|
|
122
|
+
* @returns The current builder instance.
|
|
123
|
+
*/
|
|
124
|
+
addModules(modules) {
|
|
125
|
+
Object.keys(modules).sort().forEach((key) => {
|
|
126
|
+
const m = modules[key];
|
|
127
|
+
if (m && typeof m === 'object' && m._routifyType === 'group') {
|
|
128
|
+
this.addGroup(m);
|
|
129
|
+
}
|
|
130
|
+
else if (isRoute(m)) {
|
|
131
|
+
this.addRoute(m);
|
|
132
|
+
}
|
|
133
|
+
else if (typeof m === 'object' && m !== null) {
|
|
134
|
+
Object.values(m).forEach((val) => {
|
|
135
|
+
if (val && typeof val === 'object' && val._routifyType === 'group') {
|
|
136
|
+
this.addGroup(val);
|
|
137
|
+
}
|
|
138
|
+
else if (isRoute(val)) {
|
|
139
|
+
this.addRoute(val);
|
|
140
|
+
}
|
|
141
|
+
else if (Array.isArray(val)) {
|
|
142
|
+
val.forEach((item) => {
|
|
143
|
+
if (isRoute(item)) {
|
|
144
|
+
this.addRoute(item);
|
|
145
|
+
}
|
|
146
|
+
else if (item && typeof item === 'object' && item._routifyType === 'group') {
|
|
147
|
+
this.addGroup(item);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
return this;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Prints all registered routes to the console.
|
|
158
|
+
* Useful for debugging during development.
|
|
159
|
+
*
|
|
160
|
+
* @returns The current builder instance.
|
|
161
|
+
*/
|
|
162
|
+
logRoutes() {
|
|
163
|
+
console.log(`\x1b[36m[RouterBuilder]\x1b[0m Registered routes:`);
|
|
164
|
+
const limit = 30;
|
|
165
|
+
this._routes.slice(0, limit).forEach((r) => {
|
|
166
|
+
console.log(` \x1b[32m${r.method.padEnd(7)}\x1b[0m ${r.path}`);
|
|
167
|
+
});
|
|
168
|
+
if (this._routes.length > limit) {
|
|
169
|
+
console.log(` ... and ${this._routes.length - limit} more`);
|
|
170
|
+
}
|
|
171
|
+
return this;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Disables the automatic logging of routes that happens in development mode.
|
|
175
|
+
*
|
|
176
|
+
* @returns The current builder instance.
|
|
177
|
+
*/
|
|
178
|
+
disableLogging() {
|
|
179
|
+
this._shouldLog = false;
|
|
180
|
+
return this;
|
|
37
181
|
}
|
|
38
182
|
/**
|
|
39
183
|
* @deprecated Prefer `addGroup()` or `addRoute()` for structured routing.
|
|
@@ -73,60 +217,72 @@ export class RouterBuilder {
|
|
|
73
217
|
* @returns The current builder instance (for chaining).
|
|
74
218
|
*/
|
|
75
219
|
addGroup(group) {
|
|
76
|
-
this.
|
|
220
|
+
this._groups.push(group);
|
|
77
221
|
return this;
|
|
78
222
|
}
|
|
79
223
|
/**
|
|
80
224
|
* Adds a GET route.
|
|
81
225
|
* @param path Route path (e.g., `/items/:id`)
|
|
82
|
-
* @param
|
|
226
|
+
* @param handlers Middleware(s) followed by a request handler function.
|
|
83
227
|
*/
|
|
84
|
-
addGet(path,
|
|
85
|
-
return this.add(HttpMethod.GET, path,
|
|
228
|
+
addGet(path, ...handlers) {
|
|
229
|
+
return this.add(HttpMethod.GET, path, ...handlers);
|
|
86
230
|
}
|
|
87
231
|
/**
|
|
88
232
|
* Adds a POST route.
|
|
89
233
|
* @param path Route path.
|
|
90
|
-
* @param
|
|
234
|
+
* @param handlers Middleware(s) followed by a request handler function.
|
|
91
235
|
*/
|
|
92
|
-
addPost(path,
|
|
93
|
-
return this.add(HttpMethod.POST, path,
|
|
236
|
+
addPost(path, ...handlers) {
|
|
237
|
+
return this.add(HttpMethod.POST, path, ...handlers);
|
|
94
238
|
}
|
|
95
239
|
/**
|
|
96
240
|
* Adds a PUT route.
|
|
97
241
|
* @param path Route path.
|
|
98
|
-
* @param
|
|
242
|
+
* @param handlers Middleware(s) followed by a request handler function.
|
|
99
243
|
*/
|
|
100
|
-
addPut(path,
|
|
101
|
-
return this.add(HttpMethod.PUT, path,
|
|
244
|
+
addPut(path, ...handlers) {
|
|
245
|
+
return this.add(HttpMethod.PUT, path, ...handlers);
|
|
102
246
|
}
|
|
103
247
|
/**
|
|
104
248
|
* Adds a DELETE route.
|
|
105
249
|
* @param path Route path.
|
|
106
|
-
* @param
|
|
250
|
+
* @param handlers Middleware(s) followed by a request handler function.
|
|
107
251
|
*/
|
|
108
|
-
addDelete(path,
|
|
109
|
-
return this.add(HttpMethod.DELETE, path,
|
|
252
|
+
addDelete(path, ...handlers) {
|
|
253
|
+
return this.add(HttpMethod.DELETE, path, ...handlers);
|
|
110
254
|
}
|
|
111
255
|
/**
|
|
112
256
|
* Adds a PATCH route.
|
|
113
257
|
* @param path Route path.
|
|
114
|
-
* @param
|
|
258
|
+
* @param handlers Middleware(s) followed by a request handler function.
|
|
115
259
|
*/
|
|
116
|
-
addPatch(path,
|
|
117
|
-
return this.add(HttpMethod.PATCH, path,
|
|
260
|
+
addPatch(path, ...handlers) {
|
|
261
|
+
return this.add(HttpMethod.PATCH, path, ...handlers);
|
|
118
262
|
}
|
|
119
263
|
/**
|
|
120
264
|
* Internal helper to add a route with any HTTP method.
|
|
121
265
|
*
|
|
122
266
|
* @param method The HTTP method.
|
|
123
267
|
* @param subPath Path segment (can be relative or absolute).
|
|
124
|
-
* @param
|
|
268
|
+
* @param args Middleware(s) and handler.
|
|
125
269
|
* @returns The current builder instance (for chaining).
|
|
126
270
|
*/
|
|
127
|
-
add(method, subPath,
|
|
271
|
+
add(method, subPath, ...args) {
|
|
272
|
+
let metadata;
|
|
273
|
+
if (args.length > 1 && typeof args[args.length - 1] === 'object' && typeof args[args.length - 2] === 'function') {
|
|
274
|
+
metadata = args.pop();
|
|
275
|
+
}
|
|
276
|
+
const handler = args.pop();
|
|
277
|
+
const middlewares = args;
|
|
128
278
|
const normalizedPath = subPath.startsWith('/') ? subPath : `/${subPath}`;
|
|
129
|
-
this._routes.push(defineRoute(
|
|
279
|
+
this._routes.push(defineRoute({
|
|
280
|
+
method,
|
|
281
|
+
path: normalizedPath,
|
|
282
|
+
handler,
|
|
283
|
+
middlewares,
|
|
284
|
+
metadata
|
|
285
|
+
}));
|
|
130
286
|
return this;
|
|
131
287
|
}
|
|
132
288
|
/**
|
|
@@ -138,7 +294,73 @@ export class RouterBuilder {
|
|
|
138
294
|
* @returns A fully resolved Astro route handler.
|
|
139
295
|
*/
|
|
140
296
|
build() {
|
|
141
|
-
|
|
297
|
+
if (this._shouldLog) {
|
|
298
|
+
this.logRoutes();
|
|
299
|
+
}
|
|
300
|
+
// Collect all routes from builder and groups
|
|
301
|
+
const allRoutes = [...this._routes];
|
|
302
|
+
for (const group of this._groups) {
|
|
303
|
+
allRoutes.push(...group.getRoutes());
|
|
304
|
+
}
|
|
305
|
+
// Detect duplicates in production (warnings)
|
|
306
|
+
if (!this._shouldLog) {
|
|
307
|
+
const seen = new Set();
|
|
308
|
+
for (const route of allRoutes) {
|
|
309
|
+
const key = `${route.method}:${route.path}`;
|
|
310
|
+
if (seen.has(key)) {
|
|
311
|
+
console.warn(`[RouterBuilder] Duplicate route detected: ${key}. The last one will be used.`);
|
|
312
|
+
}
|
|
313
|
+
seen.add(key);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
// Apply global middlewares to all routes before building
|
|
317
|
+
const finalRoutes = allRoutes.map(route => ({
|
|
318
|
+
...route,
|
|
319
|
+
middlewares: [...this._middlewares, ...(route.middlewares || [])]
|
|
320
|
+
}));
|
|
321
|
+
return defineRouter(finalRoutes, this._options);
|
|
142
322
|
}
|
|
143
323
|
}
|
|
144
324
|
RouterBuilder._registerWarned = false;
|
|
325
|
+
/**
|
|
326
|
+
* A global RouterBuilder instance for easy, centralized route registration.
|
|
327
|
+
*/
|
|
328
|
+
RouterBuilder.global = new RouterBuilder();
|
|
329
|
+
/**
|
|
330
|
+
* A convenience helper to create a router.
|
|
331
|
+
*
|
|
332
|
+
* If modules are provided (e.g. from Vite's `import.meta.glob`), they will be registered.
|
|
333
|
+
* If no modules are provided, it will automatically include all routes that were
|
|
334
|
+
* registered via the auto-registration flags (`defineRoute(..., true)`).
|
|
335
|
+
*
|
|
336
|
+
* @example Auto-discovery via glob:
|
|
337
|
+
* ```ts
|
|
338
|
+
* export const ALL = createRouter(import.meta.glob('./**\/*.ts', { eager: true }));
|
|
339
|
+
* ```
|
|
340
|
+
*
|
|
341
|
+
* @example Auto-registration via global registry:
|
|
342
|
+
* ```ts
|
|
343
|
+
* export const ALL = createRouter();
|
|
344
|
+
* ```
|
|
345
|
+
*
|
|
346
|
+
* @param modulesOrOptions Either modules to register or router options.
|
|
347
|
+
* @param options Router options (if first arg is modules).
|
|
348
|
+
* @returns An Astro-compatible route handler.
|
|
349
|
+
*/
|
|
350
|
+
export function createRouter(modulesOrOptions, options) {
|
|
351
|
+
let modules;
|
|
352
|
+
let finalOptions;
|
|
353
|
+
if (modulesOrOptions && !('basePath' in modulesOrOptions || 'onNotFound' in modulesOrOptions || 'debug' in modulesOrOptions)) {
|
|
354
|
+
modules = modulesOrOptions;
|
|
355
|
+
finalOptions = options;
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
finalOptions = modulesOrOptions;
|
|
359
|
+
}
|
|
360
|
+
const builder = new RouterBuilder(finalOptions);
|
|
361
|
+
if (modules) {
|
|
362
|
+
builder.addModules(modules);
|
|
363
|
+
}
|
|
364
|
+
builder.addRegistered();
|
|
365
|
+
return builder.build();
|
|
366
|
+
}
|