@askrjs/askr 0.0.62 → 0.0.65

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 (37) hide show
  1. package/README.md +5 -5
  2. package/dist/actions/index.d.ts +1 -1
  3. package/dist/authoring-CXMPffwN.js +200 -0
  4. package/dist/benchmark.js +4 -4
  5. package/dist/boot/index.d.ts +12 -32
  6. package/dist/boot/index.js +1 -1
  7. package/dist/{boot-B0mMBddb.js → boot-OnYuVVfg.js} +64 -75
  8. package/dist/components/index.d.ts +1 -1
  9. package/dist/control/index.d.ts +1 -1
  10. package/dist/{control-CFIrGbD7.d.ts → control-CMzFPMjr.d.ts} +3 -0
  11. package/dist/csp-nonce-C-8E85El.js +33 -0
  12. package/dist/{deferred-BeNnjR4r.js → deferred-B71c2eio.js} +51 -10
  13. package/dist/foundations/index.d.ts +1 -1
  14. package/dist/foundations/state/index.d.ts +1 -1
  15. package/dist/foundations/structures/index.d.ts +1 -1
  16. package/dist/{index-DaWuMuIc.d.ts → index-Bjm0__Rr.d.ts} +1 -1
  17. package/dist/{index-DlvpxdSz.d.ts → index-Byqjltu2.d.ts} +1 -1
  18. package/dist/{index-CRck4XVe.d.ts → index-NKPpdRh9.d.ts} +6 -2
  19. package/dist/{index-Bce-RQg-.d.ts → index-PgRn5wmg.d.ts} +1 -1
  20. package/dist/index.d.ts +4 -4
  21. package/dist/index.js +3 -2
  22. package/dist/{navigate-DLi79IZr.js → navigate-DTZ2SM8_.js} +68 -279
  23. package/dist/{renderer-CVr_nktj.js → renderer-aUD-acDe.js} +1 -1
  24. package/dist/{route-matching-vJpvLVbX.js → route-matching-C93To416.js} +2 -2
  25. package/dist/router/index.d.ts +4 -10
  26. package/dist/router/index.js +4 -4
  27. package/dist/{router-lmH-Tmwh.d.ts → router-eRoaj11J.d.ts} +9 -6
  28. package/dist/ssg/index.d.ts +8 -33
  29. package/dist/ssg/index.js +34 -36
  30. package/dist/ssr/index.d.ts +10 -45
  31. package/dist/ssr/index.js +1 -1
  32. package/dist/{ssr-5HlhmLhU.js → ssr-C0hzqhY8.js} +65 -43
  33. package/dist/testing/index.d.ts +4 -5
  34. package/dist/testing/index.js +7 -10
  35. package/dist/{verify-hydration-13f0FOPd.js → verify-hydration-BdKE9Hi8.js} +3 -3
  36. package/package.json +1 -1
  37. package/dist/router-internal-BC0w1_xk.js +0 -4
package/README.md CHANGED
@@ -54,16 +54,16 @@ Startup belongs in `@askrjs/askr/boot`. Routing helpers live in
54
54
 
55
55
  ```ts
56
56
  import { createSPA } from '@askrjs/askr/boot';
57
- import { getManifest, registerRoutes, route } from '@askrjs/askr/router';
57
+ import { createRouteRegistry, route } from '@askrjs/askr/router';
58
58
 
59
- registerRoutes(() => {
60
- route('/', () => <Home />);
61
- route('/about', () => <About />);
59
+ const registry = createRouteRegistry(() => {
60
+ route('/', Home);
61
+ route('/about', About);
62
62
  });
63
63
 
64
64
  createSPA({
65
65
  root: document.body,
66
- manifest: getManifest(),
66
+ registry,
67
67
  });
68
68
  ```
69
69
 
@@ -1,2 +1,2 @@
1
- import { a as action, i as ActionValidationError, n as ActionForm, o as defineAction, r as ActionStatus, t as ActionDescriptor } from "../index-DaWuMuIc.js";
1
+ import { a as action, i as ActionValidationError, n as ActionForm, o as defineAction, r as ActionStatus, t as ActionDescriptor } from "../index-Bjm0__Rr.js";
2
2
  export { ActionDescriptor, ActionForm, ActionStatus, ActionValidationError, action, defineAction };
