@devp0nt/route0 1.0.0-next.72 → 1.0.0-next.74
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/dist/cjs/index.cjs +120 -41
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +26 -9
- package/dist/esm/index.d.ts +26 -9
- package/dist/esm/index.js +120 -41
- package/dist/esm/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cjs/index.cjs
CHANGED
|
@@ -24,12 +24,12 @@ __export(index_exports, {
|
|
|
24
24
|
module.exports = __toCommonJS(index_exports);
|
|
25
25
|
var import_flat0 = require("@devp0nt/flat0");
|
|
26
26
|
const escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
27
|
-
const
|
|
27
|
+
const getRouteSegments = (definition) => {
|
|
28
28
|
if (definition === "" || definition === "/") return [];
|
|
29
29
|
return definition.split("/").filter(Boolean);
|
|
30
30
|
};
|
|
31
|
-
const
|
|
32
|
-
const segments =
|
|
31
|
+
const getRouteTokens = (definition) => {
|
|
32
|
+
const segments = getRouteSegments(definition);
|
|
33
33
|
return segments.map((segment) => {
|
|
34
34
|
const param = segment.match(/^:([A-Za-z0-9_]+)(\?)?$/);
|
|
35
35
|
if (param) {
|
|
@@ -45,8 +45,8 @@ const getPathTokens = (definition) => {
|
|
|
45
45
|
return { kind: "static", value: segment };
|
|
46
46
|
});
|
|
47
47
|
};
|
|
48
|
-
const
|
|
49
|
-
const tokens =
|
|
48
|
+
const getRouteRegexBaseStrictString = (definition) => {
|
|
49
|
+
const tokens = getRouteTokens(definition);
|
|
50
50
|
if (tokens.length === 0) return "";
|
|
51
51
|
let pattern = "";
|
|
52
52
|
for (const token of tokens) {
|
|
@@ -66,20 +66,20 @@ const getPathRegexBaseStrictString = (definition) => {
|
|
|
66
66
|
}
|
|
67
67
|
return pattern;
|
|
68
68
|
};
|
|
69
|
-
const
|
|
69
|
+
const getRouteCaptureKeys = (definition) => {
|
|
70
70
|
const keys = [];
|
|
71
|
-
for (const token of
|
|
71
|
+
for (const token of getRouteTokens(definition)) {
|
|
72
72
|
if (token.kind === "param") keys.push(token.name);
|
|
73
73
|
if (token.kind === "wildcard") keys.push("*");
|
|
74
74
|
}
|
|
75
75
|
return keys;
|
|
76
76
|
};
|
|
77
77
|
const getPathParamsDefinition = (definition) => {
|
|
78
|
-
const entries =
|
|
78
|
+
const entries = getRouteTokens(definition).filter((t) => t.kind !== "static").map((t) => t.kind === "param" ? [t.name, !t.optional] : ["*", !t.optional]);
|
|
79
79
|
return Object.fromEntries(entries);
|
|
80
80
|
};
|
|
81
|
-
const
|
|
82
|
-
const segments =
|
|
81
|
+
const validateRouteDefinition = (definition) => {
|
|
82
|
+
const segments = getRouteSegments(definition);
|
|
83
83
|
const wildcardSegments = segments.filter((segment) => segment.includes("*"));
|
|
84
84
|
if (wildcardSegments.length === 0) return;
|
|
85
85
|
if (wildcardSegments.length > 1) {
|
|
@@ -95,11 +95,42 @@ const validatePathDefinition = (definition) => {
|
|
|
95
95
|
}
|
|
96
96
|
};
|
|
97
97
|
const stripTrailingWildcard = (definition) => definition.replace(/\*\??$/, "");
|
|
98
|
+
const normalizePathname = (pathname) => {
|
|
99
|
+
if (pathname.length > 1 && pathname.endsWith("/")) {
|
|
100
|
+
return pathname.slice(0, -1);
|
|
101
|
+
}
|
|
102
|
+
return pathname;
|
|
103
|
+
};
|
|
104
|
+
const getNormalizedPathnameFromInput = (hrefOrHrefRelOrLocation) => {
|
|
105
|
+
if (hrefOrHrefRelOrLocation instanceof URL) {
|
|
106
|
+
return normalizePathname(hrefOrHrefRelOrLocation.pathname);
|
|
107
|
+
}
|
|
108
|
+
if (typeof hrefOrHrefRelOrLocation !== "string") {
|
|
109
|
+
if (typeof hrefOrHrefRelOrLocation.pathname === "string") {
|
|
110
|
+
return normalizePathname(hrefOrHrefRelOrLocation.pathname);
|
|
111
|
+
}
|
|
112
|
+
hrefOrHrefRelOrLocation = hrefOrHrefRelOrLocation.href || hrefOrHrefRelOrLocation.hrefRel;
|
|
113
|
+
}
|
|
114
|
+
const abs = /^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//.test(hrefOrHrefRelOrLocation);
|
|
115
|
+
const base = abs ? void 0 : "http://example.com";
|
|
116
|
+
const url = new URL(hrefOrHrefRelOrLocation, base);
|
|
117
|
+
return normalizePathname(url.pathname);
|
|
118
|
+
};
|
|
98
119
|
class Route0 {
|
|
99
120
|
definition;
|
|
100
121
|
params;
|
|
101
122
|
_origin;
|
|
102
123
|
_callable;
|
|
124
|
+
_regexBaseStrictString;
|
|
125
|
+
_regexBaseString;
|
|
126
|
+
_regexStrictString;
|
|
127
|
+
_regexString;
|
|
128
|
+
_regexStrict;
|
|
129
|
+
_regex;
|
|
130
|
+
_regexAncestor;
|
|
131
|
+
_captureKeys;
|
|
132
|
+
_normalizedDefinition;
|
|
133
|
+
_definitionParts;
|
|
103
134
|
Infer = null;
|
|
104
135
|
/** Base URL used when generating absolute URLs (`abs: true`). */
|
|
105
136
|
get origin() {
|
|
@@ -114,7 +145,7 @@ class Route0 {
|
|
|
114
145
|
this._origin = origin;
|
|
115
146
|
}
|
|
116
147
|
constructor(definition, config = {}) {
|
|
117
|
-
|
|
148
|
+
validateRouteDefinition(definition);
|
|
118
149
|
this.definition = definition;
|
|
119
150
|
this.params = Route0._getParamsDefinitionByDefinition(definition);
|
|
120
151
|
const { origin } = config;
|
|
@@ -257,34 +288,80 @@ class Route0 {
|
|
|
257
288
|
getParamsKeys() {
|
|
258
289
|
return Object.keys(this.params);
|
|
259
290
|
}
|
|
260
|
-
|
|
261
|
-
return
|
|
291
|
+
getTokens() {
|
|
292
|
+
return getRouteTokens(this.definition);
|
|
262
293
|
}
|
|
263
294
|
/** Clones route with optional config override. */
|
|
264
295
|
clone(config) {
|
|
265
296
|
return Route0.create(this.definition, config);
|
|
266
297
|
}
|
|
267
|
-
|
|
268
|
-
|
|
298
|
+
get regexBaseStrictString() {
|
|
299
|
+
if (this._regexBaseStrictString === void 0) {
|
|
300
|
+
this._regexBaseStrictString = getRouteRegexBaseStrictString(this.definition);
|
|
301
|
+
}
|
|
302
|
+
return this._regexBaseStrictString;
|
|
269
303
|
}
|
|
270
|
-
|
|
271
|
-
|
|
304
|
+
get regexBaseString() {
|
|
305
|
+
if (this._regexBaseString === void 0) {
|
|
306
|
+
this._regexBaseString = this.regexBaseStrictString.replace(/\/+$/, "") + "/?";
|
|
307
|
+
}
|
|
308
|
+
return this._regexBaseString;
|
|
272
309
|
}
|
|
273
|
-
|
|
274
|
-
|
|
310
|
+
get regexStrictString() {
|
|
311
|
+
if (this._regexStrictString === void 0) {
|
|
312
|
+
this._regexStrictString = `^${this.regexBaseStrictString}$`;
|
|
313
|
+
}
|
|
314
|
+
return this._regexStrictString;
|
|
275
315
|
}
|
|
276
|
-
|
|
277
|
-
|
|
316
|
+
get regexString() {
|
|
317
|
+
if (this._regexString === void 0) {
|
|
318
|
+
this._regexString = `^${this.regexBaseString}$`;
|
|
319
|
+
}
|
|
320
|
+
return this._regexString;
|
|
278
321
|
}
|
|
279
|
-
|
|
280
|
-
|
|
322
|
+
get regexStrict() {
|
|
323
|
+
if (this._regexStrict === void 0) {
|
|
324
|
+
this._regexStrict = new RegExp(this.regexStrictString);
|
|
325
|
+
}
|
|
326
|
+
return this._regexStrict;
|
|
281
327
|
}
|
|
282
|
-
|
|
283
|
-
|
|
328
|
+
get regex() {
|
|
329
|
+
if (this._regex === void 0) {
|
|
330
|
+
this._regex = new RegExp(this.regexString);
|
|
331
|
+
}
|
|
332
|
+
return this._regex;
|
|
333
|
+
}
|
|
334
|
+
get regexAncestor() {
|
|
335
|
+
if (this._regexAncestor === void 0) {
|
|
336
|
+
this._regexAncestor = new RegExp(`^${this.regexBaseString}(?:/.*)?$`);
|
|
337
|
+
}
|
|
338
|
+
return this._regexAncestor;
|
|
339
|
+
}
|
|
340
|
+
get captureKeys() {
|
|
341
|
+
if (this._captureKeys === void 0) {
|
|
342
|
+
this._captureKeys = getRouteCaptureKeys(this.definition);
|
|
343
|
+
}
|
|
344
|
+
return this._captureKeys;
|
|
345
|
+
}
|
|
346
|
+
get normalizedDefinition() {
|
|
347
|
+
if (this._normalizedDefinition === void 0) {
|
|
348
|
+
this._normalizedDefinition = this.definition.length > 1 && this.definition.endsWith("/") ? this.definition.slice(0, -1) : this.definition;
|
|
349
|
+
}
|
|
350
|
+
return this._normalizedDefinition;
|
|
351
|
+
}
|
|
352
|
+
get definitionParts() {
|
|
353
|
+
if (this._definitionParts === void 0) {
|
|
354
|
+
this._definitionParts = this.normalizedDefinition === "/" ? ["/"] : this.normalizedDefinition.split("/").filter(Boolean);
|
|
355
|
+
}
|
|
356
|
+
return this._definitionParts;
|
|
357
|
+
}
|
|
358
|
+
/** Fast pathname check without building a full location object. */
|
|
359
|
+
isExactPathnameMatch(pathname) {
|
|
360
|
+
return this.regex.test(normalizePathname(pathname));
|
|
284
361
|
}
|
|
285
362
|
/** Creates a grouped strict regex pattern string from many routes. */
|
|
286
363
|
static getRegexStrictStringGroup(routes) {
|
|
287
|
-
const patterns = routes.map((route) => route.
|
|
364
|
+
const patterns = routes.map((route) => route.regexStrictString).join("|");
|
|
288
365
|
return `(${patterns})`;
|
|
289
366
|
}
|
|
290
367
|
/** Creates a strict grouped regex from many routes. */
|
|
@@ -294,7 +371,7 @@ class Route0 {
|
|
|
294
371
|
}
|
|
295
372
|
/** Creates a grouped regex pattern string from many routes. */
|
|
296
373
|
static getRegexStringGroup(routes) {
|
|
297
|
-
const patterns = routes.map((route) => route.
|
|
374
|
+
const patterns = routes.map((route) => route.regexString).join("|");
|
|
298
375
|
return `(${patterns})`;
|
|
299
376
|
}
|
|
300
377
|
/** Creates a grouped regex from many routes. */
|
|
@@ -378,11 +455,11 @@ class Route0 {
|
|
|
378
455
|
const location = Route0.getLocation(hrefOrHrefRelOrLocation);
|
|
379
456
|
location.route = this.definition;
|
|
380
457
|
location.params = {};
|
|
381
|
-
const pathname =
|
|
382
|
-
const paramNames =
|
|
383
|
-
const
|
|
384
|
-
const exactRe =
|
|
385
|
-
const ancestorRe =
|
|
458
|
+
const pathname = normalizePathname(location.pathname);
|
|
459
|
+
const paramNames = this.captureKeys;
|
|
460
|
+
const defParts = this.definitionParts;
|
|
461
|
+
const exactRe = this.regex;
|
|
462
|
+
const ancestorRe = this.regexAncestor;
|
|
386
463
|
const exactMatch = pathname.match(exactRe);
|
|
387
464
|
const ancestorMatch = pathname.match(ancestorRe);
|
|
388
465
|
const exact = !!exactMatch;
|
|
@@ -400,9 +477,7 @@ class Route0 {
|
|
|
400
477
|
} else {
|
|
401
478
|
location.params = {};
|
|
402
479
|
}
|
|
403
|
-
const
|
|
404
|
-
const defParts = getParts(def);
|
|
405
|
-
const pathParts = getParts(pathname);
|
|
480
|
+
const pathParts = pathname === "/" ? ["/"] : pathname.split("/").filter(Boolean);
|
|
406
481
|
let isPrefix = true;
|
|
407
482
|
if (pathParts.length > defParts.length) {
|
|
408
483
|
isPrefix = false;
|
|
@@ -540,11 +615,11 @@ class Route0 {
|
|
|
540
615
|
};
|
|
541
616
|
/** True when path structure is equal (param names are ignored). */
|
|
542
617
|
isSame(other) {
|
|
543
|
-
return
|
|
618
|
+
return getRouteTokens(this.definition).map((t) => {
|
|
544
619
|
if (t.kind === "static") return `s:${t.value}`;
|
|
545
620
|
if (t.kind === "param") return `p:${t.optional ? "o" : "r"}`;
|
|
546
621
|
return `w:${t.prefix}:${t.optional ? "o" : "r"}`;
|
|
547
|
-
}).join("/") ===
|
|
622
|
+
}).join("/") === getRouteTokens(other.definition).map((t) => {
|
|
548
623
|
if (t.kind === "static") return `s:${t.value}`;
|
|
549
624
|
if (t.kind === "param") return `p:${t.optional ? "o" : "r"}`;
|
|
550
625
|
return `w:${t.prefix}:${t.optional ? "o" : "r"}`;
|
|
@@ -603,10 +678,10 @@ class Route0 {
|
|
|
603
678
|
isConflict(other) {
|
|
604
679
|
if (!other) return false;
|
|
605
680
|
other = Route0.create(other);
|
|
606
|
-
const thisRegex = this.
|
|
607
|
-
const otherRegex = other.
|
|
681
|
+
const thisRegex = this.regex;
|
|
682
|
+
const otherRegex = other.regex;
|
|
608
683
|
const makeCandidates = (definition) => {
|
|
609
|
-
const tokens =
|
|
684
|
+
const tokens = getRouteTokens(definition);
|
|
610
685
|
const values = (token) => {
|
|
611
686
|
if (token.kind === "static") return [token.value];
|
|
612
687
|
if (token.kind === "param") return token.optional ? ["", "x"] : ["x"];
|
|
@@ -733,8 +808,12 @@ class Routes {
|
|
|
733
808
|
}
|
|
734
809
|
_getLocation(hrefOrHrefRelOrLocation) {
|
|
735
810
|
const input = hrefOrHrefRelOrLocation;
|
|
811
|
+
const pathname = getNormalizedPathnameFromInput(input);
|
|
736
812
|
for (const route of this._ordered) {
|
|
737
|
-
|
|
813
|
+
if (!route.isExactPathnameMatch(pathname)) {
|
|
814
|
+
continue;
|
|
815
|
+
}
|
|
816
|
+
const loc = route.getLocation(input);
|
|
738
817
|
if (loc.exact) {
|
|
739
818
|
return loc;
|
|
740
819
|
}
|