@askrjs/askr 0.0.34 → 0.0.36

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.
Files changed (151) hide show
  1. package/dist/bench/components/benchmark-row.js.map +1 -1
  2. package/dist/bench/components/benchmark-table.js.map +1 -1
  3. package/dist/benchmark.js +2 -2
  4. package/dist/benchmark.js.map +1 -1
  5. package/dist/bin/askr-ssg.js.map +1 -1
  6. package/dist/boot/index.d.ts.map +1 -1
  7. package/dist/boot/index.js +9 -29
  8. package/dist/boot/index.js.map +1 -1
  9. package/dist/common/control.js.map +1 -1
  10. package/dist/common/env.js.map +1 -1
  11. package/dist/common/router.d.ts +8 -1
  12. package/dist/common/router.d.ts.map +1 -1
  13. package/dist/common/ssr-errors.js.map +1 -1
  14. package/dist/common/vnode.js.map +1 -1
  15. package/dist/components/error-boundary.js.map +1 -1
  16. package/dist/components/link.js.map +1 -1
  17. package/dist/control/case.js.map +1 -1
  18. package/dist/control/for.js.map +1 -1
  19. package/dist/control/shared.js.map +1 -1
  20. package/dist/control/show.js.map +1 -1
  21. package/dist/data/index.d.ts.map +1 -1
  22. package/dist/data/index.js +165 -23
  23. package/dist/data/index.js.map +1 -1
  24. package/dist/dev/invariant.js.map +1 -1
  25. package/dist/dev/logger.js.map +1 -1
  26. package/dist/foundations/icon/icon.js.map +1 -1
  27. package/dist/foundations/interactions/dismissable.js.map +1 -1
  28. package/dist/foundations/interactions/focusable.js.map +1 -1
  29. package/dist/foundations/interactions/hoverable.js.map +1 -1
  30. package/dist/foundations/interactions/interaction-policy.js.map +1 -1
  31. package/dist/foundations/interactions/pressable.js.map +1 -1
  32. package/dist/foundations/interactions/roving-focus.js.map +1 -1
  33. package/dist/foundations/state/controllable.js.map +1 -1
  34. package/dist/foundations/structures/collection.js.map +1 -1
  35. package/dist/foundations/structures/layer.js.map +1 -1
  36. package/dist/foundations/structures/layout.js.map +1 -1
  37. package/dist/foundations/structures/portal.js.map +1 -1
  38. package/dist/foundations/structures/presence.js.map +1 -1
  39. package/dist/foundations/structures/slot.js.map +1 -1
  40. package/dist/foundations/utilities/aria.js.map +1 -1
  41. package/dist/foundations/utilities/compose-handlers.js.map +1 -1
  42. package/dist/foundations/utilities/compose-ref.js.map +1 -1
  43. package/dist/foundations/utilities/merge-props.js.map +1 -1
  44. package/dist/foundations/utilities/use-id.js.map +1 -1
  45. package/dist/fx/fx.js.map +1 -1
  46. package/dist/fx/timing.js.map +1 -1
  47. package/dist/index.d.ts +3 -3
  48. package/dist/index.js +2 -2
  49. package/dist/index.js.map +1 -1
  50. package/dist/jsx/utils.js.map +1 -1
  51. package/dist/jsx-dev-runtime.js.map +1 -1
  52. package/dist/jsx-runtime.js.map +1 -1
  53. package/dist/renderer/children.js.map +1 -1
  54. package/dist/renderer/cleanup.js.map +1 -1
  55. package/dist/renderer/dom.js +128 -53
  56. package/dist/renderer/dom.js.map +1 -1
  57. package/dist/renderer/evaluate.js +7 -0
  58. package/dist/renderer/evaluate.js.map +1 -1
  59. package/dist/renderer/fastpath.js.map +1 -1
  60. package/dist/renderer/index.js.map +1 -1
  61. package/dist/renderer/keyed.js +1 -1
  62. package/dist/renderer/keyed.js.map +1 -1
  63. package/dist/renderer/reconcile.js.map +1 -1
  64. package/dist/renderer/utils.js.map +1 -1
  65. package/dist/router/index.d.ts +3 -3
  66. package/dist/router/index.js +2 -2
  67. package/dist/router/match.js.map +1 -1
  68. package/dist/router/navigate.d.ts.map +1 -1
  69. package/dist/router/navigate.js +8 -3
  70. package/dist/router/navigate.js.map +1 -1
  71. package/dist/router/policy.js.map +1 -1
  72. package/dist/router/route-context.js.map +1 -1
  73. package/dist/router/route.d.ts +11 -2
  74. package/dist/router/route.d.ts.map +1 -1
  75. package/dist/router/route.js +268 -109
  76. package/dist/router/route.js.map +1 -1
  77. package/dist/runtime/child-scope.d.ts +1 -0
  78. package/dist/runtime/child-scope.d.ts.map +1 -1
  79. package/dist/runtime/child-scope.js +2 -0
  80. package/dist/runtime/child-scope.js.map +1 -1
  81. package/dist/runtime/component.js +1 -1
  82. package/dist/runtime/component.js.map +1 -1
  83. package/dist/runtime/context.js.map +1 -1
  84. package/dist/runtime/control.js.map +1 -1
  85. package/dist/runtime/derive.d.ts +2 -1
  86. package/dist/runtime/derive.d.ts.map +1 -1
  87. package/dist/runtime/derive.js +15 -10
  88. package/dist/runtime/derive.js.map +1 -1
  89. package/dist/runtime/dev-namespace.js.map +1 -1
  90. package/dist/runtime/effect.d.ts.map +1 -1
  91. package/dist/runtime/effect.js +34 -9
  92. package/dist/runtime/effect.js.map +1 -1
  93. package/dist/runtime/events.js.map +1 -1
  94. package/dist/runtime/execution-model.js.map +1 -1
  95. package/dist/runtime/fastlane.js.map +1 -1
  96. package/dist/runtime/for-bench.d.ts +1 -1
  97. package/dist/runtime/for-bench.d.ts.map +1 -1
  98. package/dist/runtime/for-bench.js +15 -14
  99. package/dist/runtime/for-bench.js.map +1 -1
  100. package/dist/runtime/for.d.ts +1 -0
  101. package/dist/runtime/for.d.ts.map +1 -1
  102. package/dist/runtime/for.js +63 -16
  103. package/dist/runtime/for.js.map +1 -1
  104. package/dist/runtime/operations.d.ts.map +1 -1
  105. package/dist/runtime/operations.js +9 -7
  106. package/dist/runtime/operations.js.map +1 -1
  107. package/dist/runtime/perf-metrics.js +8 -2
  108. package/dist/runtime/perf-metrics.js.map +1 -1
  109. package/dist/runtime/readable.d.ts +1 -1
  110. package/dist/runtime/readable.d.ts.map +1 -1
  111. package/dist/runtime/readable.js +2 -1
  112. package/dist/runtime/readable.js.map +1 -1
  113. package/dist/runtime/resource-cell.js +3 -2
  114. package/dist/runtime/resource-cell.js.map +1 -1
  115. package/dist/runtime/scheduler.d.ts +0 -1
  116. package/dist/runtime/scheduler.d.ts.map +1 -1
  117. package/dist/runtime/scheduler.js +6 -19
  118. package/dist/runtime/scheduler.js.map +1 -1
  119. package/dist/runtime/selector.d.ts.map +1 -1
  120. package/dist/runtime/selector.js +6 -4
  121. package/dist/runtime/selector.js.map +1 -1
  122. package/dist/runtime/snapshot-source.d.ts +10 -0
  123. package/dist/runtime/snapshot-source.d.ts.map +1 -0
  124. package/dist/runtime/snapshot-source.js +17 -0
  125. package/dist/runtime/snapshot-source.js.map +1 -0
  126. package/dist/runtime/ssr-bridge.js.map +1 -1
  127. package/dist/runtime/state.d.ts.map +1 -1
  128. package/dist/runtime/state.js +1 -5
  129. package/dist/runtime/state.js.map +1 -1
  130. package/dist/ssg/batch-render.js.map +1 -1
  131. package/dist/ssg/create-static-gen.js.map +1 -1
  132. package/dist/ssg/generate-metadata.js.map +1 -1
  133. package/dist/ssg/incremental-manifest.js.map +1 -1
  134. package/dist/ssg/resolve-ssg-data.js.map +1 -1
  135. package/dist/ssg/route-utils.js.map +1 -1
  136. package/dist/ssg/write-static-files.js.map +1 -1
  137. package/dist/ssr/attrs.js.map +1 -1
  138. package/dist/ssr/context.d.ts +1 -0
  139. package/dist/ssr/context.d.ts.map +1 -1
  140. package/dist/ssr/context.js +3 -0
  141. package/dist/ssr/context.js.map +1 -1
  142. package/dist/ssr/escape.js +56 -16
  143. package/dist/ssr/escape.js.map +1 -1
  144. package/dist/ssr/index.d.ts.map +1 -1
  145. package/dist/ssr/index.js +15 -3
  146. package/dist/ssr/index.js.map +1 -1
  147. package/dist/ssr/render-keys.js.map +1 -1
  148. package/dist/ssr/sink.js.map +1 -1
  149. package/dist/ssr/stream-render.js +14 -2
  150. package/dist/ssr/stream-render.js.map +1 -1
  151. package/package.json +8 -6
