@xmachines/play-router 2.0.0 → 2.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/README.md +93 -76
- package/dist/base-route-map.d.ts +63 -57
- package/dist/base-route-map.d.ts.map +1 -1
- package/dist/base-route-map.js +65 -59
- package/dist/base-route-map.js.map +1 -1
- package/dist/build-tree.d.ts +13 -12
- package/dist/build-tree.d.ts.map +1 -1
- package/dist/build-tree.js +30 -28
- package/dist/build-tree.js.map +1 -1
- package/dist/create-route-map-from-tree.d.ts +15 -15
- package/dist/create-route-map-from-tree.js +15 -15
- package/dist/create-route-map.d.ts +18 -16
- package/dist/create-route-map.d.ts.map +1 -1
- package/dist/create-route-map.js +10 -9
- package/dist/create-route-map.js.map +1 -1
- package/dist/errors.d.ts +40 -38
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +40 -38
- package/dist/errors.js.map +1 -1
- package/dist/extract-routes.d.ts +8 -7
- package/dist/extract-routes.d.ts.map +1 -1
- package/dist/extract-routes.js +31 -27
- package/dist/extract-routes.js.map +1 -1
- package/dist/find-route.d.ts +18 -15
- package/dist/find-route.d.ts.map +1 -1
- package/dist/find-route.js +42 -38
- package/dist/find-route.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -10
- package/dist/index.js.map +1 -1
- package/dist/machine-to-graph.d.ts +3 -2
- package/dist/machine-to-graph.d.ts.map +1 -1
- package/dist/machine-to-graph.js +20 -19
- package/dist/machine-to-graph.js.map +1 -1
- package/dist/query.d.ts +39 -37
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +62 -57
- package/dist/query.js.map +1 -1
- package/dist/router-bridge-base.d.ts +240 -190
- package/dist/router-bridge-base.d.ts.map +1 -1
- package/dist/router-bridge-base.js +350 -228
- package/dist/router-bridge-base.js.map +1 -1
- package/dist/router-sync.d.ts +41 -35
- package/dist/router-sync.d.ts.map +1 -1
- package/dist/router-sync.js +53 -45
- package/dist/router-sync.js.map +1 -1
- package/dist/types.d.ts +165 -147
- package/dist/types.d.ts.map +1 -1
- package/dist/url-pattern-utils.d.ts +53 -47
- package/dist/url-pattern-utils.d.ts.map +1 -1
- package/dist/url-pattern-utils.js +61 -55
- package/dist/url-pattern-utils.js.map +1 -1
- package/dist/validate-routes.d.ts +32 -31
- package/dist/validate-routes.d.ts.map +1 -1
- package/dist/validate-routes.js +30 -29
- package/dist/validate-routes.js.map +1 -1
- package/package.json +6 -5
package/dist/base-route-map.js
CHANGED
|
@@ -1,51 +1,54 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* RouteMap —
|
|
2
|
+
* RouteMap — the shared base class of the route map for both directions
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* The class gives you the pattern match on buckets, and every framework adapter
|
|
5
|
+
* uses it. An adapter extends this class, and it therefore holds no copy of the
|
|
6
|
+
* pattern match logic.
|
|
6
7
|
*
|
|
7
|
-
*
|
|
8
|
-
* where k
|
|
9
|
-
*
|
|
8
|
+
* The algorithm: an exact match in O(1) through a Map, then a pattern match on the
|
|
9
|
+
* buckets in O(k), where k is the number of the routes in the bucket of the first
|
|
10
|
+
* segment, and that number is much smaller than the number of all the routes. The
|
|
11
|
+
* class matches a parameterized route with URLPattern.
|
|
10
12
|
*/
|
|
11
13
|
import QuickLRU from "quick-lru";
|
|
12
14
|
import { getURLPatternCtor, getCompiledPattern, getIndexKey, getCandidates, isParameterizedPattern, } from "./url-pattern-utils.js";
|
|
13
15
|
import { URLPatternUnavailableError, InvalidRoutePatternError } from "./errors.js";
|
|
14
16
|
/**
|
|
15
|
-
*
|
|
17
|
+
* The canonical key of a stateId: the bare form, without a `#` at its start.
|
|
16
18
|
*
|
|
17
|
-
* `RouteMap` accepts
|
|
18
|
-
* and on lookup.
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
19
|
+
* `RouteMap` accepts a stateId in the form `"#stateId"` and in the form `"stateId"`,
|
|
20
|
+
* on an insert and on a lookup. Inside the class, the key of every stateId is this
|
|
21
|
+
* canonical bare form. Therefore a consumer writes no handler of the two forms. The
|
|
22
|
+
* class keeps the *registered* form, and `getStateIdByPath` returns it. The map is
|
|
23
|
+
* therefore honest about the declaration of each route.
|
|
22
24
|
*/
|
|
23
25
|
function canonicalStateIdKey(stateId) {
|
|
24
26
|
return stateId.startsWith("#") ? stateId.slice(1) : stateId;
|
|
25
27
|
}
|
|
26
28
|
/**
|
|
27
|
-
*
|
|
29
|
+
* The shared base class of the route map for both directions.
|
|
28
30
|
*
|
|
29
|
-
*
|
|
30
|
-
* own and
|
|
31
|
+
* Every framework adapter uses this class as its route map. An adapter adds no logic
|
|
32
|
+
* of its own, and it inherits the complete public API from here.
|
|
31
33
|
*
|
|
32
|
-
* **
|
|
33
|
-
* -
|
|
34
|
-
* -
|
|
35
|
-
* of routes
|
|
36
|
-
* -
|
|
37
|
-
*
|
|
34
|
+
* **The strategy of a lookup:**
|
|
35
|
+
* - A static path, without a `:param` → a `Map` lookup in O(1)
|
|
36
|
+
* - A dynamic path → a scan of the bucket index in O(k), with `URLPattern`, where
|
|
37
|
+
* `k` is the number of the routes with the same first path segment
|
|
38
|
+
* - The class keeps each result of a first match in an LRU cache. The default size
|
|
39
|
+
* is 500 entries, and the `cacheSize` constructor option changes it
|
|
38
40
|
*
|
|
39
|
-
* **
|
|
40
|
-
* - `:param` —
|
|
41
|
-
* - `:param?` — optional segment
|
|
42
|
-
* - `*` — wildcard
|
|
41
|
+
* **The syntax of a pattern** (`:param`, `:param?`, and `*`):
|
|
42
|
+
* - `:param` — a necessary segment. It matches exactly one segment without a `/`
|
|
43
|
+
* - `:param?` — an optional segment. It matches zero segments or one segment without a `/`
|
|
44
|
+
* - `*` — a wildcard. It matches each number of segments, as URLPattern defines
|
|
43
45
|
*
|
|
44
|
-
* **
|
|
45
|
-
* `"#stateId"` or `"stateId"
|
|
46
|
-
* `getStateIdByPath` returns the stateId exactly as
|
|
47
|
-
* `getPathByStateId` accepts both forms.
|
|
48
|
-
* forms
|
|
46
|
+
* **The forms of a stateId:** you can register a stateId, and you can look one up,
|
|
47
|
+
* in the form `"#stateId"` or in the form `"stateId"`. `RouteMap` makes the
|
|
48
|
+
* canonical form itself. `getStateIdByPath` returns the stateId exactly as you
|
|
49
|
+
* registered it, and `getPathByStateId` accepts both forms. A registration of the
|
|
50
|
+
* same stateId in both forms gives one entry, and the later registration wins for
|
|
51
|
+
* the lookup in the other direction.
|
|
49
52
|
*
|
|
50
53
|
* @example
|
|
51
54
|
* ```typescript
|
|
@@ -67,26 +70,28 @@ function canonicalStateIdKey(stateId) {
|
|
|
67
70
|
* ```
|
|
68
71
|
*/
|
|
69
72
|
export class RouteMap {
|
|
70
|
-
/**
|
|
73
|
+
/** The key is the canonical stateId form, which is bare and without a `#`. */
|
|
71
74
|
stateIdToPath;
|
|
72
75
|
pathToStateId;
|
|
73
76
|
patternBuckets;
|
|
74
77
|
pathMatchCache;
|
|
75
78
|
/**
|
|
76
|
-
*
|
|
79
|
+
* Builds a route map from an array of the mappings between a state ID and a path.
|
|
77
80
|
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
* buckets
|
|
81
|
+
* The constructor puts each static path, which holds no `:param`, into a `Map` for a
|
|
82
|
+
* lookup in O(1). It compiles each parameterized path to a `URLPattern`, and it
|
|
83
|
+
* groups the patterns into the buckets of the first segment. The selection of the
|
|
84
|
+
* candidates is therefore efficient.
|
|
81
85
|
*
|
|
82
|
-
* @param mappings -
|
|
83
|
-
* priority when
|
|
84
|
-
* @param options -
|
|
85
|
-
* `options.cacheSize`:
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
86
|
+
* @param mappings - The array of the `{ stateId, path }` entries. The order gives the
|
|
87
|
+
* priority when more than one pattern can match the same path.
|
|
88
|
+
* @param options - The optional configuration.
|
|
89
|
+
* `options.cacheSize`: the maximum number of the resolved parameterized path
|
|
90
|
+
* lookups in the cache. The default is `500`. Raise it for an application with
|
|
91
|
+
* many different values in a parameterized URL, for example a page of a user
|
|
92
|
+
* profile with thousands of different IDs. After an eviction, the path goes to the
|
|
93
|
+
* bucket pattern scan in O(k) again, which is correct but slower. The smallest
|
|
94
|
+
* effective value is `1`, because QuickLRU requires it.
|
|
90
95
|
*/
|
|
91
96
|
constructor(mappings, { cacheSize = 500 } = {}) {
|
|
92
97
|
this.stateIdToPath = new Map();
|
|
@@ -95,8 +100,9 @@ export class RouteMap {
|
|
|
95
100
|
this.pathMatchCache = new QuickLRU({ maxSize: Math.max(1, cacheSize) });
|
|
96
101
|
let patternOrder = 0;
|
|
97
102
|
for (const { stateId, path } of mappings) {
|
|
98
|
-
//
|
|
99
|
-
// "#stateId" and "stateId". The registered form
|
|
103
|
+
// The key is the canonical bare form. Therefore getPathByStateId accepts
|
|
104
|
+
// "#stateId" and also "stateId". The map keeps the registered form for its return
|
|
105
|
+
// values.
|
|
100
106
|
this.stateIdToPath.set(canonicalStateIdKey(stateId), path);
|
|
101
107
|
if (isParameterizedPattern(path)) {
|
|
102
108
|
const URLPatternCtorFn = getURLPatternCtor();
|
|
@@ -123,14 +129,14 @@ export class RouteMap {
|
|
|
123
129
|
}
|
|
124
130
|
}
|
|
125
131
|
/**
|
|
126
|
-
*
|
|
132
|
+
* Resolves a URL path to its state ID.
|
|
127
133
|
*
|
|
128
|
-
*
|
|
129
|
-
* lookup first, then
|
|
130
|
-
*
|
|
134
|
+
* The method removes the query string and the hash fragment before the match. It
|
|
135
|
+
* tries an exact lookup in O(1) first, then it uses the pattern match on the bucket
|
|
136
|
+
* index. It keeps each result of a first pattern match in the cache.
|
|
131
137
|
*
|
|
132
|
-
* @param path - URL pathname
|
|
133
|
-
* @returns The
|
|
138
|
+
* @param path - The URL pathname. It can hold a query and a hash, for example `"/profile/123?ref=nav"`
|
|
139
|
+
* @returns The state ID of the path, or `null` when no route matches
|
|
134
140
|
*
|
|
135
141
|
* @example
|
|
136
142
|
* ```typescript
|
|
@@ -139,7 +145,7 @@ export class RouteMap {
|
|
|
139
145
|
* ```
|
|
140
146
|
*/
|
|
141
147
|
getStateIdByPath(path) {
|
|
142
|
-
//
|
|
148
|
+
// Remove the query string and the hash fragment before the match
|
|
143
149
|
const cleanPath = path.split("?")[0].split("#")[0];
|
|
144
150
|
const exactMatch = this.pathToStateId.get(cleanPath);
|
|
145
151
|
if (exactMatch !== undefined)
|
|
@@ -159,19 +165,19 @@ export class RouteMap {
|
|
|
159
165
|
return null;
|
|
160
166
|
}
|
|
161
167
|
/**
|
|
162
|
-
*
|
|
168
|
+
* Returns the path pattern of a state ID.
|
|
163
169
|
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
170
|
+
* The method accepts the stateId in the form `"#stateId"` and in the form
|
|
171
|
+
* `"stateId"`, and the form of the registration has no effect. The method makes the
|
|
172
|
+
* canonical form itself. Therefore a consumer tries never both forms.
|
|
167
173
|
*
|
|
168
|
-
* @param stateId -
|
|
169
|
-
* @returns The registered path pattern, or `null`
|
|
174
|
+
* @param stateId - The state ID of the state machine, for example `"profile"` or `"#settings"`
|
|
175
|
+
* @returns The registered path pattern, or `null` when the state ID is unknown
|
|
170
176
|
*
|
|
171
177
|
* @example
|
|
172
178
|
* ```typescript
|
|
173
179
|
* map.getPathByStateId("profile"); // "/profile/:userId"
|
|
174
|
-
* map.getPathByStateId("#profile"); // "/profile/:userId"
|
|
180
|
+
* map.getPathByStateId("#profile"); // "/profile/:userId" — the same entry
|
|
175
181
|
* map.getPathByStateId("missing"); // null
|
|
176
182
|
* ```
|
|
177
183
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-route-map.js","sourceRoot":"","sources":["../src/base-route-map.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"base-route-map.js","sourceRoot":"","sources":["../src/base-route-map.ts"],"names":[],"mappings":"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"}
|
package/dist/build-tree.d.ts
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import type { RouteInfo, RouteTree } from "./types.js";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Builds the hierarchical route tree from the flat list of the routes
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* The function makes a nested tree, and it keeps the parent-child relation of the
|
|
6
|
+
* states. An absolute route becomes a route of the top level, and a relative route
|
|
7
|
+
* goes below its parent. The function also makes the maps of both directions, for
|
|
8
|
+
* the lookup between a state ID and a path.
|
|
8
9
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* the same
|
|
13
|
-
*
|
|
10
|
+
* The detection of a duplicate runs here, after the function resolved each relative
|
|
11
|
+
* route against the complete path of its parent. Therefore two relative routes with
|
|
12
|
+
* the same raw string below two different parents are valid, and two different raw
|
|
13
|
+
* strings that resolve to the same complete path are not: those two overwrite each
|
|
14
|
+
* other in `byPath` in silence.
|
|
14
15
|
*
|
|
15
|
-
* @param routes -
|
|
16
|
-
* @returns RouteTree with root, byStateId map, and byPath map
|
|
17
|
-
* @throws {DuplicateRoutePathError}
|
|
16
|
+
* @param routes - The flat list of the RouteInfo objects, from the extraction
|
|
17
|
+
* @returns The RouteTree, with its root, its byStateId map, and its byPath map
|
|
18
|
+
* @throws {DuplicateRoutePathError} When two states resolve to the same complete path
|
|
18
19
|
*/
|
|
19
20
|
export declare const buildRouteTree: (routes: RouteInfo[]) => RouteTree;
|
|
20
21
|
//# sourceMappingURL=build-tree.d.ts.map
|
package/dist/build-tree.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-tree.d.ts","sourceRoot":"","sources":["../src/build-tree.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAA4B,SAAS,EAAE,MAAM,YAAY,CAAC;AAGjF
|
|
1
|
+
{"version":3,"file":"build-tree.d.ts","sourceRoot":"","sources":["../src/build-tree.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAA4B,SAAS,EAAE,MAAM,YAAY,CAAC;AAGjF;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,cAAc,GAAI,QAAQ,SAAS,EAAE,KAAG,SAyEpD,CAAC"}
|
package/dist/build-tree.js
CHANGED
|
@@ -1,45 +1,47 @@
|
|
|
1
1
|
import { detectDuplicateRoutes } from "./validate-routes.js";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Builds the hierarchical route tree from the flat list of the routes
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* The function makes a nested tree, and it keeps the parent-child relation of the
|
|
6
|
+
* states. An absolute route becomes a route of the top level, and a relative route
|
|
7
|
+
* goes below its parent. The function also makes the maps of both directions, for
|
|
8
|
+
* the lookup between a state ID and a path.
|
|
8
9
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* the same
|
|
13
|
-
*
|
|
10
|
+
* The detection of a duplicate runs here, after the function resolved each relative
|
|
11
|
+
* route against the complete path of its parent. Therefore two relative routes with
|
|
12
|
+
* the same raw string below two different parents are valid, and two different raw
|
|
13
|
+
* strings that resolve to the same complete path are not: those two overwrite each
|
|
14
|
+
* other in `byPath` in silence.
|
|
14
15
|
*
|
|
15
|
-
* @param routes -
|
|
16
|
-
* @returns RouteTree with root, byStateId map, and byPath map
|
|
17
|
-
* @throws {DuplicateRoutePathError}
|
|
16
|
+
* @param routes - The flat list of the RouteInfo objects, from the extraction
|
|
17
|
+
* @returns The RouteTree, with its root, its byStateId map, and its byPath map
|
|
18
|
+
* @throws {DuplicateRoutePathError} When two states resolve to the same complete path
|
|
18
19
|
*/
|
|
19
20
|
export const buildRouteTree = (routes) => {
|
|
20
|
-
// 1. Create root node
|
|
21
|
+
// 1. Create the root node
|
|
21
22
|
const root = {
|
|
22
23
|
id: "__root__",
|
|
23
24
|
path: "/",
|
|
24
25
|
fullPath: "/",
|
|
25
26
|
stateId: "__root__",
|
|
26
|
-
routable: false, //
|
|
27
|
+
routable: false, // The root is a state without a route
|
|
27
28
|
children: [],
|
|
28
29
|
parent: null,
|
|
29
|
-
metadata: "", //
|
|
30
|
+
metadata: "", // The synthetic root has no route, and therefore no real route metadata
|
|
30
31
|
};
|
|
31
|
-
// 2. Initialize maps
|
|
32
|
+
// 2. Initialize the maps
|
|
32
33
|
const byStateId = new Map([[root.stateId, root]]);
|
|
33
34
|
const byPath = new Map([[root.fullPath, root]]);
|
|
34
|
-
// 3. Sort routes by depth
|
|
35
|
+
// 3. Sort the routes by their depth. The shallowest route comes first, for the link to each parent
|
|
35
36
|
// oxlint-disable-next-line unicorn/no-array-sort
|
|
36
37
|
const sorted = routes.slice().sort((a, b) => a.statePath.length - b.statePath.length);
|
|
37
|
-
// 4. Build tree,
|
|
38
|
+
// 4. Build the tree, and collect each node for the detection of a duplicate resolved path
|
|
38
39
|
const insertedNodes = [];
|
|
39
40
|
for (const route of sorted) {
|
|
40
|
-
// Find parent
|
|
41
|
-
// Each entry
|
|
42
|
-
//
|
|
41
|
+
// Find the parent, and walk up the state path.
|
|
42
|
+
// Each entry of statePath is a complete dotted stateId, for example
|
|
43
|
+
// "chain.app.section". Therefore the code tests each ancestor stateId directly,
|
|
44
|
+
// from the end of the list.
|
|
43
45
|
let parentNode = root;
|
|
44
46
|
for (let i = route.statePath.length - 2; i >= 0; i--) {
|
|
45
47
|
const parentStateId = route.statePath[i]; // nosemgrep: gitlab.eslint.detect-object-injection
|
|
@@ -48,11 +50,11 @@ export const buildRouteTree = (routes) => {
|
|
|
48
50
|
break;
|
|
49
51
|
}
|
|
50
52
|
}
|
|
51
|
-
// Build
|
|
53
|
+
// Build the complete path
|
|
52
54
|
const fullPath = route.isAbsolute
|
|
53
55
|
? route.routePath
|
|
54
56
|
: `${parentNode.fullPath}/${route.routePath}`.replace(/\/+/g, "/");
|
|
55
|
-
// Create node
|
|
57
|
+
// Create the node
|
|
56
58
|
const node = {
|
|
57
59
|
id: route.stateId,
|
|
58
60
|
path: route.routePath,
|
|
@@ -63,19 +65,19 @@ export const buildRouteTree = (routes) => {
|
|
|
63
65
|
parent: parentNode,
|
|
64
66
|
metadata: route.metadata,
|
|
65
67
|
};
|
|
66
|
-
// Add pattern
|
|
68
|
+
// Add the pattern, when one is present
|
|
67
69
|
if (route.pattern) {
|
|
68
70
|
node.pattern = route.pattern;
|
|
69
71
|
}
|
|
70
|
-
// Link to parent
|
|
72
|
+
// Link the node to its parent
|
|
71
73
|
parentNode.children.push(node);
|
|
72
|
-
//
|
|
74
|
+
// Put the node in the maps, for the lookup in both directions
|
|
73
75
|
byStateId.set(node.stateId, node);
|
|
74
76
|
byPath.set(node.fullPath, node);
|
|
75
77
|
insertedNodes.push(node);
|
|
76
78
|
}
|
|
77
|
-
// 5.
|
|
78
|
-
//
|
|
79
|
+
// 5. Refuse a collision of two resolved complete paths. A silent overwrite in
|
|
80
|
+
// byPath leaves one state without a URL.
|
|
79
81
|
detectDuplicateRoutes(insertedNodes);
|
|
80
82
|
return { root, byStateId, byPath };
|
|
81
83
|
};
|
package/dist/build-tree.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-tree.js","sourceRoot":"","sources":["../src/build-tree.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAE7D
|
|
1
|
+
{"version":3,"file":"build-tree.js","sourceRoot":"","sources":["../src/build-tree.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,31 +2,31 @@ import type { RouteTree } from "./types.js";
|
|
|
2
2
|
import { RouteMap } from "./base-route-map.js";
|
|
3
3
|
import type { RouteMapOptions } from "./create-route-map.js";
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Creates a `RouteMap` from the node structure of a `RouteTree`.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* `
|
|
9
|
-
* `createRouteMap()` directly.
|
|
7
|
+
* A framework router adapter uses this function when it gives a `RouteTree` from
|
|
8
|
+
* `extractMachineRoutes()`, and not when it calls `createRouteMap()` directly.
|
|
10
9
|
*
|
|
11
|
-
*
|
|
12
|
-
* `node.fullPath` is always the
|
|
13
|
-
*
|
|
14
|
-
*
|
|
10
|
+
* The function walks every node, and it collects the pairs
|
|
11
|
+
* `{ stateId: node.id, path: node.fullPath }`. `node.fullPath` is always the
|
|
12
|
+
* absolute resolved path, for example `"/dashboard/overview"`, and `RouteMap` needs
|
|
13
|
+
* that path for the match of a browser URL. `createRouteMap(machine)` behaves in the
|
|
14
|
+
* same way, because it also uses `node.fullPath`.
|
|
15
15
|
*
|
|
16
|
-
* @param routeTree - A `RouteTree
|
|
17
|
-
* @param options -
|
|
18
|
-
* @returns A `RouteMap` for
|
|
16
|
+
* @param routeTree - A `RouteTree`, as `extractMachineRoutes()` returns it.
|
|
17
|
+
* @param options - The optional configuration, for example `{ cacheSize }` to change the size of the LRU cache.
|
|
18
|
+
* @returns A `RouteMap` for each adapter on `RouterBridgeBase`.
|
|
19
19
|
*
|
|
20
20
|
* @example
|
|
21
21
|
* ```typescript
|
|
22
|
-
* //
|
|
22
|
+
* // The preferred form — one call for an XState machine:
|
|
23
23
|
* import { createRouteMap } from '@xmachines/play-router';
|
|
24
|
-
* const routeMap = createRouteMap(machine); // takes AnyStateMachine
|
|
24
|
+
* const routeMap = createRouteMap(machine); // it takes an AnyStateMachine
|
|
25
25
|
*
|
|
26
|
-
* //
|
|
26
|
+
* // The two-step form, for a framework adapter that works with a route tree:
|
|
27
27
|
* import { extractMachineRoutes, createRouteMapFromTree } from '@xmachines/play-router';
|
|
28
28
|
* const routeTree = extractMachineRoutes(machine);
|
|
29
|
-
* const routeMap = createRouteMapFromTree(routeTree); // uses node.fullPath
|
|
29
|
+
* const routeMap = createRouteMapFromTree(routeTree); // it uses node.fullPath, which is absolute
|
|
30
30
|
* ```
|
|
31
31
|
*/
|
|
32
32
|
export declare function createRouteMapFromTree(routeTree: RouteTree, options?: RouteMapOptions): RouteMap;
|
|
@@ -1,30 +1,30 @@
|
|
|
1
1
|
import { RouteMap } from "./base-route-map.js";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Creates a `RouteMap` from the node structure of a `RouteTree`.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* `
|
|
7
|
-
* `createRouteMap()` directly.
|
|
5
|
+
* A framework router adapter uses this function when it gives a `RouteTree` from
|
|
6
|
+
* `extractMachineRoutes()`, and not when it calls `createRouteMap()` directly.
|
|
8
7
|
*
|
|
9
|
-
*
|
|
10
|
-
* `node.fullPath` is always the
|
|
11
|
-
*
|
|
12
|
-
*
|
|
8
|
+
* The function walks every node, and it collects the pairs
|
|
9
|
+
* `{ stateId: node.id, path: node.fullPath }`. `node.fullPath` is always the
|
|
10
|
+
* absolute resolved path, for example `"/dashboard/overview"`, and `RouteMap` needs
|
|
11
|
+
* that path for the match of a browser URL. `createRouteMap(machine)` behaves in the
|
|
12
|
+
* same way, because it also uses `node.fullPath`.
|
|
13
13
|
*
|
|
14
|
-
* @param routeTree - A `RouteTree
|
|
15
|
-
* @param options -
|
|
16
|
-
* @returns A `RouteMap` for
|
|
14
|
+
* @param routeTree - A `RouteTree`, as `extractMachineRoutes()` returns it.
|
|
15
|
+
* @param options - The optional configuration, for example `{ cacheSize }` to change the size of the LRU cache.
|
|
16
|
+
* @returns A `RouteMap` for each adapter on `RouterBridgeBase`.
|
|
17
17
|
*
|
|
18
18
|
* @example
|
|
19
19
|
* ```typescript
|
|
20
|
-
* //
|
|
20
|
+
* // The preferred form — one call for an XState machine:
|
|
21
21
|
* import { createRouteMap } from '@xmachines/play-router';
|
|
22
|
-
* const routeMap = createRouteMap(machine); // takes AnyStateMachine
|
|
22
|
+
* const routeMap = createRouteMap(machine); // it takes an AnyStateMachine
|
|
23
23
|
*
|
|
24
|
-
* //
|
|
24
|
+
* // The two-step form, for a framework adapter that works with a route tree:
|
|
25
25
|
* import { extractMachineRoutes, createRouteMapFromTree } from '@xmachines/play-router';
|
|
26
26
|
* const routeTree = extractMachineRoutes(machine);
|
|
27
|
-
* const routeMap = createRouteMapFromTree(routeTree); // uses node.fullPath
|
|
27
|
+
* const routeMap = createRouteMapFromTree(routeTree); // it uses node.fullPath, which is absolute
|
|
28
28
|
* ```
|
|
29
29
|
*/
|
|
30
30
|
export function createRouteMapFromTree(routeTree, options) {
|
|
@@ -1,33 +1,35 @@
|
|
|
1
1
|
import type { AnyStateMachine } from "xstate";
|
|
2
2
|
import { RouteMap } from "./base-route-map.js";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* The options of `createRouteMap` and of `createRouteMapFromTree`.
|
|
5
5
|
*/
|
|
6
6
|
export interface RouteMapOptions {
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
8
|
+
* The maximum number of the resolved parameterized path lookups in the cache.
|
|
9
9
|
*
|
|
10
|
-
* `RouteMap.getStateIdByPath()` resolves parameterized
|
|
11
|
-
* `/profile/:userId
|
|
12
|
-
*
|
|
10
|
+
* `RouteMap.getStateIdByPath()` resolves a parameterized pattern, for example
|
|
11
|
+
* `/profile/:userId`, with URLPattern on each call. The map keeps each path of a
|
|
12
|
+
* frequent visit in an LRU cache, and a later lookup of that path is therefore
|
|
13
|
+
* O(1).
|
|
13
14
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
15
|
+
* Raise this value for an application with a large set of parameterized routes, or
|
|
16
|
+
* with a high frequency of the navigation. The default is `500`.
|
|
16
17
|
*/
|
|
17
18
|
cacheSize?: number;
|
|
18
19
|
}
|
|
19
20
|
/**
|
|
20
|
-
*
|
|
21
|
+
* Creates a `RouteMap` from an XState state machine.
|
|
21
22
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
23
|
+
* The function reads every state with a route, which means each state with a
|
|
24
|
+
* `meta.route` field. It then builds the lookup structure between a path and a
|
|
25
|
+
* stateId, for both directions. A subclass of `RouterBridgeBase` uses the map: it
|
|
26
|
+
* converts each change of the browser URL into a `play.route` actor event, and each
|
|
27
|
+
* actor route into a URL.
|
|
26
28
|
*
|
|
27
|
-
* @param machine - XState v5 state machine with `meta.route`
|
|
28
|
-
* @param options -
|
|
29
|
-
* default LRU cache
|
|
30
|
-
* @returns A `RouteMap` for
|
|
29
|
+
* @param machine - The XState v5 state machine, with a `meta.route` annotation on each state with a route.
|
|
30
|
+
* @param options - The optional configuration. Give `{ cacheSize }` to change the
|
|
31
|
+
* default size of the LRU cache of the parameterized path lookups.
|
|
32
|
+
* @returns A `RouteMap` for each adapter on `RouterBridgeBase`.
|
|
31
33
|
*
|
|
32
34
|
* @example
|
|
33
35
|
* ```typescript
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-route-map.d.ts","sourceRoot":"","sources":["../src/create-route-map.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AAG9C,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAE/C;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B
|
|
1
|
+
{"version":3,"file":"create-route-map.d.ts","sourceRoot":"","sources":["../src/create-route-map.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AAG9C,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAE/C;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B;;;;;;;;;;OAUG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,eAAe,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,QAAQ,CAU5F"}
|
package/dist/create-route-map.js
CHANGED
|
@@ -2,17 +2,18 @@ import { extractMachineRoutes } from "./extract-routes.js";
|
|
|
2
2
|
import { getRoutableRoutes } from "./query.js";
|
|
3
3
|
import { RouteMap } from "./base-route-map.js";
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Creates a `RouteMap` from an XState state machine.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* The function reads every state with a route, which means each state with a
|
|
8
|
+
* `meta.route` field. It then builds the lookup structure between a path and a
|
|
9
|
+
* stateId, for both directions. A subclass of `RouterBridgeBase` uses the map: it
|
|
10
|
+
* converts each change of the browser URL into a `play.route` actor event, and each
|
|
11
|
+
* actor route into a URL.
|
|
11
12
|
*
|
|
12
|
-
* @param machine - XState v5 state machine with `meta.route`
|
|
13
|
-
* @param options -
|
|
14
|
-
* default LRU cache
|
|
15
|
-
* @returns A `RouteMap` for
|
|
13
|
+
* @param machine - The XState v5 state machine, with a `meta.route` annotation on each state with a route.
|
|
14
|
+
* @param options - The optional configuration. Give `{ cacheSize }` to change the
|
|
15
|
+
* default size of the LRU cache of the parameterized path lookups.
|
|
16
|
+
* @returns A `RouteMap` for each adapter on `RouterBridgeBase`.
|
|
16
17
|
*
|
|
17
18
|
* @example
|
|
18
19
|
* ```typescript
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-route-map.js","sourceRoot":"","sources":["../src/create-route-map.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"create-route-map.js","sourceRoot":"","sources":["../src/create-route-map.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAoB/C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,cAAc,CAAC,OAAwB,EAAE,OAAyB;IACjF,MAAM,SAAS,GAAG,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAChD,MAAM,MAAM,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;IAC5C,OAAO,IAAI,QAAQ,CAClB,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACrB,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,IAAI,EAAE,IAAI,CAAC,QAAQ;KACnB,CAAC,CAAC,EACH,OAAO,CACP,CAAC;AACH,CAAC"}
|