@askrjs/askr 0.0.33 → 0.0.35
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/dist/benchmark.js +2 -2
- package/dist/common/router.d.ts +8 -1
- package/dist/common/router.d.ts.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/renderer/children.js +2 -2
- package/dist/renderer/children.js.map +1 -1
- package/dist/renderer/dom.js +28 -5
- package/dist/renderer/dom.js.map +1 -1
- package/dist/renderer/evaluate.js +2 -2
- package/dist/renderer/evaluate.js.map +1 -1
- package/dist/router/index.d.ts +3 -3
- package/dist/router/index.js +2 -2
- package/dist/router/navigate.d.ts.map +1 -1
- package/dist/router/navigate.js +4 -1
- package/dist/router/navigate.js.map +1 -1
- package/dist/router/route.d.ts +11 -2
- package/dist/router/route.d.ts.map +1 -1
- package/dist/router/route.js +271 -81
- package/dist/router/route.js.map +1 -1
- package/dist/runtime/fastlane.js +2 -2
- package/dist/runtime/fastlane.js.map +1 -1
- package/dist/runtime/for.d.ts.map +1 -1
- package/dist/runtime/for.js +9 -4
- package/dist/runtime/for.js.map +1 -1
- package/dist/runtime/readable.d.ts +1 -1
- package/dist/runtime/readable.d.ts.map +1 -1
- package/dist/runtime/readable.js +2 -1
- package/dist/runtime/readable.js.map +1 -1
- package/package.json +2 -2
package/dist/router/route.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { defineContext, readContext } from "../runtime/context.js";
|
|
2
|
+
import { markReactivePropsDirtySource, markReadableDerivedSubscribersDirty, notifyReadableReaders, recordReadableRead } from "../runtime/readable.js";
|
|
1
3
|
import { getCurrentComponentInstance } from "../runtime/component.js";
|
|
2
4
|
import { computeRank, matchSegments, parseSegments } from "./match.js";
|
|
3
5
|
import { requireAuth, requireGuest, requirePermission, requireRole } from "./policy.js";
|
|
@@ -11,12 +13,14 @@ import { getRenderContext } from "../ssr/context.js";
|
|
|
11
13
|
* Primary public authoring:
|
|
12
14
|
* - `registerRoutes(() => { ... }, options?)`
|
|
13
15
|
* - `group(options, () => { ... })`
|
|
16
|
+
* - `page(path, Component, () => { ... })`
|
|
17
|
+
* - `index(Component, options?)`
|
|
14
18
|
* - `route(path, Component, options?)`
|
|
15
19
|
* - `fallback(Component)`
|
|
20
|
+
* - `Outlet()` for page child rendering
|
|
16
21
|
* - `currentRoute()` for render-time access
|
|
17
22
|
*
|
|
18
23
|
*/
|
|
19
|
-
/** Legacy flat route array — kept for resolver and route() accessor backward compat. */
|
|
20
24
|
const routes = [];
|
|
21
25
|
/** Normalized route records built by the declarative registration API. */
|
|
22
26
|
const records = [];
|
|
@@ -24,6 +28,7 @@ const records = [];
|
|
|
24
28
|
const registrationScopeStack = [];
|
|
25
29
|
const registrationSessionStack = [];
|
|
26
30
|
const namespaces = /* @__PURE__ */ new Set();
|
|
31
|
+
let registrationLocked = false;
|
|
27
32
|
let defaultRouteAuthOptions;
|
|
28
33
|
let activeClientRouteAuthOptions;
|
|
29
34
|
const HAS_ROUTES_KEY = Symbol.for("__ASKR_HAS_ROUTES__");
|
|
@@ -72,6 +77,9 @@ function cachedSortedList(routeList) {
|
|
|
72
77
|
}
|
|
73
78
|
return sorted;
|
|
74
79
|
}
|
|
80
|
+
let currentRouteSnapshot = buildRouteSnapshot("/", "", "");
|
|
81
|
+
const currentRouteSource = (() => currentRouteSnapshot);
|
|
82
|
+
currentRouteSource._readers = /* @__PURE__ */ new Map();
|
|
75
83
|
let serverLocation = null;
|
|
76
84
|
function setServerLocation(url) {
|
|
77
85
|
serverLocation = url;
|
|
@@ -79,27 +87,87 @@ function setServerLocation(url) {
|
|
|
79
87
|
function isPromise(value) {
|
|
80
88
|
return value instanceof Promise;
|
|
81
89
|
}
|
|
90
|
+
function buildRouteSnapshot(pathname, search, hash) {
|
|
91
|
+
const query = makeQuery(search);
|
|
92
|
+
const matches = computeMatchesFromRoutes(pathname, getActiveRoutes());
|
|
93
|
+
return Object.freeze({
|
|
94
|
+
path: pathname,
|
|
95
|
+
params: deepFreeze({ ...matches[0]?.params }),
|
|
96
|
+
query,
|
|
97
|
+
hash: hash || null,
|
|
98
|
+
matches: Object.freeze(matches)
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function setCurrentRouteSnapshot(pathname, search, hash) {
|
|
102
|
+
currentRouteSnapshot = buildRouteSnapshot(pathname, search, hash);
|
|
103
|
+
const instance = getCurrentComponentInstance();
|
|
104
|
+
markReadableDerivedSubscribersDirty(currentRouteSource);
|
|
105
|
+
markReactivePropsDirtySource(currentRouteSource);
|
|
106
|
+
notifyReadableReaders(currentRouteSource, instance);
|
|
107
|
+
}
|
|
108
|
+
function matchFallbackPrefix(pathname, fallbackPrefix) {
|
|
109
|
+
const normalizedPath = pathname.endsWith("/") && pathname !== "/" ? pathname.slice(0, -1) : pathname;
|
|
110
|
+
const normalizedPrefix = fallbackPrefix.endsWith("/") && fallbackPrefix !== "/" ? fallbackPrefix.slice(0, -1) : fallbackPrefix;
|
|
111
|
+
if (normalizedPrefix === "/") {
|
|
112
|
+
const urlParts = normalizedPath === "/" ? [] : normalizedPath.split("/").filter(Boolean);
|
|
113
|
+
return { "*": urlParts.length === 0 ? "/" : urlParts.length === 1 ? urlParts[0] : "/" + urlParts.join("/") };
|
|
114
|
+
}
|
|
115
|
+
if (normalizedPath !== normalizedPrefix && !normalizedPath.startsWith(`${normalizedPrefix}/`)) return null;
|
|
116
|
+
const remainder = normalizedPath === normalizedPrefix ? "/" : normalizedPath.slice(normalizedPrefix.length + 1);
|
|
117
|
+
const remainderParts = remainder === "/" ? [] : remainder.split("/").filter(Boolean);
|
|
118
|
+
return { "*": remainderParts.length === 0 ? "/" : remainderParts.length === 1 ? remainderParts[0] : "/" + remainderParts.join("/") };
|
|
119
|
+
}
|
|
82
120
|
function computeMatchesFromRoutes(pathname, routesList) {
|
|
83
|
-
const
|
|
121
|
+
const bestMatch = routesList === routes ? getMatchingRecord(pathname, records) : findBestResolvedRouteFromRoutes(pathname, routesList);
|
|
122
|
+
if (!bestMatch) return [];
|
|
123
|
+
return [{
|
|
124
|
+
path: "route" in bestMatch ? bestMatch.route.path : bestMatch.record.path,
|
|
125
|
+
params: deepFreeze({ ...bestMatch.params }),
|
|
126
|
+
name: "route" in bestMatch ? bestMatch.route.name : void 0,
|
|
127
|
+
namespace: "route" in bestMatch ? bestMatch.route.namespace : bestMatch.record.options.namespace
|
|
128
|
+
}];
|
|
129
|
+
}
|
|
130
|
+
function findBestResolvedRouteFromRoutes(pathname, routeList) {
|
|
84
131
|
const normalized = pathname.endsWith("/") && pathname !== "/" ? pathname.slice(0, -1) : pathname;
|
|
85
132
|
const urlParts = normalized === "/" ? [] : normalized.split("/").filter(Boolean);
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
133
|
+
const sorted = cachedSortedList(routeList);
|
|
134
|
+
let bestRoute = null;
|
|
135
|
+
let bestParams = {};
|
|
136
|
+
let bestRank = -Infinity;
|
|
137
|
+
for (const route of sorted) {
|
|
138
|
+
if (route.fallbackPrefix) continue;
|
|
139
|
+
const rank = cachedRank(route);
|
|
140
|
+
if (rank < bestRank) break;
|
|
141
|
+
if (bestRoute !== null && rank === bestRank) continue;
|
|
142
|
+
const params = matchSegments(urlParts, cachedSegs(route));
|
|
143
|
+
if (params !== null) {
|
|
144
|
+
bestRoute = route;
|
|
145
|
+
bestParams = params;
|
|
146
|
+
bestRank = rank;
|
|
147
|
+
}
|
|
95
148
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
149
|
+
if (bestRoute !== null) return {
|
|
150
|
+
route: bestRoute,
|
|
151
|
+
params: bestParams
|
|
152
|
+
};
|
|
153
|
+
let bestFallback = null;
|
|
154
|
+
let bestFallbackParams = null;
|
|
155
|
+
let bestPrefixLength = -1;
|
|
156
|
+
for (const route of routeList) {
|
|
157
|
+
const internalRoute = route;
|
|
158
|
+
if (!internalRoute.fallbackPrefix) continue;
|
|
159
|
+
const params = matchFallbackPrefix(normalized, internalRoute.fallbackPrefix);
|
|
160
|
+
if (params === null) continue;
|
|
161
|
+
if (internalRoute.fallbackPrefix.length > bestPrefixLength) {
|
|
162
|
+
bestFallback = internalRoute;
|
|
163
|
+
bestFallbackParams = params;
|
|
164
|
+
bestPrefixLength = internalRoute.fallbackPrefix.length;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return bestFallback && bestFallbackParams ? {
|
|
168
|
+
route: bestFallback,
|
|
169
|
+
params: bestFallbackParams
|
|
170
|
+
} : null;
|
|
103
171
|
}
|
|
104
172
|
function getActiveRoutes() {
|
|
105
173
|
return getRenderContext()?.routes ?? routes;
|
|
@@ -125,6 +193,20 @@ function getCurrentLayoutChain() {
|
|
|
125
193
|
for (const scope of registrationScopeStack) if (scope.layout) layoutChain.push({ component: scope.layout });
|
|
126
194
|
return layoutChain;
|
|
127
195
|
}
|
|
196
|
+
function getCurrentPageChain() {
|
|
197
|
+
const pageChain = [];
|
|
198
|
+
for (const scope of registrationScopeStack) if (scope.page) pageChain.push({ component: scope.page });
|
|
199
|
+
return pageChain;
|
|
200
|
+
}
|
|
201
|
+
function hasActivePageScope() {
|
|
202
|
+
return registrationScopeStack.some((scope) => !!scope.page);
|
|
203
|
+
}
|
|
204
|
+
function getCurrentScopeKind() {
|
|
205
|
+
return registrationScopeStack[registrationScopeStack.length - 1]?.kind ?? null;
|
|
206
|
+
}
|
|
207
|
+
function getCurrentPathPrefix() {
|
|
208
|
+
return registrationScopeStack[registrationScopeStack.length - 1]?.pathPrefix ?? "";
|
|
209
|
+
}
|
|
128
210
|
function getCurrentInheritedPolicies() {
|
|
129
211
|
const policies = [];
|
|
130
212
|
for (const scope of registrationScopeStack) if (scope.policies.length > 0) policies.push(...scope.policies);
|
|
@@ -134,7 +216,6 @@ function getCurrentInheritedPolicies() {
|
|
|
134
216
|
* Prevent route registrations after the app has started.
|
|
135
217
|
* Enforced in production; tests may unlock explicitly.
|
|
136
218
|
*/
|
|
137
|
-
let registrationLocked = false;
|
|
138
219
|
function lockRouteRegistration() {
|
|
139
220
|
registrationLocked = true;
|
|
140
221
|
}
|
|
@@ -145,6 +226,37 @@ function validateRoutePath(path) {
|
|
|
145
226
|
throw new Error(`Route parameter syntax uses {name} interpolation, not :name. Use "${suggested}" instead of "${path}".`);
|
|
146
227
|
}
|
|
147
228
|
}
|
|
229
|
+
function normalizeAbsoluteRoutePath(path) {
|
|
230
|
+
if (!path || path === "/") return "/";
|
|
231
|
+
return (path.endsWith("/") ? path.slice(0, -1) : path) || "/";
|
|
232
|
+
}
|
|
233
|
+
function joinRoutePaths(prefix, path) {
|
|
234
|
+
const normalizedPrefix = normalizeAbsoluteRoutePath(prefix || "/");
|
|
235
|
+
const normalizedPath = path.replace(/^\/+|\/+$/g, "");
|
|
236
|
+
if (!normalizedPath) return normalizedPrefix;
|
|
237
|
+
return normalizedPrefix === "/" ? `/${normalizedPath}` : `${normalizedPrefix}/${normalizedPath}`;
|
|
238
|
+
}
|
|
239
|
+
function resolvePageScopePath(path) {
|
|
240
|
+
if (!path) throw new Error("page(path, Component, fn) requires a non-empty path.");
|
|
241
|
+
if (path.startsWith("/")) {
|
|
242
|
+
validateRoutePath(path);
|
|
243
|
+
return normalizeAbsoluteRoutePath(path);
|
|
244
|
+
}
|
|
245
|
+
return joinRoutePaths(getCurrentPathPrefix(), path);
|
|
246
|
+
}
|
|
247
|
+
function resolveIndexPath() {
|
|
248
|
+
return normalizeAbsoluteRoutePath(getCurrentPathPrefix() || "/");
|
|
249
|
+
}
|
|
250
|
+
function resolveRouteRegistrationPath(path) {
|
|
251
|
+
if (path.startsWith("/")) {
|
|
252
|
+
if (hasActivePageScope()) throw new Error(`Child route paths inside page() must be relative. Use "${path.slice(1)}" instead of "${path}".`);
|
|
253
|
+
validateRoutePath(path);
|
|
254
|
+
return normalizeAbsoluteRoutePath(path);
|
|
255
|
+
}
|
|
256
|
+
const prefix = getCurrentPathPrefix();
|
|
257
|
+
if (!prefix) throw new Error(`Route path must begin with "/". Got: "${path}"`);
|
|
258
|
+
return joinRoutePaths(prefix, path);
|
|
259
|
+
}
|
|
148
260
|
function insertRecordSorted(record) {
|
|
149
261
|
let lo = 0;
|
|
150
262
|
let hi = records.length;
|
|
@@ -169,6 +281,10 @@ function addRouteToStores(routeObj) {
|
|
|
169
281
|
}
|
|
170
282
|
/** Promises from in-flight lazy() imports, drained by createSPA / hydrateSPA. */
|
|
171
283
|
const pendingLazy = /* @__PURE__ */ new Set();
|
|
284
|
+
const outletContext = defineContext(null);
|
|
285
|
+
function Outlet() {
|
|
286
|
+
return readContext(outletContext);
|
|
287
|
+
}
|
|
172
288
|
/**
|
|
173
289
|
* Snapshot the current in-flight lazy() imports.
|
|
174
290
|
* Boot uses this before clearing route state so manifest-based startup can
|
|
@@ -227,9 +343,30 @@ function _drainLazy(additionalPending = []) {
|
|
|
227
343
|
function group(options, fn) {
|
|
228
344
|
pushGroupScope(options, fn);
|
|
229
345
|
}
|
|
346
|
+
function page(path, Component, optionsOrFn, maybeFn) {
|
|
347
|
+
const options = typeof optionsOrFn === "function" ? {} : optionsOrFn;
|
|
348
|
+
const fn = typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
|
|
349
|
+
if (typeof Component !== "function") throw new Error("page(path, Component, fn) requires a component function as the second argument.");
|
|
350
|
+
if (typeof fn !== "function") throw new Error("page(path, Component, fn) requires a route definition callback as the final argument.");
|
|
351
|
+
pushPageScope(path, Component, options, fn);
|
|
352
|
+
}
|
|
353
|
+
function index(Component, options) {
|
|
354
|
+
registerRouteAtResolvedPath(resolveIndexPath(), Component, options);
|
|
355
|
+
}
|
|
230
356
|
function fallback(Component) {
|
|
357
|
+
if (hasActivePageScope()) {
|
|
358
|
+
if (getCurrentScopeKind() !== "page") throw new Error("fallback() inside page() must be declared directly in the page scope, not inside nested group().");
|
|
359
|
+
registerRouteAtResolvedPath(`${getCurrentPathPrefix()}/*`, Component, void 0, {
|
|
360
|
+
isFallback: true,
|
|
361
|
+
fallbackPrefix: getCurrentPathPrefix()
|
|
362
|
+
});
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
231
365
|
if (!registrationScopeStack.every((scope) => scope.policies.length === 0 && !scope.state.guestOnly && !scope.state.authenticated)) throw new Error("fallback() can only be registered at the root scope. Use route(\"/*\", Component) if you need compatibility behavior.");
|
|
232
|
-
|
|
366
|
+
registerRouteAtResolvedPath("/*", Component, void 0, {
|
|
367
|
+
isFallback: true,
|
|
368
|
+
fallbackPrefix: "/"
|
|
369
|
+
});
|
|
233
370
|
}
|
|
234
371
|
function pushRegistrationScope(scope, fn) {
|
|
235
372
|
registrationScopeStack.push(scope);
|
|
@@ -246,11 +383,28 @@ function pushGroupScope(options, fn) {
|
|
|
246
383
|
});
|
|
247
384
|
const policies = compileNodePolicies(options);
|
|
248
385
|
pushRegistrationScope({
|
|
386
|
+
kind: "group",
|
|
387
|
+
pathPrefix: getCurrentPathPrefix(),
|
|
249
388
|
layout: options.layout,
|
|
250
389
|
policies,
|
|
251
390
|
state: nextAccessScopeState(options, getCurrentAccessScopeState())
|
|
252
391
|
}, fn);
|
|
253
392
|
}
|
|
393
|
+
function pushPageScope(path, Component, options, fn) {
|
|
394
|
+
if (hasActivePageScope()) throw new Error("page() cannot be nested inside another page(). Use route() for child leaves or group() for inherited behavior inside the existing page scope.");
|
|
395
|
+
validateAccessMetadata(options, {
|
|
396
|
+
authConfigured: getCurrentRegistrationSession().authConfigured,
|
|
397
|
+
state: getCurrentAccessScopeState()
|
|
398
|
+
});
|
|
399
|
+
const policies = compileNodePolicies(options);
|
|
400
|
+
pushRegistrationScope({
|
|
401
|
+
kind: "page",
|
|
402
|
+
pathPrefix: resolvePageScopePath(path),
|
|
403
|
+
page: Component,
|
|
404
|
+
policies,
|
|
405
|
+
state: nextAccessScopeState(options, getCurrentAccessScopeState())
|
|
406
|
+
}, fn);
|
|
407
|
+
}
|
|
254
408
|
function hasBuiltInAuthMetadata(node) {
|
|
255
409
|
return node.auth !== void 0 || !!node.role || !!node.permission;
|
|
256
410
|
}
|
|
@@ -297,6 +451,55 @@ function normalizeRouteOptions(options) {
|
|
|
297
451
|
...options.namespace ? { namespace: options.namespace } : {}
|
|
298
452
|
};
|
|
299
453
|
}
|
|
454
|
+
function applyPageChain(pageChain, params, content) {
|
|
455
|
+
let nextContent = content;
|
|
456
|
+
for (let i = pageChain.length - 1; i >= 0; i--) nextContent = outletContext.Scope({
|
|
457
|
+
value: nextContent,
|
|
458
|
+
children: pageChain[i].component(params)
|
|
459
|
+
});
|
|
460
|
+
return nextContent;
|
|
461
|
+
}
|
|
462
|
+
function registerRouteAtResolvedPath(path, Component, options, metadata) {
|
|
463
|
+
validateAccessMetadata(options ?? {}, {
|
|
464
|
+
authConfigured: getCurrentRegistrationSession().authConfigured,
|
|
465
|
+
state: getCurrentAccessScopeState()
|
|
466
|
+
});
|
|
467
|
+
const chain = getCurrentLayoutChain();
|
|
468
|
+
const pageChain = getCurrentPageChain();
|
|
469
|
+
const segments = parseSegments(path);
|
|
470
|
+
const rank = computeRank(segments);
|
|
471
|
+
const isFallback = metadata?.isFallback ?? path === "/*";
|
|
472
|
+
const comp = Component;
|
|
473
|
+
const normalizedOptions = normalizeRouteOptions(options);
|
|
474
|
+
const policies = [...getCurrentInheritedPolicies(), ...normalizedOptions?.policies ?? []];
|
|
475
|
+
const handler = (params) => {
|
|
476
|
+
let content = comp(params);
|
|
477
|
+
content = applyPageChain(pageChain, params, content);
|
|
478
|
+
for (let i = chain.length - 1; i >= 0; i--) content = chain[i].component({ children: content });
|
|
479
|
+
return content;
|
|
480
|
+
};
|
|
481
|
+
insertRecordSorted({
|
|
482
|
+
path,
|
|
483
|
+
component: comp,
|
|
484
|
+
segments,
|
|
485
|
+
rank,
|
|
486
|
+
layoutChain: chain,
|
|
487
|
+
pageChain,
|
|
488
|
+
options: normalizedOptions ? {
|
|
489
|
+
...normalizedOptions,
|
|
490
|
+
...policies.length > 0 ? { policies } : {}
|
|
491
|
+
} : policies.length > 0 ? { policies } : {},
|
|
492
|
+
isFallback,
|
|
493
|
+
handler,
|
|
494
|
+
...metadata?.fallbackPrefix ? { fallbackPrefix: metadata.fallbackPrefix } : {}
|
|
495
|
+
});
|
|
496
|
+
addRouteToStores({
|
|
497
|
+
path,
|
|
498
|
+
handler,
|
|
499
|
+
namespace: normalizedOptions?.namespace ?? options?.namespace,
|
|
500
|
+
...metadata?.fallbackPrefix ? { fallbackPrefix: metadata.fallbackPrefix } : {}
|
|
501
|
+
});
|
|
502
|
+
}
|
|
300
503
|
function registerRoutes(definition, options = {}) {
|
|
301
504
|
defaultRouteAuthOptions = options.auth;
|
|
302
505
|
registrationSessionStack.push({ authConfigured: !!options.auth?.resolve });
|
|
@@ -341,8 +544,19 @@ function readCurrentRouteSnapshot() {
|
|
|
341
544
|
});
|
|
342
545
|
}
|
|
343
546
|
function currentRoute() {
|
|
547
|
+
const instance = getCurrentComponentInstance();
|
|
548
|
+
if (!instance) throw new Error("currentRoute() can only be called during component render execution. Call currentRoute() from inside your component function.");
|
|
549
|
+
if (typeof window === "undefined" || instance.ssr) return readCurrentRouteSnapshot();
|
|
550
|
+
if (currentRouteSnapshot.path !== (window.location.pathname || "/")) {
|
|
551
|
+
recordReadableRead(currentRouteSource);
|
|
552
|
+
return readCurrentRouteSnapshot();
|
|
553
|
+
}
|
|
554
|
+
recordReadableRead(currentRouteSource);
|
|
344
555
|
return readCurrentRouteSnapshot();
|
|
345
556
|
}
|
|
557
|
+
function syncCurrentRouteSnapshot(pathname, search, hash) {
|
|
558
|
+
setCurrentRouteSnapshot(pathname, search, hash);
|
|
559
|
+
}
|
|
346
560
|
function route(path, Component, options) {
|
|
347
561
|
if (getExecutionModel() === "islands") throw new Error("Routes are not supported with islands. Use createSPA (client) or createSSR (server) instead.");
|
|
348
562
|
if (typeof path === "undefined") throw new Error("route() is only for route registration. Use currentRoute() inside components.");
|
|
@@ -350,41 +564,7 @@ function route(path, Component, options) {
|
|
|
350
564
|
if (currentInst && currentInst.ssr) throw new Error("route() cannot be called during SSR rendering. Register routes at module load time instead.");
|
|
351
565
|
if (registrationLocked) throw new Error("Route registration is locked after app startup. Register routes at module load time before calling createSPA or createSSR.");
|
|
352
566
|
if (typeof Component !== "function") throw new Error("route(path, Component) requires a component function as the second argument. Passing JSX elements or VNodes directly is not supported.");
|
|
353
|
-
|
|
354
|
-
validateAccessMetadata(options ?? {}, {
|
|
355
|
-
authConfigured: getCurrentRegistrationSession().authConfigured,
|
|
356
|
-
state: getCurrentAccessScopeState()
|
|
357
|
-
});
|
|
358
|
-
const chain = getCurrentLayoutChain();
|
|
359
|
-
const segments = parseSegments(path);
|
|
360
|
-
const rank = computeRank(segments);
|
|
361
|
-
const isFallback = path === "/*";
|
|
362
|
-
const comp = Component;
|
|
363
|
-
const normalizedOptions = normalizeRouteOptions(options);
|
|
364
|
-
const policies = [...getCurrentInheritedPolicies(), ...normalizedOptions?.policies ?? []];
|
|
365
|
-
const handler = (params) => {
|
|
366
|
-
let content = comp(params);
|
|
367
|
-
for (let i = chain.length - 1; i >= 0; i--) content = chain[i].component({ children: content });
|
|
368
|
-
return content;
|
|
369
|
-
};
|
|
370
|
-
insertRecordSorted({
|
|
371
|
-
path,
|
|
372
|
-
component: comp,
|
|
373
|
-
segments,
|
|
374
|
-
rank,
|
|
375
|
-
layoutChain: chain,
|
|
376
|
-
options: normalizedOptions ? {
|
|
377
|
-
...normalizedOptions,
|
|
378
|
-
...policies.length > 0 ? { policies } : {}
|
|
379
|
-
} : policies.length > 0 ? { policies } : {},
|
|
380
|
-
isFallback,
|
|
381
|
-
handler
|
|
382
|
-
});
|
|
383
|
-
addRouteToStores({
|
|
384
|
-
path,
|
|
385
|
-
handler,
|
|
386
|
-
namespace: normalizedOptions?.namespace ?? options?.namespace
|
|
387
|
-
});
|
|
567
|
+
registerRouteAtResolvedPath(resolveRouteRegistrationPath(path), Component, options);
|
|
388
568
|
}
|
|
389
569
|
/**
|
|
390
570
|
* Return the normalized route manifest built from registered route definitions.
|
|
@@ -410,14 +590,14 @@ function getManifest() {
|
|
|
410
590
|
function _applyManifest(manifest) {
|
|
411
591
|
defaultRouteAuthOptions = manifest.auth;
|
|
412
592
|
for (const record of manifest.records) {
|
|
413
|
-
|
|
593
|
+
insertRecordSorted(record);
|
|
414
594
|
addRouteToStores({
|
|
415
595
|
path: record.path,
|
|
416
596
|
handler: record.handler,
|
|
417
|
-
namespace: record.options.namespace
|
|
597
|
+
namespace: record.options.namespace,
|
|
598
|
+
..."fallbackPrefix" in record && typeof record.fallbackPrefix === "string" ? { fallbackPrefix: record.fallbackPrefix } : {}
|
|
418
599
|
});
|
|
419
600
|
}
|
|
420
|
-
records.sort((a, b) => b.rank - a.rank);
|
|
421
601
|
}
|
|
422
602
|
/**
|
|
423
603
|
* Get all registered routes (flat list, insertion order).
|
|
@@ -467,6 +647,26 @@ function clearRoutes() {
|
|
|
467
647
|
function getLoadedNamespaces() {
|
|
468
648
|
return Array.from(namespaces);
|
|
469
649
|
}
|
|
650
|
+
function findBestScopedFallbackRecord(pathname, routeRecords) {
|
|
651
|
+
let bestRecord = null;
|
|
652
|
+
let bestParams = null;
|
|
653
|
+
let bestPrefixLength = -1;
|
|
654
|
+
for (const routeRecord of routeRecords) {
|
|
655
|
+
const record = routeRecord;
|
|
656
|
+
if (!record.fallbackPrefix) continue;
|
|
657
|
+
const params = matchFallbackPrefix(pathname, record.fallbackPrefix);
|
|
658
|
+
if (params === null) continue;
|
|
659
|
+
if (record.fallbackPrefix.length > bestPrefixLength) {
|
|
660
|
+
bestRecord = record;
|
|
661
|
+
bestParams = params;
|
|
662
|
+
bestPrefixLength = record.fallbackPrefix.length;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
return bestRecord && bestParams ? {
|
|
666
|
+
record: bestRecord,
|
|
667
|
+
params: bestParams
|
|
668
|
+
} : null;
|
|
669
|
+
}
|
|
470
670
|
/**
|
|
471
671
|
* Resolve a path to a route handler.
|
|
472
672
|
*
|
|
@@ -479,26 +679,32 @@ function resolveRoute(pathname) {
|
|
|
479
679
|
const normalized = pathname.endsWith("/") && pathname !== "/" ? pathname.slice(0, -1) : pathname;
|
|
480
680
|
const urlParts = normalized === "/" ? [] : normalized.split("/").filter(Boolean);
|
|
481
681
|
for (const record of records) {
|
|
682
|
+
if (record.fallbackPrefix) continue;
|
|
482
683
|
const params = matchSegments(urlParts, record.segments);
|
|
483
684
|
if (params !== null) return {
|
|
484
685
|
handler: record.handler,
|
|
485
686
|
params
|
|
486
687
|
};
|
|
487
688
|
}
|
|
488
|
-
|
|
689
|
+
const fallbackMatch = findBestScopedFallbackRecord(normalized, records);
|
|
690
|
+
return fallbackMatch ? {
|
|
691
|
+
handler: fallbackMatch.record.handler,
|
|
692
|
+
params: fallbackMatch.params
|
|
693
|
+
} : null;
|
|
489
694
|
}
|
|
490
695
|
function getMatchingRecord(target, routeRecords) {
|
|
491
696
|
const location = parseLocation(target);
|
|
492
697
|
const normalized = location.pathname.endsWith("/") && location.pathname !== "/" ? location.pathname.slice(0, -1) : location.pathname;
|
|
493
698
|
const urlParts = normalized === "/" ? [] : normalized.split("/").filter(Boolean);
|
|
494
699
|
for (const record of routeRecords) {
|
|
700
|
+
if (record.fallbackPrefix) continue;
|
|
495
701
|
const params = matchSegments(urlParts, record.segments);
|
|
496
702
|
if (params !== null) return {
|
|
497
703
|
record,
|
|
498
704
|
params
|
|
499
705
|
};
|
|
500
706
|
}
|
|
501
|
-
return
|
|
707
|
+
return findBestScopedFallbackRecord(normalized, routeRecords);
|
|
502
708
|
}
|
|
503
709
|
function getRoutePolicies(options) {
|
|
504
710
|
if (!options) return [];
|
|
@@ -571,29 +777,13 @@ function resolveRouteRequest(target, options = {}) {
|
|
|
571
777
|
*/
|
|
572
778
|
function resolveRouteFromRoutes(pathname, routeList) {
|
|
573
779
|
if (routeList === routes) return resolveRoute(pathname);
|
|
574
|
-
const
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
let bestParams = {};
|
|
579
|
-
let bestRank = -Infinity;
|
|
580
|
-
for (const r of sorted) {
|
|
581
|
-
const rank = cachedRank(r);
|
|
582
|
-
if (rank < bestRank) break;
|
|
583
|
-
if (bestHandler !== null && rank === bestRank) continue;
|
|
584
|
-
const params = matchSegments(urlParts, cachedSegs(r));
|
|
585
|
-
if (params !== null) {
|
|
586
|
-
bestHandler = r.handler;
|
|
587
|
-
bestParams = params;
|
|
588
|
-
bestRank = rank;
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
return bestHandler !== null ? {
|
|
592
|
-
handler: bestHandler,
|
|
593
|
-
params: bestParams
|
|
780
|
+
const match = findBestResolvedRouteFromRoutes(pathname, routeList);
|
|
781
|
+
return match ? {
|
|
782
|
+
handler: match.route.handler,
|
|
783
|
+
params: match.params
|
|
594
784
|
} : null;
|
|
595
785
|
}
|
|
596
786
|
//#endregion
|
|
597
|
-
export { _applyManifest, _drainLazy, _setActiveRouteAuthOptions, _snapshotLazy, clearRoutes, currentRoute, fallback, getLoadedNamespaces, getManifest, getNamespaceRoutes, getRoutes, group, lazy, lockRouteRegistration, registerRoutes, resolveRoute, resolveRouteFromRoutes, resolveRouteRequest, route, setServerLocation, unloadNamespace };
|
|
787
|
+
export { Outlet, _applyManifest, _drainLazy, _setActiveRouteAuthOptions, _snapshotLazy, clearRoutes, currentRoute, fallback, getLoadedNamespaces, getManifest, getNamespaceRoutes, getRoutes, group, index, lazy, lockRouteRegistration, page, registerRoutes, resolveRoute, resolveRouteFromRoutes, resolveRouteRequest, route, setServerLocation, syncCurrentRouteSnapshot, unloadNamespace };
|
|
598
788
|
|
|
599
789
|
//# sourceMappingURL=route.js.map
|