@@ -1,9 +1,11 @@
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";
4
+ import { getRenderContext } from "../ssr/context.js";
2
5
  import { computeRank, matchSegments, parseSegments } from "./match.js";
3
6
  import { requireAuth, requireGuest, requirePermission, requireRole } from "./policy.js";
4
7
  import { buildRouteContext, buildRouteContextBase, deepFreeze, makeQuery, parseLocation } from "./route-context.js";
5
8
  import { getExecutionModel } from "../runtime/execution-model.js";
6
- import { getRenderContext } from "../ssr/context.js";
7
9
  //#region src/router/route.ts
8
10
  /**
9
11
  * Route definition, registration, and matching.
@@ -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 matches = [];
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
- for (const r of routesList) {
87
- const params = matchSegments(urlParts, cachedSegs(r));
88
- if (params !== null) matches.push({
89
- pattern: r.path,
90
- params,
91
- name: r.name,
92
- namespace: r.namespace,
93
- rank: cachedRank(r)
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
- matches.sort((a, b) => b.rank - a.rank);
97
- return matches.map((m) => ({
98
- path: m.pattern,
99
- params: deepFreeze({ ...m.params }),
100
- name: m.name,
101
- namespace: m.namespace
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
- route("/*", Component);
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,50 +544,23 @@ 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
+ recordReadableRead(currentRouteSource);
344
551
  return readCurrentRouteSnapshot();
345
552
  }
553
+ function syncCurrentRouteSnapshot(pathname, search, hash) {
554
+ setCurrentRouteSnapshot(pathname, search, hash);
555
+ }
346
556
  function route(path, Component, options) {
347
- if (getExecutionModel() === "islands") throw new Error("Routes are not supported with islands. Use createSPA (client) or createSSR (server) instead.");
348
557
  if (typeof path === "undefined") throw new Error("route() is only for route registration. Use currentRoute() inside components.");
558
+ if (getExecutionModel() === "islands") throw new Error("Routes are not supported with islands. Use createSPA (client) or createSSR (server) instead.");
349
559
  const currentInst = getCurrentComponentInstance();
350
560
  if (currentInst && currentInst.ssr) throw new Error("route() cannot be called during SSR rendering. Register routes at module load time instead.");
351
561
  if (registrationLocked) throw new Error("Route registration is locked after app startup. Register routes at module load time before calling createSPA or createSSR.");
352
562
  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
- validateRoutePath(path);
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
- });
563
+ registerRouteAtResolvedPath(resolveRouteRegistrationPath(path), Component, options);
388
564
  }
389
565
  /**
390
566
  * Return the normalized route manifest built from registered route definitions.
@@ -410,14 +586,14 @@ function getManifest() {
410
586
  function _applyManifest(manifest) {
411
587
  defaultRouteAuthOptions = manifest.auth;
412
588
  for (const record of manifest.records) {
413
- records.push(record);
589
+ insertRecordSorted(record);
414
590
  addRouteToStores({
415
591
  path: record.path,
416
592
  handler: record.handler,
417
- namespace: record.options.namespace
593
+ namespace: record.options.namespace,
594
+ ..."fallbackPrefix" in record && typeof record.fallbackPrefix === "string" ? { fallbackPrefix: record.fallbackPrefix } : {}
418
595
  });
419
596
  }
420
- records.sort((a, b) => b.rank - a.rank);
421
597
  }
422
598
  /**
423
599
  * Get all registered routes (flat list, insertion order).
@@ -426,27 +602,6 @@ function _applyManifest(manifest) {
426
602
  function getRoutes() {
427
603
  return [...routes];
428
604
  }
429
- /** Get routes for a specific namespace. */
430
- function getNamespaceRoutes(namespace) {
431
- return routes.filter((r) => r.namespace === namespace);
432
- }
433
- /** Unload all routes from a namespace (for MFE unmounting). */
434
- function unloadNamespace(namespace) {
435
- const before = routes.length;
436
- for (let i = routes.length - 1; i >= 0; i--) if (routes[i].namespace === namespace) {
437
- const removed = routes[i];
438
- routes.splice(i, 1);
439
- const depth = getDepth(removed.path);
440
- const depthRoutes = routesByDepth.get(depth);
441
- if (depthRoutes) {
442
- const idx = depthRoutes.indexOf(removed);
443
- if (idx >= 0) depthRoutes.splice(idx, 1);
444
- }
445
- }
446
- for (let i = records.length - 1; i >= 0; i--) if (records[i].options.namespace === namespace) records.splice(i, 1);
447
- namespaces.delete(namespace);
448
- return before - routes.length;
449
- }
450
605
  /** Clear all registered routes and records (testing / boot reset). */
