@b9g/router 0.1.5 → 0.1.6
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 +130 -73
- package/package.json +13 -10
- package/src/index.d.ts +183 -5
- package/src/index.js +646 -1
- package/src/router.d.ts +0 -167
- package/src/router.js +0 -504
package/src/router.d.ts
DELETED
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
import type { Handler, Middleware, RouteEntry, MiddlewareEntry, HttpMethod, RouterOptions, RouteConfig, RouteCacheConfig } from "./_types.js";
|
|
2
|
-
/**
|
|
3
|
-
* RouteBuilder provides a chainable API for defining routes with multiple HTTP methods
|
|
4
|
-
*
|
|
5
|
-
* Example:
|
|
6
|
-
* router.route('/api/users/:id')
|
|
7
|
-
* .get(getUserHandler)
|
|
8
|
-
* .put(updateUserHandler)
|
|
9
|
-
* .delete(deleteUserHandler);
|
|
10
|
-
*/
|
|
11
|
-
declare class RouteBuilder {
|
|
12
|
-
private router;
|
|
13
|
-
private pattern;
|
|
14
|
-
private cacheConfig?;
|
|
15
|
-
constructor(router: Router, pattern: string, cacheConfig?: RouteCacheConfig);
|
|
16
|
-
/**
|
|
17
|
-
* Register a GET handler for this route pattern
|
|
18
|
-
*/
|
|
19
|
-
get(handler: Handler): RouteBuilder;
|
|
20
|
-
/**
|
|
21
|
-
* Register a POST handler for this route pattern
|
|
22
|
-
*/
|
|
23
|
-
post(handler: Handler): RouteBuilder;
|
|
24
|
-
/**
|
|
25
|
-
* Register a PUT handler for this route pattern
|
|
26
|
-
*/
|
|
27
|
-
put(handler: Handler): RouteBuilder;
|
|
28
|
-
/**
|
|
29
|
-
* Register a DELETE handler for this route pattern
|
|
30
|
-
*/
|
|
31
|
-
delete(handler: Handler): RouteBuilder;
|
|
32
|
-
/**
|
|
33
|
-
* Register a PATCH handler for this route pattern
|
|
34
|
-
*/
|
|
35
|
-
patch(handler: Handler): RouteBuilder;
|
|
36
|
-
/**
|
|
37
|
-
* Register a HEAD handler for this route pattern
|
|
38
|
-
*/
|
|
39
|
-
head(handler: Handler): RouteBuilder;
|
|
40
|
-
/**
|
|
41
|
-
* Register an OPTIONS handler for this route pattern
|
|
42
|
-
*/
|
|
43
|
-
options(handler: Handler): RouteBuilder;
|
|
44
|
-
/**
|
|
45
|
-
* Register a handler for all HTTP methods on this route pattern
|
|
46
|
-
*/
|
|
47
|
-
all(handler: Handler): RouteBuilder;
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Router provides Request/Response routing with middleware support
|
|
51
|
-
* Designed to work universally across all JavaScript runtimes
|
|
52
|
-
*/
|
|
53
|
-
export declare class Router {
|
|
54
|
-
private routes;
|
|
55
|
-
private middlewares;
|
|
56
|
-
private executor;
|
|
57
|
-
private dirty;
|
|
58
|
-
private caches?;
|
|
59
|
-
constructor(options?: RouterOptions);
|
|
60
|
-
/**
|
|
61
|
-
* Register middleware that applies to all routes
|
|
62
|
-
* Middleware executes in the order it was registered
|
|
63
|
-
*/
|
|
64
|
-
use(middleware: Middleware): void;
|
|
65
|
-
/**
|
|
66
|
-
* Register a handler for a specific pattern
|
|
67
|
-
*/
|
|
68
|
-
use(pattern: string, handler: Handler): void;
|
|
69
|
-
/**
|
|
70
|
-
* Create a route builder for the given pattern
|
|
71
|
-
* Returns a chainable interface for registering HTTP method handlers
|
|
72
|
-
*
|
|
73
|
-
* Example:
|
|
74
|
-
* router.route('/api/users/:id')
|
|
75
|
-
* .get(getUserHandler)
|
|
76
|
-
* .put(updateUserHandler);
|
|
77
|
-
*
|
|
78
|
-
* With cache configuration:
|
|
79
|
-
* router.route({ pattern: '/api/users/:id', cache: { name: 'users' } })
|
|
80
|
-
* .get(getUserHandler);
|
|
81
|
-
*/
|
|
82
|
-
route(pattern: string): RouteBuilder;
|
|
83
|
-
route(config: RouteConfig): RouteBuilder;
|
|
84
|
-
/**
|
|
85
|
-
* Internal method called by RouteBuilder to register routes
|
|
86
|
-
* Public for RouteBuilder access, but not intended for direct use
|
|
87
|
-
*/
|
|
88
|
-
addRoute(method: HttpMethod, pattern: string, handler: Handler, cache?: RouteCacheConfig): void;
|
|
89
|
-
/**
|
|
90
|
-
* Handle a request - main entrypoint for ServiceWorker usage
|
|
91
|
-
* Returns a response or throws if no route matches
|
|
92
|
-
*/
|
|
93
|
-
handler: (request: Request) => Promise<Response>;
|
|
94
|
-
/**
|
|
95
|
-
* Match a request against registered routes and execute the handler chain
|
|
96
|
-
* Returns the response from the matched handler, or null if no route matches
|
|
97
|
-
* Note: Global middleware executes even if no route matches
|
|
98
|
-
*/
|
|
99
|
-
match(request: Request): Promise<Response | null>;
|
|
100
|
-
/**
|
|
101
|
-
* Build the complete route context including cache access
|
|
102
|
-
*/
|
|
103
|
-
private buildContext;
|
|
104
|
-
/**
|
|
105
|
-
* Get registered routes for debugging/introspection
|
|
106
|
-
*/
|
|
107
|
-
getRoutes(): RouteEntry[];
|
|
108
|
-
/**
|
|
109
|
-
* Get registered middleware for debugging/introspection
|
|
110
|
-
*/
|
|
111
|
-
getMiddlewares(): MiddlewareEntry[];
|
|
112
|
-
/**
|
|
113
|
-
* Mount a subrouter at a specific path prefix
|
|
114
|
-
* All routes from the subrouter will be prefixed with the mount path
|
|
115
|
-
*
|
|
116
|
-
* Example:
|
|
117
|
-
* const apiRouter = new Router();
|
|
118
|
-
* apiRouter.route('/users').get(getUsersHandler);
|
|
119
|
-
* apiRouter.route('/users/:id').get(getUserHandler);
|
|
120
|
-
*
|
|
121
|
-
* const mainRouter = new Router();
|
|
122
|
-
* mainRouter.mount('/api/v1', apiRouter);
|
|
123
|
-
* // Routes become: /api/v1/users, /api/v1/users/:id
|
|
124
|
-
*/
|
|
125
|
-
mount(mountPath: string, subrouter: Router): void;
|
|
126
|
-
/**
|
|
127
|
-
* Normalize mount path: ensure it starts with / and doesn't end with /
|
|
128
|
-
*/
|
|
129
|
-
private normalizeMountPath;
|
|
130
|
-
/**
|
|
131
|
-
* Combine mount path with route pattern
|
|
132
|
-
*/
|
|
133
|
-
private combinePaths;
|
|
134
|
-
/**
|
|
135
|
-
* Validate that a function is valid middleware
|
|
136
|
-
*/
|
|
137
|
-
private isValidMiddleware;
|
|
138
|
-
/**
|
|
139
|
-
* Detect if a function is a generator middleware
|
|
140
|
-
*/
|
|
141
|
-
private isGeneratorMiddleware;
|
|
142
|
-
/**
|
|
143
|
-
* Execute middleware stack with guaranteed execution using Rack-style LIFO order
|
|
144
|
-
*/
|
|
145
|
-
private executeMiddlewareStack;
|
|
146
|
-
/**
|
|
147
|
-
* Handle errors by trying generators in reverse order
|
|
148
|
-
*/
|
|
149
|
-
private handleErrorThroughGenerators;
|
|
150
|
-
/**
|
|
151
|
-
* Create a mutable request wrapper that allows URL modification
|
|
152
|
-
*/
|
|
153
|
-
private createMutableRequest;
|
|
154
|
-
/**
|
|
155
|
-
* Handle automatic redirects when URL is modified
|
|
156
|
-
*/
|
|
157
|
-
private handleAutomaticRedirect;
|
|
158
|
-
/**
|
|
159
|
-
* Get route statistics
|
|
160
|
-
*/
|
|
161
|
-
getStats(): {
|
|
162
|
-
routeCount: number;
|
|
163
|
-
middlewareCount: number;
|
|
164
|
-
compiled: boolean;
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
export {};
|
package/src/router.js
DELETED
|
@@ -1,504 +0,0 @@
|
|
|
1
|
-
/// <reference types="./router.d.ts" />
|
|
2
|
-
// src/router.ts
|
|
3
|
-
import { MatchPattern } from "@b9g/match-pattern";
|
|
4
|
-
var LinearExecutor = class {
|
|
5
|
-
constructor(routes) {
|
|
6
|
-
this.routes = routes;
|
|
7
|
-
}
|
|
8
|
-
/**
|
|
9
|
-
* Find the first route that matches the request
|
|
10
|
-
* Returns null if no route matches
|
|
11
|
-
*/
|
|
12
|
-
match(request) {
|
|
13
|
-
const url = new URL(request.url);
|
|
14
|
-
const method = request.method.toUpperCase();
|
|
15
|
-
for (const route of this.routes) {
|
|
16
|
-
if (route.method !== method) {
|
|
17
|
-
continue;
|
|
18
|
-
}
|
|
19
|
-
if (route.pattern.test(url)) {
|
|
20
|
-
const result = route.pattern.exec(url);
|
|
21
|
-
if (result) {
|
|
22
|
-
return {
|
|
23
|
-
handler: route.handler,
|
|
24
|
-
context: {
|
|
25
|
-
params: result.params
|
|
26
|
-
},
|
|
27
|
-
cacheConfig: route.cache
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
var RouteBuilder = class {
|
|
36
|
-
constructor(router, pattern, cacheConfig) {
|
|
37
|
-
this.router = router;
|
|
38
|
-
this.pattern = pattern;
|
|
39
|
-
this.cacheConfig = cacheConfig;
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Register a GET handler for this route pattern
|
|
43
|
-
*/
|
|
44
|
-
get(handler) {
|
|
45
|
-
this.router.addRoute("GET", this.pattern, handler, this.cacheConfig);
|
|
46
|
-
return this;
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Register a POST handler for this route pattern
|
|
50
|
-
*/
|
|
51
|
-
post(handler) {
|
|
52
|
-
this.router.addRoute("POST", this.pattern, handler, this.cacheConfig);
|
|
53
|
-
return this;
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Register a PUT handler for this route pattern
|
|
57
|
-
*/
|
|
58
|
-
put(handler) {
|
|
59
|
-
this.router.addRoute("PUT", this.pattern, handler, this.cacheConfig);
|
|
60
|
-
return this;
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Register a DELETE handler for this route pattern
|
|
64
|
-
*/
|
|
65
|
-
delete(handler) {
|
|
66
|
-
this.router.addRoute("DELETE", this.pattern, handler, this.cacheConfig);
|
|
67
|
-
return this;
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Register a PATCH handler for this route pattern
|
|
71
|
-
*/
|
|
72
|
-
patch(handler) {
|
|
73
|
-
this.router.addRoute("PATCH", this.pattern, handler, this.cacheConfig);
|
|
74
|
-
return this;
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Register a HEAD handler for this route pattern
|
|
78
|
-
*/
|
|
79
|
-
head(handler) {
|
|
80
|
-
this.router.addRoute("HEAD", this.pattern, handler, this.cacheConfig);
|
|
81
|
-
return this;
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Register an OPTIONS handler for this route pattern
|
|
85
|
-
*/
|
|
86
|
-
options(handler) {
|
|
87
|
-
this.router.addRoute("OPTIONS", this.pattern, handler, this.cacheConfig);
|
|
88
|
-
return this;
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* Register a handler for all HTTP methods on this route pattern
|
|
92
|
-
*/
|
|
93
|
-
all(handler) {
|
|
94
|
-
const methods = [
|
|
95
|
-
"GET",
|
|
96
|
-
"POST",
|
|
97
|
-
"PUT",
|
|
98
|
-
"DELETE",
|
|
99
|
-
"PATCH",
|
|
100
|
-
"HEAD",
|
|
101
|
-
"OPTIONS"
|
|
102
|
-
];
|
|
103
|
-
methods.forEach((method) => {
|
|
104
|
-
this.router.addRoute(method, this.pattern, handler, this.cacheConfig);
|
|
105
|
-
});
|
|
106
|
-
return this;
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
var Router = class {
|
|
110
|
-
routes = [];
|
|
111
|
-
middlewares = [];
|
|
112
|
-
executor = null;
|
|
113
|
-
dirty = false;
|
|
114
|
-
caches;
|
|
115
|
-
constructor(options) {
|
|
116
|
-
this.caches = options?.caches;
|
|
117
|
-
}
|
|
118
|
-
use(patternOrMiddleware, handler) {
|
|
119
|
-
if (typeof patternOrMiddleware === "string" && handler) {
|
|
120
|
-
this.addRoute("GET", patternOrMiddleware, handler);
|
|
121
|
-
this.addRoute("POST", patternOrMiddleware, handler);
|
|
122
|
-
this.addRoute("PUT", patternOrMiddleware, handler);
|
|
123
|
-
this.addRoute("DELETE", patternOrMiddleware, handler);
|
|
124
|
-
this.addRoute("PATCH", patternOrMiddleware, handler);
|
|
125
|
-
this.addRoute("HEAD", patternOrMiddleware, handler);
|
|
126
|
-
this.addRoute("OPTIONS", patternOrMiddleware, handler);
|
|
127
|
-
} else if (typeof patternOrMiddleware === "function") {
|
|
128
|
-
if (!this.isValidMiddleware(patternOrMiddleware)) {
|
|
129
|
-
throw new Error(
|
|
130
|
-
"Invalid middleware type. Must be function or async generator function."
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
this.middlewares.push({ middleware: patternOrMiddleware });
|
|
134
|
-
this.dirty = true;
|
|
135
|
-
} else {
|
|
136
|
-
throw new Error(
|
|
137
|
-
"Invalid middleware type. Must be function or async generator function."
|
|
138
|
-
);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
route(patternOrConfig) {
|
|
142
|
-
if (typeof patternOrConfig === "string") {
|
|
143
|
-
return new RouteBuilder(this, patternOrConfig);
|
|
144
|
-
} else {
|
|
145
|
-
return new RouteBuilder(
|
|
146
|
-
this,
|
|
147
|
-
patternOrConfig.pattern,
|
|
148
|
-
patternOrConfig.cache
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
/**
|
|
153
|
-
* Internal method called by RouteBuilder to register routes
|
|
154
|
-
* Public for RouteBuilder access, but not intended for direct use
|
|
155
|
-
*/
|
|
156
|
-
addRoute(method, pattern, handler, cache) {
|
|
157
|
-
const matchPattern = new MatchPattern(pattern);
|
|
158
|
-
this.routes.push({
|
|
159
|
-
pattern: matchPattern,
|
|
160
|
-
method: method.toUpperCase(),
|
|
161
|
-
handler,
|
|
162
|
-
cache
|
|
163
|
-
});
|
|
164
|
-
this.dirty = true;
|
|
165
|
-
}
|
|
166
|
-
/**
|
|
167
|
-
* Handle a request - main entrypoint for ServiceWorker usage
|
|
168
|
-
* Returns a response or throws if no route matches
|
|
169
|
-
*/
|
|
170
|
-
handler = async (request) => {
|
|
171
|
-
if (this.dirty || !this.executor) {
|
|
172
|
-
this.executor = new LinearExecutor(this.routes);
|
|
173
|
-
this.dirty = false;
|
|
174
|
-
}
|
|
175
|
-
const matchResult = this.executor.match(request);
|
|
176
|
-
if (matchResult) {
|
|
177
|
-
const context = await this.buildContext(
|
|
178
|
-
matchResult.context,
|
|
179
|
-
matchResult.cacheConfig
|
|
180
|
-
);
|
|
181
|
-
const mutableRequest = this.createMutableRequest(request);
|
|
182
|
-
return this.executeMiddlewareStack(
|
|
183
|
-
this.middlewares,
|
|
184
|
-
mutableRequest,
|
|
185
|
-
context,
|
|
186
|
-
matchResult.handler,
|
|
187
|
-
request.url,
|
|
188
|
-
this.executor
|
|
189
|
-
);
|
|
190
|
-
} else {
|
|
191
|
-
const notFoundHandler = async () => {
|
|
192
|
-
return new Response("Not Found", { status: 404 });
|
|
193
|
-
};
|
|
194
|
-
const mutableRequest = this.createMutableRequest(request);
|
|
195
|
-
return this.executeMiddlewareStack(
|
|
196
|
-
this.middlewares,
|
|
197
|
-
mutableRequest,
|
|
198
|
-
{ params: {} },
|
|
199
|
-
notFoundHandler,
|
|
200
|
-
request.url,
|
|
201
|
-
this.executor
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
};
|
|
205
|
-
/**
|
|
206
|
-
* Match a request against registered routes and execute the handler chain
|
|
207
|
-
* Returns the response from the matched handler, or null if no route matches
|
|
208
|
-
* Note: Global middleware executes even if no route matches
|
|
209
|
-
*/
|
|
210
|
-
async match(request) {
|
|
211
|
-
if (this.dirty || !this.executor) {
|
|
212
|
-
this.executor = new LinearExecutor(this.routes);
|
|
213
|
-
this.dirty = false;
|
|
214
|
-
}
|
|
215
|
-
const mutableRequest = this.createMutableRequest(request);
|
|
216
|
-
const originalUrl = mutableRequest.url;
|
|
217
|
-
let matchResult = this.executor.match(request);
|
|
218
|
-
let handler;
|
|
219
|
-
let context;
|
|
220
|
-
if (matchResult) {
|
|
221
|
-
handler = matchResult.handler;
|
|
222
|
-
context = await this.buildContext(
|
|
223
|
-
matchResult.context,
|
|
224
|
-
matchResult.cacheConfig
|
|
225
|
-
);
|
|
226
|
-
} else {
|
|
227
|
-
handler = async () => new Response("Not Found", { status: 404 });
|
|
228
|
-
context = { params: {} };
|
|
229
|
-
}
|
|
230
|
-
const response = await this.executeMiddlewareStack(
|
|
231
|
-
this.middlewares,
|
|
232
|
-
mutableRequest,
|
|
233
|
-
context,
|
|
234
|
-
handler,
|
|
235
|
-
originalUrl,
|
|
236
|
-
this.executor
|
|
237
|
-
// Pass executor for re-routing
|
|
238
|
-
);
|
|
239
|
-
if (!matchResult && response?.status === 404) {
|
|
240
|
-
return null;
|
|
241
|
-
}
|
|
242
|
-
return response;
|
|
243
|
-
}
|
|
244
|
-
/**
|
|
245
|
-
* Build the complete route context including cache access
|
|
246
|
-
*/
|
|
247
|
-
async buildContext(baseContext, cacheConfig) {
|
|
248
|
-
const context = { ...baseContext };
|
|
249
|
-
if (this.caches) {
|
|
250
|
-
context.caches = this.caches;
|
|
251
|
-
if (cacheConfig?.name) {
|
|
252
|
-
try {
|
|
253
|
-
context.cache = await this.caches.open(cacheConfig.name);
|
|
254
|
-
} catch (error) {
|
|
255
|
-
console.warn(`Failed to open cache '${cacheConfig.name}':`, error);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
return context;
|
|
260
|
-
}
|
|
261
|
-
/**
|
|
262
|
-
* Get registered routes for debugging/introspection
|
|
263
|
-
*/
|
|
264
|
-
getRoutes() {
|
|
265
|
-
return [...this.routes];
|
|
266
|
-
}
|
|
267
|
-
/**
|
|
268
|
-
* Get registered middleware for debugging/introspection
|
|
269
|
-
*/
|
|
270
|
-
getMiddlewares() {
|
|
271
|
-
return [...this.middlewares];
|
|
272
|
-
}
|
|
273
|
-
/**
|
|
274
|
-
* Mount a subrouter at a specific path prefix
|
|
275
|
-
* All routes from the subrouter will be prefixed with the mount path
|
|
276
|
-
*
|
|
277
|
-
* Example:
|
|
278
|
-
* const apiRouter = new Router();
|
|
279
|
-
* apiRouter.route('/users').get(getUsersHandler);
|
|
280
|
-
* apiRouter.route('/users/:id').get(getUserHandler);
|
|
281
|
-
*
|
|
282
|
-
* const mainRouter = new Router();
|
|
283
|
-
* mainRouter.mount('/api/v1', apiRouter);
|
|
284
|
-
* // Routes become: /api/v1/users, /api/v1/users/:id
|
|
285
|
-
*/
|
|
286
|
-
mount(mountPath, subrouter) {
|
|
287
|
-
const normalizedMountPath = this.normalizeMountPath(mountPath);
|
|
288
|
-
const subroutes = subrouter.getRoutes();
|
|
289
|
-
for (const subroute of subroutes) {
|
|
290
|
-
const mountedPattern = this.combinePaths(
|
|
291
|
-
normalizedMountPath,
|
|
292
|
-
subroute.pattern.pathname
|
|
293
|
-
);
|
|
294
|
-
this.routes.push({
|
|
295
|
-
pattern: new MatchPattern(mountedPattern),
|
|
296
|
-
method: subroute.method,
|
|
297
|
-
handler: subroute.handler,
|
|
298
|
-
cache: subroute.cache
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
const submiddlewares = subrouter.getMiddlewares();
|
|
302
|
-
for (const submiddleware of submiddlewares) {
|
|
303
|
-
this.middlewares.push(submiddleware);
|
|
304
|
-
}
|
|
305
|
-
this.dirty = true;
|
|
306
|
-
}
|
|
307
|
-
/**
|
|
308
|
-
* Normalize mount path: ensure it starts with / and doesn't end with /
|
|
309
|
-
*/
|
|
310
|
-
normalizeMountPath(mountPath) {
|
|
311
|
-
if (!mountPath.startsWith("/")) {
|
|
312
|
-
mountPath = "/" + mountPath;
|
|
313
|
-
}
|
|
314
|
-
if (mountPath.endsWith("/") && mountPath.length > 1) {
|
|
315
|
-
mountPath = mountPath.slice(0, -1);
|
|
316
|
-
}
|
|
317
|
-
return mountPath;
|
|
318
|
-
}
|
|
319
|
-
/**
|
|
320
|
-
* Combine mount path with route pattern
|
|
321
|
-
*/
|
|
322
|
-
combinePaths(mountPath, routePattern) {
|
|
323
|
-
if (routePattern === "/") {
|
|
324
|
-
return mountPath;
|
|
325
|
-
}
|
|
326
|
-
if (!routePattern.startsWith("/")) {
|
|
327
|
-
routePattern = "/" + routePattern;
|
|
328
|
-
}
|
|
329
|
-
return mountPath + routePattern;
|
|
330
|
-
}
|
|
331
|
-
/**
|
|
332
|
-
* Validate that a function is valid middleware
|
|
333
|
-
*/
|
|
334
|
-
isValidMiddleware(middleware) {
|
|
335
|
-
const constructorName = middleware.constructor.name;
|
|
336
|
-
return constructorName === "AsyncGeneratorFunction" || constructorName === "AsyncFunction" || constructorName === "Function";
|
|
337
|
-
}
|
|
338
|
-
/**
|
|
339
|
-
* Detect if a function is a generator middleware
|
|
340
|
-
*/
|
|
341
|
-
isGeneratorMiddleware(middleware) {
|
|
342
|
-
return middleware.constructor.name === "AsyncGeneratorFunction";
|
|
343
|
-
}
|
|
344
|
-
/**
|
|
345
|
-
* Execute middleware stack with guaranteed execution using Rack-style LIFO order
|
|
346
|
-
*/
|
|
347
|
-
async executeMiddlewareStack(middlewares, request, context, handler, originalUrl, executor) {
|
|
348
|
-
const runningGenerators = [];
|
|
349
|
-
let currentResponse = null;
|
|
350
|
-
for (let i = 0; i < middlewares.length; i++) {
|
|
351
|
-
const middleware = middlewares[i].middleware;
|
|
352
|
-
if (this.isGeneratorMiddleware(middleware)) {
|
|
353
|
-
const generator = middleware(request, context);
|
|
354
|
-
const result = await generator.next();
|
|
355
|
-
if (result.done) {
|
|
356
|
-
if (result.value) {
|
|
357
|
-
currentResponse = result.value;
|
|
358
|
-
break;
|
|
359
|
-
}
|
|
360
|
-
} else {
|
|
361
|
-
runningGenerators.push({ generator, index: i });
|
|
362
|
-
}
|
|
363
|
-
} else {
|
|
364
|
-
const result = await middleware(request, context);
|
|
365
|
-
if (result) {
|
|
366
|
-
currentResponse = result;
|
|
367
|
-
break;
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
if (!currentResponse) {
|
|
372
|
-
let finalHandler = handler;
|
|
373
|
-
let finalContext = context;
|
|
374
|
-
if (request.url !== originalUrl && executor) {
|
|
375
|
-
const newMatchResult = executor.match(
|
|
376
|
-
new Request(request.url, {
|
|
377
|
-
method: request.method,
|
|
378
|
-
headers: request.headers,
|
|
379
|
-
body: request.body
|
|
380
|
-
})
|
|
381
|
-
);
|
|
382
|
-
if (newMatchResult) {
|
|
383
|
-
finalHandler = newMatchResult.handler;
|
|
384
|
-
finalContext = await this.buildContext(
|
|
385
|
-
newMatchResult.context,
|
|
386
|
-
newMatchResult.cacheConfig || void 0
|
|
387
|
-
);
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
let handlerError = null;
|
|
391
|
-
try {
|
|
392
|
-
currentResponse = await finalHandler(request, finalContext);
|
|
393
|
-
} catch (error) {
|
|
394
|
-
handlerError = error;
|
|
395
|
-
}
|
|
396
|
-
if (handlerError) {
|
|
397
|
-
currentResponse = await this.handleErrorThroughGenerators(
|
|
398
|
-
handlerError,
|
|
399
|
-
runningGenerators
|
|
400
|
-
);
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
if (request.url !== originalUrl && currentResponse) {
|
|
404
|
-
currentResponse = this.handleAutomaticRedirect(
|
|
405
|
-
originalUrl,
|
|
406
|
-
request.url,
|
|
407
|
-
request.method
|
|
408
|
-
);
|
|
409
|
-
}
|
|
410
|
-
for (let i = runningGenerators.length - 1; i >= 0; i--) {
|
|
411
|
-
const { generator } = runningGenerators[i];
|
|
412
|
-
const result = await generator.next(currentResponse);
|
|
413
|
-
if (result.value) {
|
|
414
|
-
currentResponse = result.value;
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
return currentResponse;
|
|
418
|
-
}
|
|
419
|
-
/**
|
|
420
|
-
* Handle errors by trying generators in reverse order
|
|
421
|
-
*/
|
|
422
|
-
async handleErrorThroughGenerators(error, runningGenerators) {
|
|
423
|
-
for (let i = runningGenerators.length - 1; i >= 0; i--) {
|
|
424
|
-
const { generator } = runningGenerators[i];
|
|
425
|
-
try {
|
|
426
|
-
const result = await generator.throw(error);
|
|
427
|
-
if (result.value) {
|
|
428
|
-
runningGenerators.splice(i, 1);
|
|
429
|
-
return result.value;
|
|
430
|
-
}
|
|
431
|
-
} catch (generatorError) {
|
|
432
|
-
runningGenerators.splice(i, 1);
|
|
433
|
-
continue;
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
throw error;
|
|
437
|
-
}
|
|
438
|
-
/**
|
|
439
|
-
* Create a mutable request wrapper that allows URL modification
|
|
440
|
-
*/
|
|
441
|
-
createMutableRequest(request) {
|
|
442
|
-
return {
|
|
443
|
-
url: request.url,
|
|
444
|
-
method: request.method,
|
|
445
|
-
headers: new Headers(request.headers),
|
|
446
|
-
body: request.body,
|
|
447
|
-
bodyUsed: request.bodyUsed,
|
|
448
|
-
cache: request.cache,
|
|
449
|
-
credentials: request.credentials,
|
|
450
|
-
destination: request.destination,
|
|
451
|
-
integrity: request.integrity,
|
|
452
|
-
keepalive: request.keepalive,
|
|
453
|
-
mode: request.mode,
|
|
454
|
-
redirect: request.redirect,
|
|
455
|
-
referrer: request.referrer,
|
|
456
|
-
referrerPolicy: request.referrerPolicy,
|
|
457
|
-
signal: request.signal,
|
|
458
|
-
// Add all other Request methods
|
|
459
|
-
arrayBuffer: () => request.arrayBuffer(),
|
|
460
|
-
blob: () => request.blob(),
|
|
461
|
-
clone: () => request.clone(),
|
|
462
|
-
formData: () => request.formData(),
|
|
463
|
-
json: () => request.json(),
|
|
464
|
-
text: () => request.text()
|
|
465
|
-
};
|
|
466
|
-
}
|
|
467
|
-
/**
|
|
468
|
-
* Handle automatic redirects when URL is modified
|
|
469
|
-
*/
|
|
470
|
-
handleAutomaticRedirect(originalUrl, newUrl, method) {
|
|
471
|
-
const originalURL = new URL(originalUrl);
|
|
472
|
-
const newURL = new URL(newUrl);
|
|
473
|
-
if (originalURL.hostname !== newURL.hostname || originalURL.port !== newURL.port && originalURL.port !== "" && newURL.port !== "") {
|
|
474
|
-
throw new Error(
|
|
475
|
-
`Cross-origin redirect not allowed: ${originalUrl} -> ${newUrl}`
|
|
476
|
-
);
|
|
477
|
-
}
|
|
478
|
-
let status = 302;
|
|
479
|
-
if (originalURL.protocol !== newURL.protocol) {
|
|
480
|
-
status = 301;
|
|
481
|
-
} else if (method.toUpperCase() !== "GET") {
|
|
482
|
-
status = 307;
|
|
483
|
-
}
|
|
484
|
-
return new Response(null, {
|
|
485
|
-
status,
|
|
486
|
-
headers: {
|
|
487
|
-
Location: newUrl
|
|
488
|
-
}
|
|
489
|
-
});
|
|
490
|
-
}
|
|
491
|
-
/**
|
|
492
|
-
* Get route statistics
|
|
493
|
-
*/
|
|
494
|
-
getStats() {
|
|
495
|
-
return {
|
|
496
|
-
routeCount: this.routes.length,
|
|
497
|
-
middlewareCount: this.middlewares.length,
|
|
498
|
-
compiled: !this.dirty && this.executor !== null
|
|
499
|
-
};
|
|
500
|
-
}
|
|
501
|
-
};
|
|
502
|
-
export {
|
|
503
|
-
Router
|
|
504
|
-
};
|