@@ -0,0 +1,200 @@
1
+ import { f as getCurrentComponentInstance } from "./component-scope-CC3cojX9.js";
2
+ import { T as getExecutionModel, x as createRouteHandler } from "./deferred-B71c2eio.js";
3
+ import { A as insertRecordSorted, C as getCurrentPathPrefix, I as computeRank, L as normalizeRouteSegmentName, M as pushRegistrationScope, O as hasActivePageScope, R as parseSegments, S as getCurrentPageScope, _ as getCurrentInheritedAuthRequirements, b as getCurrentLayoutChain, f as addRouteToStores, p as assertRouteRegistrationUnlocked, v as getCurrentInheritedMeta, w as getCurrentScopeKind, x as getCurrentPageChain, y as getCurrentInheritedPolicies } from "./route-matching-C93To416.js";
4
+ import { allOf } from "@askrjs/auth";
5
+ //#region src/router/access.ts
6
+ function compileNodePolicies(node) {
7
+ return node.policies ? [...node.policies] : [];
8
+ }
9
+ //#endregion
10
+ //#region src/router/authoring.ts
11
+ function validateRoutePath(path) {
12
+ if (!path.startsWith("/")) throw new Error(`Route path must begin with "/". Got: "${path}"`);
13
+ if (/\/{2,}/.test(path)) throw new Error("Route path cannot contain consecutive slashes.");
14
+ if (/:([^/{}]+)/.test(path)) {
15
+ const suggested = path.replace(/:([^/{}]+)/g, "{$1}");
16
+ throw new Error(`Route parameter syntax uses {name} interpolation, not :name. Use "${suggested}" instead of "${path}".`);
17
+ }
18
+ const segments = path.split("/").filter(Boolean);
19
+ const seenParamNames = /* @__PURE__ */ new Set();
20
+ for (let index = 0; index < segments.length; index++) {
21
+ const segment = segments[index];
22
+ if (segment === "*") continue;
23
+ const hasOpenBrace = segment.includes("{");
24
+ const hasCloseBrace = segment.includes("}");
25
+ if (!hasOpenBrace && !hasCloseBrace) continue;
26
+ if (!(segment.startsWith("{") && segment.endsWith("}"))) throw new Error("Route parameter segments must use complete {name} interpolation.");
27
+ const rawParamName = normalizeRouteSegmentName(segment.slice(1, -1));
28
+ const isSplat = rawParamName.startsWith("*");
29
+ const paramName = isSplat ? normalizeRouteSegmentName(rawParamName.slice(1)) : rawParamName;
30
+ if (!paramName) throw new Error(isSplat ? "Route splat parameter name cannot be empty." : "Route parameter name cannot be empty.");
31
+ if (isSplat && paramName === "*") throw new Error("Route named splat parameter name cannot be \"*\".");
32
+ if (isSplat && index !== segments.length - 1) throw new Error("Route named splat parameters must be the final segment.");
33
+ if (seenParamNames.has(paramName)) throw new Error(`Route path cannot reuse duplicate parameter name "${paramName}".`);
34
+ seenParamNames.add(paramName);
35
+ }
36
+ }
37
+ function normalizeAbsoluteRoutePath(path) {
38
+ if (!path || path === "/") return "/";
39
+ return (path.endsWith("/") ? path.slice(0, -1) : path) || "/";
40
+ }
41
+ function joinRoutePaths(prefix, path) {
42
+ const normalizedPrefix = normalizeAbsoluteRoutePath(prefix || "/");
43
+ const normalizedPath = path.replace(/^\/+|\/+$/g, "");
44
+ if (!normalizedPath) return normalizedPrefix;
45
+ return normalizedPrefix === "/" ? `/${normalizedPath}` : `${normalizedPrefix}/${normalizedPath}`;
46
+ }
47
+ function resolvePageScopePath(path) {
48
+ if (!path) throw new Error("page(path, Component, fn) requires a non-empty path.");
49
+ if (path.startsWith("/")) {
50
+ validateRoutePath(path);
51
+ return normalizeAbsoluteRoutePath(path);
52
+ }
53
+ return joinRoutePaths(getCurrentPathPrefix(), path);
54
+ }
55
+ function resolveIndexPath() {
56
+ return normalizeAbsoluteRoutePath(getCurrentPathPrefix() || "/");
57
+ }
58
+ function resolveRouteRegistrationPath(path) {
59
+ if (path.startsWith("/")) {
60
+ if (hasActivePageScope()) throw new Error(`Child route paths inside page() must be relative. Use "${path.slice(1)}" instead of "${path}".`);
61
+ validateRoutePath(path);
62
+ return normalizeAbsoluteRoutePath(path);
63
+ }
64
+ const prefix = getCurrentPathPrefix();
65
+ if (!prefix) throw new Error(`Route path must begin with "/". Got: "${path}"`);
66
+ return joinRoutePaths(prefix, path);
67
+ }
68
+ function pushGroupScope(options, fn) {
69
+ const policies = compileNodePolicies(options);
70
+ pushRegistrationScope({
71
+ kind: "group",
72
+ pathPrefix: getCurrentPathPrefix(),
73
+ layout: options.layout,
74
+ auth: options.auth,
75
+ policies,
76
+ meta: options.meta
77
+ }, fn);
78
+ }
79
+ function pushPageScope(path, Component, options, fn) {
80
+ 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.");
81
+ const policies = compileNodePolicies(options);
82
+ pushRegistrationScope({
83
+ kind: "page",
84
+ pathPrefix: resolvePageScopePath(path),
85
+ page: Component,
86
+ hasIndex: false,
87
+ auth: options.auth,
88
+ policies,
89
+ meta: options.meta
90
+ }, fn);
91
+ }
92
+ function normalizeRouteOptions(options) {
93
+ if (!options) return;
94
+ const loader = options.loader;
95
+ const preload = options.preload;
96
+ const policies = compileNodePolicies(options);
97
+ if (!loader && !preload && !options.entries && policies.length === 0 && !options.title && !options.namespace && !options.search && !options.meta && !options.actions && options.auth === void 0) return;
98
+ return {
99
+ ...loader ? { loader } : {},
100
+ ...preload ? { preload } : {},
101
+ ...options.entries ? { entries: options.entries } : {},
102
+ ...options.auth !== void 0 ? { auth: options.auth } : {},
103
+ ...policies.length > 0 ? { policies } : {},
104
+ ...options.title ? { title: options.title } : {},
105
+ ...options.namespace ? { namespace: options.namespace } : {},
106
+ ...options.search ? { search: options.search } : {},
107
+ ...options.meta ? { meta: options.meta } : {},
108
+ ...options.actions ? { actions: options.actions } : {}
109
+ };
110
+ }
111
+ function registerRouteAtResolvedPath(path, Component, options, metadata) {
112
+ validateRoutePath(path);
113
+ const chain = getCurrentLayoutChain();
114
+ const pageChain = getCurrentPageChain();
115
+ const segments = parseSegments(path);
116
+ const rank = computeRank(segments);
117
+ const isFallback = metadata?.isFallback ?? path === "/*";
118
+ const comp = Component;
119
+ const normalizedOptions = normalizeRouteOptions(options);
120
+ const policies = [...getCurrentInheritedPolicies(), ...normalizedOptions?.policies ?? []];
121
+ const authRequirements = [...getCurrentInheritedAuthRequirements(), ...normalizedOptions?.auth ? [normalizedOptions.auth] : []];
122
+ const auth = authRequirements.length === 0 ? void 0 : authRequirements.length === 1 ? authRequirements[0] : allOf(...authRequirements);
123
+ const metaChain = [...getCurrentInheritedMeta(), ...normalizedOptions?.meta ? [normalizedOptions.meta] : []];
124
+ const handler = createRouteHandler(comp, pageChain, chain);
125
+ const renderHandler = createRouteHandler(comp, pageChain, chain, true);
126
+ insertRecordSorted({
127
+ path,
128
+ component: comp,
129
+ segments,
130
+ rank,
131
+ layoutChain: chain,
132
+ pageChain,
133
+ options: normalizedOptions ? {
134
+ ...normalizedOptions,
135
+ ...auth ? { auth } : {},
136
+ ...policies.length > 0 ? { policies } : {}
137
+ } : policies.length > 0 ? {
138
+ policies,
139
+ ...auth ? { auth } : {}
140
+ } : auth ? { auth } : {},
141
+ ...metaChain.length > 0 ? { metaChain } : {},
142
+ isFallback,
143
+ handler,
144
+ renderHandler,
145
+ ...metadata?.fallbackPrefix ? { fallbackPrefix: metadata.fallbackPrefix } : {}
146
+ });
147
+ addRouteToStores({
148
+ path,
149
+ handler,
150
+ namespace: normalizedOptions?.namespace ?? options?.namespace,
151
+ ...metadata?.fallbackPrefix ? { fallbackPrefix: metadata.fallbackPrefix } : {}
152
+ });
153
+ }
154
+ function group(options, fn) {
155
+ pushGroupScope(options, fn);
156
+ }
157
+ function page(path, Component, optionsOrFn, maybeFn) {
158
+ const options = typeof optionsOrFn === "function" ? {} : optionsOrFn;
159
+ const fn = typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
160
+ if (typeof Component !== "function") throw new Error("page(path, Component, fn) requires a component function as the second argument.");
161
+ if (typeof fn !== "function") throw new Error("page(path, Component, fn) requires a route definition callback as the final argument.");
162
+ pushPageScope(path, Component, options, fn);
163
+ }
164
+ function index(Component, options) {
165
+ const pageScope = getCurrentPageScope();
166
+ if (pageScope?.hasIndex) throw new Error("page() cannot declare multiple index routes.");
167
+ if (pageScope) pageScope.hasIndex = true;
168
+ registerRouteAtResolvedPath(resolveIndexPath(), Component, options);
169
+ }
170
+ function fallback(Component) {
171
+ if (hasActivePageScope()) {
172
+ if (getCurrentScopeKind() !== "page") throw new Error("fallback() inside page() must be declared directly in the page scope, not inside nested group().");
173
+ registerRouteAtResolvedPath(`${getCurrentPathPrefix()}/*`, Component, void 0, {
174
+ isFallback: true,
175
+ fallbackPrefix: getCurrentPathPrefix()
176
+ });
177
+ return;
178
+ }
179
+ if (!(getCurrentInheritedPolicies().length === 0 && getCurrentInheritedAuthRequirements().length === 0)) throw new Error("fallback() can only be registered at the root scope. Use route(\"/*\", Component) if you need compatibility behavior.");
180
+ registerRouteAtResolvedPath("/*", Component, void 0, {
181
+ isFallback: true,
182
+ fallbackPrefix: "/"
183
+ });
184
+ }
185
+ function route(path, Component, options) {
186
+ if (typeof path === "undefined") throw new Error("route() is only for route registration. Use currentRoute() inside components.");
187
+ if (getExecutionModel() === "islands") throw new Error("Routes are not supported with islands. Use createSPA (client) or createSSR (server) instead.");
188
+ const currentInst = getCurrentComponentInstance();
189
+ if (currentInst && currentInst.ssr) throw new Error("route() cannot be called during SSR rendering. Register routes at module load time instead.");
190
+ assertRouteRegistrationUnlocked();
191
+ 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.");
192
+ if (options?.search && options.search.kind !== "object") throw new Error("route search must be an object schema.");
193
+ registerRouteAtResolvedPath(resolveRouteRegistrationPath(path), Component, options);
194
+ return Object.freeze({
195
+ path: resolveRouteRegistrationPath(path),
196
+ ...options?.search ? { searchSchema: options.search } : {}
197
+ });
198
+ }
199
+ //#endregion
200
+ export { route as a, page as i, group as n, index as r, fallback as t };
package/dist/benchmark.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { r as flushRuntimeScheduler } from "./access-ePqdQqkV.js";
2
- import { t as installRendererBridge } from "./renderer-CVr_nktj.js";
2
+ import { t as installRendererBridge } from "./renderer-aUD-acDe.js";
3
3
  import { t as state } from "./state-Cc5HumQ0.js";