451
606
  function clearRoutes() {
452
607
  routes.length = 0;
@@ -461,11 +616,25 @@ function clearRoutes() {
461
616
  setHasRoutes(false);
462
617
  pendingLazy.clear();
463
618
  }
464
- /**
465
- * Get all loaded namespaces (MFE identifiers)
466
- */
467
- function getLoadedNamespaces() {
468
- return Array.from(namespaces);
619
+ function findBestScopedFallbackRecord(pathname, routeRecords) {
620
+ let bestRecord = null;
621
+ let bestParams = null;
622
+ let bestPrefixLength = -1;
623
+ for (const routeRecord of routeRecords) {
624
+ const record = routeRecord;
625
+ if (!record.fallbackPrefix) continue;
626
+ const params = matchFallbackPrefix(pathname, record.fallbackPrefix);
627
+ if (params === null) continue;
628
+ if (record.fallbackPrefix.length > bestPrefixLength) {
629
+ bestRecord = record;
630
+ bestParams = params;
631
+ bestPrefixLength = record.fallbackPrefix.length;
632
+ }
633
+ }
634
+ return bestRecord && bestParams ? {
635
+ record: bestRecord,
636
+ params: bestParams
637
+ } : null;
469
638
  }
470
639
  /**
471
640
  * Resolve a path to a route handler.
@@ -479,26 +648,32 @@ function resolveRoute(pathname) {
479
648
  const normalized = pathname.endsWith("/") && pathname !== "/" ? pathname.slice(0, -1) : pathname;
480
649
  const urlParts = normalized === "/" ? [] : normalized.split("/").filter(Boolean);
481
650
  for (const record of records) {
651
+ if (record.fallbackPrefix) continue;
482
652
  const params = matchSegments(urlParts, record.segments);
483
653
  if (params !== null) return {
484
654
  handler: record.handler,
485
655
  params
486
656
  };
487
657
  }
488
- return null;
658
+ const fallbackMatch = findBestScopedFallbackRecord(normalized, records);
659
+ return fallbackMatch ? {
660
+ handler: fallbackMatch.record.handler,
661
+ params: fallbackMatch.params
662
+ } : null;
489
663
  }
490
664
  function getMatchingRecord(target, routeRecords) {
491
665
  const location = parseLocation(target);
492
666
  const normalized = location.pathname.endsWith("/") && location.pathname !== "/" ? location.pathname.slice(0, -1) : location.pathname;
493
667
  const urlParts = normalized === "/" ? [] : normalized.split("/").filter(Boolean);
494
668
  for (const record of routeRecords) {
669
+ if (record.fallbackPrefix) continue;
495
670
  const params = matchSegments(urlParts, record.segments);
496
671
  if (params !== null) return {
497
672
  record,
498
673
  params
499
674
  };
500
675
  }
501
- return null;
676
+ return findBestScopedFallbackRecord(normalized, routeRecords);
502
677
  }
503
678
  function getRoutePolicies(options) {
504
679
  if (!options) return [];
@@ -571,29 +746,13 @@ function resolveRouteRequest(target, options = {}) {
571
746
  */
572
747
  function resolveRouteFromRoutes(pathname, routeList) {
573
748
  if (routeList === routes) return resolveRoute(pathname);
574
- const normalized = pathname.endsWith("/") && pathname !== "/" ? pathname.slice(0, -1) : pathname;
575
- const urlParts = normalized === "/" ? [] : normalized.split("/").filter(Boolean);
576
- const sorted = cachedSortedList(routeList);
577
- let bestHandler = null;
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
749
+ const match = findBestResolvedRouteFromRoutes(pathname, routeList);
750
+ return match ? {
751
+ handler: match.route.handler,
752
+ params: match.params
594
753
  } : null;
595
754
  }
596
755
  //#endregion
597
- export { _applyManifest, _drainLazy, _setActiveRouteAuthOptions, _snapshotLazy, clearRoutes, currentRoute, fallback, getLoadedNamespaces, getManifest, getNamespaceRoutes, getRoutes, group, lazy, lockRouteRegistration, registerRoutes, resolveRoute, resolveRouteFromRoutes, resolveRouteRequest, route, setServerLocation, unloadNamespace };
756
+ export { Outlet, _applyManifest, _drainLazy, _setActiveRouteAuthOptions, _snapshotLazy, clearRoutes, currentRoute, fallback, getManifest, getRoutes, group, index, lazy, lockRouteRegistration, page, registerRoutes, resolveRoute, resolveRouteFromRoutes, resolveRouteRequest, route, setServerLocation, syncCurrentRouteSnapshot };
598
757
 
599
758
  //# sourceMappingURL=route.js.map