@askrjs/askr 0.3.0 → 0.4.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.
Files changed (123) hide show
  1. package/CHANGELOG.md +874 -0
  2. package/README.md +61 -29
  3. package/capabilities.json +19 -9
  4. package/dist/actions/index.js +56 -12
  5. package/dist/benchmark.js +10 -15
  6. package/dist/boot/index.d.ts +3 -2
  7. package/dist/boot/index.js +1 -1
  8. package/dist/boot-DaoTYoCw.js +3989 -0
  9. package/dist/browser-activity-CB8e-V4f.js +220 -0
  10. package/dist/components/index.d.ts +8 -1
  11. package/dist/components/index.js +1 -43
  12. package/dist/{aria-hKjThHcp.js → compose-ref-DWzdbxsD.js} +41 -4
  13. package/dist/control/index.d.ts +2 -0
  14. package/dist/control/index.js +1 -1
  15. package/dist/control-nfpT2fbK.js +209 -0
  16. package/dist/core.d.ts +5 -9
  17. package/dist/csp-nonce-DwGw1A6Q.js +35 -0
  18. package/dist/data/index.js +3 -3
  19. package/dist/{data-2edW0jhF.js → data-BCwClJiF.js} +283 -94
  20. package/dist/data-runtime-C1bFAlba.js +242 -0
  21. package/dist/{deferred-CbgNhf67.js → deferred-bEEHHvic.js} +42 -20
  22. package/dist/domains/actions.d.ts +8 -3
  23. package/dist/domains/component.d.ts +1 -107
  24. package/dist/domains/context.d.ts +0 -15
  25. package/dist/domains/control.d.ts +36 -249
  26. package/dist/domains/data.d.ts +31 -13
  27. package/dist/domains/diagnostics.d.ts +1 -2
  28. package/dist/domains/lifecycle.d.ts +2 -5
  29. package/dist/domains/refs.d.ts +0 -2
  30. package/dist/domains/routing.d.ts +43 -8
  31. package/dist/domains/scheduler.d.ts +1 -76
  32. package/dist/domains/server.d.ts +5 -2
  33. package/dist/domains/state.d.ts +2 -5
  34. package/dist/domains/telemetry.d.ts +0 -2
  35. package/dist/elements.d.ts +214 -51
  36. package/dist/error-boundary-Bpwofwps.js +293 -0
  37. package/dist/foundations/icon/index.d.ts +0 -58
  38. package/dist/foundations/icon/index.js +2 -2
  39. package/dist/foundations/index.js +2 -1
  40. package/dist/foundations/interactions/index.js +1 -1
  41. package/dist/foundations/state/index.d.ts +3 -3
  42. package/dist/foundations/state/index.js +5 -3
  43. package/dist/foundations/structures/index.js +2 -1
  44. package/dist/foundations/utilities/index.d.ts +2 -0
  45. package/dist/foundations/utilities/index.js +1 -2
  46. package/dist/fx/index.d.ts +31 -58
  47. package/dist/fx/index.js +313 -195
  48. package/dist/hooks-BimKStr4.js +202 -0
  49. package/dist/index.d.ts +11 -74
  50. package/dist/index.js +8 -447
  51. package/dist/instance-BAnfmXOR.js +988 -0
  52. package/dist/{interactions-CTeACdT_.js → interactions-D2BoREb-.js} +3 -2
  53. package/dist/jsx-dev-runtime.d.ts +41 -18
  54. package/dist/jsx-dev-runtime.js +1 -6
  55. package/dist/{jsx-runtime-CPJXAOBF.js → jsx-runtime-C4idqHY0.js} +18 -7
  56. package/dist/jsx-runtime.js +1 -1
  57. package/dist/jsx.d.ts +61 -37
  58. package/dist/{manifest-D1P3UNWV.js → manifest-CW9gyNBY.js} +3 -3
  59. package/dist/{navigate-prC5V8XV.js → navigate-CP_0Zy5U.js} +454 -87
  60. package/dist/navigation.d.ts +12 -3
  61. package/dist/peer-types.d.ts +8 -0
  62. package/dist/portal-DuOKfnLa.js +349 -0
  63. package/dist/promise-Dz8iIHAm.js +16 -0
  64. package/dist/query-registry-WMCN0BJL.js +320 -0
  65. package/dist/readable-DFWOsTac.js +21 -0
  66. package/dist/{render-context-B7ohlh6t.js → render-context-DGHnEveC.js} +27 -11
  67. package/dist/render-sync-IC5lMFpq.js +1240 -0
  68. package/dist/resolution-Cx82-xjW.js +1244 -0
  69. package/dist/resource-CLjB7RBQ.js +377 -0
  70. package/dist/resources/index.d.ts +21 -11
  71. package/dist/resources/index.js +45 -244
  72. package/dist/router/index.d.ts +14 -5
  73. package/dist/router/index.js +22 -53
  74. package/dist/scope-CwpVSHVf.js +55 -0
  75. package/dist/{shared-kU8yvryp.js → shared-O2ZnmYAf.js} +1 -7
  76. package/dist/{snapshot-source-An4iKajG.js → snapshot-Cjx4N988.js} +6 -1
  77. package/dist/ssg/index.d.ts +1 -2
  78. package/dist/ssg/index.js +5 -13
  79. package/dist/ssr/index.d.ts +26 -28
  80. package/dist/ssr/index.js +4 -4
  81. package/dist/{ssr-CiNm6Vzl.js → ssr-BJ8ug7zH.js} +133 -122
  82. package/dist/ssr-DoV-vJ-5.js +229 -0
  83. package/dist/state-DWO1KzkX.js +232 -0
  84. package/dist/structures-D4dDMqHX.js +231 -0
  85. package/dist/testing/index.d.ts +8 -4
  86. package/dist/testing/index.js +29 -34
  87. package/dist/url-CnbfpX0K.js +185 -0
  88. package/dist/utilities.d.ts +48 -2
  89. package/dist/verify-hydration-DpVaDnqz.js +152 -0
  90. package/package.json +19 -9
  91. package/dist/access-CEA2pOry.js +0 -1046
  92. package/dist/activity-oy2YFTFF.js +0 -201
  93. package/dist/boot-BMBMWUGI.js +0 -1084
  94. package/dist/capabilities-Bbf0mlp9.js +0 -42
  95. package/dist/compose-ref-v0qGlHoN.js +0 -26
  96. package/dist/control-BR_mrX__.js +0 -173
  97. package/dist/control-C-ApnFs1.js +0 -20
  98. package/dist/csp-nonce-BfFWcd0w.js +0 -173
  99. package/dist/data-runtime-CxoY98lX.js +0 -149
  100. package/dist/default-portal-runtime-DvMvWJVp.js +0 -20
  101. package/dist/dom-renderer.d.ts +0 -77
  102. package/dist/domains/renderer.d.ts +0 -127
  103. package/dist/eager-control.d.ts +0 -8
  104. package/dist/effect-C3Zsc1k6.js +0 -292
  105. package/dist/for-state-BDykeOJL.js +0 -1753
  106. package/dist/instance-VAFYVJOa.js +0 -1027
  107. package/dist/notify-DxxuDPeY.js +0 -266
  108. package/dist/portal-w3oQ6oQU.js +0 -29
  109. package/dist/query-registry-LrYpQsDJ.js +0 -83
  110. package/dist/render-resolved-BC0W7_5b.js +0 -1123
  111. package/dist/renderer-CT0HeY58.js +0 -8623
  112. package/dist/resolution-3DskUkjG.js +0 -577
  113. package/dist/resource-18-sFRl9.js +0 -344
  114. package/dist/route-matching-CUzZ31gT.js +0 -551
  115. package/dist/router-internal-7nuNEHfG.js +0 -36
  116. package/dist/scope-Cq_iYbTR.js +0 -403
  117. package/dist/selector-BSPVkjKP.js +0 -311
  118. package/dist/ssr-EPjwJH5R.js +0 -29
  119. package/dist/state-BXJfudq1.js +0 -91
  120. package/dist/structures-CjEzfj4h.js +0 -654
  121. package/dist/timer-C7uqnJVt.js +0 -85
  122. package/dist/verify-hydration-ikaxotf7.js +0 -27
  123. package/dist/vnode-D24p6IW2.js +0 -7
