@xmachines/play-router 1.0.0-beta.9 → 1.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/LICENSE +21 -0
- package/README.md +246 -341
- package/dist/base-route-map.d.ts +38 -17
- package/dist/base-route-map.d.ts.map +1 -1
- package/dist/base-route-map.js +69 -62
- package/dist/base-route-map.js.map +1 -1
- package/dist/build-tree.d.ts +7 -0
- package/dist/build-tree.d.ts.map +1 -1
- package/dist/build-tree.js +21 -6
- package/dist/build-tree.js.map +1 -1
- package/dist/create-route-map-from-tree.d.ts +33 -0
- package/dist/create-route-map-from-tree.d.ts.map +1 -0
- package/dist/create-route-map-from-tree.js +43 -0
- package/dist/create-route-map-from-tree.js.map +1 -0
- package/dist/create-route-map.d.ts +26 -50
- package/dist/create-route-map.d.ts.map +1 -1
- package/dist/create-route-map.js +24 -114
- package/dist/create-route-map.js.map +1 -1
- package/dist/errors.d.ts +152 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +186 -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 +73 -51
- 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 +103 -0
- package/dist/find-route.js.map +1 -0
- package/dist/index.d.ts +13 -52
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -137
- 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 +49 -1
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +98 -3
- package/dist/query.js.map +1 -1
- package/dist/router-bridge-base.d.ts +187 -48
- package/dist/router-bridge-base.d.ts.map +1 -1
- package/dist/router-bridge-base.js +312 -120
- package/dist/router-bridge-base.js.map +1 -1
- package/dist/router-sync.d.ts +95 -0
- package/dist/router-sync.d.ts.map +1 -0
- package/dist/router-sync.js +161 -0
- package/dist/router-sync.js.map +1 -0
- package/dist/types.d.ts +163 -22
- package/dist/types.d.ts.map +1 -1
- package/dist/url-pattern-utils.d.ts +117 -0
- package/dist/url-pattern-utils.d.ts.map +1 -0
- package/dist/url-pattern-utils.js +176 -0
- package/dist/url-pattern-utils.js.map +1 -0
- package/dist/validate-routes.d.ts +32 -14
- package/dist/validate-routes.d.ts.map +1 -1
- package/dist/validate-routes.js +28 -21
- package/dist/validate-routes.js.map +1 -1
- package/package.json +34 -14
- package/dist/connect-router.d.ts +0 -56
- package/dist/connect-router.d.ts.map +0 -1
- package/dist/connect-router.js +0 -112
- package/dist/connect-router.js.map +0 -1
- 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/create-browser-history.d.ts +0 -93
- package/dist/create-browser-history.d.ts.map +0 -1
- package/dist/create-browser-history.js +0 -94
- package/dist/create-browser-history.js.map +0 -1
- package/dist/create-router.d.ts +0 -73
- package/dist/create-router.d.ts.map +0 -1
- package/dist/create-router.js +0 -63
- package/dist/create-router.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,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared URLPattern utilities — internal module, NOT exported from index.ts
|
|
3
|
+
*
|
|
4
|
+
* Provides the common URLPattern plumbing shared by:
|
|
5
|
+
* - `create-route-map.ts`
|
|
6
|
+
* - `base-route-map.ts`
|
|
7
|
+
* - `find-route.ts`
|
|
8
|
+
* - `router-bridge-base.ts`
|
|
9
|
+
*
|
|
10
|
+
* URLPattern is accessed via globalThis — consumers must load a polyfill on
|
|
11
|
+
* environments without native support (Node < 24, older browsers).
|
|
12
|
+
*
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Retrieve the URLPattern constructor from `globalThis`, or `undefined` if
|
|
17
|
+
* it is not available (Node < 24, older browsers without a polyfill).
|
|
18
|
+
*/
|
|
19
|
+
export function getURLPatternCtor() {
|
|
20
|
+
return globalThis["URLPattern"];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Check whether a route path is a parameterized pattern rather than a concrete URL.
|
|
24
|
+
*
|
|
25
|
+
* A pattern contains `:param` segments (required or optional) and/or the `*`
|
|
26
|
+
* wildcard — both require URLPattern matching and carry no concrete values, so
|
|
27
|
+
* they can never be pushed to a browser URL verbatim.
|
|
28
|
+
*
|
|
29
|
+
* This is THE single source of truth for "is this path a pattern?" — used by
|
|
30
|
+
* `RouteMap` to decide static-map vs pattern-bucket indexing and by
|
|
31
|
+
* `RouterBridgeBase.resolveNavigationPath` to skip non-navigable routes, so the
|
|
32
|
+
* two checks can never drift apart.
|
|
33
|
+
*
|
|
34
|
+
* @param path - URL path or route pattern string (e.g. `/profile/:userId`, `/docs/*`)
|
|
35
|
+
* @returns `true` if the path contains `:param` or `*` pattern syntax
|
|
36
|
+
*/
|
|
37
|
+
export function isParameterizedPattern(path) {
|
|
38
|
+
return path.includes(":") || path.includes("*");
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Matches hyphenated `:param` names (e.g. `:cat-id`, `:page-num?`).
|
|
42
|
+
* URLPattern requires parameter names to be valid JS identifiers, so hyphenated
|
|
43
|
+
* names must be rewritten before compilation.
|
|
44
|
+
*/
|
|
45
|
+
const HYPHENATED_PARAM_RE = /:([A-Za-z][A-Za-z0-9]*(?:-[A-Za-z0-9]+)+)(\??)/g;
|
|
46
|
+
/**
|
|
47
|
+
* Normalize a route path so its parameter names are valid URLPattern identifiers.
|
|
48
|
+
*
|
|
49
|
+
* URLPattern requires parameter names to be valid JS identifiers (no hyphens).
|
|
50
|
+
* Hyphenated names like `:cat-id` are replaced with underscored equivalents
|
|
51
|
+
* (`:cat_id`) for the URLPattern compilation step. Callers that need the captured
|
|
52
|
+
* group values (not just a match test) must map the normalized group names back to
|
|
53
|
+
* the original param names via {@link getNormalizedParamNameMap}.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* normalizeParamNames("/docs/:cat-id/:page-num?")
|
|
57
|
+
* // → "/docs/:cat_id/:page_num?"
|
|
58
|
+
*/
|
|
59
|
+
export function normalizeParamNames(path) {
|
|
60
|
+
return path.replace(HYPHENATED_PARAM_RE, (_m, name, optional) => {
|
|
61
|
+
return `:${name.replace(/-/g, "_")}${optional}`;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Build a map from normalized URLPattern group names back to the original
|
|
66
|
+
* (hyphenated) param names for a route pattern.
|
|
67
|
+
*
|
|
68
|
+
* Only params rewritten by {@link normalizeParamNames} appear in the map —
|
|
69
|
+
* params that are already valid identifiers keep their own name and need no entry.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* getNormalizedParamNameMap("/docs/:cat-id/:plain")
|
|
73
|
+
* // → Map { "cat_id" → "cat-id" }
|
|
74
|
+
*/
|
|
75
|
+
export function getNormalizedParamNameMap(path) {
|
|
76
|
+
const nameMap = new Map();
|
|
77
|
+
for (const match of path.matchAll(HYPHENATED_PARAM_RE)) {
|
|
78
|
+
const original = match[1];
|
|
79
|
+
nameMap.set(original.replace(/-/g, "_"), original);
|
|
80
|
+
}
|
|
81
|
+
return nameMap;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Module-level cache of compiled URLPattern instances keyed by pattern string.
|
|
85
|
+
*
|
|
86
|
+
* Route patterns are compiled on every navigation-time lookup path
|
|
87
|
+
* (`extractRouteParams`, `findRouteByPath`) and once per `RouteMap`
|
|
88
|
+
* construction; compiling the same route pattern each time is wasted work.
|
|
89
|
+
* Growth is bounded by the number of distinct route patterns in the
|
|
90
|
+
* application's route maps. The cache is invalidated when the URLPattern
|
|
91
|
+
* constructor changes (tests swap `globalThis.URLPattern`).
|
|
92
|
+
*/
|
|
93
|
+
const compiledPatternCache = new Map();
|
|
94
|
+
let compiledPatternCtor;
|
|
95
|
+
/**
|
|
96
|
+
* Compile a route pattern to a `URLPattern`, reusing the module-level cache.
|
|
97
|
+
*
|
|
98
|
+
* The pattern is normalized with {@link normalizeParamNames} before compilation
|
|
99
|
+
* so hyphenated param names (`:cat-id`) become valid URLPattern identifiers
|
|
100
|
+
* (`:cat_id`). Callers that extract group values must map the normalized names
|
|
101
|
+
* back via {@link getNormalizedParamNameMap}.
|
|
102
|
+
*
|
|
103
|
+
* Compilation errors from the URLPattern constructor propagate as-is — callers
|
|
104
|
+
* that need a typed error wrap this in `InvalidRoutePatternError`.
|
|
105
|
+
*
|
|
106
|
+
* @param pattern - Route pattern string (e.g. `/profile/:userId`)
|
|
107
|
+
* @param Ctor - URLPattern constructor retrieved via {@link getURLPatternCtor}
|
|
108
|
+
*/
|
|
109
|
+
export function getCompiledPattern(pattern, Ctor) {
|
|
110
|
+
if (Ctor !== compiledPatternCtor) {
|
|
111
|
+
compiledPatternCache.clear();
|
|
112
|
+
compiledPatternCtor = Ctor;
|
|
113
|
+
}
|
|
114
|
+
let compiled = compiledPatternCache.get(pattern);
|
|
115
|
+
if (compiled === undefined) {
|
|
116
|
+
compiled = new Ctor({ pathname: normalizeParamNames(pattern) });
|
|
117
|
+
compiledPatternCache.set(pattern, compiled);
|
|
118
|
+
}
|
|
119
|
+
return compiled;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Compute the bucket index key for a given path or pattern.
|
|
123
|
+
*
|
|
124
|
+
* The key is the first path segment (e.g. `"settings"` for `"/settings/:id"`).
|
|
125
|
+
* Parameterised first segments (`:lang`) fall into the `"*"` wildcard bucket.
|
|
126
|
+
* The root path `"/"` maps to the key `"/"`.
|
|
127
|
+
*
|
|
128
|
+
* @param path - URL path or route pattern string
|
|
129
|
+
*/
|
|
130
|
+
export function getIndexKey(path) {
|
|
131
|
+
const trimmed = path.startsWith("/") ? path.slice(1) : path;
|
|
132
|
+
if (trimmed.length === 0)
|
|
133
|
+
return "/";
|
|
134
|
+
const segment = trimmed.split("/")[0];
|
|
135
|
+
if (segment === undefined || segment.startsWith(":"))
|
|
136
|
+
return "*";
|
|
137
|
+
return segment;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Merge the named-segment bucket and the wildcard bucket for a given index key,
|
|
141
|
+
* preserving insertion order (by `order` field).
|
|
142
|
+
*
|
|
143
|
+
* @param patternBuckets - Map of bucket key → ordered pattern entries
|
|
144
|
+
* @param indexKey - First-segment key computed by `getIndexKey`
|
|
145
|
+
*/
|
|
146
|
+
export function getCandidates(patternBuckets, indexKey) {
|
|
147
|
+
const bucket = patternBuckets.get(indexKey) ?? [];
|
|
148
|
+
const wildcardBucket = patternBuckets.get("*") ?? [];
|
|
149
|
+
if (bucket.length === 0)
|
|
150
|
+
return wildcardBucket;
|
|
151
|
+
if (wildcardBucket.length === 0)
|
|
152
|
+
return bucket;
|
|
153
|
+
const merged = [];
|
|
154
|
+
let b = 0;
|
|
155
|
+
let w = 0;
|
|
156
|
+
while (b < bucket.length && w < wildcardBucket.length) {
|
|
157
|
+
if (bucket[b].order < wildcardBucket[w].order) {
|
|
158
|
+
merged.push(bucket[b]);
|
|
159
|
+
b += 1;
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
merged.push(wildcardBucket[w]);
|
|
163
|
+
w += 1;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
while (b < bucket.length) {
|
|
167
|
+
merged.push(bucket[b]);
|
|
168
|
+
b += 1;
|
|
169
|
+
}
|
|
170
|
+
while (w < wildcardBucket.length) {
|
|
171
|
+
merged.push(wildcardBucket[w]);
|
|
172
|
+
w += 1;
|
|
173
|
+
}
|
|
174
|
+
return merged;
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=url-pattern-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url-pattern-utils.js","sourceRoot":"","sources":["../src/url-pattern-utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,39 +1,57 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* A route entry with its RESOLVED full path — the path after relative routes have
|
|
3
|
+
* been prefixed with their parent's full path during tree construction.
|
|
4
|
+
*
|
|
5
|
+
* `RouteNode` satisfies this shape structurally.
|
|
6
|
+
*/
|
|
7
|
+
export interface ResolvedRoutePath {
|
|
8
|
+
/** State identifier owning the route (e.g. `"app.dashboard.settings"`). */
|
|
9
|
+
stateId: string;
|
|
10
|
+
/** Resolved absolute route path (e.g. `"/dashboard/settings"`). */
|
|
11
|
+
fullPath: string;
|
|
12
|
+
}
|
|
3
13
|
/**
|
|
4
14
|
* Validate route path format
|
|
5
15
|
*
|
|
6
|
-
* Ensures route paths
|
|
7
|
-
*
|
|
16
|
+
* Ensures route paths are non-empty strings. Both absolute paths ("/foo")
|
|
17
|
+
* and relative paths ("foo", "child/nested") are accepted. Relative routes
|
|
18
|
+
* inherit their parent state's path prefix when the tree is built.
|
|
8
19
|
*
|
|
9
|
-
* @param routePath - Route path to validate
|
|
20
|
+
* @param routePath - Route path to validate (absolute or relative)
|
|
10
21
|
* @param stateId - State identifier for error messages
|
|
11
|
-
* @throws {
|
|
22
|
+
* @throws {EmptyRoutePathError} If route path is empty
|
|
12
23
|
*/
|
|
13
24
|
export declare const validateRouteFormat: (routePath: string, stateId: string) => void;
|
|
14
25
|
/**
|
|
15
|
-
* Validate state exists in state
|
|
26
|
+
* Validate state exists in state ID set
|
|
16
27
|
*
|
|
17
28
|
* Ensures referenced state IDs exist in the machine graph.
|
|
18
29
|
* Build-time validation prevents broken route references.
|
|
19
30
|
*
|
|
20
31
|
* @param stateId - State identifier to validate
|
|
21
|
-
* @param
|
|
22
|
-
* @throws {
|
|
32
|
+
* @param stateIds - Set of all known state IDs from the machine graph
|
|
33
|
+
* @throws {InvalidStateIdError} If state ID doesn't exist in set
|
|
23
34
|
*/
|
|
24
|
-
export declare const validateStateExists: (stateId: string,
|
|
35
|
+
export declare const validateStateExists: (stateId: string, stateIds: Set<string>) => void;
|
|
25
36
|
/**
|
|
26
37
|
* Detect duplicate route paths
|
|
27
38
|
*
|
|
28
|
-
* THROWS ERROR when multiple states share the same
|
|
39
|
+
* THROWS ERROR when multiple states share the same RESOLVED full path.
|
|
40
|
+
*
|
|
41
|
+
* Detection operates on resolved full paths (after relative routes have been
|
|
42
|
+
* prefixed with their parent's path in `buildRouteTree`), not on raw route
|
|
43
|
+
* strings: two relative `"settings"` routes under different parents resolve to
|
|
44
|
+
* distinct full paths and are valid, while a relative route and an absolute
|
|
45
|
+
* route resolving to the same URL genuinely collide and must be rejected.
|
|
46
|
+
* Called from `buildRouteTree` once full paths are known.
|
|
29
47
|
*
|
|
30
48
|
* Rationale: URL-based routing requires one-to-one mapping between URLs and states.
|
|
31
49
|
* Multiple states at the same path creates ambiguity for browser navigation (back button,
|
|
32
50
|
* direct URL access). Use different paths for different states, or single state with
|
|
33
51
|
* conditional rendering.
|
|
34
52
|
*
|
|
35
|
-
* @param routes - Array of
|
|
36
|
-
* @throws {
|
|
53
|
+
* @param routes - Array of route entries with resolved full paths (e.g. `RouteNode`s)
|
|
54
|
+
* @throws {DuplicateRoutePathError} If duplicate resolved route paths are detected
|
|
37
55
|
*/
|
|
38
|
-
export declare const detectDuplicateRoutes: (routes:
|
|
56
|
+
export declare const detectDuplicateRoutes: (routes: ResolvedRoutePath[]) => void;
|
|
39
57
|
//# sourceMappingURL=validate-routes.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-routes.d.ts","sourceRoot":"","sources":["../src/validate-routes.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"validate-routes.d.ts","sourceRoot":"","sources":["../src/validate-routes.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IACjC,2EAA2E;IAC3E,OAAO,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mBAAmB,GAAI,WAAW,MAAM,EAAE,SAAS,MAAM,KAAG,IAIxE,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB,GAAI,SAAS,MAAM,EAAE,UAAU,GAAG,CAAC,MAAM,CAAC,KAAG,IAI5E,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,qBAAqB,GAAI,QAAQ,iBAAiB,EAAE,KAAG,IAqBnE,CAAC"}
|
package/dist/validate-routes.js
CHANGED
|
@@ -1,53 +1,62 @@
|
|
|
1
|
+
import { EmptyRoutePathError, InvalidStateIdError, DuplicateRoutePathError } from "./errors.js";
|
|
1
2
|
/**
|
|
2
3
|
* Validate route path format
|
|
3
4
|
*
|
|
4
|
-
* Ensures route paths
|
|
5
|
-
*
|
|
5
|
+
* Ensures route paths are non-empty strings. Both absolute paths ("/foo")
|
|
6
|
+
* and relative paths ("foo", "child/nested") are accepted. Relative routes
|
|
7
|
+
* inherit their parent state's path prefix when the tree is built.
|
|
6
8
|
*
|
|
7
|
-
* @param routePath - Route path to validate
|
|
9
|
+
* @param routePath - Route path to validate (absolute or relative)
|
|
8
10
|
* @param stateId - State identifier for error messages
|
|
9
|
-
* @throws {
|
|
11
|
+
* @throws {EmptyRoutePathError} If route path is empty
|
|
10
12
|
*/
|
|
11
13
|
export const validateRouteFormat = (routePath, stateId) => {
|
|
12
|
-
if (!routePath
|
|
13
|
-
throw new
|
|
14
|
+
if (!routePath) {
|
|
15
|
+
throw new EmptyRoutePathError(stateId);
|
|
14
16
|
}
|
|
15
17
|
};
|
|
16
18
|
/**
|
|
17
|
-
* Validate state exists in state
|
|
19
|
+
* Validate state exists in state ID set
|
|
18
20
|
*
|
|
19
21
|
* Ensures referenced state IDs exist in the machine graph.
|
|
20
22
|
* Build-time validation prevents broken route references.
|
|
21
23
|
*
|
|
22
24
|
* @param stateId - State identifier to validate
|
|
23
|
-
* @param
|
|
24
|
-
* @throws {
|
|
25
|
+
* @param stateIds - Set of all known state IDs from the machine graph
|
|
26
|
+
* @throws {InvalidStateIdError} If state ID doesn't exist in set
|
|
25
27
|
*/
|
|
26
|
-
export const validateStateExists = (stateId,
|
|
27
|
-
if (!
|
|
28
|
-
throw new
|
|
28
|
+
export const validateStateExists = (stateId, stateIds) => {
|
|
29
|
+
if (!stateIds.has(stateId)) {
|
|
30
|
+
throw new InvalidStateIdError(stateId);
|
|
29
31
|
}
|
|
30
32
|
};
|
|
31
33
|
/**
|
|
32
34
|
* Detect duplicate route paths
|
|
33
35
|
*
|
|
34
|
-
* THROWS ERROR when multiple states share the same
|
|
36
|
+
* THROWS ERROR when multiple states share the same RESOLVED full path.
|
|
37
|
+
*
|
|
38
|
+
* Detection operates on resolved full paths (after relative routes have been
|
|
39
|
+
* prefixed with their parent's path in `buildRouteTree`), not on raw route
|
|
40
|
+
* strings: two relative `"settings"` routes under different parents resolve to
|
|
41
|
+
* distinct full paths and are valid, while a relative route and an absolute
|
|
42
|
+
* route resolving to the same URL genuinely collide and must be rejected.
|
|
43
|
+
* Called from `buildRouteTree` once full paths are known.
|
|
35
44
|
*
|
|
36
45
|
* Rationale: URL-based routing requires one-to-one mapping between URLs and states.
|
|
37
46
|
* Multiple states at the same path creates ambiguity for browser navigation (back button,
|
|
38
47
|
* direct URL access). Use different paths for different states, or single state with
|
|
39
48
|
* conditional rendering.
|
|
40
49
|
*
|
|
41
|
-
* @param routes - Array of
|
|
42
|
-
* @throws {
|
|
50
|
+
* @param routes - Array of route entries with resolved full paths (e.g. `RouteNode`s)
|
|
51
|
+
* @throws {DuplicateRoutePathError} If duplicate resolved route paths are detected
|
|
43
52
|
*/
|
|
44
53
|
export const detectDuplicateRoutes = (routes) => {
|
|
45
54
|
const routeMap = new Map();
|
|
46
|
-
// Build map of
|
|
55
|
+
// Build map of resolved full paths to state IDs
|
|
47
56
|
for (const route of routes) {
|
|
48
|
-
const stateIds = routeMap.get(route.
|
|
57
|
+
const stateIds = routeMap.get(route.fullPath) || [];
|
|
49
58
|
stateIds.push(route.stateId);
|
|
50
|
-
routeMap.set(route.
|
|
59
|
+
routeMap.set(route.fullPath, stateIds);
|
|
51
60
|
}
|
|
52
61
|
// Check for duplicates and throw error
|
|
53
62
|
const duplicates = [];
|
|
@@ -57,9 +66,7 @@ export const detectDuplicateRoutes = (routes) => {
|
|
|
57
66
|
}
|
|
58
67
|
}
|
|
59
68
|
if (duplicates.length > 0) {
|
|
60
|
-
throw new
|
|
61
|
-
`Each route path must map to exactly one state.\n` +
|
|
62
|
-
`Use different paths (e.g., /dashboard vs /) or conditional rendering within a single state.`);
|
|
69
|
+
throw new DuplicateRoutePathError(duplicates);
|
|
63
70
|
}
|
|
64
71
|
};
|
|
65
72
|
//# sourceMappingURL=validate-routes.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-routes.js","sourceRoot":"","sources":["../src/validate-routes.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"validate-routes.js","sourceRoot":"","sources":["../src/validate-routes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAehG;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,SAAiB,EAAE,OAAe,EAAQ,EAAE;IAC/E,IAAI,CAAC,SAAS,EAAE,CAAC;QAChB,MAAM,IAAI,mBAAmB,CAAC,OAAO,CAAC,CAAC;IACxC,CAAC;AACF,CAAC,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,OAAe,EAAE,QAAqB,EAAQ,EAAE;IACnF,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,mBAAmB,CAAC,OAAO,CAAC,CAAC;IACxC,CAAC;AACF,CAAC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,MAA2B,EAAQ,EAAE;IAC1E,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAoB,CAAC;IAE7C,gDAAgD;IAChD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC5B,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACpD,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC7B,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACxC,CAAC;IAED,uCAAuC;IACvC,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,KAAK,MAAM,CAAC,SAAS,EAAE,QAAQ,CAAC,IAAI,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC;QACxD,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzB,UAAU,CAAC,IAAI,CAAC,KAAK,SAAS,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC7D,CAAC;IACF,CAAC;IAED,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,uBAAuB,CAAC,UAAU,CAAC,CAAC;IAC/C,CAAC;AACF,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xmachines/play-router",
|
|
3
|
-
"version": "1.0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Route tree extraction from XState v5 state machines. Part of @xmachines/play Universal Player Architecture.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"routing",
|
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
],
|
|
12
12
|
"license": "MIT",
|
|
13
13
|
"author": "XMachines",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+ssh://git@gitlab.com/xmachin-es/xmachines-js.git",
|
|
17
|
+
"directory": "packages/play-router"
|
|
18
|
+
},
|
|
14
19
|
"files": [
|
|
15
20
|
"dist",
|
|
16
21
|
"README.md",
|
|
@@ -19,8 +24,14 @@
|
|
|
19
24
|
"type": "module",
|
|
20
25
|
"exports": {
|
|
21
26
|
".": {
|
|
27
|
+
"source": "./src/index.ts",
|
|
22
28
|
"types": "./dist/index.d.ts",
|
|
23
29
|
"import": "./dist/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./errors": {
|
|
32
|
+
"source": "./src/errors.ts",
|
|
33
|
+
"types": "./dist/errors.d.ts",
|
|
34
|
+
"import": "./dist/errors.js"
|
|
24
35
|
}
|
|
25
36
|
},
|
|
26
37
|
"publishConfig": {
|
|
@@ -28,33 +39,42 @@
|
|
|
28
39
|
},
|
|
29
40
|
"scripts": {
|
|
30
41
|
"build": "tsc --build",
|
|
31
|
-
"clean": "rm -rf dist *.tsbuildinfo node_modules/.
|
|
32
|
-
"
|
|
42
|
+
"clean": "rm -rf dist *.tsbuildinfo coverage .vitest-attachments test/browser/__screenshots__ node_modules/.svelte2tsx-* node_modules/.vite*",
|
|
43
|
+
"lint": "oxlint .",
|
|
44
|
+
"format": "oxfmt .",
|
|
33
45
|
"test": "vitest",
|
|
34
46
|
"prepublishOnly": "npm run build"
|
|
35
47
|
},
|
|
36
48
|
"dependencies": {
|
|
37
|
-
"@
|
|
38
|
-
"@xmachines/play
|
|
39
|
-
"@xmachines/play-
|
|
40
|
-
"
|
|
49
|
+
"@statelyai/graph": "^2.1.0",
|
|
50
|
+
"@xmachines/play": "1.0.0",
|
|
51
|
+
"@xmachines/play-actor": "1.0.0",
|
|
52
|
+
"@xmachines/play-signals": "1.0.0",
|
|
53
|
+
"quick-lru": "^7.3.0"
|
|
41
54
|
},
|
|
42
55
|
"devDependencies": {
|
|
43
|
-
"@
|
|
44
|
-
"@
|
|
45
|
-
"
|
|
56
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
57
|
+
"@types/node": "^26.1.1",
|
|
58
|
+
"@vitest/browser-playwright": "^4.1.10",
|
|
59
|
+
"@xmachines/shared": "1.0.0",
|
|
60
|
+
"oxfmt": "^0.58.0",
|
|
61
|
+
"oxlint": "^1.73.0",
|
|
62
|
+
"typescript": "^5.9.3 || ^6.0.3",
|
|
46
63
|
"urlpattern-polyfill": "^10.1.0",
|
|
47
|
-
"vitest": "^4.1.
|
|
48
|
-
"xstate": "^5.
|
|
64
|
+
"vitest": "^4.1.10",
|
|
65
|
+
"xstate": "^5.31.0"
|
|
49
66
|
},
|
|
50
67
|
"peerDependencies": {
|
|
51
68
|
"urlpattern-polyfill": "^10.1.0",
|
|
52
|
-
"xstate": "^5.
|
|
69
|
+
"xstate": "^5.31.0"
|
|
53
70
|
},
|
|
54
71
|
"peerDependenciesMeta": {
|
|
55
72
|
"urlpattern-polyfill": {
|
|
56
73
|
"optional": true
|
|
57
74
|
}
|
|
58
75
|
},
|
|
59
|
-
"
|
|
76
|
+
"engines": {
|
|
77
|
+
"node": ">=22.0.0"
|
|
78
|
+
},
|
|
79
|
+
"_devDependencies_note": "xstate appears in both peerDependencies and devDependencies intentionally. devDependencies provides workspace resolution for local builds and tests. peerDependencies declares the consumer version constraint. Both are pinned to ^5.30.0 to prevent drift. urlpattern-polyfill is an optional peer dependency — consumers on Node < 24 or older browsers install it themselves. devDependencies entry ensures workspace test resolution."
|
|
60
80
|
}
|
package/dist/connect-router.d.ts
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import type { AbstractActor, Routable } from "@xmachines/play-actor";
|
|
2
|
-
import type { AnyActorLogic } from "xstate";
|
|
3
|
-
import type { VanillaRouter } from "./create-router.js";
|
|
4
|
-
import type { RouteMap } from "./create-route-map.js";
|
|
5
|
-
export interface ConnectRouterOptions {
|
|
6
|
-
actor: AbstractActor<AnyActorLogic> & Routable;
|
|
7
|
-
router: VanillaRouter;
|
|
8
|
-
routeMap: RouteMap;
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Connect vanilla router to actor (pure browser integration).
|
|
12
|
-
*
|
|
13
|
-
* This is the LOW-LEVEL API for maximum control. No JSX, no rendering,
|
|
14
|
-
* just actor ↔ router synchronization.
|
|
15
|
-
*
|
|
16
|
-
* Use this when:
|
|
17
|
-
* - You want manual control over rendering
|
|
18
|
-
* - Using non-JSX framework (jQuery, Alpine, HTMX, etc.)
|
|
19
|
-
* - Building custom integration
|
|
20
|
-
*
|
|
21
|
-
* For JSX frameworks, use framework adapters instead:
|
|
22
|
-
* - @xmachines/play-react (React components)
|
|
23
|
-
* - Future: @xmachines/play-preact, @xmachines/play-solid, @xmachines/play-vue
|
|
24
|
-
*
|
|
25
|
-
* Architecture:
|
|
26
|
-
* - Subscribes to history changes → sends play.route to actor
|
|
27
|
-
* - Watches actor.currentRoute signal → updates browser URL
|
|
28
|
-
* - Prevents circular updates (history change triggers actor, actor triggers history)
|
|
29
|
-
* - Returns cleanup function
|
|
30
|
-
*
|
|
31
|
-
* Usage:
|
|
32
|
-
* ```typescript
|
|
33
|
-
* import { createBrowserHistory, createRouter, connectRouter } from '@xmachines/play-router';
|
|
34
|
-
*
|
|
35
|
-
* const history = createBrowserHistory({ window });
|
|
36
|
-
* const router = createRouter({ routeTree, history });
|
|
37
|
-
*
|
|
38
|
-
* // Connect router to actor
|
|
39
|
-
* const disconnect = connectRouter({ actor, router, routeMap });
|
|
40
|
-
*
|
|
41
|
-
* // User handles rendering (vanilla JS)
|
|
42
|
-
* const watcher = new Signal.subtle.Watcher(() => {
|
|
43
|
-
* queueMicrotask(() => {
|
|
44
|
-
* const view = actor.currentView.get();
|
|
45
|
-
* document.getElementById('app').innerHTML = render(view);
|
|
46
|
-
* });
|
|
47
|
-
* });
|
|
48
|
-
* watcher.watch(actor.currentView);
|
|
49
|
-
*
|
|
50
|
-
* // Later: cleanup
|
|
51
|
-
* disconnect();
|
|
52
|
-
* watcher.unwatch(actor.currentView);
|
|
53
|
-
* ```
|
|
54
|
-
*/
|
|
55
|
-
export declare function connectRouter(options: ConnectRouterOptions): () => void;
|
|
56
|
-
//# sourceMappingURL=connect-router.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
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;AAEtD,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,CA4EvE"}
|
package/dist/connect-router.js
DELETED
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { Signal } from "@xmachines/play-signals";
|
|
2
|
-
/**
|
|
3
|
-
* Connect vanilla router to actor (pure browser integration).
|
|
4
|
-
*
|
|
5
|
-
* This is the LOW-LEVEL API for maximum control. No JSX, no rendering,
|
|
6
|
-
* just actor ↔ router synchronization.
|
|
7
|
-
*
|
|
8
|
-
* Use this when:
|
|
9
|
-
* - You want manual control over rendering
|
|
10
|
-
* - Using non-JSX framework (jQuery, Alpine, HTMX, etc.)
|
|
11
|
-
* - Building custom integration
|
|
12
|
-
*
|
|
13
|
-
* For JSX frameworks, use framework adapters instead:
|
|
14
|
-
* - @xmachines/play-react (React components)
|
|
15
|
-
* - Future: @xmachines/play-preact, @xmachines/play-solid, @xmachines/play-vue
|
|
16
|
-
*
|
|
17
|
-
* Architecture:
|
|
18
|
-
* - Subscribes to history changes → sends play.route to actor
|
|
19
|
-
* - Watches actor.currentRoute signal → updates browser URL
|
|
20
|
-
* - Prevents circular updates (history change triggers actor, actor triggers history)
|
|
21
|
-
* - Returns cleanup function
|
|
22
|
-
*
|
|
23
|
-
* Usage:
|
|
24
|
-
* ```typescript
|
|
25
|
-
* import { createBrowserHistory, createRouter, connectRouter } from '@xmachines/play-router';
|
|
26
|
-
*
|
|
27
|
-
* const history = createBrowserHistory({ window });
|
|
28
|
-
* const router = createRouter({ routeTree, history });
|
|
29
|
-
*
|
|
30
|
-
* // Connect router to actor
|
|
31
|
-
* const disconnect = connectRouter({ actor, router, routeMap });
|
|
32
|
-
*
|
|
33
|
-
* // User handles rendering (vanilla JS)
|
|
34
|
-
* const watcher = new Signal.subtle.Watcher(() => {
|
|
35
|
-
* queueMicrotask(() => {
|
|
36
|
-
* const view = actor.currentView.get();
|
|
37
|
-
* document.getElementById('app').innerHTML = render(view);
|
|
38
|
-
* });
|
|
39
|
-
* });
|
|
40
|
-
* watcher.watch(actor.currentView);
|
|
41
|
-
*
|
|
42
|
-
* // Later: cleanup
|
|
43
|
-
* disconnect();
|
|
44
|
-
* watcher.unwatch(actor.currentView);
|
|
45
|
-
* ```
|
|
46
|
-
*/
|
|
47
|
-
export function connectRouter(options) {
|
|
48
|
-
const { actor, router, routeMap } = options;
|
|
49
|
-
const { history } = router;
|
|
50
|
-
// Prevent circular updates — single navigation-processing guard.
|
|
51
|
-
// Matches RouterBridgeBase.isProcessingNavigation pattern.
|
|
52
|
-
// Reduces race-condition surface from 2³=8 flag combinations to 2.
|
|
53
|
-
let isProcessingNavigation = false;
|
|
54
|
-
// Subscribe to history changes (browser navigation)
|
|
55
|
-
const unsubscribeHistory = history.subscribe((location) => {
|
|
56
|
-
if (isProcessingNavigation)
|
|
57
|
-
return;
|
|
58
|
-
const { to, params } = routeMap.resolve(location.pathname);
|
|
59
|
-
if (to) {
|
|
60
|
-
isProcessingNavigation = true;
|
|
61
|
-
actor.send({ type: "play.route", to, params });
|
|
62
|
-
// Check immediately if actor redirected (always-guard)
|
|
63
|
-
// XState processes events synchronously, so snapshot is already updated
|
|
64
|
-
const newActorRoute = actor.currentRoute.get();
|
|
65
|
-
if (newActorRoute && newActorRoute !== location.pathname) {
|
|
66
|
-
// Actor redirected - update URL
|
|
67
|
-
history.replace(newActorRoute);
|
|
68
|
-
}
|
|
69
|
-
// Clear flag synchronously to prevent race conditions
|
|
70
|
-
isProcessingNavigation = false;
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
// Watch actor's currentRoute signal (actor-driven navigation)
|
|
74
|
-
const watcher = new Signal.subtle.Watcher(() => {
|
|
75
|
-
queueMicrotask(() => {
|
|
76
|
-
if (isProcessingNavigation)
|
|
77
|
-
return;
|
|
78
|
-
for (const signal of watcher.getPending()) {
|
|
79
|
-
signal.get();
|
|
80
|
-
}
|
|
81
|
-
watcher.watch(actor.currentRoute);
|
|
82
|
-
// Sync URL from actor
|
|
83
|
-
const route = actor.currentRoute.get();
|
|
84
|
-
const currentPath = history.location.pathname;
|
|
85
|
-
// Find path from route (reverse lookup)
|
|
86
|
-
// Note: This assumes route tree has path metadata
|
|
87
|
-
// For now, simple implementation - can enhance later
|
|
88
|
-
if (route && route !== currentPath) {
|
|
89
|
-
isProcessingNavigation = true;
|
|
90
|
-
history.push(route);
|
|
91
|
-
isProcessingNavigation = false;
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
watcher.watch(actor.currentRoute);
|
|
96
|
-
// Sync initial URL to actor
|
|
97
|
-
// ONLY send route event if URL doesn't match actor's initial state
|
|
98
|
-
const initialPath = history.location.pathname;
|
|
99
|
-
const initialActorRoute = actor.currentRoute.get();
|
|
100
|
-
if (initialPath !== initialActorRoute) {
|
|
101
|
-
const { to, params } = routeMap.resolve(initialPath);
|
|
102
|
-
if (to) {
|
|
103
|
-
actor.send({ type: "play.route", to, params });
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
// Return cleanup
|
|
107
|
-
return () => {
|
|
108
|
-
unsubscribeHistory();
|
|
109
|
-
watcher.unwatch(actor.currentRoute);
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
//# sourceMappingURL=connect-router.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"connect-router.js","sourceRoot":"","sources":["../src/connect-router.ts"],"names":[],"mappings":"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"}
|