1
- import { RequestStub , RouteHandler } from './types.ts' ;
1
+ import { SlimRequestStub , RequestStub , RouteHandler } from './types.ts' ;
2
2
3
3
/**
4
4
* A step on the path of the router
5
5
*/
6
6
interface RouteStep < Req extends RequestStub = RequestStub > {
7
- /** The route */
7
+ /** The route (pathname) */
8
8
route : string ;
9
9
/** What request method to limit to; empty for USE, as we do not limit for that */
10
- type ?: 'GET' | 'POST' | 'PUT' | 'DELETE' ;
10
+ type ?: 'HEAD' | 'OPTIONS' | ' GET' | 'POST' | 'PUT' | 'DELETE' ;
11
11
/** The route handler (or router) */
12
12
handler : Router < Req > | RouteHandler < Req > ;
13
13
}
@@ -75,7 +75,7 @@ export class Router<Req extends RequestStub = RequestStub> {
75
75
* @param {string } type The route method (GET/POST/PUT/DELETE)
76
76
* @param {Array<RouteHandler> } handlers The handlers to add to our #step array (condensed)
77
77
*/
78
- #add< R extends Req = Req > ( route : string , type : 'GET' | 'POST' | 'PUT' | 'DELETE' , handlers : RouteHandler < R > [ ] ) : void {
78
+ #add< R extends Req = Req > ( route : string , type : 'HEAD' | 'OPTIONS' | ' GET' | 'POST' | 'PUT' | 'DELETE' , handlers : RouteHandler < R > [ ] ) : void {
79
79
80
80
// enforce `/{route}` with no trailing `/`'s
81
81
route = '/' + route . replace ( / ^ \/ + | \/ + $ / g, '' ) ;
@@ -87,6 +87,32 @@ export class Router<Req extends RequestStub = RequestStub> {
87
87
} ) ;
88
88
}
89
89
90
+ /**
91
+ * Add handlers to a HEAD {route} call
92
+ * @param {string } route The route
93
+ * @param {Router | RouteHandler } handler A router or route handler
94
+ * @param {Array<Router | RouteHandler> } handlers Any other route handlers to chain
95
+ * @returns {this } this
96
+ */
97
+ head < R extends Req = Req > ( route : string , handler : RouteHandler < R > , ...handlers : RouteHandler < R > [ ] ) : this {
98
+ handlers . unshift ( handler ) ;
99
+ this . #add( route , 'HEAD' , handlers ) ;
100
+ return this ;
101
+ }
102
+
103
+ /**
104
+ * Add handlers to a OPTIONS {route} call
105
+ * @param {string } route The route
106
+ * @param {Router | RouteHandler } handler A router or route handler
107
+ * @param {Array<Router | RouteHandler> } handlers Any other route handlers to chain
108
+ * @returns {this } this
109
+ */
110
+ options < R extends Req = Req > ( route : string , handler : RouteHandler < R > , ...handlers : RouteHandler < R > [ ] ) : this {
111
+ handlers . unshift ( handler ) ;
112
+ this . #add( route , 'OPTIONS' , handlers ) ;
113
+ return this ;
114
+ }
115
+
90
116
/**
91
117
* Add handlers to a GET {route} call
92
118
* @param {string } route The route
@@ -146,11 +172,11 @@ export class Router<Req extends RequestStub = RequestStub> {
146
172
* @param {string? } type The route type -- if undefined (for USE), it also matches any sub-routes
147
173
* @returns {boolean } Whether or not it matches
148
174
*/
149
- #matchRoute( url : string , route : string , type ?: string ) : boolean {
175
+ #matchRoute( url : string , route : string , matchExtra ?: boolean ) : boolean {
150
176
let pattern = new URLPattern ( { pathname : route } ) ;
151
177
let test = pattern . test ( url ) ;
152
178
153
- if ( type || test )
179
+ if ( ! matchExtra || test )
154
180
return test ;
155
181
156
182
pattern = new URLPattern ( { pathname : route + '(.*)' } ) ;
@@ -165,35 +191,37 @@ export class Router<Req extends RequestStub = RequestStub> {
165
191
* @param {string? } base The base url to append to the routes we are matching; used for sub-routers
166
192
* @returns {Array<RouteHandler> } The path through the step list -- should be iterated through to process
167
193
*/
168
- #pathfind< R extends Req = Req > ( req : R , base = '' ) : RouteHandler < R > [ ] {
194
+ #pathfind< R extends Req = Req > ( req : Partial < R > & SlimRequestStub , options ?: { base ?: string ; matchType ?: boolean } ) : { type : RouteStep [ 'type' ] , handler : RouteHandler < R > } [ ] {
195
+ let base = options ?. base ?? '' ;
196
+ const matchType = options ?. matchType !== false ;
169
197
170
198
// enforce `/{route}` with no trailing `/`'s
171
199
if ( base )
172
200
base = '/' + base . replace ( / ^ \/ + | \/ + $ / g, '' ) ;
173
201
174
- const path : RouteHandler < R > [ ] = [ ] ;
202
+ const path : { type : RouteStep [ 'type' ] , handler : RouteHandler < R > } [ ] = [ ] ;
175
203
176
204
for ( const step of this . #steps) {
177
205
// append the base to the route and remove any trailing `/`'s for the proper route to match against
178
206
const route = ( base + step . route ) . replace ( / \/ + $ / g, '' ) ;
179
207
180
- if ( ( step . type && req . method !== step . type ) ||
181
- ! this . #matchRoute( req . url , route , step . type ) )
208
+ if ( ( matchType && step . type && req . method !== step . type ) ||
209
+ ! this . #matchRoute( req . url , route , ! step . type ) )
182
210
continue ;
183
211
184
212
// move the handler into a variable in case it changes while it is being processed
185
213
const handler = step . handler ;
186
214
187
215
if ( handler instanceof Router )
188
- path . push ( ...( handler as Router < R > ) . #pathfind( req , route ) ) ;
216
+ path . push ( ...( handler as Router < R > ) . #pathfind( req , { base : route } ) ) ;
189
217
else {
190
- path . push ( ( req , next ) => {
218
+ path . push ( { type : step . type , handler : ( req , next ) => {
191
219
// match the route params for utility reasons
192
- req . params = ( new URLPattern ( { pathname : route + '/:else(.*)?' } ) ) . exec ( req . url ) ?. pathname ?. groups ;
193
- delete req . params ! . else ;
220
+ req . params = ( new URLPattern ( { pathname : route + '/:else(.*)?' } ) ) . exec ( req . url ) ?. pathname ?. groups ?? { } ;
221
+ delete req . params . else ;
194
222
195
223
return handler ( req , next ) ;
196
- } ) ;
224
+ } } ) ;
197
225
}
198
226
}
199
227
@@ -211,9 +239,9 @@ export class Router<Req extends RequestStub = RequestStub> {
211
239
* @returns {Promise<Response | undefined> } The response generated from the given routes,
212
240
* or undefined if nothing was returned
213
241
*/
214
- async process < R extends Req = Req > ( req : R , base = '' ) : Promise < Response | undefined > {
242
+ async process < R extends Req = Req > ( req : Partial < R > & SlimRequestStub , base = '' ) : Promise < Response | undefined > {
215
243
216
- const path = this . #pathfind( req , base ) ;
244
+ const path = this . #pathfind( req , { base } ) ;
217
245
218
246
if ( ! path . length )
219
247
return undefined ;
@@ -222,27 +250,43 @@ export class Router<Req extends RequestStub = RequestStub> {
222
250
223
251
const query = req . query ;
224
252
const params = req . params ;
253
+ const context = req . context ;
225
254
226
255
// generate the query object for utility reasons
227
- req . query = req . url . includes ( '?' )
256
+ req . query = ( req . url . includes ( '?' )
228
257
? Object . fromEntries ( ( new URLSearchParams ( req . url . slice ( req . url . indexOf ( '?' ) ) ) ) . entries ( ) )
229
- : undefined ;
258
+ : undefined ) ?? { } ;
259
+
260
+ req . context = { } ;
230
261
231
262
// used to check if the chain never finished and we got "ghosted"
232
263
const nextResponse = new Response ( ) ;
233
264
234
- const res = await this . #condense( path ) ( req , ( ) => nextResponse ) ;
265
+ const res = await this . #condense( path . map ( p => p . handler ) ) ( req as R , ( ) => nextResponse ) ;
235
266
236
267
// cleanup
237
268
238
269
req . query = query ;
239
270
req . params = params ;
271
+ req . context = context ;
240
272
241
273
if ( ! res || res === nextResponse )
242
274
return undefined ;
243
275
244
276
return res ;
245
277
}
278
+
279
+ parseOptions < R extends Req = Req > ( req : Partial < R > & SlimRequestStub , base = '' ) : { methods : string } {
280
+ const path = this . #pathfind( req , { base, matchType : false } ) ;
281
+
282
+ const methods = new Set < string > ( ) ;
283
+
284
+ for ( const p of path )
285
+ if ( p . type )
286
+ methods . add ( p . type ) ;
287
+
288
+ return { methods : Array . from ( methods . values ( ) ) . join ( ', ' ) } ;
289
+ }
246
290
}
247
291
248
292
export default Router ;
0 commit comments