@@ -0,0 +1,1244 @@
1
+ import { n as Fragment, t as ELEMENT_TYPE } from "./jsx-DRS-qjiN.js";
2
+ import { r as currentAppRuntime } from "./hooks-BimKStr4.js";
3
+ import { r as readScope, t as defineScope } from "./scope-CwpVSHVf.js";
4
+ import { S as replacePageRoute, n as getActiveRenderContext, w as withPageFramework } from "./render-context-DGHnEveC.js";
5
+ import { n as isPromiseLike } from "./promise-Dz8iIHAm.js";
6
+ import { c as withTelemetry, l as jsonTransportPropertyPath, p as isDeferred, t as createQueryPrefetchContext, u as validateJsonTransportValue } from "./query-registry-WMCN0BJL.js";
7
+ import { c as resolveNavigationUrl, i as formatNavigationUrl } from "./url-CnbfpX0K.js";
8
+ //#region src/router/route-hydration.ts
9
+ function isDeferredHydrationValue(value) {
10
+ return isDeferred(value);
11
+ }
12
+ const omissionGuards = /* @__PURE__ */ new WeakMap();
13
+ const propertyPath = jsonTransportPropertyPath;
14
+ /** Validate the exact value that will enter the route hydration envelope. */
15
+ function validateRouteHydrationData(value, route) {
16
+ validateJsonTransportValue(value, `Route hydration data for "${route}"`, "Return JSON-compatible data or use route(..., { dehydrate(data) { ... } }) to omit server-only values.");
17
+ }
18
+ function omissionMap(complete, selected, ancestors = /* @__PURE__ */ new Set()) {
19
+ if (!complete || typeof complete !== "object" || isDeferredHydrationValue(complete)) return void 0;
20
+ if (ancestors.has(complete)) return void 0;
21
+ ancestors.add(complete);
22
+ const selectedObject = selected && typeof selected === "object" ? selected : void 0;
23
+ const output = Object.create(null);
24
+ try {
25
+ for (const key of Object.keys(complete)) {
26
+ if (!selectedObject || !Object.prototype.hasOwnProperty.call(selectedObject, key)) {
27
+ output[key] = 1;
28
+ continue;
29
+ }
30
+ const completeDescriptor = Object.getOwnPropertyDescriptor(complete, key);
31
+ const selectedDescriptor = Object.getOwnPropertyDescriptor(selectedObject, key);
32
+ if (!completeDescriptor || !selectedDescriptor) continue;
33
+ if (!("value" in completeDescriptor) || !("value" in selectedDescriptor)) continue;
34
+ const nested = omissionMap(completeDescriptor.value, selectedDescriptor.value, ancestors);
35
+ if (nested) output[key] = nested;
36
+ }
37
+ } finally {
38
+ ancestors.delete(complete);
39
+ }
40
+ return Object.keys(output).length > 0 ? Object.freeze(output) : void 0;
41
+ }
42
+ function prepareRouteHydrationData(complete, dehydrate, context, route = context.href || context.pathname) {
43
+ const selected = dehydrate ? dehydrate(complete, context) : complete;
44
+ if (isPromiseLike(selected)) throw new TypeError(`[Askr] Route dehydrate() for "${route}" returned a Promise. dehydrate(data, context) must be synchronous.`);
45
+ validateRouteHydrationData(selected, route);
46
+ return {
47
+ data: selected,
48
+ metadata: Object.freeze({
49
+ r: route,
50
+ ...dehydrate ? { o: omissionMap(complete, selected) } : {}
51
+ })
52
+ };
53
+ }
54
+ function readMetadata(framework) {
55
+ const value = framework["rh"];
56
+ if (!value || typeof value !== "object" || Array.isArray(value)) return;
57
+ const metadata = value;
58
+ return typeof metadata.r === "string" ? metadata : void 0;
59
+ }
60
+ function omittedRead(route, path) {
61
+ throw new Error(`[Askr] routeData() read "${path}" on the initial hydration of "${route}", but that branch was intentionally omitted by dehydrate(). Keep initial hydrated rendering within the selected data, or wait for a client navigation that reruns the loader with the complete result.`);
62
+ }
63
+ function guardOmissions(value, omissions, route, path = "$") {
64
+ if (!omissions || !value || typeof value !== "object" || isDeferredHydrationValue(value)) return value;
65
+ let guards = omissionGuards.get(value);
66
+ if (!guards) {
67
+ guards = /* @__PURE__ */ new WeakMap();
68
+ omissionGuards.set(value, guards);
69
+ }
70
+ const cached = guards.get(omissions);
71
+ if (cached) return cached;
72
+ const guarded = new Proxy(value, { get(target, property, receiver) {
73
+ if (typeof property === "string") {
74
+ const omitted = omissions[property];
75
+ if (omitted && !Object.prototype.hasOwnProperty.call(target, property)) omittedRead(route, propertyPath(path, property));
76
+ const child = Reflect.get(target, property, receiver);
77
+ return omitted === 1 ? child : guardOmissions(child, omitted, route, propertyPath(path, property));
78
+ }
79
+ return Reflect.get(target, property, receiver);
80
+ } });
81
+ guards.set(omissions, guarded);
82
+ return guarded;
83
+ }
84
+ function guardHydratedRouteData(value, framework) {
85
+ const metadata = readMetadata(framework);
86
+ return metadata ? guardOmissions(value, metadata.o, metadata.r) : value;
87
+ }
88
+ function getRouteHydrationMetadata(framework) {
89
+ return readMetadata(framework);
90
+ }
91
+ /** @internal Remove initial-only omission guards after a client loader reruns. */
92
+ function withoutRouteHydrationMetadata(framework) {
93
+ if (!framework || !("rh" in framework)) return framework;
94
+ const next = { ...framework };
95
+ delete next["rh"];
96
+ return next;
97
+ }
98
+ //#endregion
99
+ //#region src/router/match.ts
100
+ function splitPathSegments(path) {
101
+ const normalized = path.endsWith("/") && path !== "/" ? path.slice(0, -1) : path;
102
+ if (!normalized || normalized === "/") return [];
103
+ const withoutLeadingSlash = normalized.startsWith("/") ? normalized.slice(1) : normalized;
104
+ return withoutLeadingSlash.length === 0 ? [] : withoutLeadingSlash.split("/");
105
+ }
106
+ function normalizeRouteSegmentName(value) {
107
+ return value.trim();
108
+ }
109
+ /**
110
+ * Parse a route template path into typed segments.
111
+ *
112
+ * @example
113
+ * parseSegments('/users/{id}') // [{kind:'static',value:'users'},{kind:'param',value:'id'}]
114
+ * parseSegments('/*') // [{kind:'catchall',value:'*'}]
115
+ * parseSegments('/posts/*') // [{kind:'static',value:'posts'},{kind:'wildcard',value:'*'}]
116
+ * parseSegments('/files/{*path}') // [{kind:'static',value:'files'},{kind:'splat',value:'path'}]
117
+ */
118
+ function parseSegments(path) {
119
+ const parts = (path.endsWith("/") && path !== "/" ? path.slice(0, -1) : path).split("/").filter(Boolean);
120
+ if (parts.length === 1 && parts[0] === "*") return [{
121
+ kind: "catchall",
122
+ value: "*"
123
+ }];
124
+ return parts.map((segment) => {
125
+ if (segment.startsWith("{") && segment.endsWith("}")) {
126
+ const value = normalizeRouteSegmentName(segment.slice(1, -1));
127
+ if (value.startsWith("*")) return {
128
+ kind: "splat",
129
+ value: normalizeRouteSegmentName(value.slice(1))
130
+ };
131
+ return {
132
+ kind: "param",
133
+ value
134
+ };
135
+ }
136
+ if (segment === "*") return {
137
+ kind: "wildcard",
138
+ value: "*"
139
+ };
140
+ return {
141
+ kind: "static",
142
+ value: segment
143
+ };
144
+ });
145
+ }
146
+ /**
147
+ * Per-segment specificity weights. Routes are compared segment by segment and
148
+ * the first differing segment decides: static > param > wildcard > (end of
149
+ * route) > splat. "End of route" only ever meets a splat at the same position
150
+ * for routes that match the same URL, so an exact route beats an empty splat.
151
+ */
152
+ const SEGMENT_WEIGHT = {
153
+ static: 4,
154
+ param: 3,
155
+ wildcard: 2,
156
+ splat: 0,
157
+ catchall: 0
158
+ };
159
+ const END_OF_ROUTE_WEIGHT = 1;
160
+ const RANK_BASE = 8;
161
+ function segmentWeight(segment) {
162
+ if (segment === void 0) return END_OF_ROUTE_WEIGHT;
163
+ return SEGMENT_WEIGHT[segment.kind];
164
+ }
165
+ function isCatchAll(segments) {
166
+ return segments.length === 1 && segments[0].kind === "catchall";
167
+ }
168
+ /**
169
+ * Order two parsed routes by specificity: negative when `a` is more specific
170
+ * than `b`, positive when less, `0` when they tie (declaration order decides).
171
+ *
172
+ * Segments are compared left to right and the first differing segment decides
173
+ * (static > param > wildcard > splat); the bare `/*` catch-all is always last.
174
+ */
175
+ function compareRouteSpecificity(a, b) {
176
+ const aCatchAll = isCatchAll(a);
177
+ const bCatchAll = isCatchAll(b);
178
+ if (aCatchAll || bCatchAll) return Number(aCatchAll) - Number(bCatchAll);
179
+ const length = Math.max(a.length, b.length);
180
+ for (let i = 0; i < length; i++) {
181
+ const difference = segmentWeight(b[i]) - segmentWeight(a[i]);
182
+ if (difference !== 0) return difference;
183
+ }
184
+ return 0;
185
+ }
186
+ /**
187
+ * Key for the set of URLs a route matches, used to reject duplicate
188
+ * registrations. Parameter and splat names do not affect matching, a `*`
189
+ * wildcard matches exactly one segment like a param (and always loses to it),
190
+ * static segments compare decoded (see {@link staticSegmentMatches}), and a
191
+ * scoped fallback matches exactly what a splat at its prefix matches, so
192
+ * routes with equal keys match the same URLs and the later one could never be
193
+ * reached.
194
+ */
195
+ function routeMatchKey(segments, fallbackPrefix) {
196
+ const parts = (fallbackPrefix === void 0 ? segments : parseSegments(fallbackPrefix)).map((segment) => {
197
+ switch (segment.kind) {
198
+ case "static": return encodePathSegment(decodePathSegment(segment.value));
199
+ case "param":
200
+ case "wildcard": return "{}";
201
+ case "splat":
202
+ case "catchall": return "{*}";
203
+ }
204
+ });
205
+ if (fallbackPrefix !== void 0) parts.push("{*}");
206
+ return "/" + parts.join("/");
207
+ }
208
+ /**
209
+ * Compute a numeric specificity rank from a parsed segment list.
210
+ *
211
+ * The rank encodes the segment-by-segment order of
212
+ * {@link compareRouteSpecificity} as base-8 digits (higher = more specific);
213
+ * the bare `/*` catch-all is `-1`. It is informational: route ordering uses
214
+ * `compareRouteSpecificity` so arbitrarily deep routes compare exactly.
215
+ */
216
+ function computeRank(segments) {
217
+ if (isCatchAll(segments)) return -1;
218
+ let rank = 0;
219
+ let scale = 1;
220
+ for (const segment of segments) {
221
+ scale /= RANK_BASE;
222
+ rank += segmentWeight(segment) * scale;
223
+ }
224
+ return rank + END_OF_ROUTE_WEIGHT * scale / 7;
225
+ }
226
+ /** Reused frozen empty params object — returned for purely-static (no-capture) routes. */
227
+ const emptyParams = Object.freeze(Object.create(null));
228
+ Object.freeze({
229
+ matched: false,
230
+ params: emptyParams
231
+ });
232
+ /**
233
+ * Encoded path separators (`/` and backslash) that stay percent-encoded when a URL
234
+ * segment is decoded, so a capture never gains a separator the URL did not
235
+ * have (`..%2F..%2Fetc` must not become `../../etc`).
236
+ */
237
+ const ENCODED_SEPARATOR = /(%2F|%5C)/i;
238
+ function decodeSegmentPiece(piece) {
239
+ if (!piece.includes("%")) return piece;
240
+ try {
241
+ return decodeURIComponent(piece);
242
+ } catch {
243
+ return piece;
244
+ }
245
+ }
246
+ /**
247
+ * Decode one URL path segment, keeping encoded separators as upper-case
248
+ * `%2F`/`%5C` and malformed encodings as written.
249
+ */
250
+ function decodePathSegment(part) {
251
+ if (!part.includes("%")) return part;
252
+ return part.split(ENCODED_SEPARATOR).map((piece, index) => index % 2 === 1 ? piece.toUpperCase() : decodeSegmentPiece(piece)).join("");
253
+ }
254
+ /**
255
+ * Percent-encode one path segment value, the inverse of
256
+ * {@link decodePathSegment}: kept `%2F`/`%5C` separators pass through so a
257
+ * capture round-trips to the URL it came from.
258
+ */
259
+ function encodePathSegment(value) {
260
+ return value.split(ENCODED_SEPARATOR).map((piece, index) => index % 2 === 1 ? piece.toUpperCase() : encodeURIComponent(piece)).join("");
261
+ }
262
+ /**
263
+ * Compare a static route segment with a URL segment. URL parts arrive
264
+ * percent-encoded (`caf%C3%A9`), so decoded forms are compared and `/café`,
265
+ * `/a b` and reserved-character routes match.
266
+ */
267
+ function staticSegmentMatches(value, part) {
268
+ return value === part || decodePathSegment(value) === decodePathSegment(part);
269
+ }
270
+ /**
271
+ * Format the `*` capture of a catch-all or fallback from the remaining URL
272
+ * parts, decoding each segment like param and splat captures.
273
+ */
274
+ function formatCatchAllCapture(parts) {
275
+ if (parts.length === 0) return "/";
276
+ if (parts.length === 1) return decodePathSegment(parts[0]);
277
+ return "/" + parts.map(decodePathSegment).join("/");
278
+ }
279
+ /**
280
+ * Match pre-split URL parts against pre-parsed route segments.
281
+ *
282
+ * This is the hot-path matcher used by `resolveRoute` and
283
+ * `resolveRouteFromRoutes`. Callers split the URL path **once** and reuse
284
+ * `urlParts` across all route comparisons in a single resolution call.
285
+ *
286
+ * - Returns a params object (possibly the shared `{}`) on match.
287
+ * - Returns `null` on no match.
288
+ * - Params are allocated lazily — purely-static routes return the frozen
289
+ * empty sentinel without any heap allocation.
290
+ */
291
+ function matchSegments(urlParts, segments) {
292
+ if (segments.length === 1 && segments[0].kind === "catchall") return { "*": formatCatchAllCapture(urlParts) };
293
+ const splatIndex = segments.findIndex((segment) => segment.kind === "splat");
294
+ if (splatIndex !== -1) {
295
+ if (splatIndex !== segments.length - 1) return null;
296
+ if (urlParts.length < splatIndex) return null;
297
+ } else if (urlParts.length !== segments.length) return null;
298
+ let params = null;
299
+ const normalizeCapturedSplatParts = (parts) => {
300
+ let start = 0;
301
+ while (start < parts.length && parts[start] === "") start += 1;
302
+ return start === 0 ? parts : parts.slice(start);
303
+ };
304
+ for (let i = 0; i < segments.length; i++) {
305
+ const seg = segments[i];
306
+ const part = urlParts[i];
307
+ if (seg.kind === "static") {
308
+ if (!staticSegmentMatches(seg.value, part)) return null;
309
+ } else if (seg.kind === "splat") {
310
+ if (params === null) params = {};
311
+ params[seg.value] = normalizeCapturedSplatParts(urlParts.slice(i).map(decodePathSegment)).join("/");
312
+ return params;
313
+ } else {
314
+ if (params === null) params = {};
315
+ if (seg.kind === "param") params[seg.value] = decodePathSegment(part);
316
+ else params["*"] = decodePathSegment(part);
317
+ }
318
+ }
319
+ return params ?? emptyParams;
320
+ }
321
+ //#endregion
322
+ //#region src/common/router-internal.ts
323
+ const ROUTE_ROOT_COMPONENT = Symbol.for("askr.route-root-component");
324
+ //#endregion
325
+ //#region src/router/route-data-loader.ts
326
+ /** Rejection of a `lazyRouteData()` loader whose import or `select` failed; wraps the original `cause`. */
327
+ var RouteDataLoadError = class extends Error {
328
+ constructor(route, phase, cause) {
329
+ const detail = cause instanceof Error ? cause.message : String(cause);
330
+ super("[Askr] Route data loader failed for \"" + route + "\" during " + phase + " loading: " + detail);
331
+ this.name = "RouteDataLoadError";
332
+ this.route = route;
333
+ this.phase = phase;
334
+ this.cause = cause;
335
+ }
336
+ };
337
+ function isAbortError(error) {
338
+ return typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
339
+ }
340
+ function loadPhase(mode) {
341
+ if (mode === "spa") return "client";
342
+ if (mode === "ssg") return "ssg";
343
+ return "server";
344
+ }
345
+ /** @internal Add route context to failures from lazyRouteData(). */
346
+ function _wrapRouteDataLoadError(error, context) {
347
+ if (error instanceof RouteDataLoadError || isAbortError(error)) throw error;
348
+ throw new RouteDataLoadError(context.href || context.pathname, loadPhase(context.mode), error);
349
+ }
350
+ //#endregion
351
+ //#region src/router/lazy.ts
352
+ const pendingLazy = /* @__PURE__ */ new Set();
353
+ const lazyStates = /* @__PURE__ */ new WeakMap();
354
+ const handlerLazyComponents = /* @__PURE__ */ new WeakMap();
355
+ function startLazyLoad(state) {
356
+ if (state.promise) return state.promise;
357
+ let promise;
358
+ promise = Promise.resolve().then(state.factory).then((mod) => {
359
+ state.resolved = typeof mod === "function" ? mod : mod.default;
360
+ state.loadError = null;
361
+ state.hasLoadError = false;
362
+ }, (err) => {
363
+ state.loadError = err;
364
+ state.hasLoadError = true;
365
+ }).finally(() => {
366
+ if (state.hasLoadError) state.promise = null;
367
+ pendingLazy.delete(promise);
368
+ });
369
+ state.promise = promise;
370
+ pendingLazy.add(promise);
371
+ return promise;
372
+ }
373
+ /** Wrap a dynamic-import factory as a {@link LazyRouteComponent}, loaded on first use. */
374
+ function lazy(factory) {
375
+ const state = {
376
+ factory,
377
+ promise: null,
378
+ resolved: null,
379
+ loadError: null,
380
+ hasLoadError: false
381
+ };
382
+ const component = ((params) => {
383
+ if (state.hasLoadError) throw state.loadError;
384
+ if (!state.resolved) throw new Error("lazy() component used before it was resolved. Matched routes load automatically; call component.preload() for explicit preloading.");
385
+ return state.resolved(params);
386
+ });
387
+ Object.defineProperty(component, "preload", { value: () => startLazyLoad(state) });
388
+ lazyStates.set(component, state);
389
+ return component;
390
+ }
391
+ /**
392
+ * Create a cached route loader backed by a dynamic import. The module is only
393
+ * requested when the owning route matches, unless preload() is called.
394
+ */
395
+ function lazyRouteData(factory, select) {
396
+ let imported = null;
397
+ const load = () => {
398
+ if (imported) return imported;
399
+ imported = Promise.resolve().then(factory).catch((error) => {
400
+ imported = null;
401
+ throw error;
402
+ });
403
+ return imported;
404
+ };
405
+ const loader = ((context) => load().then((module) => select ? select(module, context) : module).catch((error) => _wrapRouteDataLoadError(error, context)));
406
+ Object.defineProperty(loader, "preload", { value: () => load().then(() => void 0) });
407
+ return loader;
408
+ }
409
+ function _preloadRouteRecord(record) {
410
+ const imports = [
411
+ record.component,
412
+ ...record.pageChain.map((scope) => scope.component),
413
+ ...record.layoutChain.map((scope) => scope.component)
414
+ ].flatMap((component) => {
415
+ const state = lazyStates.get(component);
416
+ return state && !state.resolved ? [startLazyLoad(state)] : [];
417
+ });
418
+ return imports.length > 0 ? Promise.all(imports).then(() => void 0) : null;
419
+ }
420
+ function _associateLazyHandler(handler, components) {
421
+ handlerLazyComponents.set(handler, components);
422
+ }
423
+ function _preloadRouteHandler(handler) {
424
+ const imports = (handlerLazyComponents.get(handler) ?? []).flatMap((component) => {
425
+ const state = lazyStates.get(component);
426
+ return state ? [startLazyLoad(state)] : [];
427
+ });
428
+ return imports.length > 0 ? Promise.all(imports).then(() => void 0) : null;
429
+ }
430
+ //#endregion
431
+ //#region src/router/rendering.ts
432
+ const outletScope = defineScope(null);
433
+ const clientLayouts = /* @__PURE__ */ new WeakMap();
434
+ function clientLayout(layout) {
435
+ let wrapped = clientLayouts.get(layout);
436
+ if (!wrapped) {
437
+ wrapped = (props) => {
438
+ const output = layout(props);
439
+ return typeof Node !== "undefined" && output instanceof Node ? null : output;
440
+ };
441
+ clientLayouts.set(layout, wrapped);
442
+ }
443
+ return wrapped;
444
+ }
445
+ /** Renders the nested route content for the enclosing layout or page scope. */
446
+ function Outlet() {
447
+ return {
448
+ $$typeof: ELEMENT_TYPE,
449
+ type: Fragment,
450
+ props: { children: readScope(outletScope) },
451
+ key: null
452
+ };
453
+ }
454
+ function applyPageChain(pageChain, params, content, deferComponents = false) {
455
+ let nextContent = content;
456
+ for (let i = pageChain.length - 1; i >= 0; i--) nextContent = outletScope({
457
+ value: nextContent,
458
+ children: deferComponents ? createRouteComponentVNode(pageChain[i].component, params) : pageChain[i].component(params)
459
+ });
460
+ return nextContent;
461
+ }
462
+ function createRouteComponentVNode(component, params, routeRoot = false) {
463
+ return {
464
+ $$typeof: ELEMENT_TYPE,
465
+ type: component,
466
+ props: params,
467
+ key: routeRoot ? currentAppRuntime()?.lifetime ?? null : null,
468
+ ...routeRoot ? { [ROUTE_ROOT_COMPONENT]: true } : {}
469
+ };
470
+ }
471
+ function createRouteHandler(component, pageChain, layoutChain, deferComponents = false) {
472
+ const handler = (params) => {
473
+ let content = deferComponents ? createRouteComponentVNode(component, params, true) : component(params);
474
+ content = applyPageChain(pageChain, params, content, deferComponents);
475
+ for (let i = layoutChain.length - 1; i >= 0; i--) {
476
+ const layout = layoutChain[i].component;
477
+ const rendered = deferComponents ? {
478
+ $$typeof: ELEMENT_TYPE,
479
+ type: clientLayout(layout),
480
+ props: { children: content },
481
+ key: null
482
+ } : layout({ children: content });
483
+ content = typeof Node !== "undefined" && rendered instanceof Node ? null : rendered;
484
+ }
485
+ return content;
486
+ };
487
+ _associateLazyHandler(handler, [
488
+ component,
489
+ ...pageChain.map((scope) => scope.component),
490
+ ...layoutChain.map((scope) => scope.component)
491
+ ]);
492
+ return handler;
493
+ }
494
+ function getRenderHandler(record) {
495
+ return record.renderHandler ?? createRouteHandler(record.component, record.pageChain, record.layoutChain, true);
496
+ }
497
+ //#endregion
498
+ //#region src/router/store.ts
499
+ function createRouteTable() {
500
+ return {
501
+ routes: [],
502
+ records: [],
503
+ namespaces: /* @__PURE__ */ new Set(),
504
+ routesByDepth: /* @__PURE__ */ new Map(),
505
+ registrationScopeStack: [],
506
+ registrationLocked: false,
507
+ defaultRouteAuthOptions: void 0,
508
+ activeClientRouteAuthOptions: void 0,
509
+ defaultRouteBasePath: ""
510
+ };
511
+ }
512
+ let activeTable = createRouteTable();
513
+ /** Run `fn` with `table` as the registration target, then restore the previous one. */
514
+ function withRouteTable(table, fn) {
515
+ const previous = activeTable;
516
+ activeTable = table;
517
+ try {
518
+ return fn();
519
+ } finally {
520
+ activeTable = previous;
521
+ }
522
+ }
523
+ function getDepth(path) {
524
+ const normalized = path.endsWith("/") && path !== "/" ? path.slice(0, -1) : path;
525
+ return normalized === "/" ? 0 : normalized.split("/").filter(Boolean).length;
526
+ }
527
+ function getRouteRecords() {
528
+ return activeTable.records;
529
+ }
530
+ function isRouteStoreRoutes(routeList) {
531
+ return routeList === activeTable.routes;
532
+ }
533
+ function insertRecordSorted(record) {
534
+ let lo = 0;
535
+ let hi = activeTable.records.length;
536
+ while (lo < hi) {
537
+ const mid = lo + hi >>> 1;
538
+ if (compareRouteSpecificity(activeTable.records[mid].segments, record.segments) <= 0) lo = mid + 1;
539
+ else hi = mid;
540
+ }
541
+ activeTable.records.splice(lo, 0, record);
542
+ }
543
+ function addRouteToStores(routeObj) {
544
+ activeTable.routes.push(routeObj);
545
+ const depth = getDepth(routeObj.path);
546
+ let depthRoutes = activeTable.routesByDepth.get(depth);
547
+ if (!depthRoutes) {
548
+ depthRoutes = [];
549
+ activeTable.routesByDepth.set(depth, depthRoutes);
550
+ }
551
+ depthRoutes.push(routeObj);
552
+ if (routeObj.namespace) activeTable.namespaces.add(routeObj.namespace);
553
+ }
554
+ function getActiveRoutes() {
555
+ return getActiveRenderContext()?.routes ?? currentAppRuntime()?.routeRegistry?.routes ?? activeTable.routes;
556
+ }
557
+ function getActiveRouteAuthOptions(override) {
558
+ if (override !== void 0) return override;
559
+ const renderContext = getActiveRenderContext();
560
+ if (renderContext) return renderContext.routeAuth;
561
+ const appRuntime = currentAppRuntime();
562
+ if (appRuntime) return appRuntime.routeAuth;
563
+ return activeTable.activeClientRouteAuthOptions ?? activeTable.defaultRouteAuthOptions;
564
+ }
565
+ function _setActiveRouteAuthOptions(auth) {
566
+ activeTable.activeClientRouteAuthOptions = auth;
567
+ }
568
+ function getDefaultRouteAuthOptions() {
569
+ return activeTable.defaultRouteAuthOptions;
570
+ }
571
+ function setDefaultRouteAuthOptions(auth) {
572
+ activeTable.defaultRouteAuthOptions = auth;
573
+ }
574
+ function getDefaultRouteBasePath() {
575
+ return activeTable.defaultRouteBasePath;
576
+ }
577
+ function setDefaultRouteBasePath(basePath) {
578
+ activeTable.defaultRouteBasePath = basePath;
579
+ }
580
+ function getActiveRouteBasePath() {
581
+ const renderContext = getActiveRenderContext();
582
+ if (renderContext?.basePath !== void 0) return renderContext.basePath;
583
+ const appRegistry = currentAppRuntime()?.routeRegistry;
584
+ if (appRegistry) return appRegistry.manifest.basePath ?? "";
585
+ return activeTable.defaultRouteBasePath;
586
+ }
587
+ function getCurrentLayoutChain() {
588
+ const layoutChain = [];
589
+ for (const scope of activeTable.registrationScopeStack) if (scope.layout) layoutChain.push({ component: scope.layout });
590
+ return layoutChain;
591
+ }
592
+ function getCurrentPageChain() {
593
+ const pageChain = [];
594
+ for (const scope of activeTable.registrationScopeStack) if (scope.page) pageChain.push({ component: scope.page });
595
+ return pageChain;
596
+ }
597
+ function hasActivePageScope() {
598
+ return activeTable.registrationScopeStack.some((scope) => !!scope.page);
599
+ }
600
+ function getCurrentPageScope() {
601
+ for (let index = activeTable.registrationScopeStack.length - 1; index >= 0; index -= 1) {
602
+ const scope = activeTable.registrationScopeStack[index];
603
+ if (scope.kind === "page") return scope;
604
+ }
605
+ return null;
606
+ }
607
+ function getCurrentScopeKind() {
608
+ return activeTable.registrationScopeStack[activeTable.registrationScopeStack.length - 1]?.kind ?? null;
609
+ }
610
+ function getCurrentPathPrefix() {
611
+ return activeTable.registrationScopeStack[activeTable.registrationScopeStack.length - 1]?.pathPrefix ?? "";
612
+ }
613
+ function getCurrentInheritedPolicies() {
614
+ const policies = [];
615
+ for (const scope of activeTable.registrationScopeStack) if (scope.policies.length > 0) policies.push(...scope.policies);
616
+ return policies;
617
+ }
618
+ function getCurrentInheritedAuthRequirements() {
619
+ return activeTable.registrationScopeStack.flatMap((scope) => scope.auth ? [scope.auth] : []);
620
+ }
621
+ function getCurrentInheritedMeta() {
622
+ return activeTable.registrationScopeStack.flatMap((scope) => scope.meta ? [scope.meta] : []);
623
+ }
624
+ function pushRegistrationScope(scope, fn) {
625
+ activeTable.registrationScopeStack.push(scope);
626
+ try {
627
+ fn();
628
+ } finally {
629
+ activeTable.registrationScopeStack.pop();
630
+ }
631
+ }
632
+ function lockRouteRegistration() {
633
+ activeTable.registrationLocked = true;
634
+ }
635
+ function assertRouteRegistrationUnlocked() {
636
+ if (activeTable.registrationLocked) throw new Error("Route registration is locked after app startup. Register routes at module load time before calling createSPA or hydrateSPA.");
637
+ }
638
+ function getRouteList() {
639
+ return [...activeTable.routes];
640
+ }
641
+ function hasRegisteredRoutes() {
642
+ return activeTable.routes.length > 0 || activeTable.records.length > 0;
643
+ }
644
+ function clearRouteState() {
645
+ activeTable.routes.length = 0;
646
+ activeTable.records.length = 0;
647
+ activeTable.namespaces.clear();
648
+ activeTable.routesByDepth.clear();
649
+ activeTable.registrationScopeStack.length = 0;
650
+ activeTable.registrationLocked = false;
651
+ activeTable.defaultRouteAuthOptions = void 0;
652
+ activeTable.activeClientRouteAuthOptions = void 0;
653
+ activeTable.defaultRouteBasePath = "";
654
+ }
655
+ //#endregion
656
+ //#region src/router/route-context.ts
657
+ function parseLocation(url) {
658
+ try {
659
+ const parsedUrl = new URL(url, "http://localhost");
660
+ return {
661
+ pathname: parsedUrl.pathname,
662
+ search: parsedUrl.search,
663
+ hash: parsedUrl.hash
664
+ };
665
+ } catch {
666
+ return {
667
+ pathname: "/",
668
+ search: "",
669
+ hash: ""
670
+ };
671
+ }
672
+ }
673
+ function deepFreeze(obj) {
674
+ if (obj && typeof obj === "object" && !Object.isFrozen(obj)) {
675
+ Object.freeze(obj);
676
+ for (const key of Object.keys(obj)) {
677
+ const value = obj[key];
678
+ if (value && typeof value === "object") deepFreeze(value);
679
+ }
680
+ }
681
+ return obj;
682
+ }
683
+ function makeQuery(search) {
684
+ const searchParams = new URLSearchParams(search || "");
685
+ const mapping = /* @__PURE__ */ new Map();
686
+ for (const [key, value] of searchParams.entries()) {
687
+ const existing = mapping.get(key);
688
+ if (existing) existing.push(value);
689
+ else mapping.set(key, [value]);
690
+ }
691
+ return deepFreeze({
692
+ get(key) {
693
+ const values = mapping.get(key);
694
+ return values ? values[0] : null;
695
+ },
696
+ getAll(key) {
697
+ const values = mapping.get(key);
698
+ return values ? [...values] : [];
699
+ },
700
+ has(key) {
701
+ return mapping.has(key);
702
+ },
703
+ toJSON() {
704
+ const output = {};
705
+ for (const [key, values] of mapping.entries()) output[key] = values.length > 1 ? [...values] : values[0];
706
+ return output;
707
+ }
708
+ });
709
+ }
710
+ function buildRouteContextBase(target, params, options) {
711
+ const parsed = parseLocation(target);
712
+ const href = `${parsed.pathname}${parsed.search}${parsed.hash}`;
713
+ return {
714
+ mode: options.mode,
715
+ params,
716
+ pathname: parsed.pathname,
717
+ search: parsed.search,
718
+ hash: parsed.hash,
719
+ href,
720
+ signal: options.signal
721
+ };
722
+ }
723
+ function buildRouteContext(target, params, options) {
724
+ return {
725
+ ...buildRouteContextBase(target, params, options),
726
+ auth: options.authContext
727
+ };
728
+ }
729
+ //#endregion
730
+ //#region src/router/base-path.ts
731
+ /** Normalize the public mount point used by one route registry. */
732
+ function normalizeRouteBasePath(value) {
733
+ if (value === void 0 || value === "" || value === "/") return "";
734
+ if (!value.startsWith("/") || /\/{2,}/.test(value) || value.includes("?") || value.includes("#") || value.includes("\\")) throw new TypeError(`Route basePath must be an absolute pathname without a query or hash. Received: ${JSON.stringify(value)}`);
735
+ const normalized = value.endsWith("/") ? value.slice(0, -1) : value;
736
+ if (normalized.split("/").some((segment) => segment === "." || segment === "..")) throw new TypeError("Route basePath must not contain dot segments.");
737
+ return normalized;
738
+ }
739
+ function parsedTarget(target) {
740
+ return new URL(target, "http://askr.invalid");
741
+ }
742
+ /**
743
+ * Strip `basePath` from a pathname, comparing segments in decoded form so an
744
+ * encoded pathname (`/caf%C3%A9/menu`) matches its base (`/café`). Returns the
745
+ * remaining pathname (at least `/`), or `undefined` outside the base.
746
+ */
747
+ function stripRouteBasePath(pathname, basePath) {
748
+ const pathParts = pathname.split("/");
749
+ const baseParts = basePath.split("/");
750
+ if (pathParts.length < baseParts.length) return void 0;
751
+ for (let i = 1; i < baseParts.length; i++) if (!staticSegmentMatches(baseParts[i], pathParts[i])) return void 0;
752
+ return `/${pathParts.slice(baseParts.length).join("/")}`;
753
+ }
754
+ /**
755
+ * Add a registry mount point to one logical root-relative route target. The
756
+ * target is always logical, so `/app/settings` under `/app` becomes
757
+ * `/app/app/settings`; callers holding a public href must not pass it here.
758
+ */
759
+ function addRouteBasePath(target, basePath) {
760
+ if (!basePath || !target.startsWith("/") || target.startsWith("//")) return target;
761
+ const parsed = parsedTarget(target);
762
+ if (parsed.origin !== "http://askr.invalid") return target;
763
+ return `${basePath}${parsed.pathname}${parsed.search}${parsed.hash}`;
764
+ }
765
+ /**
766
+ * Add the mount point to an app-supplied string target (`navigate()`,
767
+ * `<Link href>`, `redirect()`). In development, warn when the target already
768
+ * starts with the base: strings are logical, so it gains the base again.
769
+ */
770
+ function addLogicalRouteBasePath(target, basePath) {
771
+ return addRouteBasePath(target, basePath);
772
+ }
773
+ /** Remove a registry mount point and retain query/hash for logical matching. */
774
+ function removeRouteBasePath(target, basePath) {
775
+ const parsed = parsedTarget(target);
776
+ if (!basePath) return `${parsed.pathname}${parsed.search}${parsed.hash}`;
777
+ const logicalPath = stripRouteBasePath(parsed.pathname, basePath);
778
+ if (logicalPath === void 0) return void 0;
779
+ return `${logicalPath}${parsed.search}${parsed.hash}`;
780
+ }
781
+ //#endregion
782
+ //#region src/router/route-matching.ts
783
+ const routeSegsCache = /* @__PURE__ */ new WeakMap();
784
+ const sortedListCache = /* @__PURE__ */ new WeakMap();
785
+ function cachedSegs(route) {
786
+ let segments = routeSegsCache.get(route);
787
+ if (!segments) {
788
+ segments = parseSegments(route.path);
789
+ routeSegsCache.set(route, segments);
790
+ }
791
+ return segments;
792
+ }
793
+ function cachedSortedList(routeList) {
794
+ let sorted = sortedListCache.get(routeList);
795
+ if (!sorted) {
796
+ sorted = [...routeList].sort((a, b) => compareRouteSpecificity(cachedSegs(a), cachedSegs(b)));
797
+ sortedListCache.set(routeList, sorted);
798
+ }
799
+ return sorted;
800
+ }
801
+ const prefixSegsCache = /* @__PURE__ */ new Map();
802
+ function cachedPrefixSegs(prefix) {
803
+ let segments = prefixSegsCache.get(prefix);
804
+ if (!segments) {
805
+ segments = parseSegments(prefix);
806
+ prefixSegsCache.set(prefix, segments);
807
+ }
808
+ return segments;
809
+ }
810
+ /**
811
+ * Match a scoped fallback's page prefix against the start of `pathname`.
812
+ * Prefix segments match like route segments, so a fallback inside
813
+ * `page('/{lang}')` matches `/en/...` and captures `lang`; the rest of the
814
+ * path is the `*` capture.
815
+ */
816
+ function matchFallbackPrefix(pathname, fallbackPrefix) {
817
+ const urlParts = splitPathSegments(pathname);
818
+ const prefixSegments = cachedPrefixSegs(fallbackPrefix);
819
+ if (urlParts.length < prefixSegments.length) return null;
820
+ const prefixParams = matchSegments(urlParts.slice(0, prefixSegments.length), prefixSegments);
821
+ if (prefixParams === null) return null;
822
+ return {
823
+ ...prefixParams,
824
+ "*": formatCatchAllCapture(urlParts.slice(prefixSegments.length))
825
+ };
826
+ }
827
+ function findBestResolvedRouteFromRoutes(pathname, routeList) {
828
+ const normalized = pathname.endsWith("/") && pathname !== "/" ? pathname.slice(0, -1) : pathname;
829
+ const urlParts = splitPathSegments(normalized);
830
+ for (const route of cachedSortedList(routeList)) {
831
+ if (route.fallbackPrefix) continue;
832
+ const params = matchSegments(urlParts, cachedSegs(route));
833
+ if (params !== null) return {
834
+ route,
835
+ params
836
+ };
837
+ }
838
+ const fallback = findDeepestFallback(normalized, routeList);
839
+ return fallback ? {
840
+ route: fallback.entry,
841
+ params: fallback.params
842
+ } : null;
843
+ }
844
+ /**
845
+ * Pick the scoped fallback whose prefix matches `pathname` with the most
846
+ * segments. Depth is counted in segments, not characters, so an encoded prefix
847
+ * such as `/caf%C3%A9` does not outrank a deeper `/café/x`; equal depths are
848
+ * ordered by segment specificity.
849
+ */
850
+ function findDeepestFallback(pathname, entries) {
851
+ let best = null;
852
+ let bestSegments = [];
853
+ for (const entry of entries) {
854
+ if (!entry.fallbackPrefix) continue;
855
+ const params = matchFallbackPrefix(pathname, entry.fallbackPrefix);
856
+ if (params === null) continue;
857
+ const segments = cachedPrefixSegs(entry.fallbackPrefix);
858
+ if (best === null || segments.length > bestSegments.length || segments.length === bestSegments.length && compareRouteSpecificity(segments, bestSegments) < 0) {
859
+ best = {
860
+ entry,
861
+ params
862
+ };
863
+ bestSegments = segments;
864
+ }
865
+ }
866
+ return best;
867
+ }
868
+ function findBestScopedFallbackRecord(pathname, routeRecords) {
869
+ const fallback = findDeepestFallback(pathname, routeRecords);
870
+ return fallback ? {
871
+ record: fallback.entry,
872
+ params: fallback.params
873
+ } : null;
874
+ }
875
+ function getMatchingRouteRecord(target, routeRecords) {
876
+ const location = parseLocation(target);
877
+ const normalized = location.pathname.endsWith("/") && location.pathname !== "/" ? location.pathname.slice(0, -1) : location.pathname;
878
+ const urlParts = splitPathSegments(normalized);
879
+ for (const record of routeRecords) {
880
+ if (record.fallbackPrefix) continue;
881
+ const params = matchSegments(urlParts, record.segments);
882
+ if (params !== null) return {
883
+ record,
884
+ params
885
+ };
886
+ }
887
+ return findBestScopedFallbackRecord(normalized, routeRecords);
888
+ }
889
+ function computeMatchesFromRoutes(pathname, routesList) {
890
+ const bestMatch = isRouteStoreRoutes(routesList) ? getMatchingRouteRecord(pathname, getRouteRecords()) : findBestResolvedRouteFromRoutes(pathname, routesList);
891
+ if (!bestMatch) return [];
892
+ return [{
893
+ path: "route" in bestMatch ? bestMatch.route.path : bestMatch.record.path,
894
+ params: deepFreeze({ ...bestMatch.params }),
895
+ name: "route" in bestMatch ? bestMatch.route.name : void 0,
896
+ namespace: "route" in bestMatch ? bestMatch.route.namespace : bestMatch.record.options.namespace
897
+ }];
898
+ }
899
+ function computeMatchesFromRouteRecords(pathname, routeRecords) {
900
+ const bestMatch = getMatchingRouteRecord(pathname, routeRecords);
901
+ if (!bestMatch) return [];
902
+ return [{
903
+ path: bestMatch.record.path,
904
+ params: deepFreeze({ ...bestMatch.params }),
905
+ namespace: bestMatch.record.options.namespace
906
+ }];
907
+ }
908
+ function computeRouteActivityMatches(pathname, options) {
909
+ const logicalPath = removeRouteBasePath(pathname, normalizeRouteBasePath(options.registry.manifest.basePath));
910
+ if (logicalPath === void 0) return [];
911
+ return computeMatchesFromRouteRecords(logicalPath, options.registry.manifest.records);
912
+ }
913
+ /** Match a path against a route registry without resolving route policies or loading data. */
914
+ function matchRoute(pathname, options) {
915
+ if (!options?.registry) throw new TypeError("matchRoute requires options.registry.");
916
+ return computeRouteActivityMatches(pathname, options)[0] ?? null;
917
+ }
918
+ function resolveRoute(pathname) {
919
+ const normalized = pathname.endsWith("/") && pathname !== "/" ? pathname.slice(0, -1) : pathname;
920
+ const urlParts = splitPathSegments(normalized);
921
+ const records = getRouteRecords();
922
+ for (const record of records) {
923
+ if (record.fallbackPrefix) continue;
924
+ const params = matchSegments(urlParts, record.segments);
925
+ if (params !== null) return {
926
+ handler: record.handler,
927
+ params
928
+ };
929
+ }
930
+ const fallbackMatch = findBestScopedFallbackRecord(normalized, records);
931
+ return fallbackMatch ? {
932
+ handler: fallbackMatch.record.handler,
933
+ params: fallbackMatch.params
934
+ } : null;
935
+ }
936
+ function resolveRouteFromRoutes(pathname, routeList) {
937
+ if (isRouteStoreRoutes(routeList)) return resolveRoute(pathname);
938
+ const match = findBestResolvedRouteFromRoutes(pathname, routeList);
939
+ return match ? {
940
+ handler: match.route.handler,
941
+ params: match.params
942
+ } : null;
943
+ }
944
+ const anonymous = Object.freeze({
945
+ authenticated: false,
946
+ principal: null,
947
+ session: null,
948
+ tenant: null
949
+ });
950
+ let clientAuth = anonymous;
951
+ let hydratedAuth;
952
+ /** Return the identity resolved for the route currently being rendered. */
953
+ function currentAuth() {
954
+ const render = getActiveRenderContext();
955
+ if (render) return render.authContext ?? anonymous;
956
+ return clientAuth;
957
+ }
958
+ /** @internal Updated atomically with route resolution. */
959
+ function setCurrentAuth(context, mode) {
960
+ const render = getActiveRenderContext();
961
+ if (render) {
962
+ render.authContext = context;
963
+ return;
964
+ }
965
+ if (mode === "spa") clientAuth = context;
966
+ }
967
+ /**
968
+ * @internal Read and validate the server's opted-in identity snapshot. It
969
+ * decides the initial route the server already authorized and, when the app
970
+ * configures no `resolve`, stays the client identity for navigations. It is
971
+ * never a credential; the server enforces access.
972
+ */
973
+ function readHydratedAuth(envelope) {
974
+ hydratedAuth = void 0;
975
+ const value = envelope?.framework["au"];
976
+ if (value === void 0) return void 0;
977
+ const { authenticated, principal, tenant, scopes } = value ?? {};
978
+ if (typeof authenticated !== "boolean" || principal !== null && typeof principal?.id !== "string" || tenant !== null && typeof tenant !== "string" || scopes !== void 0 && !(Array.isArray(scopes) && scopes.every((s) => typeof s === "string"))) throw new TypeError("[Askr] Malformed hydration auth snapshot.");
979
+ return hydratedAuth = Object.freeze({
980
+ authenticated,
981
+ principal,
982
+ session: null,
983
+ tenant,
984
+ ...scopes ? { scopes } : {}
985
+ });
986
+ }
987
+ /** @internal The hydration envelope without the auth snapshot. */
988
+ function withoutHydratedAuth(envelope) {
989
+ if (!envelope || !("au" in envelope.framework)) return envelope;
990
+ const framework = { ...envelope.framework };
991
+ delete framework["au"];
992
+ return withPageFramework(envelope, framework);
993
+ }
994
+ /** @internal Identity for a request when no `resolve` is configured. */
995
+ function unresolvedAuth(mode) {
996
+ return mode === "spa" && !getActiveRenderContext() && hydratedAuth || anonymous;
997
+ }
998
+ //#endregion
999
+ //#region src/router/policy.ts
1000
+ /**
1001
+ * Records the public href of a redirect built from `to()`. It is an own
1002
+ * enumerable symbol, so `{ ...redirect(to(route)), status: 303 }` keeps it;
1003
+ * it only applies while `to` still equals that href. Trade-off: the key is
1004
+ * visible to `toEqual()` and `util.inspect()`, while `structuredClone()` and
1005
+ * JSON drop it, so a cloned decision is treated as logical and re-prefixed.
1006
+ */
1007
+ const PUBLIC_REDIRECT_HREF = Symbol("askr.publicRedirectHref");
1008
+ /**
1009
+ * Validate a non-allow access decision before it leaves route resolution: a
1010
+ * redirect to a path-like target that resolves to another origin throws.
1011
+ */
1012
+ function checkAccessDecision(decision) {
1013
+ if (decision.kind === "redirect") resolveNavigationUrl(decision.to);
1014
+ return decision;
1015
+ }
1016
+ /** Build a redirect decision; a typed destination's href is kept public. */
1017
+ function redirectDecision(to, init = {}, mapHref = (href) => href) {
1018
+ const decision = {
1019
+ kind: "redirect",
1020
+ to: mapHref(typeof to === "string" ? to : to.href),
1021
+ ...init.status ? { status: init.status } : {},
1022
+ ...init.replace !== void 0 ? { replace: init.replace } : {}
1023
+ };
1024
+ if (typeof to !== "string") decision[PUBLIC_REDIRECT_HREF] = decision.to;
1025
+ return checkAccessDecision(decision);
1026
+ }
1027
+ /**
1028
+ * Expose a redirect with a public target: a logical string gains the registry
1029
+ * `basePath`, while a typed destination is already public.
1030
+ */
1031
+ function exposeRedirectDecision(decision, basePath) {
1032
+ const publicHref = decision[PUBLIC_REDIRECT_HREF];
1033
+ if (publicHref !== void 0 && publicHref === decision.to) return decision;
1034
+ return {
1035
+ ...decision,
1036
+ to: addLogicalRouteBasePath(decision.to, basePath)
1037
+ };
1038
+ }
1039
+ /** Policy decision: allow the route to render. */
1040
+ function allow() {
1041
+ return { kind: "allow" };
1042
+ }
1043
+ /**
1044
+ * Policy decision: redirect the visitor to `to`. A string is a logical path
1045
+ * that gains the registry `basePath`; a `to()` destination is used as-is.
1046
+ */
1047
+ function redirect(to, init = {}) {
1048
+ return redirectDecision(to, init);
1049
+ }
1050
+ /** Policy decision: deny the request with the given HTTP status. */
1051
+ function deny(status) {
1052
+ return {
1053
+ kind: "deny",
1054
+ status
1055
+ };
1056
+ }
1057
+ /** Policy decision: deny with 401 Unauthorized. */
1058
+ function unauthorized() {
1059
+ return deny(401);
1060
+ }
1061
+ /** Policy decision: deny with 403 Forbidden. */
1062
+ function forbidden() {
1063
+ return deny(403);
1064
+ }
1065
+ /** Policy decision: deny with 404 Not Found. */
1066
+ function notFound() {
1067
+ return deny(404);
1068
+ }
1069
+ //#endregion
1070
+ //#region src/router/resolution.ts
1071
+ const routeRenderContexts = /* @__PURE__ */ new WeakMap();
1072
+ const routeRenderData = /* @__PURE__ */ new WeakMap();
1073
+ function getRouteRenderContext(result) {
1074
+ return routeRenderContexts.get(result);
1075
+ }
1076
+ function getRouteRenderData(result) {
1077
+ return routeRenderData.get(result);
1078
+ }
1079
+ function createRouteRenderResult(record, params, context, handler) {
1080
+ const result = {
1081
+ kind: "render",
1082
+ handler,
1083
+ params
1084
+ };
1085
+ Object.defineProperty(result, "record", {
1086
+ value: record,
1087
+ enumerable: false
1088
+ });
1089
+ routeRenderContexts.set(result, context);
1090
+ return result;
1091
+ }
1092
+ function getDefaultRouteMode() {
1093
+ return typeof window !== "undefined" ? "spa" : "ssr";
1094
+ }
1095
+ function withoutInitialRouteHydrationMetadata(envelope) {
1096
+ return withPageFramework(envelope, withoutRouteHydrationMetadata(envelope.framework) ?? {});
1097
+ }
1098
+ /** @internal Bind resolved loader output to one route subtree and its hydration envelope. */
1099
+ function bindResolvedRouteData(handler, data, hydration) {
1100
+ return (params, context) => {
1101
+ const renderContext = getActiveRenderContext();
1102
+ if (renderContext) {
1103
+ const envelope = replacePageRoute(renderContext.renderData, data);
1104
+ renderContext.renderData = hydration ? envelope : withoutInitialRouteHydrationMetadata(envelope);
1105
+ const hydrationEnvelope = replacePageRoute(renderContext.hydrationData, hydration?.data ?? data);
1106
+ renderContext.hydrationData = hydration ? withPageFramework(hydrationEnvelope, {
1107
+ ...hydrationEnvelope.framework,
1108
+ ["rh"]: hydration.metadata
1109
+ }) : withoutInitialRouteHydrationMetadata(hydrationEnvelope);
1110
+ }
1111
+ return handler(params, context);
1112
+ };
1113
+ }
1114
+ function hasRouteRenderData(result) {
1115
+ return routeRenderData.has(result);
1116
+ }
1117
+ function buildRenderResult(record, params, context, request, telemetry, load, dataRuntime) {
1118
+ const lazyImport = _preloadRouteRecord(record);
1119
+ if (lazyImport) return lazyImport.then(() => buildLoadedRenderResult(record, params, context, request, telemetry, load, dataRuntime));
1120
+ return buildLoadedRenderResult(record, params, context, request, telemetry, load, dataRuntime);
1121
+ }
1122
+ function buildLoadedRenderResult(record, params, context, request, telemetry, load, dataRuntime) {
1123
+ const renderHandler = getRenderHandler(record);
1124
+ const loader = context.mode !== "ssg" && load ? record.options?.loader : void 0;
1125
+ const preload = record.options?.preload;
1126
+ const active = getActiveRenderContext();
1127
+ const prefetch = active?.queryPrefetch ?? createQueryPrefetchContext({
1128
+ mode: context.mode === "ssg" ? "ssr" : context.mode,
1129
+ request,
1130
+ signal: context.signal,
1131
+ runtime: active?.dataRuntime ?? dataRuntime,
1132
+ telemetry
1133
+ });
1134
+ const runPreload = preload ? Promise.resolve(preload({
1135
+ ...context,
1136
+ params,
1137
+ request,
1138
+ data: prefetch
1139
+ })).then(() => void 0) : void 0;
1140
+ if (loader) {
1141
+ const load = () => withTelemetry(telemetry?.loader, { route: record.path }, () => loader({
1142
+ ...context,
1143
+ params,
1144
+ request
1145
+ }));
1146
+ const loaded = runPreload ? runPreload.then(load) : load();
1147
+ const finalize = (data) => {
1148
+ const hydration = context.mode === "ssr" ? prepareRouteHydrationData(data, record.options?.dehydrate, {
1149
+ ...context,
1150
+ params,
1151
+ request
1152
+ }, context.href || context.pathname || record.path) : void 0;
1153
+ const result = createRouteRenderResult(record, params, context, bindResolvedRouteData(renderHandler, data, hydration));
1154
+ routeRenderData.set(result, data);
1155
+ return result;
1156
+ };
1157
+ return isPromiseLike(loaded) ? Promise.resolve(loaded).then(finalize) : finalize(loaded);
1158
+ }
1159
+ if (runPreload) return runPreload.then(() => createRouteRenderResult(record, params, context, renderHandler));
1160
+ return createRouteRenderResult(record, params, context, renderHandler);
1161
+ }
1162
+ function runPolicies(policies, context, record, params, request, telemetry, load, dataRuntime, start = 0) {
1163
+ for (let index = start; index < policies.length; index += 1) {
1164
+ const result = policies[index](context);
1165
+ if (isPromiseLike(result)) return Promise.resolve(result).then((decision) => decision.kind === "allow" ? runPolicies(policies, context, record, params, request, telemetry, load, dataRuntime, index + 1) : checkAccessDecision(decision));
1166
+ if (result.kind !== "allow") return checkAccessDecision(result);
1167
+ }
1168
+ return buildRenderResult(record, params, context, request, telemetry, load, dataRuntime);
1169
+ }
1170
+ function resolvePath(value, fallback, context) {
1171
+ return typeof value === "function" ? value(context) : value ?? fallback;
1172
+ }
1173
+ function appendNext(path, href) {
1174
+ const target = resolveNavigationUrl(path);
1175
+ target.searchParams.set("next", href);
1176
+ return formatNavigationUrl(target);
1177
+ }
1178
+ function mapAuthDecision(decision, context, options) {
1179
+ if (decision.allowed) return { kind: "allow" };
1180
+ if (decision.reason === "forbidden") return {
1181
+ kind: "deny",
1182
+ status: 403
1183
+ };
1184
+ const target = resolvePath(decision.reason === "unauthenticated" ? options?.loginPath : options?.authenticatedRedirectTo, decision.reason === "unauthenticated" ? "/login" : "/", context);
1185
+ const finalize = (path) => redirectDecision(path, { replace: decision.reason === "unauthenticated" ? context.mode === "spa" : true }, (href) => decision.reason === "unauthenticated" ? appendNext(href, context.href) : href);
1186
+ return isPromiseLike(target) ? Promise.resolve(target).then(finalize) : finalize(target);
1187
+ }
1188
+ function resolveMatchedRoute(record, params, context, authOptions, request, telemetry, load, dataRuntime) {
1189
+ const continueResolution = (decision) => {
1190
+ if (decision) {
1191
+ const access = mapAuthDecision(decision, context, authOptions);
1192
+ if (isPromiseLike(access)) return Promise.resolve(access).then((next) => next.kind === "allow" ? runPolicies(record.options.policies ?? [], context, record, params, request, telemetry, load, dataRuntime) : next);
1193
+ if (access.kind !== "allow") return access;
1194
+ }
1195
+ return runPolicies(record.options.policies ?? [], context, record, params, request, telemetry, load, dataRuntime);
1196
+ };
1197
+ if (!record.options.auth) return continueResolution();
1198
+ const decision = record.options.auth(context.auth);
1199
+ return isPromiseLike(decision) ? Promise.resolve(decision).then(continueResolution) : continueResolution(decision);
1200
+ }
1201
+ /**
1202
+ * Callers may omit `signal`, and `RouteRequestOptions.signal` is optional in the
1203
+ * published contract. Route context still needs an `AbortSignal`, so this stands
1204
+ * in for "this caller supplied no cancellation". It never aborts: a route loader
1205
+ * receiving it cannot be cancelled through `context.signal`. Prefer passing a
1206
+ * real signal.
1207
+ */
1208
+ const NEVER_ABORTED = new AbortController().signal;
1209
+ /** Resolve `target` against a route registry, applying auth/policies to produce a render/redirect/deny result. */
1210
+ function resolveRouteRequest(target, options) {
1211
+ if (!options?.registry) throw new TypeError("resolveRouteRequest requires options.registry.");
1212
+ const basePath = normalizeRouteBasePath(options.registry.manifest.basePath);
1213
+ const logicalTarget = removeRouteBasePath(target, basePath);
1214
+ if (logicalTarget === void 0) return null;
1215
+ return withTelemetry(options.telemetry?.routeMatch, {}, () => {
1216
+ const records = options.registry.manifest.records;
1217
+ const match = getMatchingRouteRecord(logicalTarget, records);
1218
+ if (!match) return null;
1219
+ const mode = options.mode ?? getDefaultRouteMode();
1220
+ const signal = options.signal ?? getActiveRenderContext()?.signal ?? NEVER_ABORTED;
1221
+ const authOptions = getActiveRouteAuthOptions(options.auth ?? options.registry.manifest.auth);
1222
+ const base = buildRouteContextBase(logicalTarget, match.params, {
1223
+ mode,
1224
+ signal
1225
+ });
1226
+ const exposeRedirect = (result) => result?.kind === "redirect" ? exposeRedirectDecision(result, basePath) : result;
1227
+ const finalize = (authContext) => {
1228
+ if (!signal.aborted) setCurrentAuth(authContext, mode);
1229
+ const result = resolveMatchedRoute(match.record, match.params, buildRouteContext(logicalTarget, match.params, {
1230
+ mode,
1231
+ signal,
1232
+ authContext
1233
+ }), authOptions, options.request, options.telemetry, options.load !== false, options.dataRuntime);
1234
+ if (!basePath) return result;
1235
+ return isPromiseLike(result) ? Promise.resolve(result).then(exposeRedirect) : exposeRedirect(result);
1236
+ };
1237
+ if (options.authContext) return finalize(options.authContext);
1238
+ if (!authOptions?.resolve) return finalize(unresolvedAuth(mode));
1239
+ const resolved = authOptions.resolve(base);
1240
+ return isPromiseLike(resolved) ? Promise.resolve(resolved).then(finalize) : finalize(resolved);
1241
+ });
1242
+ }
1243
+ //#endregion
1244
+ export { withRouteTable as $, clearRouteState as A, getCurrentPathPrefix as B, removeRouteBasePath as C, _setActiveRouteAuthOptions as D, parseLocation as E, getCurrentInheritedMeta as F, getRouteRecords as G, getDefaultRouteAuthOptions as H, getCurrentInheritedPolicies as I, insertRecordSorted as J, hasActivePageScope as K, getCurrentLayoutChain as L, getActiveRouteBasePath as M, getActiveRoutes as N, addRouteToStores as O, getCurrentInheritedAuthRequirements as P, setDefaultRouteBasePath as Q, getCurrentPageChain as R, normalizeRouteBasePath as S, makeQuery as T, getDefaultRouteBasePath as U, getCurrentScopeKind as V, getRouteList as W, pushRegistrationScope as X, lockRouteRegistration as Y, setDefaultRouteAuthOptions as Z, getMatchingRouteRecord as _, resolveRouteRequest as a, lazyRouteData as at, addLogicalRouteBasePath as b, forbidden as c, encodePathSegment as ct, unauthorized as d, routeMatchKey as dt, Outlet as et, currentAuth as f, getRouteHydrationMetadata as ft, computeRouteActivityMatches as g, withoutRouteHydrationMetadata as gt, computeMatchesFromRoutes as h, validateRouteHydrationData as ht, hasRouteRenderData as i, lazy as it, createRouteTable as j, assertRouteRegistrationUnlocked as k, notFound as l, normalizeRouteSegmentName as lt, withoutHydratedAuth as m, prepareRouteHydrationData as mt, getRouteRenderContext as n, getRenderHandler as nt, allow as o, RouteDataLoadError as ot, readHydratedAuth as p, guardHydratedRouteData as pt, hasRegisteredRoutes as q, getRouteRenderData as r, _preloadRouteHandler as rt, deny as s, computeRank as st, bindResolvedRouteData as t, createRouteHandler as tt, redirect as u, parseSegments as ut, matchRoute as v, deepFreeze as w, addRouteBasePath as x, resolveRouteFromRoutes as y, getCurrentPageScope as z };