4
4
  import { t as selector } from "./selector-pApF_lGd.js";
5
5
  import { jsx, jsxs } from "./jsx-runtime.js";
6
6
  import { i as For } from "./control-KVP6LMEQ.js";
7
- import { t as createIsland } from "./boot-B0mMBddb.js";
7
+ import { t as createIsland } from "./boot-OnYuVVfg.js";
8
8
  //#region src/bench/components/benchmark-row.tsx
9
9
  function BenchmarkRow({ item, isSelected, onSelect, onRemove }) {
10
10
  return /* @__PURE__ */ jsxs("tr", {
@@ -63,8 +63,8 @@ function BenchmarkTable({ rows, isSelected, onSelect, onRemove }) {
63
63
  installRendererBridge();
64
64
  const benchmarkMetadata = {
65
65
  packageName: "@askrjs/askr",
66
- packageVersion: "0.0.62",
67
- buildLabel: "0.0.62-local"
66
+ packageVersion: "0.0.65",
67
+ buildLabel: "0.0.65-local"
68
68
  };
69
69
  function getBenchmarkMetadata() {
70
70
  return { ...benchmarkMetadata };
@@ -1,41 +1,26 @@
1
- import { s as ComponentFunction } from "../control-CFIrGbD7.js";
1
+ import { s as ComponentFunction } from "../control-CMzFPMjr.js";
2
2
  import { v as DataRuntime } from "../index-Bki2rqcr.js";
3
- import { P as RouteRegistry, m as RouteAuthOptions, p as Route, x as RouteManifest } from "../router-lmH-Tmwh.js";
3
+ import { N as RouteRegistry, p as RouteAuthOptions } from "../router-eRoaj11J.js";
4
4
  import { d as ScrollRestorationOptions } from "../navigate-BVDCzOuB.js";
5
5
 
6
6
  //#region src/boot/types.d.ts
7
7
  type IslandConfig = {
8
8
  root: Element | string;
9
9
  component: ComponentFunction;
10
+ cspNonce?: string;
10
11
  cleanupStrict?: boolean;
11
12
  routes?: never;
12
13
  };
13
14
  type IslandsConfig = {
14
15
  islands: IslandConfig[];
16
+ cspNonce?: string;
15
17
  };
16
18
  type BootRouteSource = {
17
19
  registry: RouteRegistry;
18
- manifest?: RouteManifest;
19
- routes?: Route[];
20
- } | {
21
- manifest: RouteManifest;
22
- registry?: RouteRegistry;
23
- routes?: Route[];
24
- } | {
25
- manifest?: RouteManifest;
26
- registry?: RouteRegistry;
27
- routes: Route[];
28
20
  };
29
21
  type SPAConfig = BootRouteSource & {
30
22
  root: Element | string;
31
- /**
32
- * Preferred: pass a route registry built via `createRouteRegistry(() => { ... })`.
33
- * ```ts
34
- * import { createRouteRegistry } from '@askrjs/askr/router';
35
- * const registry = createRouteRegistry(() => { ... });
36
- * await createSPA({ root: '#app', registry });
37
- * ```
38
- */
23
+ cspNonce?: string; /** Pass a route registry built via `createRouteRegistry(() => { ... })`. */
39
24
  auth?: RouteAuthOptions;
40
25
  scrollRestoration?: boolean | ScrollRestorationOptions;
41
26
  cleanupStrict?: boolean;
@@ -43,7 +28,8 @@ type SPAConfig = BootRouteSource & {
43
28
  };
44
29
  type HydrateSPAConfig = BootRouteSource & {
45
30
  root: Element | string;
46
- dataRuntime?: DataRuntime; /** Preferred route input - see `SPAConfig`. */
31
+ cspNonce?: string;
32
+ dataRuntime?: DataRuntime; /** Pass the same explicit route registry used for the server render. */
47
33
  auth?: RouteAuthOptions;
48
34
  scrollRestoration?: boolean | ScrollRestorationOptions;
49
35
  cleanupStrict?: boolean;
@@ -71,23 +57,17 @@ declare function createIsland(config: IslandConfig): void;
71
57
  */
72
58
  declare function createIslands(config: IslandsConfig): void;
73
59
  /**
74
- * createSPA: Initializes router and mounts the app with the provided route manifest or route table.
75
- *
76
- * Preferred usage with manifest:
60
+ * createSPA: Initializes the router and mounts the app with an explicit route registry.
77
61
  * ```ts
78
- * import { getManifest } from '@askrjs/askr/router';
79
- * await createSPA({ root: '#app', manifest: getManifest() });
62
+ * import { createRouteRegistry } from '@askrjs/askr/router';
63
+ * const registry = createRouteRegistry(() => { ... });
64
+ * await createSPA({ root: '#app', registry });
80
65
  * ```
81
66
  *
82
- * Legacy usage with plain routes array (still supported):
83
- * ```ts
84
- * await createSPA({ root: '#app', routes: getRoutes() });
85
- * ```
86
67
  */
87
68
  declare function createSPA(config: SPAConfig): Promise<void>;
88
69
  /**
89
- * hydrateSPA: Hydrate server-rendered HTML.
90
- * Accepts either a `manifest` (preferred) or a legacy `routes` array.
70
+ * hydrateSPA: Hydrate server-rendered HTML with an explicit route registry.
91
71
  */
92
72
  declare function hydrateSPA(config: HydrateSPAConfig): Promise<void>;
93
73
  //#endregion
@@ -1,2 +1,2 @@
1
- import { a as cleanupApp, i as hydrateSPA, n as createIslands, o as hasApp, r as createSPA, t as createIsland } from "../boot-B0mMBddb.js";
1
+ import { a as cleanupApp, i as hydrateSPA, n as createIslands, o as hasApp, r as createSPA, t as createIsland } from "../boot-OnYuVVfg.js";
2
2
  export { cleanupApp, createIsland, createIslands, createSPA, hasApp, hydrateSPA };
@@ -2,15 +2,16 @@ import { b as isDevelopmentEnvironment, r as flushRuntimeScheduler, v as logger,
2
2
  import { n as Fragment, t as ELEMENT_TYPE } from "./jsx-CUpRxrLH.js";
3
3
  import { n as createComponentInstance, r as executeComponent } from "./component-internal-CbALfPGD.js";
4
4
  import { h as cleanupComponent, m as isPromiseLike } from "./component-lifecycle-DEMPihoZ.js";
5
- import { a as withIntrinsicHydrationAdoption, i as registerDeferredHydrationBoundary, n as activateHydrationBoundary$1, o as setStaticChildSlotsCacheEnabled, r as clearDeferredHydrationBoundaries, t as installRendererBridge } from "./renderer-CVr_nktj.js";
5
+ import { a as withIntrinsicHydrationAdoption, i as registerDeferredHydrationBoundary, n as activateHydrationBoundary$1, o as setStaticChildSlotsCacheEnabled, r as clearDeferredHydrationBoundaries, t as installRendererBridge } from "./renderer-aUD-acDe.js";
6
6
  import { _ as teardownNodeSubtree, d as removeAllListeners } from "./cleanup-B6KovDZZ.js";
7
- import { C as assertExecutionModel, S as route, a as resolveRouteMeta, c as registerAppInstance, d as clearRoutes, g as createAppRenderRuntime, h as setServerLocation, i as reconcileRouteMeta, l as unregisterAppInstance, s as configureScrollRestoration, t as initializeNavigation, u as _applyManifest } from "./navigate-DLi79IZr.js";
7
+ import { c as _applyManifest, d as getRouteRenderContext, f as getRouteRenderData, m as resolveRouteRequest, o as reviveDeferredValue, p as hasRouteRenderData, w as assertExecutionModel, y as pageRenderEnvelope } from "./deferred-B71c2eio.js";
8
8
  import { a as startHydrationRenderPhase, s as stopHydrationRenderPhase } from "./render-context-Clw3rFiO.js";
9
9
  import { a as disposeDefaultPortalScope, t as DefaultPortal } from "./portal-D2tPWNrC.js";
10
+ import { r as validateCspNonce, t as CspNonceScope } from "./csp-nonce-C-8E85El.js";
10
11
  import { i as getDefaultDataRuntime } from "./data-runtime-SJ1BSUvk.js";
11
12
  import { a as hydrateDataRuntime } from "./query-registry-CQIT4mXr.js";
12
- import { a as resolveRouteFromRoutes, d as _setActiveRouteAuthOptions, j as lockRouteRegistration, k as hasRegisteredRoutes } from "./route-matching-vJpvLVbX.js";
13
- import { _ as pageRenderEnvelope, d as hasRouteRenderData, f as resolveRouteRequest, l as getRouteRenderContext, o as reviveDeferredValue, u as getRouteRenderData, x as _preloadRouteHandler } from "./deferred-BeNnjR4r.js";
13
+ import { d as _setActiveRouteAuthOptions, j as lockRouteRegistration, k as hasRegisteredRoutes, m as clearRouteState } from "./route-matching-C93To416.js";
14
+ import { a as resolveRouteMeta, c as registerAppInstance, d as setServerLocation, f as createAppRenderRuntime, i as reconcileRouteMeta, l as unregisterAppInstance, s as configureScrollRestoration, t as initializeNavigation } from "./navigate-DTZ2SM8_.js";
14
15
  import "./ssr-CbQO5xd3.js";
15
16
  //#region src/boot/hydration.ts
16
17
  const DEFERRED_PAYLOAD = "__askr_deferred__";
@@ -259,6 +260,7 @@ function attachCleanupForRoot(rootElement, instance) {
259
260
  } catch {}
260
261
  }
261
262
  function mountOrUpdate(rootElement, componentFn, options) {
263
+ const nonce = validateCspNonce(options?.cspNonce);
262
264
  const wrappedFn = (props, ctx) => {
263
265
  const out = componentFn(props, ctx);
264
266
  if (isPromiseLike(out)) throw new Error("Async components are not supported. Components must return synchronously.");
@@ -268,11 +270,15 @@ function mountOrUpdate(rootElement, componentFn, options) {
268
270
  props: { __askrAutoDefaultPortal: true },
269
271
  key: "__default_portal"
270
272
  };
271
- return {
273
+ const root = {
272
274
  $$typeof: ELEMENT_TYPE,
273
275
  type: Fragment,
274
276
  props: { children: out === void 0 || out === null ? [portalVNode] : [out, portalVNode] }
275
277
  };
278
+ return nonce === void 0 ? root : CspNonceScope({
279
+ value: nonce,
280
+ children: root
281
+ });
276
282
  };
277
283
  Object.defineProperty(wrappedFn, "name", { value: componentFn.name || "Component" });
278
284
  const existingCleanup = rootElement[CLEANUP_SYMBOL];
@@ -298,6 +304,7 @@ function mountOrUpdate(rootElement, componentFn, options) {
298
304
  instance.isRoot = true;
299
305
  instance.portalScope = instance;
300
306
  instance._appRenderRuntime = options?.appRuntime;
307
+ instance._cspNonce = nonce;
301
308
  if (shouldResetHookState) {
302
309
  instance.stateValues = [];
303
310
  instance.hasPendingUpdate = false;
@@ -323,6 +330,7 @@ function mountOrUpdate(rootElement, componentFn, options) {
323
330
  instance._rootComponentFn = componentFn;
324
331
  instance.portalScope = instance;
325
332
  instance._appRenderRuntime = options?.appRuntime;
333
+ instance._cspNonce = nonce;
326
334
  if (options && typeof options.cleanupStrict === "boolean") instance.cleanupStrict = options.cleanupStrict;
327
335
  }
328
336
  attachCleanupForRoot(rootElement, instance);
@@ -356,7 +364,7 @@ function cleanupApp(root) {
356
364
  const wasRoutedRoot = routedRoots.delete(rootElement);
357
365
  instancesByRoot.delete(rootElement);
358
366
  clearRootCleanupCallbacks(rootElement);
359
- if (wasRoutedRoot && routedRoots.size === 0) clearRoutes();
367
+ if (wasRoutedRoot && routedRoots.size === 0) clearRouteState();
360
368
  try {
361
369
  delete rootElement[CLEANUP_SYMBOL];
362
370
  } catch {}
@@ -400,21 +408,11 @@ async function resolveInitialRoute(auth, source) {
400
408
  for (let redirects = 0; redirects <= MAX_INITIAL_ROUTE_REDIRECTS; redirects++) {
401
409
  if (visited.has(href)) throw new Error(`[Askr] Route redirect cycle detected at ${href}.`);
402
410
  visited.add(href);
403
- const resolved = source?.manifest ? await resolveRouteRequest(href, {
404
- manifest: source.manifest,
411
+ const resolved = source?.registry ? await resolveRouteRequest(href, {
412
+ manifest: source.registry.manifest,
405
413
  auth,
406
414
  load: source.load
407
- }) : source?.routes ? await (() => {
408
- const match = resolveRouteFromRoutes(path, source.routes);
409
- if (!match) return null;
410
- const lazyImport = _preloadRouteHandler(match.handler);
411
- const result = {
412
- kind: "render",
413
- handler: match.handler,
414
- params: match.params
415
- };
416
- return lazyImport ? lazyImport.then(() => result) : result;
417
- })() : await resolveRouteRequest(href, { auth });
415
+ }) : await resolveRouteRequest(href, { auth });
418
416
  if (typeof window === "undefined" || !resolved || resolved.kind !== "redirect") return {
419
417
  path,
420
418
  href,
@@ -440,11 +438,15 @@ function createIsland(config) {
440
438
  assertExecutionModel("islands");
441
439
  if (!config || typeof config !== "object") throw new Error("createIsland requires a config object");
442
440
  if (typeof config.component !== "function") throw new Error("createIsland: component must be a function");
441
+ validateCspNonce(config.cspNonce);
443
442
  const rootElement = resolveRootElement(config.root);
444
443
  if (!rootElement) throw new Error(`Root element not found: ${config.root}`);
445
444
  if ("routes" in config) throw new Error("createIsland does not accept routes; use createSPA for routed apps");
446
445
  if (hasRegisteredRoutes()) throw new Error("Routes are not supported with islands. Use createSPA (client) or createSSR (server) instead.");
447
- mountOrUpdate(rootElement, config.component, { cleanupStrict: config.cleanupStrict });
446
+ mountOrUpdate(rootElement, config.component, {
447
+ cleanupStrict: config.cleanupStrict,
448
+ cspNonce: config.cspNonce
449
+ });
448
450
  }
449
451
  /**
450
452
  * createIslands: Enhances one or more existing DOM roots (no router).
@@ -453,50 +455,42 @@ function createIsland(config) {
453
455
  function createIslands(config) {
454
456
  assertExecutionModel("islands");
455
457
  if (!config || typeof config !== "object") throw new Error("createIslands requires a config object");
458
+ validateCspNonce(config.cspNonce);
456
459
  if (!Array.isArray(config.islands) || config.islands.length === 0) throw new Error("createIslands requires a non-empty islands array");
457
- for (const island of config.islands) createIsland(island);
460
+ for (const island of config.islands) createIsland({
461
+ ...island,
462
+ cspNonce: island.cspNonce ?? config.cspNonce
463
+ });
458
464
  }
459
465
  /**
460
- * createSPA: Initializes router and mounts the app with the provided route manifest or route table.
461
- *
462
- * Preferred usage with manifest:
466
+ * createSPA: Initializes the router and mounts the app with an explicit route registry.
463
467
  * ```ts
464
- * import { getManifest } from '@askrjs/askr/router';
465
- * await createSPA({ root: '#app', manifest: getManifest() });
468
+ * import { createRouteRegistry } from '@askrjs/askr/router';
469
+ * const registry = createRouteRegistry(() => { ... });
470
+ * await createSPA({ root: '#app', registry });
466
471
  * ```
467
472
  *
468
- * Legacy usage with plain routes array (still supported):
469
- * ```ts
470
- * await createSPA({ root: '#app', routes: getRoutes() });
471
- * ```
472
473
  */
473
474
  async function createSPA(config) {
474
475
  assertExecutionModel("spa");
475
476
  if (!config || typeof config !== "object") throw new Error("createSPA requires a config object");
476
- const manifest = config.manifest ?? config.registry?.manifest;
477
- const routeTable = config.routes ?? config.registry?.routes;
478
- const hasManifest = manifest != null && manifest.records.length > 0;
479
- const hasRoutes = Array.isArray(routeTable) && routeTable.length > 0;
480
- if (!hasManifest && !hasRoutes) throw new Error("createSPA requires a route manifest or route table. Pass `manifest: getManifest()` or `routes: getRoutes()`. If you are enhancing existing HTML, use createIsland instead.");
477
+ validateCspNonce(config.cspNonce);
478
+ if (!config.registry) throw new Error("createSPA requires a route registry.");
479
+ const { manifest } = config.registry;
480
+ if (manifest.records.length === 0) throw new Error("createSPA requires a route registry with at least one route. If you are enhancing existing HTML, use createIsland instead.");
481
481
  const rootElement = resolveRootElement(config.root);
482
482
  if (!rootElement) throw new Error(`Root element not found: ${config.root}`);
483
483
  configureScrollRestoration(config.scrollRestoration);
484
- clearRoutes();
485
- if (hasManifest) _applyManifest(manifest);
486
- else for (const r of routeTable) route(r.path, r.handler);
487
- const routeAuth = config.auth ?? manifest?.auth;
488
- const activeManifest = hasManifest ? manifest : void 0;
484
+ clearRouteState();
485
+ _applyManifest(manifest);
486
+ const routeAuth = config.auth ?? manifest.auth;
489
487
  const appRouteSource = {
490
- manifest: activeManifest,
491
- routes: hasManifest ? void 0 : routeTable,
488
+ registry: config.registry,
492
489
  auth: routeAuth
493
490
  };
494
491
  _setActiveRouteAuthOptions(routeAuth);
495
492
  if (isProductionEnvironment()) lockRouteRegistration();
496
- const { path, resolved } = await resolveInitialRoute(routeAuth, {
497
- manifest: activeManifest,
498
- routes: hasManifest ? void 0 : routeTable
499
- });
493
+ const { path, resolved } = await resolveInitialRoute(routeAuth, { registry: config.registry });
500
494
  const appRuntime = createAppRenderRuntime(resolved?.kind === "render" && hasRouteRenderData(resolved) ? {
501
495
  route: getRouteRenderData(resolved),
502
496
  hasRoute: true
@@ -507,7 +501,8 @@ async function createSPA(config) {
507
501
  children: []
508
502
  }), {
509
503
  cleanupStrict: config.cleanupStrict,
510
- appRuntime
504
+ appRuntime,
505
+ cspNonce: config.cspNonce
511
506
  });
512
507
  await registerAppNavigation(rootElement, path, { ...appRouteSource });
513
508
  return;
@@ -518,7 +513,8 @@ async function createSPA(config) {
518
513
  children: []
519
514
  }), {
520
515
  cleanupStrict: config.cleanupStrict,
521
- appRuntime
516
+ appRuntime,
517
+ cspNonce: config.cspNonce
522
518
  });
523
519
  await registerAppNavigation(rootElement, path, { ...appRouteSource });
524
520
  return;
@@ -529,22 +525,21 @@ async function createSPA(config) {
529
525
  params: resolved.params
530
526
  }), {
531
527
  cleanupStrict: config.cleanupStrict,
532
- appRuntime
528
+ appRuntime,
529
+ cspNonce: config.cspNonce
533
530
  });
534
531
  await registerAppNavigation(rootElement, path, { ...appRouteSource });
535
532
  }
536
533
  /**
537
- * hydrateSPA: Hydrate server-rendered HTML.
538
- * Accepts either a `manifest` (preferred) or a legacy `routes` array.
534
+ * hydrateSPA: Hydrate server-rendered HTML with an explicit route registry.
539
535
  */
540
536
  async function hydrateSPA(config) {
541
537
  assertExecutionModel("spa");
542
538
  if (!config || typeof config !== "object") throw new Error("hydrateSPA requires a config object");
543
- const manifest = config.manifest ?? config.registry?.manifest;
544
- const routeTable = config.routes ?? config.registry?.routes;
545
- const hasManifest = manifest != null && manifest.records.length > 0;
546
- const hasRoutes = Array.isArray(routeTable) && routeTable.length > 0;
547
- if (!hasManifest && !hasRoutes) throw new Error("hydrateSPA requires a route manifest or route table. Pass `manifest: getManifest()` or `routes: getRoutes()`. If you are enhancing existing HTML, use createIsland instead.");
539
+ validateCspNonce(config.cspNonce);
540
+ if (!config.registry) throw new Error("hydrateSPA requires a route registry.");
541
+ const { manifest } = config.registry;
542
+ if (manifest.records.length === 0) throw new Error("hydrateSPA requires a route registry with at least one route. If you are enhancing existing HTML, use createIsland instead.");
548
543
  const rootElement = resolveRootElement(config.root);
549
544
  if (!rootElement) throw new Error(`Root element not found: ${config.root}`);
550
545
  applyDeferredStreamPatches(rootElement);
@@ -553,14 +548,11 @@ async function hydrateSPA(config) {
553
548
  if (hydrationQueryCache) hydrateDataRuntime(config.dataRuntime ?? getDefaultDataRuntime(), hydrationQueryCache);
554
549
  const hydrationRenderDataForApp = hydrationRenderData;
555
550
  configureScrollRestoration(config.scrollRestoration);
556
- clearRoutes();
557
- if (hasManifest) _applyManifest(manifest);
558
- else for (const r of routeTable) route(r.path, r.handler);
559
- const routeAuth = config.auth ?? manifest?.auth;
560
- const activeManifest = hasManifest ? manifest : void 0;
551
+ clearRouteState();
552
+ _applyManifest(manifest);
553
+ const routeAuth = config.auth ?? manifest.auth;
561
554
  const appRouteSource = {
562
- manifest: activeManifest,
563
- routes: hasManifest ? void 0 : routeTable,
555
+ registry: config.registry,
564
556
  auth: routeAuth,
565
557
  runtime: createAppRenderRuntime({
566
558
  framework: hydrationRenderData?.framework,
@@ -570,8 +562,7 @@ async function hydrateSPA(config) {
570
562
  };
571
563
  _setActiveRouteAuthOptions(routeAuth);
572
564
  const { path, href: currentUrl, resolved } = await resolveInitialRoute(routeAuth, {
573
- manifest: activeManifest,
574
- routes: hasManifest ? void 0 : routeTable,
565
+ registry: config.registry,
575
566
  load: false
576
567
  });
577
568
  setServerLocation(currentUrl);
@@ -586,24 +577,22 @@ async function hydrateSPA(config) {
586
577
  handler: resolved.handler,
587
578
  params: resolved.params
588
579
  };
589
- const mountHydratedRoot = (...args) => withIntrinsicHydrationAdoption(() => mountOrUpdate(...args));
580
+ const mountHydratedRoot = (...args) => withIntrinsicHydrationAdoption(() => mountOrUpdate(args[0], args[1], {
581
+ ...args[2],
582
+ cspNonce: config.cspNonce
583
+ }));
590
584
  if (shouldVerifyHydrationMarkup(config)) {
591
- const legacyRouteTable = hasManifest ? manifest.records.map((r) => ({
592
- ...r,
593
- path: r.path,
594
- handler: r.handler,
595
- namespace: r.options.namespace
596
- })) : routeTable;
597
- const { verifyHydrationSyncForUrl } = await import("./verify-hydration-13f0FOPd.js");
585
+ const { verifyHydrationSyncForUrl } = await import("./verify-hydration-BdKE9Hi8.js");
598
586
  if (!verifyHydrationSyncForUrl({
599
587
  root: rootElement,
600
588
  url: currentUrl,
601
- routes: legacyRouteTable,
589
+ registry: config.registry,
602
590
  resolved: hydrationResolved,
603
591
  options: {
604
592
  data: hydrationRenderDataForApp?.resources,
605
593
  dataRuntime: config.dataRuntime ?? getDefaultDataRuntime(),
606
- envelope: hydrationRenderDataForApp ?? void 0
594
+ envelope: hydrationRenderDataForApp ?? void 0,
595
+ cspNonce: config.cspNonce
607
596
  }
608
597
  })) throw new Error("[Askr] Hydration mismatch detected. Server HTML does not match expected server-render output.");
609
598
  }
@@ -1,4 +1,4 @@
1
- import { l as VNode } from "../control-CFIrGbD7.js";
1
+ import { l as VNode } from "../control-CMzFPMjr.js";
2
2
  import { r as JSXElement, s as Props } from "../jsx-Dvr2SGRG.js";
3
3
 
4
4
  //#region src/common/error-boundary.d.ts
@@ -1,2 +1,2 @@
1
- import { a as Show, c as ForProps, i as MatchProps, n as CaseProps, o as ShowProps, r as Match, s as For, t as Case } from "../index-DlvpxdSz.js";
1
+ import { a as Show, c as ForProps, i as MatchProps, n as CaseProps, o as ShowProps, r as Match, s as For, t as Case } from "../index-Byqjltu2.js";
2
2
  export { Case, type CaseProps, For, type ForProps, Match, type MatchProps, Show, type ShowProps };