@xmachines/play-router 1.0.0-beta.2 → 1.0.0-beta.21
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 +169 -47
- package/dist/base-route-map.d.ts +116 -0
- package/dist/base-route-map.d.ts.map +1 -0
- package/dist/base-route-map.js +206 -0
- package/dist/base-route-map.js.map +1 -0
- package/dist/build-tree.d.ts.map +1 -1
- package/dist/build-tree.js +6 -5
- package/dist/build-tree.js.map +1 -1
- package/dist/connect-router.d.ts.map +1 -1
- package/dist/connect-router.js +35 -45
- package/dist/connect-router.js.map +1 -1
- package/dist/create-browser-history.d.ts +38 -5
- package/dist/create-browser-history.d.ts.map +1 -1
- package/dist/create-browser-history.js +43 -17
- package/dist/create-browser-history.js.map +1 -1
- package/dist/create-route-map.d.ts +21 -1
- package/dist/create-route-map.d.ts.map +1 -1
- package/dist/create-route-map.js +73 -22
- package/dist/create-route-map.js.map +1 -1
- package/dist/errors.d.ts +75 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +85 -0
- package/dist/errors.js.map +1 -0
- package/dist/extract-routes.d.ts +5 -31
- package/dist/extract-routes.d.ts.map +1 -1
- package/dist/extract-routes.js +70 -49
- package/dist/extract-routes.js.map +1 -1
- package/dist/find-route.d.ts +44 -0
- package/dist/find-route.d.ts.map +1 -0
- package/dist/find-route.js +126 -0
- package/dist/find-route.js.map +1 -0
- package/dist/index.d.ts +9 -48
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -131
- package/dist/index.js.map +1 -1
- package/dist/machine-to-graph.d.ts +17 -0
- package/dist/machine-to-graph.d.ts.map +1 -0
- package/dist/machine-to-graph.js +115 -0
- package/dist/machine-to-graph.js.map +1 -0
- package/dist/query.d.ts +44 -1
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +80 -3
- package/dist/query.js.map +1 -1
- package/dist/router-bridge-base.d.ts +49 -19
- package/dist/router-bridge-base.d.ts.map +1 -1
- package/dist/router-bridge-base.js +120 -56
- package/dist/router-bridge-base.js.map +1 -1
- package/dist/router-sync.d.ts +62 -0
- package/dist/router-sync.d.ts.map +1 -0
- package/dist/router-sync.js +87 -0
- package/dist/router-sync.js.map +1 -0
- package/dist/types.d.ts +73 -14
- package/dist/types.d.ts.map +1 -1
- package/dist/validate-routes.d.ts +9 -9
- package/dist/validate-routes.d.ts.map +1 -1
- package/dist/validate-routes.js +12 -11
- package/dist/validate-routes.js.map +1 -1
- package/package.json +36 -18
- package/dist/crawl-machine.d.ts +0 -74
- package/dist/crawl-machine.d.ts.map +0 -1
- package/dist/crawl-machine.js +0 -95
- package/dist/crawl-machine.js.map +0 -1
- package/dist/extract-route.d.ts +0 -25
- package/dist/extract-route.d.ts.map +0 -1
- package/dist/extract-route.js +0 -63
- package/dist/extract-route.js.map +0 -1
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BaseRouteMap — Shared bidirectional route mapping base class
|
|
3
|
+
*
|
|
4
|
+
* Provides bucket-based pattern matching shared across all framework adapters.
|
|
5
|
+
* Adapters extend this class rather than duplicating the pattern-match logic.
|
|
6
|
+
*
|
|
7
|
+
* Algorithm: O(1) exact match via Map, then bucket-based O(k) pattern match
|
|
8
|
+
* where k = routes in the first-segment bucket (typically << total routes).
|
|
9
|
+
* Uses URLPattern for parameterized route matching (same engine as createRouteMap).
|
|
10
|
+
*/
|
|
11
|
+
import QuickLRU from "quick-lru";
|
|
12
|
+
function getURLPatternCtor() {
|
|
13
|
+
return globalThis["URLPattern"];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Normalize a route path so its parameter names are valid URLPattern identifiers.
|
|
17
|
+
*
|
|
18
|
+
* URLPattern requires parameter names to be valid JS identifiers (no hyphens).
|
|
19
|
+
* Hyphenated names like `:cat-id` are replaced with underscored equivalents
|
|
20
|
+
* (`:cat_id`) for the URLPattern compilation step. Since BaseRouteMap only tests
|
|
21
|
+
* for a match (not the captured group values), this substitution is transparent.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* normalizeParamNames("/docs/:cat-id/:page-num?")
|
|
25
|
+
* // → "/docs/:cat_id/:page_num?"
|
|
26
|
+
*/
|
|
27
|
+
function normalizeParamNames(path) {
|
|
28
|
+
return path.replace(/:([A-Za-z][A-Za-z0-9]*(?:-[A-Za-z0-9]+)+)(\??)/g, (_m, name, optional) => {
|
|
29
|
+
return `:${name.replace(/-/g, "_")}${optional}`;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Shared bidirectional route map base class.
|
|
34
|
+
*
|
|
35
|
+
* All framework adapter `RouteMap` classes extend this — they add no logic of their
|
|
36
|
+
* own and inherit the full public API from here.
|
|
37
|
+
*
|
|
38
|
+
* **Lookup strategy:**
|
|
39
|
+
* - Static paths (no `:param`) → O(1) `Map` lookup
|
|
40
|
+
* - Dynamic paths → O(k) bucket-indexed scan using `URLPattern`, where `k` is the number
|
|
41
|
+
* of routes sharing the same first path segment
|
|
42
|
+
* - Results are cached after the first match
|
|
43
|
+
*
|
|
44
|
+
* **Pattern syntax** (`:param` / `:param?`):
|
|
45
|
+
* - `:param` — required segment, matches exactly one non-`/` segment
|
|
46
|
+
* - `:param?` — optional segment, matches zero or one non-`/` segment
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* import { BaseRouteMap } from "@xmachines/play-router";
|
|
51
|
+
*
|
|
52
|
+
* const map = new BaseRouteMap([
|
|
53
|
+
* { stateId: "home", path: "/" },
|
|
54
|
+
* { stateId: "profile", path: "/profile/:userId" },
|
|
55
|
+
* { stateId: "settings", path: "/settings/:section?" },
|
|
56
|
+
* ]);
|
|
57
|
+
*
|
|
58
|
+
* map.getStateIdByPath("/"); // "home"
|
|
59
|
+
* map.getStateIdByPath("/profile/123"); // "profile"
|
|
60
|
+
* map.getStateIdByPath("/settings"); // "settings"
|
|
61
|
+
* map.getStateIdByPath("/unknown"); // null
|
|
62
|
+
*
|
|
63
|
+
* map.getPathByStateId("profile"); // "/profile/:userId"
|
|
64
|
+
* map.getPathByStateId("missing"); // null
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export class BaseRouteMap {
|
|
68
|
+
stateIdToPath;
|
|
69
|
+
pathToStateId;
|
|
70
|
+
patternBuckets;
|
|
71
|
+
pathMatchCache;
|
|
72
|
+
/**
|
|
73
|
+
* Build a route map from an array of state ID ↔ path mappings.
|
|
74
|
+
*
|
|
75
|
+
* Static paths (no `:param`) are indexed in an O(1) `Map`.
|
|
76
|
+
* Parameterized paths are compiled to `URLPattern` and grouped into first-segment
|
|
77
|
+
* buckets for efficient candidate selection.
|
|
78
|
+
*
|
|
79
|
+
* @param mappings - Array of `{ stateId, path }` entries. Order determines
|
|
80
|
+
* priority when multiple patterns could match the same path.
|
|
81
|
+
*/
|
|
82
|
+
constructor(mappings) {
|
|
83
|
+
this.stateIdToPath = new Map();
|
|
84
|
+
this.pathToStateId = new Map();
|
|
85
|
+
this.patternBuckets = new Map();
|
|
86
|
+
this.pathMatchCache = new QuickLRU({ maxSize: 500 });
|
|
87
|
+
let patternOrder = 0;
|
|
88
|
+
for (const { stateId, path } of mappings) {
|
|
89
|
+
this.stateIdToPath.set(stateId, path);
|
|
90
|
+
if (path.includes(":")) {
|
|
91
|
+
const URLPatternCtorFn = getURLPatternCtor();
|
|
92
|
+
if (!URLPatternCtorFn) {
|
|
93
|
+
// Graceful degradation: skip parameterized route when URLPattern unavailable
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const bucketKey = this.getIndexKey(path);
|
|
97
|
+
const bucket = this.patternBuckets.get(bucketKey) ?? [];
|
|
98
|
+
try {
|
|
99
|
+
bucket.push({
|
|
100
|
+
pattern: new URLPatternCtorFn({ pathname: normalizeParamNames(path) }),
|
|
101
|
+
stateId,
|
|
102
|
+
order: patternOrder++,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// Invalid pattern — skip entry
|
|
107
|
+
}
|
|
108
|
+
this.patternBuckets.set(bucketKey, bucket);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
this.pathToStateId.set(path, stateId);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Resolve a URL path to its mapped state ID.
|
|
117
|
+
*
|
|
118
|
+
* Strips query strings and hash fragments before matching. Tries an O(1) exact
|
|
119
|
+
* lookup first, then falls back to bucket-indexed pattern matching. Results are
|
|
120
|
+
* cached after the first pattern match.
|
|
121
|
+
*
|
|
122
|
+
* @param path - URL pathname, optionally including query/hash (e.g., `"/profile/123?ref=nav"`)
|
|
123
|
+
* @returns The mapped state ID, or `null` if no route matches
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```typescript
|
|
127
|
+
* map.getStateIdByPath("/profile/123"); // "profile"
|
|
128
|
+
* map.getStateIdByPath("/unknown"); // null
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
getStateIdByPath(path) {
|
|
132
|
+
// Strip query string and hash fragment for matching
|
|
133
|
+
const cleanPath = path.split("?")[0].split("#")[0];
|
|
134
|
+
const exactMatch = this.pathToStateId.get(cleanPath);
|
|
135
|
+
if (exactMatch !== undefined)
|
|
136
|
+
return exactMatch;
|
|
137
|
+
const cachedMatch = this.pathMatchCache.get(cleanPath);
|
|
138
|
+
if (cachedMatch !== undefined)
|
|
139
|
+
return cachedMatch;
|
|
140
|
+
const candidates = this.getCandidates(this.getIndexKey(cleanPath));
|
|
141
|
+
for (const { pattern, stateId } of candidates) {
|
|
142
|
+
const match = pattern.exec({ pathname: cleanPath });
|
|
143
|
+
if (match) {
|
|
144
|
+
this.pathMatchCache.set(cleanPath, stateId);
|
|
145
|
+
return stateId;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
this.pathMatchCache.set(cleanPath, null);
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Look up the path pattern registered for a state ID.
|
|
153
|
+
*
|
|
154
|
+
* @param stateId - State machine state ID (e.g., `"profile"`, `"#settings"`)
|
|
155
|
+
* @returns The registered path pattern, or `null` if the state ID is unknown
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* ```typescript
|
|
159
|
+
* map.getPathByStateId("profile"); // "/profile/:userId"
|
|
160
|
+
* map.getPathByStateId("missing"); // null
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
getPathByStateId(stateId) {
|
|
164
|
+
return this.stateIdToPath.get(stateId) ?? null;
|
|
165
|
+
}
|
|
166
|
+
getIndexKey(path) {
|
|
167
|
+
const trimmed = path.startsWith("/") ? path.slice(1) : path;
|
|
168
|
+
if (trimmed.length === 0)
|
|
169
|
+
return "/";
|
|
170
|
+
const segment = trimmed.split("/")[0];
|
|
171
|
+
if (segment === undefined || segment.startsWith(":"))
|
|
172
|
+
return "*";
|
|
173
|
+
return segment;
|
|
174
|
+
}
|
|
175
|
+
getCandidates(indexKey) {
|
|
176
|
+
const bucket = this.patternBuckets.get(indexKey) ?? [];
|
|
177
|
+
const wildcardBucket = this.patternBuckets.get("*") ?? [];
|
|
178
|
+
if (bucket.length === 0)
|
|
179
|
+
return wildcardBucket;
|
|
180
|
+
if (wildcardBucket.length === 0)
|
|
181
|
+
return bucket;
|
|
182
|
+
const merged = [];
|
|
183
|
+
let b = 0;
|
|
184
|
+
let w = 0;
|
|
185
|
+
while (b < bucket.length && w < wildcardBucket.length) {
|
|
186
|
+
if (bucket[b].order < wildcardBucket[w].order) {
|
|
187
|
+
merged.push(bucket[b]);
|
|
188
|
+
b += 1;
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
merged.push(wildcardBucket[w]);
|
|
192
|
+
w += 1;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
while (b < bucket.length) {
|
|
196
|
+
merged.push(bucket[b]);
|
|
197
|
+
b += 1;
|
|
198
|
+
}
|
|
199
|
+
while (w < wildcardBucket.length) {
|
|
200
|
+
merged.push(wildcardBucket[w]);
|
|
201
|
+
w += 1;
|
|
202
|
+
}
|
|
203
|
+
return merged;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
//# sourceMappingURL=base-route-map.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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.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,
|
|
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;AAEjF;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,GAAI,QAAQ,SAAS,EAAE,KAAG,SAiEpD,CAAC"}
|
package/dist/build-tree.js
CHANGED
|
@@ -18,7 +18,7 @@ export const buildRouteTree = (routes) => {
|
|
|
18
18
|
routable: false, // Root is not a routable state
|
|
19
19
|
children: [],
|
|
20
20
|
parent: null,
|
|
21
|
-
metadata:
|
|
21
|
+
metadata: "", // Synthetic non-routable root — no real route metadata
|
|
22
22
|
};
|
|
23
23
|
// 2. Initialize maps
|
|
24
24
|
const byStateId = new Map([[root.stateId, root]]);
|
|
@@ -27,11 +27,12 @@ export const buildRouteTree = (routes) => {
|
|
|
27
27
|
const sorted = routes.slice().sort((a, b) => a.statePath.length - b.statePath.length);
|
|
28
28
|
// 4. Build tree
|
|
29
29
|
for (const route of sorted) {
|
|
30
|
-
// Find parent by walking up state path
|
|
30
|
+
// Find parent by walking up state path.
|
|
31
|
+
// Each entry in statePath is a full dotted stateId (e.g., "chain.app.section"),
|
|
32
|
+
// so we check each ancestor stateId directly from the end of the list.
|
|
31
33
|
let parentNode = root;
|
|
32
|
-
for (let i = route.statePath.length -
|
|
33
|
-
const
|
|
34
|
-
const parentStateId = parentPath.join(".");
|
|
34
|
+
for (let i = route.statePath.length - 2; i >= 0; i--) {
|
|
35
|
+
const parentStateId = route.statePath[i];
|
|
35
36
|
if (byStateId.has(parentStateId)) {
|
|
36
37
|
parentNode = byStateId.get(parentStateId);
|
|
37
38
|
break;
|
package/dist/build-tree.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-tree.js","sourceRoot":"","sources":["../src/build-tree.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,MAAmB,EAAa,EAAE;IAChE,sBAAsB;IACtB,MAAM,IAAI,GAAc;QACvB,EAAE,EAAE,UAAU;QACd,IAAI,EAAE,GAAG;QACT,QAAQ,EAAE,GAAG;QACb,OAAO,EAAE,UAAU;QACnB,QAAQ,EAAE,KAAK,EAAE,+BAA+B;QAChD,QAAQ,EAAE,EAAE;QACZ,MAAM,EAAE,IAAI;QACZ,QAAQ,EAAE,EAAE;
|
|
1
|
+
{"version":3,"file":"build-tree.js","sourceRoot":"","sources":["../src/build-tree.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,MAAmB,EAAa,EAAE;IAChE,sBAAsB;IACtB,MAAM,IAAI,GAAc;QACvB,EAAE,EAAE,UAAU;QACd,IAAI,EAAE,GAAG;QACT,QAAQ,EAAE,GAAG;QACb,OAAO,EAAE,UAAU;QACnB,QAAQ,EAAE,KAAK,EAAE,+BAA+B;QAChD,QAAQ,EAAE,EAAE;QACZ,MAAM,EAAE,IAAI;QACZ,QAAQ,EAAE,EAAmB,EAAE,uDAAuD;KACtF,CAAC;IAEF,qBAAqB;IACrB,MAAM,SAAS,GAAG,IAAI,GAAG,CAAoB,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IACrE,MAAM,MAAM,GAAG,IAAI,GAAG,CAAoB,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IAEnE,uEAAuE;IACvE,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAEtF,gBAAgB;IAChB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC5B,wCAAwC;QACxC,gFAAgF;QAChF,uEAAuE;QACvE,IAAI,UAAU,GAAG,IAAI,CAAC;QACtB,KAAK,IAAI,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YACtD,MAAM,aAAa,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;YACzC,IAAI,SAAS,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,CAAC;gBAClC,UAAU,GAAG,SAAS,CAAC,GAAG,CAAC,aAAa,CAAE,CAAC;gBAC3C,MAAM;YACP,CAAC;QACF,CAAC;QAED,kBAAkB;QAClB,MAAM,QAAQ,GAAG,KAAK,CAAC,UAAU;YAChC,CAAC,CAAC,KAAK,CAAC,SAAS;YACjB,CAAC,CAAC,GAAG,UAAU,CAAC,QAAQ,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAEpE,cAAc;QACd,MAAM,IAAI,GAAc;YACvB,EAAE,EAAE,KAAK,CAAC,OAAO;YACjB,IAAI,EAAE,KAAK,CAAC,SAAS;YACrB,QAAQ;YACR,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,QAAQ,EAAE,EAAE;YACZ,MAAM,EAAE,UAAU;YAClB,QAAQ,EAAE,KAAK,CAAC,QAAQ;SACxB,CAAC;QAEF,2BAA2B;QAC3B,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;YACnB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;QAC9B,CAAC;QAED,iBAAiB;QACjB,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAE/B,uCAAuC;QACvC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAClC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IACjC,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;AACpC,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connect-router.d.ts","sourceRoot":"","sources":["../src/connect-router.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAGtD,MAAM,WAAW,oBAAoB;IACpC,KAAK,EAAE,aAAa,CAAC,aAAa,CAAC,GAAG,QAAQ,CAAC;IAC/C,MAAM,EAAE,aAAa,CAAC;IACtB,QAAQ,EAAE,QAAQ,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,MAAM,IAAI,
|
|
1
|
+
{"version":3,"file":"connect-router.d.ts","sourceRoot":"","sources":["../src/connect-router.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAGtD,MAAM,WAAW,oBAAoB;IACpC,KAAK,EAAE,aAAa,CAAC,aAAa,CAAC,GAAG,QAAQ,CAAC;IAC/C,MAAM,EAAE,aAAa,CAAC;IACtB,QAAQ,EAAE,QAAQ,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,MAAM,IAAI,CAqEvE"}
|
package/dist/connect-router.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { watchSignal } from "@xmachines/play-signals";
|
|
2
|
+
import { buildPlayRouteEvent } from "./router-sync.js";
|
|
2
3
|
/**
|
|
3
4
|
* Connect vanilla router to actor (pure browser integration).
|
|
4
5
|
*
|
|
@@ -47,73 +48,62 @@ import { Signal } from "@xmachines/play-signals";
|
|
|
47
48
|
export function connectRouter(options) {
|
|
48
49
|
const { actor, router, routeMap } = options;
|
|
49
50
|
const { history } = router;
|
|
50
|
-
// Prevent circular updates
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
let
|
|
51
|
+
// Prevent circular updates — single navigation-processing guard.
|
|
52
|
+
// Matches RouterBridgeBase.isProcessingNavigation pattern.
|
|
53
|
+
// Reduces race-condition surface from 2³=8 flag combinations to 2.
|
|
54
|
+
let isProcessingNavigation = false;
|
|
54
55
|
// Subscribe to history changes (browser navigation)
|
|
55
56
|
const unsubscribeHistory = history.subscribe((location) => {
|
|
56
|
-
if (
|
|
57
|
+
if (isProcessingNavigation)
|
|
57
58
|
return;
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
const nextRoute = buildPlayRouteEvent({
|
|
60
|
+
pathname: location.pathname,
|
|
61
|
+
search: location.search,
|
|
62
|
+
resolve: (pathname) => routeMap.resolve(pathname),
|
|
63
|
+
});
|
|
64
|
+
if (nextRoute) {
|
|
65
|
+
isProcessingNavigation = true;
|
|
66
|
+
actor.send(nextRoute.event);
|
|
63
67
|
// Check immediately if actor redirected (always-guard)
|
|
64
68
|
// XState processes events synchronously, so snapshot is already updated
|
|
65
69
|
const newActorRoute = actor.currentRoute.get();
|
|
66
|
-
if (newActorRoute && newActorRoute !==
|
|
70
|
+
if (newActorRoute && newActorRoute !== nextRoute.pathname) {
|
|
67
71
|
// Actor redirected - update URL
|
|
68
|
-
isProcessingActorChange = true;
|
|
69
72
|
history.replace(newActorRoute);
|
|
70
|
-
isProcessingActorChange = false;
|
|
71
73
|
}
|
|
72
|
-
// Clear
|
|
73
|
-
|
|
74
|
-
isProcessingPopstate = false;
|
|
74
|
+
// Clear flag synchronously to prevent race conditions
|
|
75
|
+
isProcessingNavigation = false;
|
|
75
76
|
}
|
|
76
77
|
});
|
|
77
78
|
// Watch actor's currentRoute signal (actor-driven navigation)
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
watcher.watch(actor.currentRoute);
|
|
89
|
-
// Sync URL from actor
|
|
90
|
-
const route = actor.currentRoute.get();
|
|
91
|
-
const currentPath = history.location.pathname;
|
|
92
|
-
// Find path from route (reverse lookup)
|
|
93
|
-
// Note: This assumes route tree has path metadata
|
|
94
|
-
// For now, simple implementation - can enhance later
|
|
95
|
-
if (route && route !== currentPath) {
|
|
96
|
-
isProcessingActorChange = true;
|
|
97
|
-
history.push(route);
|
|
98
|
-
isProcessingActorChange = false;
|
|
99
|
-
}
|
|
100
|
-
});
|
|
79
|
+
const unwatchRoute = watchSignal(actor.currentRoute, (route) => {
|
|
80
|
+
if (isProcessingNavigation)
|
|
81
|
+
return;
|
|
82
|
+
const currentPath = history.location.pathname;
|
|
83
|
+
if (route && route !== currentPath) {
|
|
84
|
+
isProcessingNavigation = true;
|
|
85
|
+
history.push(route);
|
|
86
|
+
isProcessingNavigation = false;
|
|
87
|
+
}
|
|
101
88
|
});
|
|
102
|
-
watcher.watch(actor.currentRoute);
|
|
103
89
|
// Sync initial URL to actor
|
|
104
90
|
// ONLY send route event if URL doesn't match actor's initial state
|
|
105
91
|
const initialPath = history.location.pathname;
|
|
106
92
|
const initialActorRoute = actor.currentRoute.get();
|
|
107
93
|
if (initialPath !== initialActorRoute) {
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
94
|
+
const nextRoute = buildPlayRouteEvent({
|
|
95
|
+
pathname: initialPath,
|
|
96
|
+
search: history.location.search,
|
|
97
|
+
resolve: (pathname) => routeMap.resolve(pathname),
|
|
98
|
+
});
|
|
99
|
+
if (nextRoute) {
|
|
100
|
+
actor.send(nextRoute.event);
|
|
111
101
|
}
|
|
112
102
|
}
|
|
113
103
|
// Return cleanup
|
|
114
104
|
return () => {
|
|
115
105
|
unsubscribeHistory();
|
|
116
|
-
|
|
106
|
+
unwatchRoute();
|
|
117
107
|
};
|
|
118
108
|
}
|
|
119
109
|
//# sourceMappingURL=connect-router.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connect-router.js","sourceRoot":"","sources":["../src/connect-router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"connect-router.js","sourceRoot":"","sources":["../src/connect-router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAKtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAQvD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,MAAM,UAAU,aAAa,CAAC,OAA6B;IAC1D,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC;IAC5C,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC;IAE3B,iEAAiE;IACjE,2DAA2D;IAC3D,mEAAmE;IACnE,IAAI,sBAAsB,GAAG,KAAK,CAAC;IAEnC,oDAAoD;IACpD,MAAM,kBAAkB,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,QAAQ,EAAE,EAAE;QACzD,IAAI,sBAAsB;YAAE,OAAO;QAEnC,MAAM,SAAS,GAAG,mBAAmB,CAAC;YACrC,QAAQ,EAAE,QAAQ,CAAC,QAAQ;YAC3B,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,OAAO,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC;SACjD,CAAC,CAAC;QACH,IAAI,SAAS,EAAE,CAAC;YACf,sBAAsB,GAAG,IAAI,CAAC;YAE9B,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;YAE5B,uDAAuD;YACvD,wEAAwE;YACxE,MAAM,aAAa,GAAG,KAAK,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC;YAC/C,IAAI,aAAa,IAAI,aAAa,KAAK,SAAS,CAAC,QAAQ,EAAE,CAAC;gBAC3D,gCAAgC;gBAChC,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;YAChC,CAAC;YAED,sDAAsD;YACtD,sBAAsB,GAAG,KAAK,CAAC;QAChC,CAAC;IACF,CAAC,CAAC,CAAC;IAEH,8DAA8D;IAC9D,MAAM,YAAY,GAAG,WAAW,CAAC,KAAK,CAAC,YAAY,EAAE,CAAC,KAAK,EAAE,EAAE;QAC9D,IAAI,sBAAsB;YAAE,OAAO;QAEnC,MAAM,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAC9C,IAAI,KAAK,IAAI,KAAK,KAAK,WAAW,EAAE,CAAC;YACpC,sBAAsB,GAAG,IAAI,CAAC;YAC9B,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACpB,sBAAsB,GAAG,KAAK,CAAC;QAChC,CAAC;IACF,CAAC,CAAC,CAAC;IAEH,4BAA4B;IAC5B,mEAAmE;IACnE,MAAM,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAC9C,MAAM,iBAAiB,GAAG,KAAK,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC;IAEnD,IAAI,WAAW,KAAK,iBAAiB,EAAE,CAAC;QACvC,MAAM,SAAS,GAAG,mBAAmB,CAAC;YACrC,QAAQ,EAAE,WAAW;YACrB,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAC,MAAM;YAC/B,OAAO,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC;SACjD,CAAC,CAAC;QACH,IAAI,SAAS,EAAE,CAAC;YACf,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAC7B,CAAC;IACF,CAAC;IAED,iBAAiB;IACjB,OAAO,GAAG,EAAE;QACX,kBAAkB,EAAE,CAAC;QACrB,YAAY,EAAE,CAAC;IAChB,CAAC,CAAC;AACH,CAAC"}
|
|
@@ -6,16 +6,16 @@ export interface BrowserHistory {
|
|
|
6
6
|
pathname: string;
|
|
7
7
|
search: string;
|
|
8
8
|
hash: string;
|
|
9
|
-
state:
|
|
9
|
+
state: unknown;
|
|
10
10
|
};
|
|
11
11
|
/**
|
|
12
12
|
* Push new URL to history
|
|
13
13
|
*/
|
|
14
|
-
push(path: string, state?:
|
|
14
|
+
push(path: string, state?: unknown): void;
|
|
15
15
|
/**
|
|
16
16
|
* Replace current URL in history
|
|
17
17
|
*/
|
|
18
|
-
replace(path: string, state?:
|
|
18
|
+
replace(path: string, state?: unknown): void;
|
|
19
19
|
/**
|
|
20
20
|
* Go back/forward
|
|
21
21
|
*/
|
|
@@ -32,10 +32,40 @@ export interface BrowserHistory {
|
|
|
32
32
|
*/
|
|
33
33
|
createHref(path: string): string;
|
|
34
34
|
/**
|
|
35
|
-
* Cleanup
|
|
35
|
+
* Cleanup.
|
|
36
|
+
*
|
|
37
|
+
* Safe to call more than once. When multiple wrappers share the same
|
|
38
|
+
* `window.history`, the original methods are restored only after the last
|
|
39
|
+
* wrapper is destroyed.
|
|
36
40
|
*/
|
|
37
41
|
destroy(): void;
|
|
38
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Minimal window interface for createBrowserHistory
|
|
45
|
+
*
|
|
46
|
+
* Structural interface covering only the properties actually used.
|
|
47
|
+
* Accepts Window, JSDOM window, or any compatible test double.
|
|
48
|
+
* Avoids coupling to `Window & typeof globalThis` which varies across environments.
|
|
49
|
+
*
|
|
50
|
+
* @public
|
|
51
|
+
*/
|
|
52
|
+
export interface BrowserWindow {
|
|
53
|
+
history: {
|
|
54
|
+
readonly state: unknown;
|
|
55
|
+
pushState(state: unknown, title: string, url?: string | null): void;
|
|
56
|
+
replaceState(state: unknown, title: string, url?: string | null): void;
|
|
57
|
+
go(delta: number): void;
|
|
58
|
+
back(): void;
|
|
59
|
+
forward(): void;
|
|
60
|
+
};
|
|
61
|
+
location: {
|
|
62
|
+
readonly pathname: string;
|
|
63
|
+
readonly search: string;
|
|
64
|
+
readonly hash: string;
|
|
65
|
+
};
|
|
66
|
+
addEventListener(type: "popstate", listener: () => void): void;
|
|
67
|
+
removeEventListener(type: "popstate", listener: () => void): void;
|
|
68
|
+
}
|
|
39
69
|
/**
|
|
40
70
|
* Create browser history that wraps window.history
|
|
41
71
|
*
|
|
@@ -61,8 +91,11 @@ export interface BrowserHistory {
|
|
|
61
91
|
* unsubscribe();
|
|
62
92
|
* history.destroy();
|
|
63
93
|
* ```
|
|
94
|
+
*
|
|
95
|
+
* `destroy()` is idempotent and cooperates with other wrappers created for the
|
|
96
|
+
* same `window` instance.
|
|
64
97
|
*/
|
|
65
98
|
export declare function createBrowserHistory(options: {
|
|
66
|
-
window:
|
|
99
|
+
window: BrowserWindow;
|
|
67
100
|
}): BrowserHistory;
|
|
68
101
|
//# sourceMappingURL=create-browser-history.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-browser-history.d.ts","sourceRoot":"","sources":["../src/create-browser-history.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC9B;;OAEG;IACH,QAAQ,CAAC,QAAQ,EAAE;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"create-browser-history.d.ts","sourceRoot":"","sources":["../src/create-browser-history.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC9B;;OAEG;IACH,QAAQ,CAAC,QAAQ,EAAE;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,OAAO,CAAC;KACf,CAAC;IAEF;;OAEG;IACH,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAE1C;;OAEG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAE7C;;OAEG;IACH,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,IAAI,IAAI,IAAI,CAAC;IACb,OAAO,IAAI,IAAI,CAAC;IAEhB;;;OAGG;IACH,SAAS,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,cAAc,CAAC,UAAU,CAAC,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IAEhF;;OAEG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAEjC;;;;;;OAMG;IACH,OAAO,IAAI,IAAI,CAAC;CAChB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,aAAa;IAC7B,OAAO,EAAE;QACR,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;QACxB,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;QACpE,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;QACvE,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;QACxB,IAAI,IAAI,IAAI,CAAC;QACb,OAAO,IAAI,IAAI,CAAC;KAChB,CAAC;IACF,QAAQ,EAAE;QACT,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,gBAAgB,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IAC/D,mBAAmB,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;CAClE;AAWD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE;IAAE,MAAM,EAAE,aAAa,CAAA;CAAE,GAAG,cAAc,CA8EvF"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const historyPatchStates = new WeakMap();
|
|
1
2
|
/**
|
|
2
3
|
* Create browser history that wraps window.history
|
|
3
4
|
*
|
|
@@ -23,13 +24,15 @@
|
|
|
23
24
|
* unsubscribe();
|
|
24
25
|
* history.destroy();
|
|
25
26
|
* ```
|
|
27
|
+
*
|
|
28
|
+
* `destroy()` is idempotent and cooperates with other wrappers created for the
|
|
29
|
+
* same `window` instance.
|
|
26
30
|
*/
|
|
27
31
|
export function createBrowserHistory(options) {
|
|
28
32
|
const win = options.window;
|
|
29
33
|
const listeners = new Set();
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const originalReplaceState = win.history.replaceState.bind(win.history);
|
|
34
|
+
const patchState = getOrCreatePatchState(win);
|
|
35
|
+
let destroyed = false;
|
|
33
36
|
function getLocation() {
|
|
34
37
|
return {
|
|
35
38
|
pathname: win.location.pathname,
|
|
@@ -42,15 +45,7 @@ export function createBrowserHistory(options) {
|
|
|
42
45
|
const location = getLocation();
|
|
43
46
|
listeners.forEach((listener) => listener(location));
|
|
44
47
|
}
|
|
45
|
-
|
|
46
|
-
win.history.pushState = function (state, title, url) {
|
|
47
|
-
originalPushState(state, title, url);
|
|
48
|
-
notify();
|
|
49
|
-
};
|
|
50
|
-
win.history.replaceState = function (state, title, url) {
|
|
51
|
-
originalReplaceState(state, title, url);
|
|
52
|
-
notify();
|
|
53
|
-
};
|
|
48
|
+
patchState.wrappers.add(notify);
|
|
54
49
|
// Listen to popstate
|
|
55
50
|
const popstateHandler = () => notify();
|
|
56
51
|
win.addEventListener("popstate", popstateHandler);
|
|
@@ -81,14 +76,45 @@ export function createBrowserHistory(options) {
|
|
|
81
76
|
return path;
|
|
82
77
|
},
|
|
83
78
|
destroy() {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
79
|
+
if (destroyed) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
destroyed = true;
|
|
83
|
+
patchState.wrappers.delete(notify);
|
|
84
|
+
patchState.refCount -= 1;
|
|
88
85
|
win.removeEventListener("popstate", popstateHandler);
|
|
89
|
-
// Clear subscribers
|
|
90
86
|
listeners.clear();
|
|
87
|
+
if (patchState.refCount === 0) {
|
|
88
|
+
win.history.pushState = patchState.originalPushState;
|
|
89
|
+
win.history.replaceState = patchState.originalReplaceState;
|
|
90
|
+
historyPatchStates.delete(win.history);
|
|
91
|
+
}
|
|
91
92
|
},
|
|
92
93
|
};
|
|
93
94
|
}
|
|
95
|
+
function getOrCreatePatchState(win) {
|
|
96
|
+
const existing = historyPatchStates.get(win.history);
|
|
97
|
+
if (existing) {
|
|
98
|
+
existing.refCount += 1;
|
|
99
|
+
return existing;
|
|
100
|
+
}
|
|
101
|
+
const originalPushState = win.history.pushState.bind(win.history);
|
|
102
|
+
const originalReplaceState = win.history.replaceState.bind(win.history);
|
|
103
|
+
const patchState = {
|
|
104
|
+
originalPushState,
|
|
105
|
+
originalReplaceState,
|
|
106
|
+
wrappers: new Set(),
|
|
107
|
+
refCount: 1,
|
|
108
|
+
};
|
|
109
|
+
win.history.pushState = function (state, title, url) {
|
|
110
|
+
patchState.originalPushState(state, title, url);
|
|
111
|
+
patchState.wrappers.forEach((notify) => notify());
|
|
112
|
+
};
|
|
113
|
+
win.history.replaceState = function (state, title, url) {
|
|
114
|
+
patchState.originalReplaceState(state, title, url);
|
|
115
|
+
patchState.wrappers.forEach((notify) => notify());
|
|
116
|
+
};
|
|
117
|
+
historyPatchStates.set(win.history, patchState);
|
|
118
|
+
return patchState;
|
|
119
|
+
}
|
|
94
120
|
//# sourceMappingURL=create-browser-history.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-browser-history.js","sourceRoot":"","sources":["../src/create-browser-history.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"create-browser-history.js","sourceRoot":"","sources":["../src/create-browser-history.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
import type { RouteTree } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Bidirectional route resolution interface returned by `createRouteMap()`.
|
|
4
|
+
*
|
|
5
|
+
* Resolves incoming URL paths to state IDs (with `#` prefix) and extracts
|
|
6
|
+
* route parameters. Used by `RouterBridgeBase` to translate router navigation
|
|
7
|
+
* events into `play.route` actor events.
|
|
8
|
+
*
|
|
9
|
+
* @see {@link createRouteMap} for the factory function
|
|
10
|
+
* @see {@link BaseRouteMap} for direct state ID ↔ path lookups without params extraction
|
|
11
|
+
*/
|
|
2
12
|
export interface RouteMap {
|
|
3
13
|
/**
|
|
4
14
|
* Resolve a URL path to a state ID and params.
|
|
@@ -27,10 +37,20 @@ export interface RouteMap {
|
|
|
27
37
|
* Architecture:
|
|
28
38
|
* - Pure function - no side effects
|
|
29
39
|
* - O(1) lookups for exact matches via Map
|
|
30
|
-
* - O(
|
|
40
|
+
* - O(k) bucket-indexed pattern matching with URLPattern, where k = routes in the
|
|
41
|
+
* first-segment bucket (typically << total routes)
|
|
31
42
|
* - Returns formatted state IDs (with # prefix)
|
|
32
43
|
* - Extracts params from matched patterns
|
|
33
44
|
*
|
|
45
|
+
* **URLPattern requirement:** Dynamic route pattern matching requires `URLPattern`
|
|
46
|
+
* to be available on `globalThis`. On Node.js < 24 and older browsers, load a polyfill
|
|
47
|
+
* before calling this function:
|
|
48
|
+
* ```typescript
|
|
49
|
+
* import "urlpattern-polyfill"; // before importing @xmachines/play-router
|
|
50
|
+
* ```
|
|
51
|
+
* If `URLPattern` is unavailable, a `URLPatternUnavailableError` is thrown.
|
|
52
|
+
* Static (exact) routes are unaffected by URLPattern availability.
|
|
53
|
+
*
|
|
34
54
|
* Usage:
|
|
35
55
|
* ```typescript
|
|
36
56
|
* const routeTree = extractMachineRoutes(machine);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-route-map.d.ts","sourceRoot":"","sources":["../src/create-route-map.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,
|
|
1
|
+
{"version":3,"file":"create-route-map.d.ts","sourceRoot":"","sources":["../src/create-route-map.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAa,MAAM,YAAY,CAAC;AAiBvD;;;;;;;;;GASG;AACH,MAAM,WAAW,QAAQ;IACxB;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG;QACtB,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;QAClB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KAC/B,CAAC;CACF;AAyCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,SAAS,GAAG,QAAQ,CAuD7D"}
|