@boringnode/route-matcher 0.1.1

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.md ADDED
@@ -0,0 +1,9 @@
1
+ # The MIT License
2
+
3
+ Copyright 2026 Romain Lanz, contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,73 @@
1
+ # @boringnode/route-matcher
2
+
3
+ A small, zero-dependency route parser and indexed route table for Node.js.
4
+
5
+ ```sh
6
+ yarn add @boringnode/route-matcher
7
+ ```
8
+
9
+ ## Parse a route
10
+
11
+ ```ts
12
+ import { parseRoute } from '@boringnode/route-matcher'
13
+
14
+ const tokens = parseRoute('/users/:id.json', {
15
+ id: { match: /^\d+$/, cast: Number },
16
+ })
17
+ ```
18
+
19
+ Patterns support static segments, required parameters (`:id`), optional parameters (`:id?`),
20
+ parameter suffixes (`:id.json`), and trailing wildcards (`*`). A matcher may constrain a parameter
21
+ with `match` and transform its extracted value with `cast`.
22
+
23
+ ## Match routes
24
+
25
+ ```ts
26
+ import { parseRoute, RouteTable } from '@boringnode/route-matcher'
27
+
28
+ type Route = { name: string }
29
+
30
+ const routes = new RouteTable<Route>()
31
+
32
+ routes.add(parseRoute('/:slug'), { name: 'page' })
33
+ routes.add(parseRoute('/about'), { name: 'about' })
34
+
35
+ routes.match('/about')
36
+ // { value: { name: 'page' }, params: { slug: 'about' } }
37
+ ```
38
+
39
+ By default, the first registered matching route wins. Route shape does not change precedence: an
40
+ earlier parameter or wildcard route can win over a later static route. The table uses static lookup
41
+ and a private segment index to discard impossible candidates while preserving registration order.
42
+
43
+ ### Specificity precedence
44
+
45
+ Applications that need the most specific match can opt in when creating the table:
46
+
47
+ ```ts
48
+ const routes = new RouteTable<Route>({ precedence: 'specificity' })
49
+ ```
50
+
51
+ Specificity is compared segment by segment from left to right. Static segments rank above required
52
+ parameters, followed by optional parameters and wildcards. When common segments have equal
53
+ specificity, the longer pattern wins. Equally specific patterns retain registration order.
54
+
55
+ `match(pathname, true)` decodes parameters with `decodeURIComponent`. Decoding is off by default.
56
+ Invalid percent-encoded values are kept unchanged, and `cast` functions run after decoding.
57
+ Wildcards are returned under `'*'` as an array of path segments. This package matches path strings
58
+ as provided; it does not remove query strings.
59
+
60
+ For a short-lived list that does not need an index, use `matchRouteTokens`:
61
+
62
+ ```ts
63
+ import { matchRouteTokens, parseRoute } from '@boringnode/route-matcher'
64
+
65
+ const params = matchRouteTokens('/posts/42', [parseRoute('/posts/:id')])
66
+ // { id: '42' }
67
+ ```
68
+
69
+ `extractRouteParams` is also exported for consumers that keep their own route index.
70
+
71
+ ## License
72
+
73
+ [MIT](./LICENSE.md)
@@ -0,0 +1,42 @@
1
+ type RouteTokenType = 0 | 1 | 2 | 3;
2
+ type RouteMatcher = {
3
+ match?: RegExp;
4
+ cast?: (value: string) => any;
5
+ };
6
+ type RouteMatchers = Record<string, RouteMatcher>;
7
+ type RouteToken = {
8
+ old: string;
9
+ type: RouteTokenType;
10
+ val: string;
11
+ end: string;
12
+ matcher?: RegExp;
13
+ cast?: (value: string) => any;
14
+ };
15
+ type RouteParams = Record<string, any>;
16
+ type RouteMatch<T> = {
17
+ params: RouteParams;
18
+ value: T;
19
+ };
20
+ type RoutePrecedence = 'registration' | 'specificity';
21
+ type RouteTableOptions = {
22
+ precedence?: RoutePrecedence;
23
+ };
24
+
25
+ /**
26
+ * Parses a route pattern into tokens. A single leading and trailing separator
27
+ * is stripped for compatibility with established route pattern semantics.
28
+ */
29
+ declare function parseRoute(pattern: string, matchers?: RouteMatchers): RouteToken[];
30
+
31
+ declare function extractRouteParams(tokens: RouteToken[], pathname: string, shouldDecodeParams?: boolean): RouteParams;
32
+ /** Matches a transient list of tokenized routes without building an index. */
33
+ declare function matchRouteTokens(pathname: string, routes: RouteToken[][], shouldDecodeParams?: boolean): RouteParams | null;
34
+ /** An indexed route matcher with configurable precedence. */
35
+ declare class RouteTable<T> {
36
+ #private;
37
+ constructor(options?: RouteTableOptions);
38
+ add(tokens: RouteToken[], value: T): this;
39
+ match(pathname: string, shouldDecodeParams?: boolean): RouteMatch<T> | null;
40
+ }
41
+
42
+ export { type RouteMatch, type RouteMatcher, type RouteMatchers, type RouteParams, type RoutePrecedence, RouteTable, type RouteTableOptions, type RouteToken, type RouteTokenType, extractRouteParams, matchRouteTokens, parseRoute };
package/build/index.js ADDED
@@ -0,0 +1,445 @@
1
+ // src/route_parser.ts
2
+ function stripRouteSeparators(value) {
3
+ if (value === "/") {
4
+ return value;
5
+ }
6
+ if (value.charCodeAt(0) === 47) {
7
+ value = value.substring(1);
8
+ }
9
+ const lastIndex = value.length - 1;
10
+ return value.charCodeAt(lastIndex) === 47 ? value.substring(0, lastIndex) : value;
11
+ }
12
+ function parseRoute(pattern, matchers = {}) {
13
+ if (pattern === "/") {
14
+ return [{ old: pattern, type: 0, val: pattern, end: "" }];
15
+ }
16
+ if (typeof matchers !== "object") {
17
+ matchers = {};
18
+ }
19
+ let remaining = stripRouteSeparators(pattern);
20
+ let index = -1;
21
+ let parameterNameEnd = 0;
22
+ let segmentStart = 0;
23
+ let remainingLength = remaining.length;
24
+ const tokens = [];
25
+ while (++index < remainingLength) {
26
+ let character = remaining.charCodeAt(index);
27
+ if (character === 58) {
28
+ segmentStart = index + 1;
29
+ let type = 1;
30
+ parameterNameEnd = 0;
31
+ let suffix = "";
32
+ while (index < remainingLength && remaining.charCodeAt(index) !== 47) {
33
+ character = remaining.charCodeAt(index);
34
+ if (character === 63) {
35
+ parameterNameEnd = index;
36
+ type = 3;
37
+ } else if (character === 46 && suffix.length === 0) {
38
+ parameterNameEnd = index;
39
+ suffix = remaining.substring(index);
40
+ }
41
+ index++;
42
+ }
43
+ const value2 = remaining.substring(segmentStart, parameterNameEnd || index);
44
+ const matcher = matchers[value2];
45
+ tokens.push({
46
+ old: pattern,
47
+ type,
48
+ val: value2,
49
+ end: suffix,
50
+ matcher: matcher?.match,
51
+ cast: matcher?.cast
52
+ });
53
+ remaining = remaining.substring(index);
54
+ remainingLength -= index;
55
+ index = 0;
56
+ continue;
57
+ }
58
+ if (character === 42) {
59
+ tokens.push({
60
+ old: pattern,
61
+ type: 2,
62
+ val: remaining.substring(index),
63
+ end: ""
64
+ });
65
+ continue;
66
+ }
67
+ segmentStart = index;
68
+ while (index < remainingLength && remaining.charCodeAt(index) !== 47) {
69
+ index++;
70
+ }
71
+ const value = remaining.substring(segmentStart, index);
72
+ tokens.push({ old: pattern, type: 0, val: value, end: "" });
73
+ remaining = remaining.substring(index);
74
+ remainingLength -= index;
75
+ index = segmentStart = 0;
76
+ }
77
+ return tokens;
78
+ }
79
+
80
+ // src/route_table.ts
81
+ function createNode() {
82
+ return { minimumOrder: Number.POSITIVE_INFINITY };
83
+ }
84
+ function splitRoutePath(pathname) {
85
+ pathname = stripRouteSeparators(pathname);
86
+ return pathname === "/" ? ["/"] : pathname.split("/");
87
+ }
88
+ function getStaticRouteKey(tokens) {
89
+ if (!tokens.length || tokens.some((token) => token.type !== 0)) {
90
+ return null;
91
+ }
92
+ return tokens.length === 1 && tokens[0].val === "/" ? "root" : `segments:${tokens.map((token) => token.val).join("/")}`;
93
+ }
94
+ function getStaticRequestKey(normalizedPathname) {
95
+ return normalizedPathname === "/" ? "root" : `segments:${normalizedPathname}`;
96
+ }
97
+ function getOrCreateChild(children, key) {
98
+ let child = children.get(key);
99
+ if (!child) {
100
+ child = createNode();
101
+ children.set(key, child);
102
+ }
103
+ return child;
104
+ }
105
+ function getTokenSpecificity(type) {
106
+ if (type === 0) return 3;
107
+ if (type === 1) return 2;
108
+ if (type === 3) return 1;
109
+ return 0;
110
+ }
111
+ function compareRouteSpecificity(a, b) {
112
+ const length = Math.max(a.length, b.length);
113
+ for (let index = 0; index < length; index++) {
114
+ const aSpecificity = a[index] ? getTokenSpecificity(a[index].type) : -1;
115
+ const bSpecificity = b[index] ? getTokenSpecificity(b[index].type) : -1;
116
+ if (aSpecificity !== bSpecificity) {
117
+ return bSpecificity - aSpecificity;
118
+ }
119
+ }
120
+ return 0;
121
+ }
122
+ function matchesSegment(token, segment) {
123
+ if (token.type === 0) {
124
+ return token.val === segment;
125
+ }
126
+ if (segment === "/") {
127
+ return token.type > 1;
128
+ }
129
+ if (segment === "") {
130
+ return token.end === "" && (token.matcher ? token.matcher.test(segment) : true);
131
+ }
132
+ if (!segment) {
133
+ return token.end === "";
134
+ }
135
+ return segment.endsWith(token.end) && (token.matcher ? token.matcher.test(segment) : true);
136
+ }
137
+ function matchesRoute(tokens, segments) {
138
+ if (!tokens.length) {
139
+ return segments.length === 1 && segments[0] === "/";
140
+ }
141
+ if (tokens.length !== segments.length && !(tokens.length < segments.length && tokens[tokens.length - 1].type === 2) && !(tokens.length > segments.length && tokens[tokens.length - 1].type === 3)) {
142
+ return false;
143
+ }
144
+ let index = 0;
145
+ while (index < tokens.length) {
146
+ if (!matchesSegment(tokens[index], segments[index])) {
147
+ return false;
148
+ }
149
+ index++;
150
+ }
151
+ return true;
152
+ }
153
+ function matchesIndexedRoute(route, segments) {
154
+ if (!route.isStructurallyMatched) {
155
+ return matchesRoute(route.tokens, segments);
156
+ }
157
+ const matcher = route.matcher;
158
+ if (matcher) {
159
+ const segment = segments[route.matcherSegmentIndex];
160
+ if (segment !== void 0 && segment !== "/" && !matcher.test(segment)) {
161
+ return false;
162
+ }
163
+ }
164
+ for (const { index, matcher: additionalMatcher } of route.additionalMatcherChecks ?? []) {
165
+ const segment = segments[index];
166
+ if (segment !== void 0 && segment !== "/" && !additionalMatcher.test(segment)) {
167
+ return false;
168
+ }
169
+ }
170
+ return true;
171
+ }
172
+ function extractRouteParamsFromSegments(tokens, segments, shouldDecodeParams) {
173
+ const params = {};
174
+ let index = 0;
175
+ while (index < tokens.length) {
176
+ const token = tokens[index];
177
+ const segment = segments[index];
178
+ if (segment === "/") {
179
+ index++;
180
+ continue;
181
+ }
182
+ if (token.val === "*") {
183
+ params[token.val] = segments.slice(index).map((value2) => {
184
+ if (!shouldDecodeParams) {
185
+ return value2;
186
+ }
187
+ try {
188
+ return decodeURIComponent(value2);
189
+ } catch {
190
+ return value2;
191
+ }
192
+ });
193
+ break;
194
+ }
195
+ if (segment === void 0 || token.type === 0) {
196
+ index++;
197
+ continue;
198
+ }
199
+ let value = segment.replace(token.end, "");
200
+ if (shouldDecodeParams) {
201
+ try {
202
+ value = decodeURIComponent(value);
203
+ } catch {
204
+ }
205
+ }
206
+ params[token.val] = token.cast ? token.cast(value) : value;
207
+ index++;
208
+ }
209
+ return params;
210
+ }
211
+ function extractRouteParams(tokens, pathname, shouldDecodeParams = false) {
212
+ return extractRouteParamsFromSegments(tokens, splitRoutePath(pathname), shouldDecodeParams);
213
+ }
214
+ function matchRouteTokens(pathname, routes, shouldDecodeParams = false) {
215
+ const segments = splitRoutePath(pathname);
216
+ for (const tokens of routes) {
217
+ if (matchesRoute(tokens, segments)) {
218
+ return extractRouteParamsFromSegments(tokens, segments, shouldDecodeParams);
219
+ }
220
+ }
221
+ return null;
222
+ }
223
+ var RouteTable = class {
224
+ #nextOrder = 0;
225
+ #precedence;
226
+ #root = createNode();
227
+ #staticRoutes = /* @__PURE__ */ new Map();
228
+ #unindexedRoutes = [];
229
+ constructor(options = {}) {
230
+ this.#precedence = options.precedence ?? "registration";
231
+ }
232
+ add(tokens, value) {
233
+ const indexedRoute = { order: this.#nextOrder++, tokens, value };
234
+ const staticKey = getStaticRouteKey(tokens);
235
+ if (staticKey !== null) {
236
+ if (!this.#staticRoutes.has(staticKey)) {
237
+ this.#staticRoutes.set(staticKey, indexedRoute);
238
+ }
239
+ return this;
240
+ }
241
+ const hasStatefulMatcher = tokens.some((token, index) => {
242
+ const matcher = token.matcher;
243
+ const isStateful = matcher && (matcher.global || matcher.sticky || matcher.exec !== RegExp.prototype.exec || matcher.test !== RegExp.prototype.test);
244
+ if (isStateful) {
245
+ return true;
246
+ }
247
+ if (matcher) {
248
+ if (!indexedRoute.matcher) {
249
+ indexedRoute.matcher = matcher;
250
+ indexedRoute.matcherSegmentIndex = index;
251
+ } else {
252
+ indexedRoute.additionalMatcherChecks ||= [];
253
+ indexedRoute.additionalMatcherChecks.push({ index, matcher });
254
+ }
255
+ }
256
+ return false;
257
+ });
258
+ if (!tokens.length || hasStatefulMatcher) {
259
+ this.#insertCandidate(this.#unindexedRoutes, indexedRoute);
260
+ return this;
261
+ }
262
+ let node = this.#root;
263
+ node.minimumOrder = Math.min(node.minimumOrder, indexedRoute.order);
264
+ for (const token of tokens) {
265
+ if (token.type === 0) {
266
+ node.literals ||= /* @__PURE__ */ new Map();
267
+ node = getOrCreateChild(node.literals, token.val);
268
+ } else if (token.type === 1) {
269
+ node.parameters ||= /* @__PURE__ */ new Map();
270
+ node = getOrCreateChild(node.parameters, token.end);
271
+ } else if (token.type === 3) {
272
+ node.optionals ||= /* @__PURE__ */ new Map();
273
+ node = getOrCreateChild(node.optionals, token.end);
274
+ } else {
275
+ node.wildcards ||= [];
276
+ this.#insertCandidate(node.wildcards, indexedRoute);
277
+ return this;
278
+ }
279
+ node.minimumOrder = Math.min(node.minimumOrder, indexedRoute.order);
280
+ }
281
+ node.terminals ||= [];
282
+ indexedRoute.isStructurallyMatched = true;
283
+ this.#insertCandidate(node.terminals, indexedRoute);
284
+ return this;
285
+ }
286
+ match(pathname, shouldDecodeParams = false) {
287
+ const normalizedPathname = stripRouteSeparators(pathname);
288
+ const staticRoute = this.#staticRoutes.get(getStaticRequestKey(normalizedPathname));
289
+ const registrationPrecedence = this.#precedence === "registration";
290
+ const cutoff = registrationPrecedence ? staticRoute?.order ?? Number.POSITIVE_INFINITY : Number.POSITIVE_INFINITY;
291
+ const firstUnindexedRoute = this.#unindexedRoutes[0];
292
+ if (registrationPrecedence && staticRoute && this.#root.minimumOrder >= cutoff && (!firstUnindexedRoute || firstUnindexedRoute.order >= cutoff)) {
293
+ return { value: staticRoute.value, params: {} };
294
+ }
295
+ const segments = normalizedPathname === "/" ? ["/"] : normalizedPathname.split("/");
296
+ const candidateLists = [];
297
+ if (firstUnindexedRoute && (!registrationPrecedence || firstUnindexedRoute.order < cutoff)) {
298
+ candidateLists.push(this.#unindexedRoutes);
299
+ }
300
+ this.#collectCandidates(this.#root, segments, 0, cutoff, candidateLists);
301
+ if (!registrationPrecedence && staticRoute) {
302
+ candidateLists.push([staticRoute]);
303
+ }
304
+ const candidate = candidateLists.length === 1 ? this.#findCandidateInList(candidateLists[0], segments, cutoff) : this.#findCandidateAcrossLists(candidateLists, segments, cutoff);
305
+ if (candidate) {
306
+ return {
307
+ value: candidate.value,
308
+ params: extractRouteParamsFromSegments(candidate.tokens, segments, shouldDecodeParams)
309
+ };
310
+ }
311
+ return registrationPrecedence && staticRoute ? { value: staticRoute.value, params: {} } : null;
312
+ }
313
+ #findCandidateInList(candidates, segments, cutoff) {
314
+ for (const candidate of candidates) {
315
+ if (candidate.order >= cutoff) {
316
+ break;
317
+ }
318
+ if (matchesIndexedRoute(candidate, segments)) {
319
+ return candidate;
320
+ }
321
+ }
322
+ return void 0;
323
+ }
324
+ #findCandidateAcrossLists(candidateLists, segments, cutoff) {
325
+ const positions = new Uint32Array(candidateLists.length);
326
+ while (true) {
327
+ let selectedList = -1;
328
+ let selectedRoute;
329
+ for (const [listIndex, candidates] of candidateLists.entries()) {
330
+ const candidate = candidates[positions[listIndex]];
331
+ if (candidate && (!selectedRoute || this.#compareRoutes(candidate, selectedRoute) < 0)) {
332
+ selectedList = listIndex;
333
+ selectedRoute = candidate;
334
+ }
335
+ }
336
+ if (!selectedRoute || selectedRoute.order >= cutoff) {
337
+ return void 0;
338
+ }
339
+ positions[selectedList]++;
340
+ if (matchesIndexedRoute(selectedRoute, segments)) {
341
+ return selectedRoute;
342
+ }
343
+ }
344
+ }
345
+ #compareRoutes(a, b) {
346
+ if (this.#precedence === "specificity") {
347
+ const specificity = compareRouteSpecificity(a.tokens, b.tokens);
348
+ if (specificity !== 0) return specificity;
349
+ }
350
+ return a.order - b.order;
351
+ }
352
+ #insertCandidate(candidates, route) {
353
+ if (this.#precedence === "registration") {
354
+ candidates.push(route);
355
+ return;
356
+ }
357
+ let start = 0;
358
+ let end = candidates.length;
359
+ while (start < end) {
360
+ const middle = start + end >>> 1;
361
+ if (this.#compareRoutes(candidates[middle], route) <= 0) {
362
+ start = middle + 1;
363
+ } else {
364
+ end = middle;
365
+ }
366
+ }
367
+ candidates.splice(start, 0, route);
368
+ }
369
+ #collectCandidates(node, segments, segmentIndex, cutoff, candidateLists, canMatchTerminal = true) {
370
+ if (node.minimumOrder >= cutoff) {
371
+ return;
372
+ }
373
+ const wildcards = node.wildcards;
374
+ if (wildcards?.length && wildcards[0].order < cutoff) {
375
+ candidateLists.push(wildcards);
376
+ }
377
+ if (segmentIndex === segments.length) {
378
+ this.#collectTerminalCandidates(
379
+ node,
380
+ segments,
381
+ segmentIndex,
382
+ cutoff,
383
+ candidateLists,
384
+ canMatchTerminal
385
+ );
386
+ return;
387
+ }
388
+ const segment = segments[segmentIndex];
389
+ this.#collectSegmentCandidates(node, segments, segmentIndex, segment, cutoff, candidateLists);
390
+ }
391
+ #collectTerminalCandidates(node, segments, segmentIndex, cutoff, candidateLists, canMatchTerminal) {
392
+ const terminals = node.terminals;
393
+ if (canMatchTerminal && terminals?.length && terminals[0].order < cutoff) {
394
+ candidateLists.push(terminals);
395
+ }
396
+ for (const [suffix, optionalChild] of node.optionals ?? []) {
397
+ if (suffix === "") {
398
+ this.#collectCandidates(optionalChild, segments, segmentIndex, cutoff, candidateLists);
399
+ }
400
+ }
401
+ for (const [suffix, parameterChild] of node.parameters ?? []) {
402
+ if (suffix === "") {
403
+ this.#collectCandidates(
404
+ parameterChild,
405
+ segments,
406
+ segmentIndex,
407
+ cutoff,
408
+ candidateLists,
409
+ false
410
+ );
411
+ }
412
+ }
413
+ }
414
+ #collectSegmentCandidates(node, segments, segmentIndex, segment, cutoff, candidateLists) {
415
+ const literalChild = node.literals?.get(segment);
416
+ if (literalChild) {
417
+ this.#collectCandidates(literalChild, segments, segmentIndex + 1, cutoff, candidateLists);
418
+ }
419
+ if (segment !== "/") {
420
+ for (const [suffix, parameterChild] of node.parameters ?? []) {
421
+ if (segment.endsWith(suffix)) {
422
+ this.#collectCandidates(
423
+ parameterChild,
424
+ segments,
425
+ segmentIndex + 1,
426
+ cutoff,
427
+ candidateLists
428
+ );
429
+ }
430
+ }
431
+ }
432
+ for (const [suffix, optionalChild] of node.optionals ?? []) {
433
+ if (segment === "/" || segment.endsWith(suffix)) {
434
+ this.#collectCandidates(optionalChild, segments, segmentIndex + 1, cutoff, candidateLists);
435
+ }
436
+ }
437
+ }
438
+ };
439
+ export {
440
+ RouteTable,
441
+ extractRouteParams,
442
+ matchRouteTokens,
443
+ parseRoute
444
+ };
445
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/route_parser.ts","../src/route_table.ts"],"sourcesContent":["/*\n * Portions derived from @adonisjs/http-server.\n * Copyright (c) 2023 Harminder Virk. Licensed under the MIT License.\n */\n\nimport type { RouteMatchers, RouteToken } from './types.ts'\n\nexport function stripRouteSeparators(value: string): string {\n if (value === '/') {\n return value\n }\n if (value.charCodeAt(0) === 47) {\n value = value.substring(1)\n }\n\n const lastIndex = value.length - 1\n return value.charCodeAt(lastIndex) === 47 ? value.substring(0, lastIndex) : value\n}\n\n/**\n * Parses a route pattern into tokens. A single leading and trailing separator\n * is stripped for compatibility with established route pattern semantics.\n */\nexport function parseRoute(pattern: string, matchers: RouteMatchers = {}): RouteToken[] {\n if (pattern === '/') {\n return [{ old: pattern, type: 0, val: pattern, end: '' }]\n }\n\n if (typeof matchers !== 'object') {\n matchers = {}\n }\n\n let remaining = stripRouteSeparators(pattern)\n let index = -1\n let parameterNameEnd = 0\n let segmentStart = 0\n let remainingLength = remaining.length\n const tokens: RouteToken[] = []\n\n while (++index < remainingLength) {\n let character = remaining.charCodeAt(index)\n\n if (character === 58) {\n segmentStart = index + 1\n let type: 1 | 3 = 1\n parameterNameEnd = 0\n let suffix = ''\n\n while (index < remainingLength && remaining.charCodeAt(index) !== 47) {\n character = remaining.charCodeAt(index)\n if (character === 63) {\n parameterNameEnd = index\n type = 3\n } else if (character === 46 && suffix.length === 0) {\n parameterNameEnd = index\n suffix = remaining.substring(index)\n }\n index++\n }\n\n const value = remaining.substring(segmentStart, parameterNameEnd || index)\n const matcher = matchers[value]\n tokens.push({\n old: pattern,\n type,\n val: value,\n end: suffix,\n matcher: matcher?.match,\n cast: matcher?.cast,\n })\n\n remaining = remaining.substring(index)\n remainingLength -= index\n index = 0\n continue\n }\n\n if (character === 42) {\n tokens.push({\n old: pattern,\n type: 2,\n val: remaining.substring(index),\n end: '',\n })\n continue\n }\n\n segmentStart = index\n while (index < remainingLength && remaining.charCodeAt(index) !== 47) {\n index++\n }\n\n const value = remaining.substring(segmentStart, index)\n tokens.push({ old: pattern, type: 0, val: value, end: '' })\n remaining = remaining.substring(index)\n remainingLength -= index\n index = segmentStart = 0\n }\n\n return tokens\n}\n","/*\n * Portions derived from @adonisjs/http-server.\n * Copyright (c) 2023 Harminder Virk. Licensed under the MIT License.\n */\n\nimport { stripRouteSeparators } from './route_parser.ts'\nimport type {\n RouteMatch,\n RouteParams,\n RoutePrecedence,\n RouteTableOptions,\n RouteToken,\n RouteTokenType,\n} from './types.ts'\n\ntype IndexedRoute<T> = {\n additionalMatcherChecks?: { index: number; matcher: RegExp }[]\n isStructurallyMatched?: boolean\n matcher?: RegExp\n matcherSegmentIndex?: number\n order: number\n tokens: RouteToken[]\n value: T\n}\n\ntype RouteNode<T> = {\n literals?: Map<string, RouteNode<T>>\n minimumOrder: number\n optionals?: Map<string, RouteNode<T>>\n parameters?: Map<string, RouteNode<T>>\n terminals?: IndexedRoute<T>[]\n wildcards?: IndexedRoute<T>[]\n}\n\nfunction createNode<T>(): RouteNode<T> {\n return { minimumOrder: Number.POSITIVE_INFINITY }\n}\n\nfunction splitRoutePath(pathname: string): string[] {\n pathname = stripRouteSeparators(pathname)\n return pathname === '/' ? ['/'] : pathname.split('/')\n}\n\nfunction getStaticRouteKey(tokens: RouteToken[]): string | null {\n if (!tokens.length || tokens.some((token) => token.type !== 0)) {\n return null\n }\n\n return tokens.length === 1 && tokens[0].val === '/'\n ? 'root'\n : `segments:${tokens.map((token) => token.val).join('/')}`\n}\n\nfunction getStaticRequestKey(normalizedPathname: string): string {\n return normalizedPathname === '/' ? 'root' : `segments:${normalizedPathname}`\n}\n\nfunction getOrCreateChild<T>(children: Map<string, RouteNode<T>>, key: string): RouteNode<T> {\n let child = children.get(key)\n if (!child) {\n child = createNode<T>()\n children.set(key, child)\n }\n return child\n}\n\nfunction getTokenSpecificity(type: RouteTokenType): number {\n if (type === 0) return 3\n if (type === 1) return 2\n if (type === 3) return 1\n return 0\n}\n\nfunction compareRouteSpecificity(a: RouteToken[], b: RouteToken[]): number {\n const length = Math.max(a.length, b.length)\n for (let index = 0; index < length; index++) {\n const aSpecificity = a[index] ? getTokenSpecificity(a[index].type) : -1\n const bSpecificity = b[index] ? getTokenSpecificity(b[index].type) : -1\n if (aSpecificity !== bSpecificity) {\n return bSpecificity - aSpecificity\n }\n }\n return 0\n}\n\nfunction matchesSegment(token: RouteToken, segment: string | undefined): boolean {\n if (token.type === 0) {\n return token.val === segment\n }\n if (segment === '/') {\n return token.type > 1\n }\n if (segment === '') {\n return token.end === '' && (token.matcher ? token.matcher.test(segment) : true)\n }\n if (!segment) {\n return token.end === ''\n }\n return segment.endsWith(token.end) && (token.matcher ? token.matcher.test(segment) : true)\n}\n\nfunction matchesRoute(tokens: RouteToken[], segments: string[]): boolean {\n if (!tokens.length) {\n return segments.length === 1 && segments[0] === '/'\n }\n\n if (\n tokens.length !== segments.length &&\n !(tokens.length < segments.length && tokens[tokens.length - 1].type === 2) &&\n !(tokens.length > segments.length && tokens[tokens.length - 1].type === 3)\n ) {\n return false\n }\n\n let index = 0\n while (index < tokens.length) {\n if (!matchesSegment(tokens[index], segments[index])) {\n return false\n }\n index++\n }\n\n return true\n}\n\nfunction matchesIndexedRoute<T>(route: IndexedRoute<T>, segments: string[]): boolean {\n if (!route.isStructurallyMatched) {\n return matchesRoute(route.tokens, segments)\n }\n\n const matcher = route.matcher\n if (matcher) {\n const segment = segments[route.matcherSegmentIndex!]\n if (segment !== undefined && segment !== '/' && !matcher.test(segment)) {\n return false\n }\n }\n\n for (const { index, matcher: additionalMatcher } of route.additionalMatcherChecks ?? []) {\n const segment = segments[index]\n if (segment !== undefined && segment !== '/' && !additionalMatcher.test(segment)) {\n return false\n }\n }\n return true\n}\n\nfunction extractRouteParamsFromSegments(\n tokens: RouteToken[],\n segments: string[],\n shouldDecodeParams: boolean\n): RouteParams {\n const params: RouteParams = {}\n let index = 0\n\n while (index < tokens.length) {\n const token = tokens[index]\n const segment = segments[index]\n\n if (segment === '/') {\n index++\n continue\n }\n\n if (token.val === '*') {\n params[token.val] = segments.slice(index).map((value) => {\n if (!shouldDecodeParams) {\n return value\n }\n try {\n return decodeURIComponent(value)\n } catch {\n return value\n }\n })\n break\n }\n\n if (segment === undefined || token.type === 0) {\n index++\n continue\n }\n\n let value = segment.replace(token.end, '')\n if (shouldDecodeParams) {\n try {\n value = decodeURIComponent(value)\n } catch {}\n }\n params[token.val] = token.cast ? token.cast(value) : value\n index++\n }\n\n return params\n}\n\nexport function extractRouteParams(\n tokens: RouteToken[],\n pathname: string,\n shouldDecodeParams: boolean = false\n): RouteParams {\n return extractRouteParamsFromSegments(tokens, splitRoutePath(pathname), shouldDecodeParams)\n}\n\n/** Matches a transient list of tokenized routes without building an index. */\nexport function matchRouteTokens(\n pathname: string,\n routes: RouteToken[][],\n shouldDecodeParams: boolean = false\n): RouteParams | null {\n const segments = splitRoutePath(pathname)\n for (const tokens of routes) {\n if (matchesRoute(tokens, segments)) {\n return extractRouteParamsFromSegments(tokens, segments, shouldDecodeParams)\n }\n }\n return null\n}\n\n/** An indexed route matcher with configurable precedence. */\nexport class RouteTable<T> {\n #nextOrder = 0\n #precedence: RoutePrecedence\n #root = createNode<T>()\n #staticRoutes = new Map<string, IndexedRoute<T>>()\n #unindexedRoutes: IndexedRoute<T>[] = []\n\n constructor(options: RouteTableOptions = {}) {\n this.#precedence = options.precedence ?? 'registration'\n }\n\n add(tokens: RouteToken[], value: T): this {\n const indexedRoute: IndexedRoute<T> = { order: this.#nextOrder++, tokens, value }\n const staticKey = getStaticRouteKey(tokens)\n\n if (staticKey !== null) {\n if (!this.#staticRoutes.has(staticKey)) {\n this.#staticRoutes.set(staticKey, indexedRoute)\n }\n return this\n }\n\n const hasStatefulMatcher = tokens.some((token, index) => {\n const matcher = token.matcher\n const isStateful =\n matcher &&\n (matcher.global ||\n matcher.sticky ||\n matcher.exec !== RegExp.prototype.exec ||\n matcher.test !== RegExp.prototype.test)\n if (isStateful) {\n return true\n }\n if (matcher) {\n if (!indexedRoute.matcher) {\n indexedRoute.matcher = matcher\n indexedRoute.matcherSegmentIndex = index\n } else {\n indexedRoute.additionalMatcherChecks ||= []\n indexedRoute.additionalMatcherChecks.push({ index, matcher })\n }\n }\n return false\n })\n\n if (!tokens.length || hasStatefulMatcher) {\n this.#insertCandidate(this.#unindexedRoutes, indexedRoute)\n return this\n }\n\n let node = this.#root\n node.minimumOrder = Math.min(node.minimumOrder, indexedRoute.order)\n for (const token of tokens) {\n if (token.type === 0) {\n node.literals ||= new Map()\n node = getOrCreateChild(node.literals, token.val)\n } else if (token.type === 1) {\n node.parameters ||= new Map()\n node = getOrCreateChild(node.parameters, token.end)\n } else if (token.type === 3) {\n node.optionals ||= new Map()\n node = getOrCreateChild(node.optionals, token.end)\n } else {\n node.wildcards ||= []\n this.#insertCandidate(node.wildcards, indexedRoute)\n return this\n }\n node.minimumOrder = Math.min(node.minimumOrder, indexedRoute.order)\n }\n\n node.terminals ||= []\n indexedRoute.isStructurallyMatched = true\n this.#insertCandidate(node.terminals, indexedRoute)\n return this\n }\n\n match(pathname: string, shouldDecodeParams: boolean = false): RouteMatch<T> | null {\n const normalizedPathname = stripRouteSeparators(pathname)\n const staticRoute = this.#staticRoutes.get(getStaticRequestKey(normalizedPathname))\n const registrationPrecedence = this.#precedence === 'registration'\n const cutoff = registrationPrecedence\n ? (staticRoute?.order ?? Number.POSITIVE_INFINITY)\n : Number.POSITIVE_INFINITY\n const firstUnindexedRoute = this.#unindexedRoutes[0]\n if (\n registrationPrecedence &&\n staticRoute &&\n this.#root.minimumOrder >= cutoff &&\n (!firstUnindexedRoute || firstUnindexedRoute.order >= cutoff)\n ) {\n return { value: staticRoute.value, params: {} }\n }\n\n const segments = normalizedPathname === '/' ? ['/'] : normalizedPathname.split('/')\n const candidateLists: IndexedRoute<T>[][] = []\n if (firstUnindexedRoute && (!registrationPrecedence || firstUnindexedRoute.order < cutoff)) {\n candidateLists.push(this.#unindexedRoutes)\n }\n this.#collectCandidates(this.#root, segments, 0, cutoff, candidateLists)\n if (!registrationPrecedence && staticRoute) {\n candidateLists.push([staticRoute])\n }\n\n const candidate =\n candidateLists.length === 1\n ? this.#findCandidateInList(candidateLists[0], segments, cutoff)\n : this.#findCandidateAcrossLists(candidateLists, segments, cutoff)\n if (candidate) {\n return {\n value: candidate.value,\n params: extractRouteParamsFromSegments(candidate.tokens, segments, shouldDecodeParams),\n }\n }\n return registrationPrecedence && staticRoute ? { value: staticRoute.value, params: {} } : null\n }\n\n #findCandidateInList(\n candidates: IndexedRoute<T>[],\n segments: string[],\n cutoff: number\n ): IndexedRoute<T> | undefined {\n for (const candidate of candidates) {\n if (candidate.order >= cutoff) {\n break\n }\n if (matchesIndexedRoute(candidate, segments)) {\n return candidate\n }\n }\n return undefined\n }\n\n #findCandidateAcrossLists(\n candidateLists: IndexedRoute<T>[][],\n segments: string[],\n cutoff: number\n ): IndexedRoute<T> | undefined {\n const positions = new Uint32Array(candidateLists.length)\n while (true) {\n let selectedList = -1\n let selectedRoute: IndexedRoute<T> | undefined\n for (const [listIndex, candidates] of candidateLists.entries()) {\n const candidate = candidates[positions[listIndex]]\n if (candidate && (!selectedRoute || this.#compareRoutes(candidate, selectedRoute) < 0)) {\n selectedList = listIndex\n selectedRoute = candidate\n }\n }\n\n if (!selectedRoute || selectedRoute.order >= cutoff) {\n return undefined\n }\n positions[selectedList]++\n\n if (matchesIndexedRoute(selectedRoute, segments)) {\n return selectedRoute\n }\n }\n }\n\n #compareRoutes(a: IndexedRoute<T>, b: IndexedRoute<T>): number {\n if (this.#precedence === 'specificity') {\n const specificity = compareRouteSpecificity(a.tokens, b.tokens)\n if (specificity !== 0) return specificity\n }\n return a.order - b.order\n }\n\n #insertCandidate(candidates: IndexedRoute<T>[], route: IndexedRoute<T>): void {\n if (this.#precedence === 'registration') {\n candidates.push(route)\n return\n }\n\n let start = 0\n let end = candidates.length\n while (start < end) {\n const middle = (start + end) >>> 1\n if (this.#compareRoutes(candidates[middle], route) <= 0) {\n start = middle + 1\n } else {\n end = middle\n }\n }\n candidates.splice(start, 0, route)\n }\n\n #collectCandidates(\n node: RouteNode<T>,\n segments: string[],\n segmentIndex: number,\n cutoff: number,\n candidateLists: IndexedRoute<T>[][],\n canMatchTerminal: boolean = true\n ): void {\n if (node.minimumOrder >= cutoff) {\n return\n }\n\n const wildcards = node.wildcards\n if (wildcards?.length && wildcards[0].order < cutoff) {\n candidateLists.push(wildcards)\n }\n\n if (segmentIndex === segments.length) {\n this.#collectTerminalCandidates(\n node,\n segments,\n segmentIndex,\n cutoff,\n candidateLists,\n canMatchTerminal\n )\n return\n }\n\n const segment = segments[segmentIndex]\n this.#collectSegmentCandidates(node, segments, segmentIndex, segment, cutoff, candidateLists)\n }\n\n #collectTerminalCandidates(\n node: RouteNode<T>,\n segments: string[],\n segmentIndex: number,\n cutoff: number,\n candidateLists: IndexedRoute<T>[][],\n canMatchTerminal: boolean\n ): void {\n const terminals = node.terminals\n if (canMatchTerminal && terminals?.length && terminals[0].order < cutoff) {\n candidateLists.push(terminals)\n }\n for (const [suffix, optionalChild] of node.optionals ?? []) {\n if (suffix === '') {\n this.#collectCandidates(optionalChild, segments, segmentIndex, cutoff, candidateLists)\n }\n }\n for (const [suffix, parameterChild] of node.parameters ?? []) {\n if (suffix === '') {\n this.#collectCandidates(\n parameterChild,\n segments,\n segmentIndex,\n cutoff,\n candidateLists,\n false\n )\n }\n }\n }\n\n #collectSegmentCandidates(\n node: RouteNode<T>,\n segments: string[],\n segmentIndex: number,\n segment: string,\n cutoff: number,\n candidateLists: IndexedRoute<T>[][]\n ): void {\n const literalChild = node.literals?.get(segment)\n if (literalChild) {\n this.#collectCandidates(literalChild, segments, segmentIndex + 1, cutoff, candidateLists)\n }\n if (segment !== '/') {\n for (const [suffix, parameterChild] of node.parameters ?? []) {\n if (segment.endsWith(suffix)) {\n this.#collectCandidates(\n parameterChild,\n segments,\n segmentIndex + 1,\n cutoff,\n candidateLists\n )\n }\n }\n }\n for (const [suffix, optionalChild] of node.optionals ?? []) {\n if (segment === '/' || segment.endsWith(suffix)) {\n this.#collectCandidates(optionalChild, segments, segmentIndex + 1, cutoff, candidateLists)\n }\n }\n }\n}\n"],"mappings":"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","names":["value","value"]}
package/package.json ADDED
@@ -0,0 +1,88 @@
1
+ {
2
+ "name": "@boringnode/route-matcher",
3
+ "description": "A fast, registration-ordered route matcher",
4
+ "version": "0.1.1",
5
+ "main": "./build/index.js",
6
+ "types": "./build/index.d.ts",
7
+ "type": "module",
8
+ "files": [
9
+ "build"
10
+ ],
11
+ "exports": {
12
+ ".": {
13
+ "types": "./build/index.d.ts",
14
+ "import": "./build/index.js"
15
+ }
16
+ },
17
+ "scripts": {
18
+ "build": "yarn clean && tsup-node",
19
+ "check:package": "yarn build && publint && attw --pack --profile esm-only .",
20
+ "clean": "del-cli build",
21
+ "format": "oxfmt --check .",
22
+ "format:fix": "oxfmt --write .",
23
+ "lint": "oxlint",
24
+ "lint:fix": "oxlint --fix",
25
+ "prepublishOnly": "yarn build",
26
+ "release": "yarn dlx release-it",
27
+ "test": "node --import=@poppinss/ts-exec --enable-source-maps bin/test.ts --reporters=dot",
28
+ "test:coverage": "c8 yarn test",
29
+ "typecheck": "tsc --noEmit"
30
+ },
31
+ "devDependencies": {
32
+ "@adonisjs/tsconfig": "^2.0.0",
33
+ "@arethetypeswrong/cli": "^0.18.2",
34
+ "@japa/assert": "^4.2.0",
35
+ "@japa/expect-type": "^2.0.4",
36
+ "@japa/runner": "^5.3.0",
37
+ "@poppinss/matchit": "^3.2.0",
38
+ "@poppinss/ts-exec": "^1.4.4",
39
+ "@types/node": "^20.19.24",
40
+ "c8": "^11.0.0",
41
+ "del-cli": "^7.0.0",
42
+ "oxfmt": "^0.56.0",
43
+ "oxlint": "^1.71.0",
44
+ "publint": "^0.3.14",
45
+ "release-it": "^20.2.1",
46
+ "tsup": "^8.5.1",
47
+ "typescript": "^5.9.3"
48
+ },
49
+ "author": "Romain Lanz <romain.lanz@pm.me>",
50
+ "license": "MIT",
51
+ "homepage": "https://github.com/boringnode/route-matcher#readme",
52
+ "bugs": {
53
+ "url": "https://github.com/boringnode/route-matcher/issues"
54
+ },
55
+ "repository": {
56
+ "type": "git",
57
+ "url": "git+https://github.com/boringnode/route-matcher.git"
58
+ },
59
+ "keywords": [
60
+ "route",
61
+ "router",
62
+ "matcher",
63
+ "routing"
64
+ ],
65
+ "publishConfig": {
66
+ "access": "public",
67
+ "tag": "latest"
68
+ },
69
+ "release-it": {
70
+ "git": {
71
+ "commitMessage": "chore(release): ${version}",
72
+ "tagAnnotation": "v${version}",
73
+ "tagName": "v${version}"
74
+ },
75
+ "github": {
76
+ "release": true,
77
+ "releaseName": "v${version}"
78
+ },
79
+ "npm": {
80
+ "publish": true,
81
+ "skipChecks": true
82
+ }
83
+ },
84
+ "packageManager": "yarn@4.17.1",
85
+ "engines": {
86
+ "node": ">=20.6"
87
+ }
88
+ }