@xmachines/play-router 6.1.0 → 7.0.0
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 +73 -16
- package/dist/base-route-map.js +240 -270
- package/dist/base-route-map.js.map +1 -1
- package/dist/build-tree.js +84 -96
- package/dist/build-tree.js.map +1 -1
- package/dist/create-route-map-from-tree.js +50 -47
- package/dist/create-route-map-from-tree.js.map +1 -1
- package/dist/errors.js +124 -133
- package/dist/errors.js.map +1 -1
- package/dist/find-route.js +88 -105
- package/dist/find-route.js.map +1 -1
- package/dist/index.js +10 -28
- package/dist/provider-lifecycle.js +109 -143
- package/dist/provider-lifecycle.js.map +1 -1
- package/dist/router-bridge-base.d.ts +4 -4
- package/dist/router-bridge-base.d.ts.map +1 -1
- package/dist/router-bridge-base.js +1026 -1601
- package/dist/router-bridge-base.js.map +1 -1
- package/dist/router-sync.js +107 -139
- package/dist/router-sync.js.map +1 -1
- package/dist/validate-routes.js +58 -66
- package/dist/validate-routes.js.map +1 -1
- package/dist/xstate/create-route-map.js +36 -35
- package/dist/xstate/create-route-map.js.map +1 -1
- package/dist/xstate/extract-routes.js +66 -83
- package/dist/xstate/extract-routes.js.map +1 -1
- package/dist/xstate/index.js +5 -33
- package/dist/xstate/machine-to-graph.js +98 -109
- package/dist/xstate/machine-to-graph.js.map +1 -1
- package/dist/xstate/query.js +190 -212
- package/dist/xstate/query.js.map +1 -1
- package/package.json +16 -16
- package/dist/index.js.map +0 -1
- package/dist/types.js +0 -2
- package/dist/types.js.map +0 -1
- package/dist/xstate/index.js.map +0 -1
- package/dist/xstate/machine-types.js +0 -2
- package/dist/xstate/machine-types.js.map +0 -1
package/dist/base-route-map.js
CHANGED
|
@@ -1,280 +1,250 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* RouteMap — the shared base class of the route map for both directions
|
|
3
|
-
*
|
|
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.
|
|
7
|
-
*
|
|
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.
|
|
12
|
-
*/
|
|
13
|
-
import { createLruCache, getCandidates, getCompiledPattern, getIndexKey, parsePattern, splitLocation, } from "@xmachines/play-url";
|
|
14
1
|
import { InvalidRoutePatternError } from "./errors.js";
|
|
2
|
+
import { createLruCache, getCandidates, getCompiledPattern, getIndexKey, parsePattern, splitLocation } from "@xmachines/play-url";
|
|
3
|
+
//#region packages/play-router/src/base-route-map.ts
|
|
4
|
+
/**
|
|
5
|
+
* RouteMap — the shared base class of the route map for both directions
|
|
6
|
+
*
|
|
7
|
+
* The class gives you the pattern match on buckets, and every framework adapter
|
|
8
|
+
* uses it. An adapter extends this class, and it therefore holds no copy of the
|
|
9
|
+
* pattern match logic.
|
|
10
|
+
*
|
|
11
|
+
* The algorithm: an exact match in O(1) through a Map, then a pattern match on the
|
|
12
|
+
* buckets in O(k), where k is the number of the routes in the bucket of the first
|
|
13
|
+
* segment, and that number is much smaller than the number of all the routes. The
|
|
14
|
+
* class matches a parameterized route with URLPattern.
|
|
15
|
+
*/
|
|
15
16
|
/**
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
* The canonical key of a stateId: the bare form, without a `#` at its start.
|
|
18
|
+
*
|
|
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.
|
|
24
|
+
*/
|
|
24
25
|
function canonicalStateIdKey(stateId) {
|
|
25
|
-
|
|
26
|
+
return stateId.startsWith("#") ? stateId.slice(1) : stateId;
|
|
26
27
|
}
|
|
27
28
|
/**
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
* The bound of the path cache that a map builds for itself.
|
|
30
|
+
*
|
|
31
|
+
* `createLruCache` keeps two generations, so the cache holds twice this number and one
|
|
32
|
+
* more at most. A caller that wants another bound passes a cache: `createLruCache(5000)`
|
|
33
|
+
* builds one. A separate size option would configure THIS cache alone and say so nowhere,
|
|
34
|
+
* and it would do nothing at all beside a cache that the caller passed.
|
|
35
|
+
*/
|
|
36
|
+
var DEFAULT_PATH_CACHE_SIZE = 500;
|
|
36
37
|
/**
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
38
|
+
* The id of one route table: a digest of its mappings, in order.
|
|
39
|
+
*
|
|
40
|
+
* It is the key space that each ROUTE TABLE owns inside a shared path cache. An entry of
|
|
41
|
+
* that cache says which state a path resolves to, and the answer is a pure function of
|
|
42
|
+
* the mappings and the path: two maps built from the SAME mappings resolve every path the
|
|
43
|
+
* same way, and two maps built from different mappings do not. A map with no route for
|
|
44
|
+
* `/settings/x` writes `null`, and a map that DOES hold `/settings/:s` then read that
|
|
45
|
+
* `null` and reported no state for a route of its own.
|
|
46
|
+
*
|
|
47
|
+
* The key therefore names the TABLE, and not the instance. An instance counter kept the
|
|
48
|
+
* two apart as well, and it also gave a server that rebuilds its map for each request a
|
|
49
|
+
* cache that answered nothing: every request opened a key space of its own, and the dead
|
|
50
|
+
* keys of the last one evicted the live entries of this one.
|
|
51
|
+
*
|
|
52
|
+
* Two maps built from the same mappings share the id, which is what a server that
|
|
53
|
+
* rebuilds its map for each request needs, and two maps built from different mappings do
|
|
54
|
+
* not — a map with no route for a path writes `null` for it, and a map that HOLDS one
|
|
55
|
+
* must not read that.
|
|
56
|
+
*
|
|
57
|
+
* A REGISTER of the serialized tables answered this before, and it kept every table
|
|
58
|
+
* string it had seen for the life of the process: a per-tenant table of fifty routes is a
|
|
59
|
+
* few kilobytes, and a bounded register of them still retained megabytes in exactly the
|
|
60
|
+
* per-request case that the cache exists for. A digest retains nothing.
|
|
61
|
+
*
|
|
62
|
+
* ## The collision
|
|
63
|
+
*
|
|
64
|
+
* Two DIFFERENT tables that share a digest share a key space, and the negative entries of
|
|
65
|
+
* one then answer for the other. The digest is 64 bits over the full signature, so a
|
|
66
|
+
* process that built ten thousand distinct route tables carries a chance of about 3 in
|
|
67
|
+
* 10^12 of one collision — far below the chance of the bit flip that would corrupt the
|
|
68
|
+
* cache anyway. This library already identifies by digest: an XMachines id is a Git OID.
|
|
69
|
+
*/
|
|
69
70
|
function routeTableId(mappings) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
hashHigh = Math.imul(hashHigh ^ (code + index), 0x01000193) >>> 0;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
return `${hashLow.toString(36)}.${hashHigh.toString(36)}`;
|
|
71
|
+
let hashLow = 2166136261;
|
|
72
|
+
let hashHigh = 2166136261;
|
|
73
|
+
for (const { stateId, path } of mappings) {
|
|
74
|
+
const entry = `${stateId}\u0000${path}\u0001`;
|
|
75
|
+
for (let index = 0; index < entry.length; index += 1) {
|
|
76
|
+
const code = entry.charCodeAt(index);
|
|
77
|
+
hashLow = Math.imul(hashLow ^ code, 16777619) >>> 0;
|
|
78
|
+
hashHigh = Math.imul(hashHigh ^ code + index, 16777619) >>> 0;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return `${hashLow.toString(36)}.${hashHigh.toString(36)}`;
|
|
85
82
|
}
|
|
86
83
|
/**
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
return stateId;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
this.pathMatchCache.set(cacheKey, null);
|
|
257
|
-
return null;
|
|
258
|
-
}
|
|
259
|
-
/**
|
|
260
|
-
* Returns the path pattern of a state ID.
|
|
261
|
-
*
|
|
262
|
-
* The method accepts the stateId in the form `"#stateId"` and in the form
|
|
263
|
-
* `"stateId"`, and the form of the registration has no effect. The method makes the
|
|
264
|
-
* canonical form itself. Therefore a consumer tries never both forms.
|
|
265
|
-
*
|
|
266
|
-
* @param stateId - The state ID of the state machine, for example `"profile"` or `"#settings"`
|
|
267
|
-
* @returns The registered path pattern, or `null` when the state ID is unknown
|
|
268
|
-
*
|
|
269
|
-
* @example
|
|
270
|
-
* ```typescript
|
|
271
|
-
* map.getPathByStateId("profile"); // "/profile/:userId"
|
|
272
|
-
* map.getPathByStateId("#profile"); // "/profile/:userId" — the same entry
|
|
273
|
-
* map.getPathByStateId("missing"); // null
|
|
274
|
-
* ```
|
|
275
|
-
*/
|
|
276
|
-
getPathByStateId(stateId) {
|
|
277
|
-
return this.stateIdToPath.get(canonicalStateIdKey(stateId)) ?? null;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
84
|
+
* The shared base class of the route map for both directions.
|
|
85
|
+
*
|
|
86
|
+
* Every framework adapter uses this class as its route map. An adapter adds no logic
|
|
87
|
+
* of its own, and it inherits the complete public API from here.
|
|
88
|
+
*
|
|
89
|
+
* **The strategy of a lookup:**
|
|
90
|
+
* - A static path, without a `:param` → a `Map` lookup in O(1)
|
|
91
|
+
* - A dynamic path → a scan of the bucket index in O(k), with `URLPattern`, where
|
|
92
|
+
* `k` is the number of the routes with the same first path segment
|
|
93
|
+
* - The class keeps each result of a first match in an LRU cache. The default cache
|
|
94
|
+
* holds 1001 entries at most, and the `pathCache` constructor option replaces it
|
|
95
|
+
*
|
|
96
|
+
* **The syntax of a pattern:** the whole URLPattern pathname grammar. The forms used most
|
|
97
|
+
* often are:
|
|
98
|
+
* - `:param` — a necessary segment. It matches exactly one segment without a `/`
|
|
99
|
+
* - `:param?` — an optional segment. It matches zero segments or one segment without a `/`
|
|
100
|
+
* - `*` — a wildcard. It matches each number of segments, as URLPattern defines
|
|
101
|
+
*
|
|
102
|
+
* A `{…}` group, a `:param(\d+)` constraint, a `+` modifier and a `\:` escape are read
|
|
103
|
+
* too. The [routing guide](../../docs/guides/routing.md) states every form, the prefix
|
|
104
|
+
* rule, and the one divergence from the standard: a param name of this library can hold a
|
|
105
|
+
* hyphen.
|
|
106
|
+
*
|
|
107
|
+
* **The forms of a stateId:** you can register a stateId, and you can look one up,
|
|
108
|
+
* in the form `"#stateId"` or in the form `"stateId"`. `RouteMap` makes the
|
|
109
|
+
* canonical form itself. `getStateIdByPath` returns the stateId exactly as you
|
|
110
|
+
* registered it, and `getPathByStateId` accepts both forms. A registration of the
|
|
111
|
+
* same stateId in both forms gives one entry, and the later registration wins for
|
|
112
|
+
* the lookup in the other direction.
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```typescript
|
|
116
|
+
* import { RouteMap } from "@xmachines/play-router";
|
|
117
|
+
*
|
|
118
|
+
* const map = new RouteMap([
|
|
119
|
+
* { stateId: "home", path: "/" },
|
|
120
|
+
* { stateId: "profile", path: "/profile/:userId" },
|
|
121
|
+
* { stateId: "settings", path: "/settings/:section?" },
|
|
122
|
+
* ]);
|
|
123
|
+
*
|
|
124
|
+
* map.getStateIdByPath("/"); // "home"
|
|
125
|
+
* map.getStateIdByPath("/profile/123"); // "profile"
|
|
126
|
+
* map.getStateIdByPath("/settings"); // "settings"
|
|
127
|
+
* map.getStateIdByPath("/unknown"); // null
|
|
128
|
+
*
|
|
129
|
+
* map.getPathByStateId("profile"); // "/profile/:userId"
|
|
130
|
+
* map.getPathByStateId("missing"); // null
|
|
131
|
+
* ```
|
|
132
|
+
*/
|
|
133
|
+
var RouteMap = class {
|
|
134
|
+
/** The key is the canonical stateId form, which is bare and without a `#`. */
|
|
135
|
+
stateIdToPath;
|
|
136
|
+
pathToStateId;
|
|
137
|
+
patternBuckets;
|
|
138
|
+
pathMatchCache;
|
|
139
|
+
/** The key space of this ROUTE TABLE inside {@link pathMatchCache}, which a caller may share. */
|
|
140
|
+
pathCacheKey;
|
|
141
|
+
/**
|
|
142
|
+
* The caches that every parse and every compilation of this map reads.
|
|
143
|
+
*
|
|
144
|
+
* Each default stays SHARED on purpose. A map that built one of its own would keep its
|
|
145
|
+
* patterns out of the cache that `find-route.ts` and `router-sync.ts` read, so the same
|
|
146
|
+
* pattern would parse two times. A caller that wants the isolation passes a cache.
|
|
147
|
+
*/
|
|
148
|
+
compileOptions;
|
|
149
|
+
/**
|
|
150
|
+
* Builds a route map from an array of the mappings between a state ID and a path.
|
|
151
|
+
*
|
|
152
|
+
* The constructor puts each static path, which holds no `:param`, into a `Map` for a
|
|
153
|
+
* lookup in O(1). It compiles each parameterized path to a `URLPattern`, and it
|
|
154
|
+
* groups the patterns into the buckets of the first segment. The selection of the
|
|
155
|
+
* candidates is therefore efficient.
|
|
156
|
+
*
|
|
157
|
+
* @param mappings - The array of the `{ stateId, path }` entries. The order gives the
|
|
158
|
+
* priority when more than one pattern can match the same path.
|
|
159
|
+
* @param options - See {@link RouteMapOptions}. A map holds TWO caches, so no option is
|
|
160
|
+
* named `cache`: each one names the cache that it carries.
|
|
161
|
+
*/
|
|
162
|
+
constructor(mappings, options = {}) {
|
|
163
|
+
const { pathCache, ...compileOptions } = options;
|
|
164
|
+
this.compileOptions = compileOptions;
|
|
165
|
+
this.stateIdToPath = /* @__PURE__ */ new Map();
|
|
166
|
+
this.pathToStateId = /* @__PURE__ */ new Map();
|
|
167
|
+
this.patternBuckets = /* @__PURE__ */ new Map();
|
|
168
|
+
this.pathMatchCache = pathCache ?? createLruCache(DEFAULT_PATH_CACHE_SIZE);
|
|
169
|
+
if (pathCache === void 0) this.pathCacheKey = (path) => path;
|
|
170
|
+
else {
|
|
171
|
+
const prefix = `${routeTableId(mappings)}\u0002`;
|
|
172
|
+
this.pathCacheKey = (path) => prefix + path;
|
|
173
|
+
}
|
|
174
|
+
let patternOrder = 0;
|
|
175
|
+
for (const { stateId, path } of mappings) {
|
|
176
|
+
this.stateIdToPath.set(canonicalStateIdKey(stateId), path);
|
|
177
|
+
const parsed = parsePattern(path, this.compileOptions);
|
|
178
|
+
if (parsed.parameterized) {
|
|
179
|
+
const bucketKey = parsed.indexKey;
|
|
180
|
+
const bucket = this.patternBuckets.get(bucketKey) ?? [];
|
|
181
|
+
try {
|
|
182
|
+
bucket.push({
|
|
183
|
+
pattern: getCompiledPattern(path, this.compileOptions),
|
|
184
|
+
stateId,
|
|
185
|
+
order: patternOrder++
|
|
186
|
+
});
|
|
187
|
+
} catch (err) {
|
|
188
|
+
if (err instanceof InvalidRoutePatternError) throw err;
|
|
189
|
+
throw new InvalidRoutePatternError(path, { cause: err });
|
|
190
|
+
}
|
|
191
|
+
this.patternBuckets.set(bucketKey, bucket);
|
|
192
|
+
} else this.pathToStateId.set(path, stateId);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Resolves a URL path to its state ID.
|
|
197
|
+
*
|
|
198
|
+
* The method removes the query string and the hash fragment before the match. It
|
|
199
|
+
* tries an exact lookup in O(1) first, then it uses the pattern match on the bucket
|
|
200
|
+
* index. It keeps each result of a first pattern match in the cache.
|
|
201
|
+
*
|
|
202
|
+
* @param path - The URL pathname. It can hold a query and a hash, for example `"/profile/123?ref=nav"`
|
|
203
|
+
* @returns The state ID of the path, or `null` when no route matches
|
|
204
|
+
*
|
|
205
|
+
* @example
|
|
206
|
+
* ```typescript
|
|
207
|
+
* map.getStateIdByPath("/profile/123"); // "profile"
|
|
208
|
+
* map.getStateIdByPath("/unknown"); // null
|
|
209
|
+
* ```
|
|
210
|
+
*/
|
|
211
|
+
getStateIdByPath(path) {
|
|
212
|
+
const cleanPath = splitLocation(path).pathname;
|
|
213
|
+
const exactMatch = this.pathToStateId.get(cleanPath);
|
|
214
|
+
if (exactMatch !== void 0) return exactMatch;
|
|
215
|
+
const cacheKey = this.pathCacheKey(cleanPath);
|
|
216
|
+
const cachedMatch = this.pathMatchCache.get(cacheKey);
|
|
217
|
+
if (cachedMatch !== void 0) return cachedMatch;
|
|
218
|
+
const candidates = getCandidates(this.patternBuckets, getIndexKey(cleanPath));
|
|
219
|
+
for (const { pattern, stateId } of candidates) if (pattern.exec({ pathname: cleanPath })) {
|
|
220
|
+
this.pathMatchCache.set(cacheKey, stateId);
|
|
221
|
+
return stateId;
|
|
222
|
+
}
|
|
223
|
+
this.pathMatchCache.set(cacheKey, null);
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Returns the path pattern of a state ID.
|
|
228
|
+
*
|
|
229
|
+
* The method accepts the stateId in the form `"#stateId"` and in the form
|
|
230
|
+
* `"stateId"`, and the form of the registration has no effect. The method makes the
|
|
231
|
+
* canonical form itself. Therefore a consumer tries never both forms.
|
|
232
|
+
*
|
|
233
|
+
* @param stateId - The state ID of the state machine, for example `"profile"` or `"#settings"`
|
|
234
|
+
* @returns The registered path pattern, or `null` when the state ID is unknown
|
|
235
|
+
*
|
|
236
|
+
* @example
|
|
237
|
+
* ```typescript
|
|
238
|
+
* map.getPathByStateId("profile"); // "/profile/:userId"
|
|
239
|
+
* map.getPathByStateId("#profile"); // "/profile/:userId" — the same entry
|
|
240
|
+
* map.getPathByStateId("missing"); // null
|
|
241
|
+
* ```
|
|
242
|
+
*/
|
|
243
|
+
getPathByStateId(stateId) {
|
|
244
|
+
return this.stateIdToPath.get(canonicalStateIdKey(stateId)) ?? null;
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
//#endregion
|
|
248
|
+
export { RouteMap };
|
|
249
|
+
|
|
280
250
|
//# sourceMappingURL=base-route-map.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-route-map.js","sourceRoot":"","sources":["../src/base-route-map.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"base-route-map.js","names":[],"sources":["../src/base-route-map.ts"],"sourcesContent":["/**\n * RouteMap — the shared base class of the route map for both directions\n *\n * The class gives you the pattern match on buckets, and every framework adapter\n * uses it. An adapter extends this class, and it therefore holds no copy of the\n * pattern match logic.\n *\n * The algorithm: an exact match in O(1) through a Map, then a pattern match on the\n * buckets in O(k), where k is the number of the routes in the bucket of the first\n * segment, and that number is much smaller than the number of all the routes. The\n * class matches a parameterized route with URLPattern.\n */\n\nimport {\n\tcreateLruCache,\n\tgetCandidates,\n\tgetCompiledPattern,\n\tgetIndexKey,\n\tparsePattern,\n\tsplitLocation,\n\ttype CompileOptions,\n\ttype KeyValueCache,\n\ttype URLPatternLike,\n} from \"@xmachines/play-url\";\n\nimport { InvalidRoutePatternError } from \"./errors.js\";\nimport type { RouteMapOptions } from \"./types.js\";\n\n/**\n * The canonical key of a stateId: the bare form, without a `#` at its start.\n *\n * `RouteMap` accepts a stateId in the form `\"#stateId\"` and in the form `\"stateId\"`,\n * on an insert and on a lookup. Inside the class, the key of every stateId is this\n * canonical bare form. Therefore a consumer writes no handler of the two forms. The\n * class keeps the *registered* form, and `getStateIdByPath` returns it. The map is\n * therefore honest about the declaration of each route.\n */\nfunction canonicalStateIdKey(stateId: string): string {\n\treturn stateId.startsWith(\"#\") ? stateId.slice(1) : stateId;\n}\n\n/**\n * One entry of the map between a state ID and a path.\n *\n * Both fields are `readonly`, because a mapping is immutable after the caller gives\n * it to `RouteMap`. An adapter package re-exports a structurally compatible\n * `RouteMapping` type under its own name. `@xmachines/play-router` publishes this\n * type as `RouteMapping`, and its name therefore does not collide with such a local\n * type of an adapter.\n *\n * @example\n * ```typescript\n * const mapping: RouteMapping = { stateId: \"home\", path: \"/\" };\n * const paramMapping: RouteMapping = { stateId: \"profile\", path: \"/profile/:userId\" };\n * const optionalMapping: RouteMapping = { stateId: \"settings\", path: \"/settings/:section?\" };\n * ```\n */\nexport interface RouteMapping {\n\t/** The state ID of the state machine, for example `\"home\"` or `\"#profile\"` */\n\treadonly stateId: string;\n\t/** The pattern of the URL path, for example `\"/\"`, `\"/profile/:userId\"`, or `\"/settings/:section?\"` */\n\treadonly path: string;\n}\n\n/**\n * The bound of the path cache that a map builds for itself.\n *\n * `createLruCache` keeps two generations, so the cache holds twice this number and one\n * more at most. A caller that wants another bound passes a cache: `createLruCache(5000)`\n * builds one. A separate size option would configure THIS cache alone and say so nowhere,\n * and it would do nothing at all beside a cache that the caller passed.\n */\nconst DEFAULT_PATH_CACHE_SIZE = 500;\n\n/**\n * The id of one route table: a digest of its mappings, in order.\n *\n * It is the key space that each ROUTE TABLE owns inside a shared path cache. An entry of\n * that cache says which state a path resolves to, and the answer is a pure function of\n * the mappings and the path: two maps built from the SAME mappings resolve every path the\n * same way, and two maps built from different mappings do not. A map with no route for\n * `/settings/x` writes `null`, and a map that DOES hold `/settings/:s` then read that\n * `null` and reported no state for a route of its own.\n *\n * The key therefore names the TABLE, and not the instance. An instance counter kept the\n * two apart as well, and it also gave a server that rebuilds its map for each request a\n * cache that answered nothing: every request opened a key space of its own, and the dead\n * keys of the last one evicted the live entries of this one.\n *\n * Two maps built from the same mappings share the id, which is what a server that\n * rebuilds its map for each request needs, and two maps built from different mappings do\n * not — a map with no route for a path writes `null` for it, and a map that HOLDS one\n * must not read that.\n *\n * A REGISTER of the serialized tables answered this before, and it kept every table\n * string it had seen for the life of the process: a per-tenant table of fifty routes is a\n * few kilobytes, and a bounded register of them still retained megabytes in exactly the\n * per-request case that the cache exists for. A digest retains nothing.\n *\n * ## The collision\n *\n * Two DIFFERENT tables that share a digest share a key space, and the negative entries of\n * one then answer for the other. The digest is 64 bits over the full signature, so a\n * process that built ten thousand distinct route tables carries a chance of about 3 in\n * 10^12 of one collision — far below the chance of the bit flip that would corrupt the\n * cache anyway. This library already identifies by digest: an XMachines id is a Git OID.\n */\nfunction routeTableId(mappings: readonly RouteMapping[]): string {\n\t// FNV-1a, in two 32-bit halves, because a bitwise operator of JavaScript truncates to\n\t// 32 bits and `BigInt` costs an allocation for each character.\n\tlet hashLow = 0x811c9dc5;\n\tlet hashHigh = 0x811c9dc5;\n\tfor (const { stateId, path } of mappings) {\n\t\t// The ORDER is significant: it gives the priority when more than one pattern matches,\n\t\t// so the separators below keep `[{a, /x}, {b, /y}]` apart from `[{a, /x/b}, {/y}]`.\n\t\tconst entry = `${stateId}\\u0000${path}\\u0001`;\n\t\tfor (let index = 0; index < entry.length; index += 1) {\n\t\t\tconst code = entry.charCodeAt(index);\n\t\t\thashLow = Math.imul(hashLow ^ code, 0x01000193) >>> 0;\n\t\t\thashHigh = Math.imul(hashHigh ^ (code + index), 0x01000193) >>> 0;\n\t\t}\n\t}\n\treturn `${hashLow.toString(36)}.${hashHigh.toString(36)}`;\n}\n\n/**\n * The shared base class of the route map for both directions.\n *\n * Every framework adapter uses this class as its route map. An adapter adds no logic\n * of its own, and it inherits the complete public API from here.\n *\n * **The strategy of a lookup:**\n * - A static path, without a `:param` → a `Map` lookup in O(1)\n * - A dynamic path → a scan of the bucket index in O(k), with `URLPattern`, where\n * `k` is the number of the routes with the same first path segment\n * - The class keeps each result of a first match in an LRU cache. The default cache\n * holds 1001 entries at most, and the `pathCache` constructor option replaces it\n *\n * **The syntax of a pattern:** the whole URLPattern pathname grammar. The forms used most\n * often are:\n * - `:param` — a necessary segment. It matches exactly one segment without a `/`\n * - `:param?` — an optional segment. It matches zero segments or one segment without a `/`\n * - `*` — a wildcard. It matches each number of segments, as URLPattern defines\n *\n * A `{…}` group, a `:param(\\d+)` constraint, a `+` modifier and a `\\:` escape are read\n * too. The [routing guide](../../docs/guides/routing.md) states every form, the prefix\n * rule, and the one divergence from the standard: a param name of this library can hold a\n * hyphen.\n *\n * **The forms of a stateId:** you can register a stateId, and you can look one up,\n * in the form `\"#stateId\"` or in the form `\"stateId\"`. `RouteMap` makes the\n * canonical form itself. `getStateIdByPath` returns the stateId exactly as you\n * registered it, and `getPathByStateId` accepts both forms. A registration of the\n * same stateId in both forms gives one entry, and the later registration wins for\n * the lookup in the other direction.\n *\n * @example\n * ```typescript\n * import { RouteMap } from \"@xmachines/play-router\";\n *\n * const map = new RouteMap([\n * { stateId: \"home\", path: \"/\" },\n * { stateId: \"profile\", path: \"/profile/:userId\" },\n * { stateId: \"settings\", path: \"/settings/:section?\" },\n * ]);\n *\n * map.getStateIdByPath(\"/\"); // \"home\"\n * map.getStateIdByPath(\"/profile/123\"); // \"profile\"\n * map.getStateIdByPath(\"/settings\"); // \"settings\"\n * map.getStateIdByPath(\"/unknown\"); // null\n *\n * map.getPathByStateId(\"profile\"); // \"/profile/:userId\"\n * map.getPathByStateId(\"missing\"); // null\n * ```\n */\nexport class RouteMap {\n\t/** The key is the canonical stateId form, which is bare and without a `#`. */\n\tprivate stateIdToPath: Map<string, string>;\n\tprivate pathToStateId: Map<string, string>;\n\tprivate patternBuckets: Map<\n\t\tstring,\n\t\tArray<{ pattern: URLPatternLike; stateId: string; order: number }>\n\t>;\n\tprivate pathMatchCache: KeyValueCache<string | null>;\n\t/** The key space of this ROUTE TABLE inside {@link pathMatchCache}, which a caller may share. */\n\tprivate readonly pathCacheKey: (path: string) => string;\n\t/**\n\t * The caches that every parse and every compilation of this map reads.\n\t *\n\t * Each default stays SHARED on purpose. A map that built one of its own would keep its\n\t * patterns out of the cache that `find-route.ts` and `router-sync.ts` read, so the same\n\t * pattern would parse two times. A caller that wants the isolation passes a cache.\n\t */\n\tprivate compileOptions: CompileOptions;\n\n\t/**\n\t * Builds a route map from an array of the mappings between a state ID and a path.\n\t *\n\t * The constructor puts each static path, which holds no `:param`, into a `Map` for a\n\t * lookup in O(1). It compiles each parameterized path to a `URLPattern`, and it\n\t * groups the patterns into the buckets of the first segment. The selection of the\n\t * candidates is therefore efficient.\n\t *\n\t * @param mappings - The array of the `{ stateId, path }` entries. The order gives the\n\t * priority when more than one pattern can match the same path.\n\t * @param options - See {@link RouteMapOptions}. A map holds TWO caches, so no option is\n\t * named `cache`: each one names the cache that it carries.\n\t */\n\tconstructor(mappings: RouteMapping[], options: RouteMapOptions = {}) {\n\t\tconst { pathCache, ...compileOptions } = options;\n\t\tthis.compileOptions = compileOptions;\n\t\tthis.stateIdToPath = new Map();\n\t\tthis.pathToStateId = new Map();\n\t\tthis.patternBuckets = new Map();\n\t\tthis.pathMatchCache = pathCache ?? createLruCache<string | null>(DEFAULT_PATH_CACHE_SIZE);\n\t\t// A map that BUILDS its own cache owns every key in it, and a prefix would only make\n\t\t// each key longer. A map that RECEIVES one shares it, so its keys carry the prefix.\n\t\tif (pathCache === undefined) {\n\t\t\tthis.pathCacheKey = (path) => path;\n\t\t} else {\n\t\t\tconst prefix = `${routeTableId(mappings)}\\u0002`;\n\t\t\tthis.pathCacheKey = (path) => prefix + path;\n\t\t}\n\t\tlet patternOrder = 0;\n\n\t\tfor (const { stateId, path } of mappings) {\n\t\t\t// The key is the canonical bare form. Therefore getPathByStateId accepts\n\t\t\t// \"#stateId\" and also \"stateId\". The map keeps the registered form for its return\n\t\t\t// values.\n\t\t\tthis.stateIdToPath.set(canonicalStateIdKey(stateId), path);\n\n\t\t\t// ONE parse answers both questions: is the path a pattern, and which bucket does\n\t\t\t// it belong to. The key comes from that parse, and not from a split of the raw\n\t\t\t// string. A pattern such as `/books{/:id}?` keeps the literal first segment\n\t\t\t// `books`, and a split on `/` read the key `books{` and put the route in a bucket\n\t\t\t// that the path `/books/1` reaches never.\n\t\t\tconst parsed = parsePattern(path, this.compileOptions);\n\t\t\tif (parsed.parameterized) {\n\t\t\t\tconst bucketKey = parsed.indexKey;\n\t\t\t\tconst bucket = this.patternBuckets.get(bucketKey) ?? [];\n\t\t\t\ttry {\n\t\t\t\t\tbucket.push({\n\t\t\t\t\t\tpattern: getCompiledPattern(path, this.compileOptions),\n\t\t\t\t\t\tstateId,\n\t\t\t\t\t\torder: patternOrder++,\n\t\t\t\t\t});\n\t\t\t\t} catch (err) {\n\t\t\t\t\t// `getCompiledPattern` reports an invalid pattern itself, and with the REASON\n\t\t\t\t\t// when it knows one. A second wrap here replaced that message with the\n\t\t\t\t\t// generic one, so the explanation reached the caller never.\n\t\t\t\t\tif (err instanceof InvalidRoutePatternError) throw err;\n\t\t\t\t\tthrow new InvalidRoutePatternError(path, { cause: err });\n\t\t\t\t}\n\t\t\t\tthis.patternBuckets.set(bucketKey, bucket);\n\t\t\t} else {\n\t\t\t\tthis.pathToStateId.set(path, stateId);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Resolves a URL path to its state ID.\n\t *\n\t * The method removes the query string and the hash fragment before the match. It\n\t * tries an exact lookup in O(1) first, then it uses the pattern match on the bucket\n\t * index. It keeps each result of a first pattern match in the cache.\n\t *\n\t * @param path - The URL pathname. It can hold a query and a hash, for example `\"/profile/123?ref=nav\"`\n\t * @returns The state ID of the path, or `null` when no route matches\n\t *\n\t * @example\n\t * ```typescript\n\t * map.getStateIdByPath(\"/profile/123\"); // \"profile\"\n\t * map.getStateIdByPath(\"/unknown\"); // null\n\t * ```\n\t */\n\tgetStateIdByPath(path: string): string | null {\n\t\t// Remove the query string and the hash fragment before the match. `splitLocation` is\n\t\t// the ONE answer to \"where does a pathname end\", and `RouterBridgeBase`,\n\t\t// `framework-params.ts` and `sanitizePathname` read it too, so the four cannot\n\t\t// disagree. Two splits allocated two arrays of every part on the hottest lookup of\n\t\t// the library, and this function then read one element of each.\n\t\tconst cleanPath = splitLocation(path).pathname;\n\n\t\tconst exactMatch = this.pathToStateId.get(cleanPath);\n\t\tif (exactMatch !== undefined) return exactMatch;\n\n\t\tconst cacheKey = this.pathCacheKey(cleanPath);\n\t\tconst cachedMatch = this.pathMatchCache.get(cacheKey);\n\t\tif (cachedMatch !== undefined) return cachedMatch;\n\n\t\tconst candidates = getCandidates(this.patternBuckets, getIndexKey(cleanPath));\n\t\tfor (const { pattern, stateId } of candidates) {\n\t\t\tconst match = pattern.exec({ pathname: cleanPath });\n\t\t\tif (match) {\n\t\t\t\tthis.pathMatchCache.set(cacheKey, stateId);\n\t\t\t\treturn stateId;\n\t\t\t}\n\t\t}\n\n\t\tthis.pathMatchCache.set(cacheKey, null);\n\t\treturn null;\n\t}\n\n\t/**\n\t * Returns the path pattern of a state ID.\n\t *\n\t * The method accepts the stateId in the form `\"#stateId\"` and in the form\n\t * `\"stateId\"`, and the form of the registration has no effect. The method makes the\n\t * canonical form itself. Therefore a consumer tries never both forms.\n\t *\n\t * @param stateId - The state ID of the state machine, for example `\"profile\"` or `\"#settings\"`\n\t * @returns The registered path pattern, or `null` when the state ID is unknown\n\t *\n\t * @example\n\t * ```typescript\n\t * map.getPathByStateId(\"profile\"); // \"/profile/:userId\"\n\t * map.getPathByStateId(\"#profile\"); // \"/profile/:userId\" — the same entry\n\t * map.getPathByStateId(\"missing\"); // null\n\t * ```\n\t */\n\tgetPathByStateId(stateId: string): string | null {\n\t\treturn this.stateIdToPath.get(canonicalStateIdKey(stateId)) ?? null;\n\t}\n}\n"],"mappings":"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"}
|