@lwrjs/router 0.10.0-alpha.1 → 0.10.0-alpha.10
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/build/bundle/prod/lwr/navigation/es/modules/lwr/currentView/currentView.d.ts +28 -0
- package/build/bundle/prod/lwr/navigation/navigation.js +1 -1
- package/build/bundle/prod/lwr/router/es/modules/lwr/currentView/currentView.d.ts +28 -0
- package/build/bundle/prod/lwr/router/router.js +1 -1
- package/build/bundle/prod/lwr/routerContainer/es/modules/lwr/currentView/currentView.d.ts +28 -0
- package/build/bundle/prod/lwr/routerContainer/routerContainer.js +1 -1
- package/build/cjs/modules/lwr/outlet/outlet.cjs +4 -1
- package/build/es/modules/lwr/contextProvider/contextProvider.js +30 -0
- package/build/es/modules/lwr/contextUtils/contextInfo.js +93 -0
- package/build/es/modules/lwr/contextUtils/contextUtils.js +77 -0
- package/build/es/modules/lwr/contextUtils/navigationApiStore.js +46 -0
- package/build/{modules → es/modules}/lwr/currentPageReference/currentPageReference.d.ts +1 -1
- package/build/es/modules/lwr/currentPageReference/currentPageReference.js +14 -0
- package/build/{modules → es/modules}/lwr/currentView/currentView.d.ts +2 -2
- package/build/es/modules/lwr/currentView/currentView.js +62 -0
- package/build/{modules → es/modules}/lwr/domRouter/domRouter.d.ts +2 -2
- package/build/es/modules/lwr/domRouter/domRouter.js +441 -0
- package/build/es/modules/lwr/domRouterUtils/domRouterUtils.js +3 -0
- package/build/es/modules/lwr/domRouterUtils/historyUtils.js +30 -0
- package/build/{modules → es/modules}/lwr/domRouterUtils/types.d.ts +1 -1
- package/build/es/modules/lwr/domRouterUtils/types.js +2 -0
- package/build/es/modules/lwr/domRouterUtils/uriUtils.js +69 -0
- package/build/es/modules/lwr/historyRouter/historyRouter.js +88 -0
- package/build/es/modules/lwr/navigation/navigation.js +20 -0
- package/build/es/modules/lwr/navigation/navigationApi.js +27 -0
- package/build/es/modules/lwr/navigation/navigationMixin.js +76 -0
- package/build/{modules → es/modules}/lwr/navigationContext/navigationContext.d.ts +2 -2
- package/build/es/modules/lwr/navigationContext/navigationContext.js +10 -0
- package/build/es/modules/lwr/navigationMixinHacks/navigationMixinHacks.d.ts +7 -0
- package/build/es/modules/lwr/navigationMixinHacks/navigationMixinHacks.js +5 -0
- package/build/es/modules/lwr/observable/observable.js +71 -0
- package/build/{modules → es/modules}/lwr/outlet/outlet.d.ts +1 -0
- package/build/es/modules/lwr/outlet/outlet.js +69 -0
- package/build/es/modules/lwr/router/router.js +201 -0
- package/build/es/modules/lwr/routerBridge/routerBridge.js +85 -0
- package/build/es/modules/lwr/routerContainer/routerContainer.js +116 -0
- package/build/es/modules/lwr/routerContainer/utils.js +83 -0
- package/build/es/modules/lwr/routerErrors/routerErrors.js +154 -0
- package/build/es/modules/lwr/routerUtils/domUtils.js +3 -0
- package/build/{modules → es/modules}/lwr/routerUtils/filterUtils.d.ts +1 -1
- package/build/es/modules/lwr/routerUtils/filterUtils.js +74 -0
- package/build/es/modules/lwr/routerUtils/parseUtils.js +182 -0
- package/build/{modules → es/modules}/lwr/routerUtils/pathToRegexp.d.ts +5 -5
- package/build/es/modules/lwr/routerUtils/pathToRegexp.js +415 -0
- package/build/es/modules/lwr/routerUtils/routeDefUtils.js +204 -0
- package/build/es/modules/lwr/routerUtils/routeUtils.js +239 -0
- package/build/es/modules/lwr/routerUtils/routerUtils.js +19 -0
- package/build/es/modules/lwr/routerUtils/typeUtils.js +112 -0
- package/build/{modules → es/modules}/lwr/routerUtils/types.d.ts +23 -23
- package/build/es/modules/lwr/routerUtils/types.js +2 -0
- package/build/es/modules/lwr/routerUtils/uriUtils.js +134 -0
- package/build/modules/lwr/contextUtils/contextUtils.js +5 -0
- package/build/modules/lwr/contextUtils/navigationApiStore.js +7 -0
- package/build/modules/lwr/domRouter/domRouter.js +6 -0
- package/build/modules/lwr/domRouterUtils/historyUtils.js +0 -1
- package/build/modules/lwr/outlet/outlet.css +1 -1
- package/build/modules/lwr/outlet/outlet.html +2 -2
- package/build/modules/lwr/outlet/outlet.js +3 -4
- package/build/modules/lwr/router/router.js +3 -0
- package/build/modules/lwr/routerContainer/utils.js +1 -3
- package/build/modules/lwr/routerUtils/pathToRegexp.js +17 -0
- package/build/modules/lwr/routerUtils/typeUtils.js +0 -1
- package/package.json +17 -9
- package/pageObjects/outlet.cjs +21 -4
- package/pageObjects/outlet.d.ts +10 -2
- package/pageObjects/outlet.js +22 -5
- package/build/modules/lwr/navigationMixinHacks/navigationMixinHacks.d.ts +0 -7
- /package/build/{modules → es/modules}/lwr/contextProvider/contextProvider.d.ts +0 -0
- /package/build/{modules → es/modules}/lwr/contextUtils/contextInfo.d.ts +0 -0
- /package/build/{modules → es/modules}/lwr/contextUtils/contextUtils.d.ts +0 -0
- /package/build/{modules → es/modules}/lwr/contextUtils/navigationApiStore.d.ts +0 -0
- /package/build/{modules → es/modules}/lwr/domRouterUtils/domRouterUtils.d.ts +0 -0
- /package/build/{modules → es/modules}/lwr/domRouterUtils/historyUtils.d.ts +0 -0
- /package/build/{modules → es/modules}/lwr/domRouterUtils/uriUtils.d.ts +0 -0
- /package/build/{modules → es/modules}/lwr/historyRouter/historyRouter.d.ts +0 -0
- /package/build/{modules → es/modules}/lwr/navigation/navigation.d.ts +0 -0
- /package/build/{modules → es/modules}/lwr/navigation/navigationApi.d.ts +0 -0
- /package/build/{modules → es/modules}/lwr/navigation/navigationMixin.d.ts +0 -0
- /package/build/{modules → es/modules}/lwr/observable/observable.d.ts +0 -0
- /package/build/{modules → es/modules}/lwr/router/router.d.ts +0 -0
- /package/build/{modules → es/modules}/lwr/routerBridge/routerBridge.d.ts +0 -0
- /package/build/{modules → es/modules}/lwr/routerContainer/routerContainer.d.ts +0 -0
- /package/build/{modules → es/modules}/lwr/routerContainer/utils.d.ts +0 -0
- /package/build/{modules → es/modules}/lwr/routerErrors/routerErrors.d.ts +0 -0
- /package/build/{modules → es/modules}/lwr/routerUtils/domUtils.d.ts +0 -0
- /package/build/{modules → es/modules}/lwr/routerUtils/parseUtils.d.ts +0 -0
- /package/build/{modules → es/modules}/lwr/routerUtils/routeDefUtils.d.ts +0 -0
- /package/build/{modules → es/modules}/lwr/routerUtils/routeUtils.d.ts +0 -0
- /package/build/{modules → es/modules}/lwr/routerUtils/routerUtils.d.ts +0 -0
- /package/build/{modules → es/modules}/lwr/routerUtils/typeUtils.d.ts +0 -0
- /package/build/{modules → es/modules}/lwr/routerUtils/uriUtils.d.ts +0 -0
- /package/build/{services → es/services}/index.d.ts +0 -0
- /package/build/{services → es/services}/index.js +0 -0
- /package/build/{services → es/services}/module-provider/index.d.ts +0 -0
- /package/build/{services → es/services}/module-provider/index.js +0 -0
- /package/build/{services → es/services}/module-provider/utils.d.ts +0 -0
- /package/build/{services → es/services}/module-provider/utils.js +0 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { messages, invariant } from 'lwr/routerErrors';
|
|
2
|
+
import { pathToRegexp, compile } from './pathToRegexp';
|
|
3
|
+
import { getQueryFromUrl, getPathFromUrl, isParam, getParamName, getQueryNames } from './uriUtils';
|
|
4
|
+
const { INVALID_ROUTE_QUERY, MISSING_ROUTE_TEMPLATE, MISSING_PAGE_BINDING, INVALID_PAGE_BINDING, INVALID_URI_SYNTAX, } = messages;
|
|
5
|
+
/**
|
|
6
|
+
* Parse the route definitions with path-to-regex functionality for paths, and
|
|
7
|
+
* query parameter validation
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* {
|
|
11
|
+
* original: the user-defined route definition
|
|
12
|
+
* regex: regular expression based on the route definition path
|
|
13
|
+
* toPath: function which takes an object of parameters and creates a path
|
|
14
|
+
* params: an array of objects with info on each path parameter
|
|
15
|
+
* compiledQuery: an object that represents defined routeDefintion query params
|
|
16
|
+
* queryMatcher: a function that can be used to see if this route defintion
|
|
17
|
+
* mataches against a given input QueryObject
|
|
18
|
+
* }
|
|
19
|
+
*/
|
|
20
|
+
export function parseRoutes(config) {
|
|
21
|
+
const { routes, caseSensitive } = config;
|
|
22
|
+
return routes.map((def) => {
|
|
23
|
+
return parseUriRoute(def, caseSensitive);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Given a RouteDefintion, create a CompiledRouteDefinition that can be used to match
|
|
28
|
+
* against input URIs.
|
|
29
|
+
*
|
|
30
|
+
* @param {RouteDefinition} def - A RouteDefintion to parse into a CompiledRouteDefiniton
|
|
31
|
+
* @param {boolean} caseSensitive - determines whether or not the given RouteDefintion should
|
|
32
|
+
* be case sensitive against path literals, and query parameter values
|
|
33
|
+
* @returns {CompiledRouteDefinition} - Object that represents the compiled path so as to be used
|
|
34
|
+
* for matching input URIs
|
|
35
|
+
*/
|
|
36
|
+
function parseUriRoute(def, caseSensitive = false) {
|
|
37
|
+
const params = [];
|
|
38
|
+
const { uri, page } = def;
|
|
39
|
+
invariant(!!uri, MISSING_ROUTE_TEMPLATE);
|
|
40
|
+
invariant(isValidUri(uri), INVALID_URI_SYNTAX);
|
|
41
|
+
invariant(!!page, MISSING_PAGE_BINDING);
|
|
42
|
+
const path = getPathFromUrl(uri);
|
|
43
|
+
const query = getQueryFromUrl(uri);
|
|
44
|
+
const regex = pathToRegexp(path, params, {
|
|
45
|
+
sensitive: caseSensitive,
|
|
46
|
+
// True if this is a leaf route, and must match URLs exactly with no trailing segments.
|
|
47
|
+
end: def.exact === false ? false : true,
|
|
48
|
+
});
|
|
49
|
+
const toPath = compile(path, { encode: encodeURIComponent });
|
|
50
|
+
const compiledQuery = compileQueryObject(query);
|
|
51
|
+
const queryMatcher = getQueryMatcher(compiledQuery, caseSensitive);
|
|
52
|
+
const compiledRoute = { original: def, regex, params, toPath, compiledQuery, queryMatcher };
|
|
53
|
+
invariant(isValidPageBinding(compiledRoute), INVALID_PAGE_BINDING);
|
|
54
|
+
return compiledRoute;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Returns true if URI format contains invalid characters
|
|
58
|
+
* - No *, (, ), ;
|
|
59
|
+
* @param uri
|
|
60
|
+
*/
|
|
61
|
+
function isValidUri(uri = '') {
|
|
62
|
+
const invalid = ['*', '(', ')', ';'];
|
|
63
|
+
const containsInvalidCharacter = invalid.some((invalidChar) => uri.indexOf(invalidChar) >= 0);
|
|
64
|
+
return !containsInvalidCharacter;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Returns true if the given compiledDef has a valid pageReference binding.
|
|
68
|
+
* - All params in uri are accounted for
|
|
69
|
+
* - No params are used more than once
|
|
70
|
+
* - All params used in binding are defined in uri
|
|
71
|
+
* @param compiledDef
|
|
72
|
+
*/
|
|
73
|
+
function isValidPageBinding(compiledDef) {
|
|
74
|
+
const { original: { page } = {}, params, compiledQuery } = compiledDef;
|
|
75
|
+
const pageType = page ? page.type : page;
|
|
76
|
+
const pageAttributes = (page ? page.attributes : page) || {};
|
|
77
|
+
const pageState = (page ? page.state : page) || {};
|
|
78
|
+
if (typeof pageType !== 'string' || typeof pageAttributes !== 'object' || typeof pageState !== 'object') {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
const pathParams = Object.values(params).map(({ name }) => name);
|
|
82
|
+
const queryParams = getQueryNames(compiledQuery);
|
|
83
|
+
const allParams = [...pathParams, ...queryParams];
|
|
84
|
+
const attributeBindings = Object.values(pageAttributes).filter(isParam).map(getParamName);
|
|
85
|
+
const stateBindings = Object.values(pageState).filter(isParam).map(getParamName);
|
|
86
|
+
const hasAllParams = allParams.every((paramName) => {
|
|
87
|
+
// Key.name could be an index if it's an unnamed parameter (https://github.com/pillarjs/path-to-regexp#unnamed-parameters)
|
|
88
|
+
// We do not support unnamed parameters.
|
|
89
|
+
if (typeof paramName !== 'string') {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
return attributeBindings.indexOf(paramName) >= 0 || stateBindings.indexOf(paramName) >= 0;
|
|
93
|
+
});
|
|
94
|
+
const paramsUsedOnlyOnce = allParams.length === attributeBindings.length + stateBindings.length;
|
|
95
|
+
return !!(page && pageType && pageAttributes && pageState && hasAllParams && paramsUsedOnlyOnce);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Converts a QueryObject create from the routeDefintion.uri, and converts it into a
|
|
99
|
+
* CompiledQuery object that represents the defined 1) queryStringKeys, 2) routeParameter name,
|
|
100
|
+
* and 3) and what input is valid for this query. e.g.,
|
|
101
|
+
*
|
|
102
|
+
* Given the following uri: /path?someKey=:qParam, then "someKey" is the queryStringKey, ":qParam"
|
|
103
|
+
* is the route parameter name, and any input would be valid.
|
|
104
|
+
*
|
|
105
|
+
* @param {QueryObject} queryObject - queryObject retrieved from the routeDefinition uri
|
|
106
|
+
* @returns {CompiledQuery} - CompiledQuery
|
|
107
|
+
*/
|
|
108
|
+
export function compileQueryObject(queryObject) {
|
|
109
|
+
const compiled = {};
|
|
110
|
+
Object.keys(queryObject).forEach((qKey) => {
|
|
111
|
+
const qValue = queryObject[qKey];
|
|
112
|
+
// INVALID: ?:qParam=value
|
|
113
|
+
invariant(isParam(qKey) ? qValue === null : true, INVALID_ROUTE_QUERY);
|
|
114
|
+
if (isParam(qKey)) {
|
|
115
|
+
// ?:qParam => ?qParam=:qParam
|
|
116
|
+
compiled[qKey.substr(1)] = {
|
|
117
|
+
routeParamName: qKey,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
else if (qValue && isParam(qValue)) {
|
|
121
|
+
// ?qKey=:qParam
|
|
122
|
+
compiled[qKey] = {
|
|
123
|
+
routeParamName: qValue,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
// ?qKey OR ?qKey=literal
|
|
128
|
+
compiled[qKey] = {
|
|
129
|
+
literalValue: qValue === null ? null : qValue,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
return compiled;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Takes a CompiledQuery a returns a QueryMatcher function that will match a QueryObject
|
|
137
|
+
* against the CompiledQuery and returns either a MatchedQuery, representing all the
|
|
138
|
+
* input values that match the CompiledQuery, or null if any input query parameter
|
|
139
|
+
* doesn't match the CompiledQuery.
|
|
140
|
+
*
|
|
141
|
+
* @param compiledQuery - CompiledQuery object to use to create the matcher
|
|
142
|
+
* @param {boolean} caseSensitive - true if this should case-sensitive match query values
|
|
143
|
+
* @returns {QueryMatcher} - The queryMatcher function
|
|
144
|
+
*/
|
|
145
|
+
export function getQueryMatcher(compiledQuery, caseSensitive = false) {
|
|
146
|
+
const queryMatcher = (queryObject) => {
|
|
147
|
+
const inputKeys = Object.keys(queryObject);
|
|
148
|
+
const defKeys = Object.keys(compiledQuery);
|
|
149
|
+
const hasAllDefKeys = defKeys.every((defKey) => inputKeys.indexOf(defKey) >= 0);
|
|
150
|
+
if (hasAllDefKeys) {
|
|
151
|
+
return defKeys.reduce((matched, defKey) => {
|
|
152
|
+
// If we find that any param doesn't match, escape out.
|
|
153
|
+
if (matched === null)
|
|
154
|
+
return null;
|
|
155
|
+
const { literalValue, routeParamName } = compiledQuery[defKey];
|
|
156
|
+
const inputValue = queryObject[defKey];
|
|
157
|
+
// no literal was provided, default true
|
|
158
|
+
let literalValueMatches = true;
|
|
159
|
+
if (typeof literalValue === 'string') {
|
|
160
|
+
literalValueMatches = caseSensitive
|
|
161
|
+
? literalValue === inputValue
|
|
162
|
+
: literalValue.toUpperCase() ===
|
|
163
|
+
(inputValue == null ? inputValue : inputValue.toUpperCase());
|
|
164
|
+
}
|
|
165
|
+
else if (literalValue === null) {
|
|
166
|
+
literalValueMatches = inputValue === literalValue;
|
|
167
|
+
}
|
|
168
|
+
if (literalValueMatches) {
|
|
169
|
+
// force null to string for matching
|
|
170
|
+
matched = { ...matched, [defKey]: { value: inputValue, routeParamName } };
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
matched = null;
|
|
174
|
+
}
|
|
175
|
+
return matched;
|
|
176
|
+
}, {});
|
|
177
|
+
}
|
|
178
|
+
return null;
|
|
179
|
+
};
|
|
180
|
+
return queryMatcher;
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=parseUtils.js.map
|
|
@@ -26,7 +26,7 @@ export interface TokensToFunctionOptions {
|
|
|
26
26
|
*/
|
|
27
27
|
validate?: boolean;
|
|
28
28
|
}
|
|
29
|
-
export
|
|
29
|
+
export type PathFunction<P extends object = object> = (data?: P) => string;
|
|
30
30
|
/**
|
|
31
31
|
* Expose a method for transforming tokens into the path function.
|
|
32
32
|
*/
|
|
@@ -52,11 +52,11 @@ export interface MatchResult<P extends object = object> {
|
|
|
52
52
|
/**
|
|
53
53
|
* A match is either `false` (no match) or a match result.
|
|
54
54
|
*/
|
|
55
|
-
export
|
|
55
|
+
export type Match<P extends object = object> = false | MatchResult<P>;
|
|
56
56
|
/**
|
|
57
57
|
* The match function takes a string and returns whether it matched the path.
|
|
58
58
|
*/
|
|
59
|
-
export
|
|
59
|
+
export type MatchFunction<P extends object = object> = (path: string) => Match<P>;
|
|
60
60
|
/**
|
|
61
61
|
* Create a path match function from `path-to-regexp` output.
|
|
62
62
|
*/
|
|
@@ -74,7 +74,7 @@ export interface Key {
|
|
|
74
74
|
/**
|
|
75
75
|
* A token is a string (nothing special) or key metadata (capture group).
|
|
76
76
|
*/
|
|
77
|
-
export
|
|
77
|
+
export type Token = string | Key;
|
|
78
78
|
export interface TokensToRegexpOptions {
|
|
79
79
|
/**
|
|
80
80
|
* When `true` the regexp will be case sensitive. (default: `false`)
|
|
@@ -112,7 +112,7 @@ export declare function tokensToRegexp(tokens: Token[], keys?: Key[], options?:
|
|
|
112
112
|
/**
|
|
113
113
|
* Supported `path-to-regexp` input types.
|
|
114
114
|
*/
|
|
115
|
-
export
|
|
115
|
+
export type Path = string | RegExp | Array<string | RegExp>;
|
|
116
116
|
/**
|
|
117
117
|
* Normalize the given path string, returning a regular expression.
|
|
118
118
|
*
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
/*
|
|
2
|
+
The MIT License (MIT)
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2014 Blake Embrey (hello@blakeembrey.com)
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in
|
|
14
|
+
all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
22
|
+
THE SOFTWARE.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* Tokenize input string.
|
|
26
|
+
*/
|
|
27
|
+
function lexer(str) {
|
|
28
|
+
const tokens = [];
|
|
29
|
+
let i = 0;
|
|
30
|
+
while (i < str.length) {
|
|
31
|
+
const char = str[i];
|
|
32
|
+
if (char === '*' || char === '+' || char === '?') {
|
|
33
|
+
tokens.push({ type: 'MODIFIER', index: i, value: str[i++] });
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (char === '\\') {
|
|
37
|
+
tokens.push({ type: 'ESCAPED_CHAR', index: i++, value: str[i++] });
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (char === '{') {
|
|
41
|
+
tokens.push({ type: 'OPEN', index: i, value: str[i++] });
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (char === '}') {
|
|
45
|
+
tokens.push({ type: 'CLOSE', index: i, value: str[i++] });
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (char === ':') {
|
|
49
|
+
let name = '';
|
|
50
|
+
let j = i + 1;
|
|
51
|
+
while (j < str.length) {
|
|
52
|
+
const code = str.charCodeAt(j);
|
|
53
|
+
if (
|
|
54
|
+
// `0-9`
|
|
55
|
+
(code >= 48 && code <= 57) ||
|
|
56
|
+
// `A-Z`
|
|
57
|
+
(code >= 65 && code <= 90) ||
|
|
58
|
+
// `a-z`
|
|
59
|
+
(code >= 97 && code <= 122) ||
|
|
60
|
+
// `_`
|
|
61
|
+
code === 95) {
|
|
62
|
+
name += str[j++];
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
if (!name)
|
|
68
|
+
throw new TypeError(`Missing parameter name at ${i}`);
|
|
69
|
+
tokens.push({ type: 'NAME', index: i, value: name });
|
|
70
|
+
i = j;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (char === '(') {
|
|
74
|
+
let count = 1;
|
|
75
|
+
let pattern = '';
|
|
76
|
+
let j = i + 1;
|
|
77
|
+
if (str[j] === '?') {
|
|
78
|
+
throw new TypeError(`Pattern cannot start with "?" at ${j}`);
|
|
79
|
+
}
|
|
80
|
+
while (j < str.length) {
|
|
81
|
+
if (str[j] === '\\') {
|
|
82
|
+
pattern += str[j++] + str[j++];
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (str[j] === ')') {
|
|
86
|
+
count--;
|
|
87
|
+
if (count === 0) {
|
|
88
|
+
j++;
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
else if (str[j] === '(') {
|
|
93
|
+
count++;
|
|
94
|
+
if (str[j + 1] !== '?') {
|
|
95
|
+
throw new TypeError(`Capturing groups are not allowed at ${j}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
pattern += str[j++];
|
|
99
|
+
}
|
|
100
|
+
if (count)
|
|
101
|
+
throw new TypeError(`Unbalanced pattern at ${i}`);
|
|
102
|
+
if (!pattern)
|
|
103
|
+
throw new TypeError(`Missing pattern at ${i}`);
|
|
104
|
+
tokens.push({ type: 'PATTERN', index: i, value: pattern });
|
|
105
|
+
i = j;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
tokens.push({ type: 'CHAR', index: i, value: str[i++] });
|
|
109
|
+
}
|
|
110
|
+
tokens.push({ type: 'END', index: i, value: '' });
|
|
111
|
+
return tokens;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Escape a regular expression string.
|
|
115
|
+
*/
|
|
116
|
+
function escapeString(str) {
|
|
117
|
+
return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, '\\$1');
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Get the flags for a regexp from the options.
|
|
121
|
+
*/
|
|
122
|
+
function flags(options) {
|
|
123
|
+
return options && options.sensitive ? '' : 'i';
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Parse a string for the raw tokens.
|
|
127
|
+
*/
|
|
128
|
+
export function parse(str, options = {}) {
|
|
129
|
+
const tokens = lexer(str);
|
|
130
|
+
const { prefixes = './' } = options;
|
|
131
|
+
const defaultPattern = `[^${escapeString(options.delimiter || '/#?')}]+?`;
|
|
132
|
+
const result = [];
|
|
133
|
+
let key = 0;
|
|
134
|
+
let i = 0;
|
|
135
|
+
let path = '';
|
|
136
|
+
const tryConsume = (type) => {
|
|
137
|
+
if (i < tokens.length && tokens[i].type === type)
|
|
138
|
+
return tokens[i++].value;
|
|
139
|
+
};
|
|
140
|
+
const mustConsume = (type) => {
|
|
141
|
+
const value = tryConsume(type);
|
|
142
|
+
if (value !== undefined)
|
|
143
|
+
return value;
|
|
144
|
+
const { type: nextType, index } = tokens[i];
|
|
145
|
+
throw new TypeError(`Unexpected ${nextType} at ${index}, expected ${type}`);
|
|
146
|
+
};
|
|
147
|
+
const consumeText = () => {
|
|
148
|
+
let result = '';
|
|
149
|
+
let value;
|
|
150
|
+
// tslint:disable-next-line
|
|
151
|
+
while ((value = tryConsume('CHAR') || tryConsume('ESCAPED_CHAR'))) {
|
|
152
|
+
result += value;
|
|
153
|
+
}
|
|
154
|
+
return result;
|
|
155
|
+
};
|
|
156
|
+
while (i < tokens.length) {
|
|
157
|
+
const char = tryConsume('CHAR');
|
|
158
|
+
const name = tryConsume('NAME');
|
|
159
|
+
const pattern = tryConsume('PATTERN');
|
|
160
|
+
if (name || pattern) {
|
|
161
|
+
let prefix = char || '';
|
|
162
|
+
if (prefixes.indexOf(prefix) === -1) {
|
|
163
|
+
path += prefix;
|
|
164
|
+
prefix = '';
|
|
165
|
+
}
|
|
166
|
+
if (path) {
|
|
167
|
+
result.push(path);
|
|
168
|
+
path = '';
|
|
169
|
+
}
|
|
170
|
+
result.push({
|
|
171
|
+
name: name || key++,
|
|
172
|
+
prefix,
|
|
173
|
+
suffix: '',
|
|
174
|
+
pattern: pattern || defaultPattern,
|
|
175
|
+
modifier: tryConsume('MODIFIER') || '',
|
|
176
|
+
});
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const value = char || tryConsume('ESCAPED_CHAR');
|
|
180
|
+
if (value) {
|
|
181
|
+
path += value;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (path) {
|
|
185
|
+
result.push(path);
|
|
186
|
+
path = '';
|
|
187
|
+
}
|
|
188
|
+
const open = tryConsume('OPEN');
|
|
189
|
+
if (open) {
|
|
190
|
+
const prefix = consumeText();
|
|
191
|
+
const name = tryConsume('NAME') || '';
|
|
192
|
+
const pattern = tryConsume('PATTERN') || '';
|
|
193
|
+
const suffix = consumeText();
|
|
194
|
+
mustConsume('CLOSE');
|
|
195
|
+
result.push({
|
|
196
|
+
name: name || (pattern ? key++ : ''),
|
|
197
|
+
pattern: name && !pattern ? defaultPattern : pattern,
|
|
198
|
+
prefix,
|
|
199
|
+
suffix,
|
|
200
|
+
modifier: tryConsume('MODIFIER') || '',
|
|
201
|
+
});
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
mustConsume('END');
|
|
205
|
+
}
|
|
206
|
+
return result;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Expose a method for transforming tokens into the path function.
|
|
210
|
+
*/
|
|
211
|
+
export function tokensToFunction(tokens, options = {}) {
|
|
212
|
+
const reFlags = flags(options);
|
|
213
|
+
const { encode = (x) => x, validate = true } = options;
|
|
214
|
+
// Compile all the tokens into regexps.
|
|
215
|
+
const matches = tokens.map((token) => {
|
|
216
|
+
if (typeof token === 'object') {
|
|
217
|
+
return new RegExp(`^(?:${token.pattern})$`, reFlags);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
221
|
+
return (data) => {
|
|
222
|
+
let path = '';
|
|
223
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
224
|
+
const token = tokens[i];
|
|
225
|
+
if (typeof token === 'string') {
|
|
226
|
+
path += token;
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
const value = data ? data[token.name] : undefined;
|
|
230
|
+
const optional = token.modifier === '?' || token.modifier === '*';
|
|
231
|
+
const repeat = token.modifier === '*' || token.modifier === '+';
|
|
232
|
+
if (Array.isArray(value)) {
|
|
233
|
+
if (!repeat) {
|
|
234
|
+
throw new TypeError(`Expected "${token.name}" to not repeat, but got an array`);
|
|
235
|
+
}
|
|
236
|
+
if (value.length === 0) {
|
|
237
|
+
if (optional)
|
|
238
|
+
continue;
|
|
239
|
+
throw new TypeError(`Expected "${token.name}" to not be empty`);
|
|
240
|
+
}
|
|
241
|
+
for (let j = 0; j < value.length; j++) {
|
|
242
|
+
const segment = encode(value[j], token);
|
|
243
|
+
if (validate && !matches[i].test(segment)) {
|
|
244
|
+
throw new TypeError(`Expected all "${token.name}" to match "${token.pattern}", but got "${segment}"`);
|
|
245
|
+
}
|
|
246
|
+
path += token.prefix + segment + token.suffix;
|
|
247
|
+
}
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
if (typeof value === 'string' || typeof value === 'number') {
|
|
251
|
+
const segment = encode(String(value), token);
|
|
252
|
+
if (validate && !matches[i].test(segment)) {
|
|
253
|
+
throw new TypeError(`Expected "${token.name}" to match "${token.pattern}", but got "${segment}"`);
|
|
254
|
+
}
|
|
255
|
+
path += token.prefix + segment + token.suffix;
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
if (optional)
|
|
259
|
+
continue;
|
|
260
|
+
const typeOfMessage = repeat ? 'an array' : 'a string';
|
|
261
|
+
throw new TypeError(`Expected "${token.name}" to be ${typeOfMessage}`);
|
|
262
|
+
}
|
|
263
|
+
return path;
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Compile a string to a template function for the path.
|
|
268
|
+
*/
|
|
269
|
+
export function compile(str, options) {
|
|
270
|
+
return tokensToFunction(parse(str, options), options);
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Create a path match function from `path-to-regexp` output.
|
|
274
|
+
*/
|
|
275
|
+
export function regexpToFunction(re, keys, options = {}) {
|
|
276
|
+
const { decode = (x) => x } = options;
|
|
277
|
+
return function (pathname) {
|
|
278
|
+
const m = re.exec(pathname);
|
|
279
|
+
if (!m)
|
|
280
|
+
return false;
|
|
281
|
+
const { 0: path, index } = m;
|
|
282
|
+
const params = Object.create(null);
|
|
283
|
+
for (let i = 1; i < m.length; i++) {
|
|
284
|
+
// tslint:disable-next-line
|
|
285
|
+
if (m[i] === undefined)
|
|
286
|
+
continue;
|
|
287
|
+
const key = keys[i - 1];
|
|
288
|
+
if (key.modifier === '*' || key.modifier === '+') {
|
|
289
|
+
params[key.name] = m[i].split(key.prefix + key.suffix).map((value) => {
|
|
290
|
+
return decode(value, key);
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
params[key.name] = decode(m[i], key);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return { path, index, params };
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Pull out keys from a regexp.
|
|
302
|
+
*/
|
|
303
|
+
function regexpToRegexp(path, keys) {
|
|
304
|
+
if (!keys)
|
|
305
|
+
return path;
|
|
306
|
+
// Use a negative lookahead to match only capturing groups.
|
|
307
|
+
const groups = path.source.match(/\((?!\?)/g);
|
|
308
|
+
if (groups) {
|
|
309
|
+
for (let i = 0; i < groups.length; i++) {
|
|
310
|
+
keys.push({
|
|
311
|
+
name: i,
|
|
312
|
+
prefix: '',
|
|
313
|
+
suffix: '',
|
|
314
|
+
modifier: '',
|
|
315
|
+
pattern: '',
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return path;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Expose a function for taking tokens and returning a RegExp.
|
|
323
|
+
*/
|
|
324
|
+
export function tokensToRegexp(tokens, keys, options = {}) {
|
|
325
|
+
const { strict = false, start = true, end = true, encode = (x) => x } = options;
|
|
326
|
+
const endsWith = `[${escapeString(options.endsWith || '')}]|$`;
|
|
327
|
+
const delimiter = `[${escapeString(options.delimiter || '/#?')}]`;
|
|
328
|
+
let route = start ? '^' : '';
|
|
329
|
+
// Iterate over the tokens and create our regexp string.
|
|
330
|
+
for (const token of tokens) {
|
|
331
|
+
if (typeof token === 'string') {
|
|
332
|
+
route += escapeString(encode(token));
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
const prefix = escapeString(encode(token.prefix));
|
|
336
|
+
const suffix = escapeString(encode(token.suffix));
|
|
337
|
+
if (token.pattern) {
|
|
338
|
+
if (keys)
|
|
339
|
+
keys.push(token);
|
|
340
|
+
if (prefix || suffix) {
|
|
341
|
+
if (token.modifier === '+' || token.modifier === '*') {
|
|
342
|
+
const mod = token.modifier === '*' ? '?' : '';
|
|
343
|
+
route += `(?:${prefix}((?:${token.pattern})(?:${suffix}${prefix}(?:${token.pattern}))*)${suffix})${mod}`;
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
route += `(?:${prefix}(${token.pattern})${suffix})${token.modifier}`;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
route += `(${token.pattern})${token.modifier}`;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
route += `(?:${prefix}${suffix})${token.modifier}`;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
if (end) {
|
|
359
|
+
if (!strict)
|
|
360
|
+
route += `${delimiter}?`;
|
|
361
|
+
route += !options.endsWith ? '$' : `(?=${endsWith})`;
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
const endToken = tokens[tokens.length - 1];
|
|
365
|
+
const isEndDelimited = typeof endToken === 'string'
|
|
366
|
+
? delimiter.indexOf(endToken[endToken.length - 1]) > -1
|
|
367
|
+
: // tslint:disable-next-line
|
|
368
|
+
endToken === undefined;
|
|
369
|
+
if (!strict) {
|
|
370
|
+
route += `(?:${delimiter}(?=${endsWith}))?`;
|
|
371
|
+
}
|
|
372
|
+
if (!isEndDelimited) {
|
|
373
|
+
route += `(?=${delimiter}|${endsWith})`;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return new RegExp(route, flags(options));
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Create a path regexp from string input.
|
|
380
|
+
*/
|
|
381
|
+
function stringToRegexp(path, keys, options) {
|
|
382
|
+
return tokensToRegexp(parse(path, options), keys, options);
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Normalize the given path string, returning a regular expression.
|
|
386
|
+
*
|
|
387
|
+
* An empty array can be passed in for the keys, which will hold the
|
|
388
|
+
* placeholder key descriptions. For example, using `/user/:id`, `keys` will
|
|
389
|
+
* contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`.
|
|
390
|
+
*/
|
|
391
|
+
export function pathToRegexp(path, keys, options) {
|
|
392
|
+
if (path instanceof RegExp)
|
|
393
|
+
return regexpToRegexp(path, keys);
|
|
394
|
+
// eslint disable reason: co-dependent functions
|
|
395
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
396
|
+
if (Array.isArray(path))
|
|
397
|
+
return arrayToRegexp(path, keys, options);
|
|
398
|
+
return stringToRegexp(path, keys, options);
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Create path match function from `path-to-regexp` spec.
|
|
402
|
+
*/
|
|
403
|
+
export function match(str, options) {
|
|
404
|
+
const keys = [];
|
|
405
|
+
const re = pathToRegexp(str, keys, options);
|
|
406
|
+
return regexpToFunction(re, keys, options);
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Transform an array into a regexp.
|
|
410
|
+
*/
|
|
411
|
+
function arrayToRegexp(paths, keys, options) {
|
|
412
|
+
const parts = paths.map((path) => pathToRegexp(path, keys, options).source);
|
|
413
|
+
return new RegExp(`(?:${parts.join('|')})`, flags(options));
|
|
414
|
+
}
|
|
415
|
+
//# sourceMappingURL=pathToRegexp.js.map
|