@askrjs/askr 0.0.64 → 0.0.66
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/authoring-Di8MInO-.js +200 -0
- package/dist/benchmark.js +3 -3
- package/dist/boot/index.d.ts +8 -32
- package/dist/boot/index.js +1 -1
- package/dist/{boot-BwqMApN4.js → boot-B4UZumvB.js} +31 -66
- package/dist/{deferred-vBQJwHl5.js → deferred-Eh9bCVGH.js} +73 -30
- package/dist/{navigate-CaFA0DdW.js → navigate-QMRcR581.js} +6 -274
- package/dist/{route-matching-vJpvLVbX.js → route-matching-DLBWDeXX.js} +4 -6
- package/dist/router/index.d.ts +4 -10
- package/dist/router/index.js +4 -4
- package/dist/{router-Dz7ZoLja.d.ts → router-4EhVGcma.d.ts} +12 -5
- package/dist/ssg/index.d.ts +8 -33
- package/dist/ssg/index.js +34 -36
- package/dist/ssr/index.d.ts +3 -44
- package/dist/ssr/index.js +1 -1
- package/dist/{ssr-CAYo-kNy.js → ssr-Y8Pq8riM.js} +22 -37
- package/dist/testing/index.d.ts +4 -5
- package/dist/testing/index.js +7 -10
- package/dist/{verify-hydration-DfsBcGSf.js → verify-hydration-BcZXRU-q.js} +3 -3
- package/package.json +1 -1
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 {
|
|
57
|
+
import { createRouteRegistry, route } from '@askrjs/askr/router';
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
route('/',
|
|
61
|
-
route('/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
|
-
|
|
66
|
+
registry,
|
|
67
67
|
});
|
|
68
68
|
```
|
|
69
69
|
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { f as getCurrentComponentInstance } from "./component-scope-CC3cojX9.js";
|
|
2
|
+
import { T as getExecutionModel, x as createRouteHandler } from "./deferred-Eh9bCVGH.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-DLBWDeXX.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
|
@@ -4,7 +4,7 @@ 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-
|
|
7
|
+
import { t as createIsland } from "./boot-B4UZumvB.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.
|
|
67
|
-
buildLabel: "0.0.
|
|
66
|
+
packageVersion: "0.0.66",
|
|
67
|
+
buildLabel: "0.0.66-local"
|
|
68
68
|
};
|
|
69
69
|
function getBenchmarkMetadata() {
|
|
70
70
|
return { ...benchmarkMetadata };
|
package/dist/boot/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { s as ComponentFunction } from "../control-CMzFPMjr.js";
|
|
2
2
|
import { v as DataRuntime } from "../index-Bki2rqcr.js";
|
|
3
|
-
import {
|
|
3
|
+
import { N as RouteRegistry, p as RouteAuthOptions } from "../router-4EhVGcma.js";
|
|
4
4
|
import { d as ScrollRestorationOptions } from "../navigate-BVDCzOuB.js";
|
|
5
5
|
|
|
6
6
|
//#region src/boot/types.d.ts
|
|
@@ -17,28 +17,10 @@ type IslandsConfig = {
|
|
|
17
17
|
};
|
|
18
18
|
type BootRouteSource = {
|
|
19
19
|
registry: RouteRegistry;
|
|
20
|
-
manifest?: RouteManifest;
|
|
21
|
-
routes?: Route[];
|
|
22
|
-
} | {
|
|
23
|
-
manifest: RouteManifest;
|
|
24
|
-
registry?: RouteRegistry;
|
|
25
|
-
routes?: Route[];
|
|
26
|
-
} | {
|
|
27
|
-
manifest?: RouteManifest;
|
|
28
|
-
registry?: RouteRegistry;
|
|
29
|
-
routes: Route[];
|
|
30
20
|
};
|
|
31
21
|
type SPAConfig = BootRouteSource & {
|
|
32
22
|
root: Element | string;
|
|
33
|
-
cspNonce?: string;
|
|
34
|
-
/**
|
|
35
|
-
* Preferred: pass a route registry built via `createRouteRegistry(() => { ... })`.
|
|
36
|
-
* ```ts
|
|
37
|
-
* import { createRouteRegistry } from '@askrjs/askr/router';
|
|
38
|
-
* const registry = createRouteRegistry(() => { ... });
|
|
39
|
-
* await createSPA({ root: '#app', registry });
|
|
40
|
-
* ```
|
|
41
|
-
*/
|
|
23
|
+
cspNonce?: string; /** Pass a route registry built via `createRouteRegistry(() => { ... })`. */
|
|
42
24
|
auth?: RouteAuthOptions;
|
|
43
25
|
scrollRestoration?: boolean | ScrollRestorationOptions;
|
|
44
26
|
cleanupStrict?: boolean;
|
|
@@ -47,7 +29,7 @@ type SPAConfig = BootRouteSource & {
|
|
|
47
29
|
type HydrateSPAConfig = BootRouteSource & {
|
|
48
30
|
root: Element | string;
|
|
49
31
|
cspNonce?: string;
|
|
50
|
-
dataRuntime?: DataRuntime; /**
|
|
32
|
+
dataRuntime?: DataRuntime; /** Pass the same explicit route registry used for the server render. */
|
|
51
33
|
auth?: RouteAuthOptions;
|
|
52
34
|
scrollRestoration?: boolean | ScrollRestorationOptions;
|
|
53
35
|
cleanupStrict?: boolean;
|
|
@@ -75,23 +57,17 @@ declare function createIsland(config: IslandConfig): void;
|
|
|
75
57
|
*/
|
|
76
58
|
declare function createIslands(config: IslandsConfig): void;
|
|
77
59
|
/**
|
|
78
|
-
* createSPA: Initializes router and mounts the app with
|
|
79
|
-
*
|
|
80
|
-
* Preferred usage with manifest:
|
|
60
|
+
* createSPA: Initializes the router and mounts the app with an explicit route registry.
|
|
81
61
|
* ```ts
|
|
82
|
-
* import {
|
|
83
|
-
*
|
|
62
|
+
* import { createRouteRegistry } from '@askrjs/askr/router';
|
|
63
|
+
* const registry = createRouteRegistry(() => { ... });
|
|
64
|
+
* await createSPA({ root: '#app', registry });
|
|
84
65
|
* ```
|
|
85
66
|
*
|
|
86
|
-
* Legacy usage with plain routes array (still supported):
|
|
87
|
-
* ```ts
|
|
88
|
-
* await createSPA({ root: '#app', routes: getRoutes() });
|
|
89
|
-
* ```
|
|
90
67
|
*/
|
|
91
68
|
declare function createSPA(config: SPAConfig): Promise<void>;
|
|
92
69
|
/**
|
|
93
|
-
* hydrateSPA: Hydrate server-rendered HTML.
|
|
94
|
-
* Accepts either a `manifest` (preferred) or a legacy `routes` array.
|
|
70
|
+
* hydrateSPA: Hydrate server-rendered HTML with an explicit route registry.
|
|
95
71
|
*/
|
|
96
72
|
declare function hydrateSPA(config: HydrateSPAConfig): Promise<void>;
|
|
97
73
|
//#endregion
|
package/dist/boot/index.js
CHANGED
|
@@ -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-
|
|
1
|
+
import { a as cleanupApp, i as hydrateSPA, n as createIslands, o as hasApp, r as createSPA, t as createIsland } from "../boot-B4UZumvB.js";
|
|
2
2
|
export { cleanupApp, createIsland, createIslands, createSPA, hasApp, hydrateSPA };
|
|
@@ -4,14 +4,14 @@ import { n as createComponentInstance, r as executeComponent } from "./component
|
|
|
4
4
|
import { h as cleanupComponent, m as isPromiseLike } from "./component-lifecycle-DEMPihoZ.js";
|
|
5
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 {
|
|
7
|
+
import { c as _applyManifest, d as getRouteRenderContext, f as getRouteRenderData, m as resolveRouteRequest, o as reviveDeferredValue, p as hasRouteRenderData, v as pageRenderEnvelope, w as assertExecutionModel } from "./deferred-Eh9bCVGH.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
10
|
import { r as validateCspNonce, t as CspNonceScope } from "./csp-nonce-C-8E85El.js";
|
|
11
11
|
import { i as getDefaultDataRuntime } from "./data-runtime-SJ1BSUvk.js";
|
|
12
12
|
import { a as hydrateDataRuntime } from "./query-registry-CQIT4mXr.js";
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
13
|
+
import { d as _setActiveRouteAuthOptions, j as lockRouteRegistration, k as hasRegisteredRoutes, m as clearRouteState } from "./route-matching-DLBWDeXX.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-QMRcR581.js";
|
|
15
15
|
import "./ssr-CbQO5xd3.js";
|
|
16
16
|
//#region src/boot/hydration.ts
|
|
17
17
|
const DEFERRED_PAYLOAD = "__askr_deferred__";
|
|
@@ -364,7 +364,7 @@ function cleanupApp(root) {
|
|
|
364
364
|
const wasRoutedRoot = routedRoots.delete(rootElement);
|
|
365
365
|
instancesByRoot.delete(rootElement);
|
|
366
366
|
clearRootCleanupCallbacks(rootElement);
|
|
367
|
-
if (wasRoutedRoot && routedRoots.size === 0)
|
|
367
|
+
if (wasRoutedRoot && routedRoots.size === 0) clearRouteState();
|
|
368
368
|
try {
|
|
369
369
|
delete rootElement[CLEANUP_SYMBOL];
|
|
370
370
|
} catch {}
|
|
@@ -408,21 +408,12 @@ async function resolveInitialRoute(auth, source) {
|
|
|
408
408
|
for (let redirects = 0; redirects <= MAX_INITIAL_ROUTE_REDIRECTS; redirects++) {
|
|
409
409
|
if (visited.has(href)) throw new Error(`[Askr] Route redirect cycle detected at ${href}.`);
|
|
410
410
|
visited.add(href);
|
|
411
|
-
|
|
412
|
-
|
|
411
|
+
if (!source?.registry) throw new TypeError("resolveInitialRoute requires a route registry.");
|
|
412
|
+
const resolved = await resolveRouteRequest(href, {
|
|
413
|
+
registry: source.registry,
|
|
413
414
|
auth,
|
|
414
415
|
load: source.load
|
|
415
|
-
})
|
|
416
|
-
const match = resolveRouteFromRoutes(path, source.routes);
|
|
417
|
-
if (!match) return null;
|
|
418
|
-
const lazyImport = _preloadRouteHandler(match.handler);
|
|
419
|
-
const result = {
|
|
420
|
-
kind: "render",
|
|
421
|
-
handler: match.handler,
|
|
422
|
-
params: match.params
|
|
423
|
-
};
|
|
424
|
-
return lazyImport ? lazyImport.then(() => result) : result;
|
|
425
|
-
})() : await resolveRouteRequest(href, { auth });
|
|
416
|
+
});
|
|
426
417
|
if (typeof window === "undefined" || !resolved || resolved.kind !== "redirect") return {
|
|
427
418
|
path,
|
|
428
419
|
href,
|
|
@@ -473,47 +464,34 @@ function createIslands(config) {
|
|
|
473
464
|
});
|
|
474
465
|
}
|
|
475
466
|
/**
|
|
476
|
-
* createSPA: Initializes router and mounts the app with
|
|
477
|
-
*
|
|
478
|
-
* Preferred usage with manifest:
|
|
467
|
+
* createSPA: Initializes the router and mounts the app with an explicit route registry.
|
|
479
468
|
* ```ts
|
|
480
|
-
* import {
|
|
481
|
-
*
|
|
469
|
+
* import { createRouteRegistry } from '@askrjs/askr/router';
|
|
470
|
+
* const registry = createRouteRegistry(() => { ... });
|
|
471
|
+
* await createSPA({ root: '#app', registry });
|
|
482
472
|
* ```
|
|
483
473
|
*
|
|
484
|
-
* Legacy usage with plain routes array (still supported):
|
|
485
|
-
* ```ts
|
|
486
|
-
* await createSPA({ root: '#app', routes: getRoutes() });
|
|
487
|
-
* ```
|
|
488
474
|
*/
|
|
489
475
|
async function createSPA(config) {
|
|
490
476
|
assertExecutionModel("spa");
|
|
491
477
|
if (!config || typeof config !== "object") throw new Error("createSPA requires a config object");
|
|
492
478
|
validateCspNonce(config.cspNonce);
|
|
493
|
-
|
|
494
|
-
const
|
|
495
|
-
|
|
496
|
-
const hasRoutes = Array.isArray(routeTable) && routeTable.length > 0;
|
|
497
|
-
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.");
|
|
479
|
+
if (!config.registry) throw new Error("createSPA requires a route registry.");
|
|
480
|
+
const { manifest } = config.registry;
|
|
481
|
+
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.");
|
|
498
482
|
const rootElement = resolveRootElement(config.root);
|
|
499
483
|
if (!rootElement) throw new Error(`Root element not found: ${config.root}`);
|
|
500
484
|
configureScrollRestoration(config.scrollRestoration);
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
const routeAuth = config.auth ?? manifest?.auth;
|
|
505
|
-
const activeManifest = hasManifest ? manifest : void 0;
|
|
485
|
+
clearRouteState();
|
|
486
|
+
_applyManifest(manifest);
|
|
487
|
+
const routeAuth = config.auth ?? manifest.auth;
|
|
506
488
|
const appRouteSource = {
|
|
507
|
-
|
|
508
|
-
routes: hasManifest ? void 0 : routeTable,
|
|
489
|
+
registry: config.registry,
|
|
509
490
|
auth: routeAuth
|
|
510
491
|
};
|
|
511
492
|
_setActiveRouteAuthOptions(routeAuth);
|
|
512
493
|
if (isProductionEnvironment()) lockRouteRegistration();
|
|
513
|
-
const { path, resolved } = await resolveInitialRoute(routeAuth, {
|
|
514
|
-
manifest: activeManifest,
|
|
515
|
-
routes: hasManifest ? void 0 : routeTable
|
|
516
|
-
});
|
|
494
|
+
const { path, resolved } = await resolveInitialRoute(routeAuth, { registry: config.registry });
|
|
517
495
|
const appRuntime = createAppRenderRuntime(resolved?.kind === "render" && hasRouteRenderData(resolved) ? {
|
|
518
496
|
route: getRouteRenderData(resolved),
|
|
519
497
|
hasRoute: true
|
|
@@ -554,18 +532,15 @@ async function createSPA(config) {
|
|
|
554
532
|
await registerAppNavigation(rootElement, path, { ...appRouteSource });
|
|
555
533
|
}
|
|
556
534
|
/**
|
|
557
|
-
* hydrateSPA: Hydrate server-rendered HTML.
|
|
558
|
-
* Accepts either a `manifest` (preferred) or a legacy `routes` array.
|
|
535
|
+
* hydrateSPA: Hydrate server-rendered HTML with an explicit route registry.
|
|
559
536
|
*/
|
|
560
537
|
async function hydrateSPA(config) {
|
|
561
538
|
assertExecutionModel("spa");
|
|
562
539
|
if (!config || typeof config !== "object") throw new Error("hydrateSPA requires a config object");
|
|
563
540
|
validateCspNonce(config.cspNonce);
|
|
564
|
-
|
|
565
|
-
const
|
|
566
|
-
|
|
567
|
-
const hasRoutes = Array.isArray(routeTable) && routeTable.length > 0;
|
|
568
|
-
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.");
|
|
541
|
+
if (!config.registry) throw new Error("hydrateSPA requires a route registry.");
|
|
542
|
+
const { manifest } = config.registry;
|
|
543
|
+
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.");
|
|
569
544
|
const rootElement = resolveRootElement(config.root);
|
|
570
545
|
if (!rootElement) throw new Error(`Root element not found: ${config.root}`);
|
|
571
546
|
applyDeferredStreamPatches(rootElement);
|
|
@@ -574,14 +549,11 @@ async function hydrateSPA(config) {
|
|
|
574
549
|
if (hydrationQueryCache) hydrateDataRuntime(config.dataRuntime ?? getDefaultDataRuntime(), hydrationQueryCache);
|
|
575
550
|
const hydrationRenderDataForApp = hydrationRenderData;
|
|
576
551
|
configureScrollRestoration(config.scrollRestoration);
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
const routeAuth = config.auth ?? manifest?.auth;
|
|
581
|
-
const activeManifest = hasManifest ? manifest : void 0;
|
|
552
|
+
clearRouteState();
|
|
553
|
+
_applyManifest(manifest);
|
|
554
|
+
const routeAuth = config.auth ?? manifest.auth;
|
|
582
555
|
const appRouteSource = {
|
|
583
|
-
|
|
584
|
-
routes: hasManifest ? void 0 : routeTable,
|
|
556
|
+
registry: config.registry,
|
|
585
557
|
auth: routeAuth,
|
|
586
558
|
runtime: createAppRenderRuntime({
|
|
587
559
|
framework: hydrationRenderData?.framework,
|
|
@@ -591,8 +563,7 @@ async function hydrateSPA(config) {
|
|
|
591
563
|
};
|
|
592
564
|
_setActiveRouteAuthOptions(routeAuth);
|
|
593
565
|
const { path, href: currentUrl, resolved } = await resolveInitialRoute(routeAuth, {
|
|
594
|
-
|
|
595
|
-
routes: hasManifest ? void 0 : routeTable,
|
|
566
|
+
registry: config.registry,
|
|
596
567
|
load: false
|
|
597
568
|
});
|
|
598
569
|
setServerLocation(currentUrl);
|
|
@@ -612,17 +583,11 @@ async function hydrateSPA(config) {
|
|
|
612
583
|
cspNonce: config.cspNonce
|
|
613
584
|
}));
|
|
614
585
|
if (shouldVerifyHydrationMarkup(config)) {
|
|
615
|
-
const
|
|
616
|
-
...r,
|
|
617
|
-
path: r.path,
|
|
618
|
-
handler: r.handler,
|
|
619
|
-
namespace: r.options.namespace
|
|
620
|
-
})) : routeTable;
|
|
621
|
-
const { verifyHydrationSyncForUrl } = await import("./verify-hydration-DfsBcGSf.js");
|
|
586
|
+
const { verifyHydrationSyncForUrl } = await import("./verify-hydration-BcZXRU-q.js");
|
|
622
587
|
if (!verifyHydrationSyncForUrl({
|
|
623
588
|
root: rootElement,
|
|
624
589
|
url: currentUrl,
|
|
625
|
-
|
|
590
|
+
registry: config.registry,
|
|
626
591
|
resolved: hydrationResolved,
|
|
627
592
|
options: {
|
|
628
593
|
data: hydrationRenderDataForApp?.resources,
|
|
@@ -6,7 +6,19 @@ import { n as getActiveRenderContext, r as getCurrentRenderData } from "./render
|
|
|
6
6
|
import { t as resource } from "./resource-operation-Dz2Nxi1q.js";
|
|
7
7
|
import { i as ROUTE_ROOT_COMPONENT } from "./csp-nonce-C-8E85El.js";
|
|
8
8
|
import { c as withTelemetry, t as createQueryPrefetchContext } from "./query-registry-CQIT4mXr.js";
|
|
9
|
-
import {
|
|
9
|
+
import { A as insertRecordSorted, D as getRouteRecords, E as getRouteList, F as snapshotRouteState, N as restoreRouteState, P as setDefaultRouteAuthOptions, T as getDefaultRouteAuthOptions, f as addRouteToStores, h as getActiveRouteAuthOptions, i as getMatchingRouteRecord, m as clearRouteState, o as buildRouteContext, s as buildRouteContextBase } from "./route-matching-DLBWDeXX.js";
|
|
10
|
+
//#region src/runtime/execution-model.ts
|
|
11
|
+
const EXECUTION_MODEL_KEY = Symbol.for("__ASKR_EXECUTION_MODEL__");
|
|
12
|
+
function getExecutionModel() {
|
|
13
|
+
return globalThis[EXECUTION_MODEL_KEY];
|
|
14
|
+
}
|
|
15
|
+
function assertExecutionModel(model) {
|
|
16
|
+
const g = globalThis;
|
|
17
|
+
const cur = g[EXECUTION_MODEL_KEY];
|
|
18
|
+
if (cur && cur !== model) throw new Error(`[Askr] mixing execution models is not allowed (current: ${cur}, attempted: ${model}). Choose exactly one: createSPA, createSSR, or createIslands.`);
|
|
19
|
+
if (!cur) g[EXECUTION_MODEL_KEY] = model;
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
10
22
|
//#region src/router/lazy.ts
|
|
11
23
|
const pendingLazy = /* @__PURE__ */ new Set();
|
|
12
24
|
const lazyStates = /* @__PURE__ */ new WeakMap();
|
|
@@ -55,13 +67,6 @@ function _preloadRouteRecord(record) {
|
|
|
55
67
|
function _associateLazyHandler(handler, components) {
|
|
56
68
|
handlerLazyComponents.set(handler, components);
|
|
57
69
|
}
|
|
58
|
-
function _preloadRouteHandler(handler) {
|
|
59
|
-
const imports = (handlerLazyComponents.get(handler) ?? []).flatMap((component) => {
|
|
60
|
-
const state = lazyStates.get(component);
|
|
61
|
-
return state ? [startLazyLoad(state)] : [];
|
|
62
|
-
});
|
|
63
|
-
return imports.length > 0 ? Promise.all(imports).then(() => void 0) : null;
|
|
64
|
-
}
|
|
65
70
|
//#endregion
|
|
66
71
|
//#region src/router/rendering.ts
|
|
67
72
|
const outletScope = defineScope(null);
|
|
@@ -111,6 +116,24 @@ function getRenderHandler(record) {
|
|
|
111
116
|
return record.renderHandler ?? createRouteHandler(record.component, record.pageChain, record.layoutChain, true);
|
|
112
117
|
}
|
|
113
118
|
//#endregion
|
|
119
|
+
//#region src/router/auth.ts
|
|
120
|
+
let clientAuth = Object.freeze({
|
|
121
|
+
authenticated: false,
|
|
122
|
+
principal: null,
|
|
123
|
+
session: null,
|
|
124
|
+
tenant: null
|
|
125
|
+
});
|
|
126
|
+
/** Return the identity resolved for the route currently being rendered. */
|
|
127
|
+
function currentAuth() {
|
|
128
|
+
return getActiveRenderContext()?.authContext ?? clientAuth;
|
|
129
|
+
}
|
|
130
|
+
/** @internal Updated atomically with route resolution. */
|
|
131
|
+
function setCurrentAuth(context) {
|
|
132
|
+
clientAuth = context;
|
|
133
|
+
const render = getActiveRenderContext();
|
|
134
|
+
if (render) render.authContext = context;
|
|
135
|
+
}
|
|
136
|
+
//#endregion
|
|
114
137
|
//#region src/common/page-render-envelope.ts
|
|
115
138
|
const PAGE_RENDER_ENVELOPE_VERSION = 1;
|
|
116
139
|
function ownedRecord(value) {
|
|
@@ -148,24 +171,6 @@ function isEmptyPageRenderEnvelope(value) {
|
|
|
148
171
|
return value.route === void 0 && Object.keys(value.resources).length === 0 && Object.keys(value.framework).length === 0;
|
|
149
172
|
}
|
|
150
173
|
//#endregion
|
|
151
|
-
//#region src/router/auth.ts
|
|
152
|
-
let clientAuth = Object.freeze({
|
|
153
|
-
authenticated: false,
|
|
154
|
-
principal: null,
|
|
155
|
-
session: null,
|
|
156
|
-
tenant: null
|
|
157
|
-
});
|
|
158
|
-
/** Return the identity resolved for the route currently being rendered. */
|
|
159
|
-
function currentAuth() {
|
|
160
|
-
return getActiveRenderContext()?.authContext ?? clientAuth;
|
|
161
|
-
}
|
|
162
|
-
/** @internal Updated atomically with route resolution. */
|
|
163
|
-
function setCurrentAuth(context) {
|
|
164
|
-
clientAuth = context;
|
|
165
|
-
const render = getActiveRenderContext();
|
|
166
|
-
if (render) render.authContext = context;
|
|
167
|
-
}
|
|
168
|
-
//#endregion
|
|
169
174
|
//#region src/router/resolution.ts
|
|
170
175
|
const anonymous = () => ({
|
|
171
176
|
authenticated: false,
|
|
@@ -295,13 +300,15 @@ function resolveMatchedRoute(record, params, context, authOptions, request, tele
|
|
|
295
300
|
const decision = record.options.auth(context.auth);
|
|
296
301
|
return isPromiseLike(decision) ? Promise.resolve(decision).then(continueResolution) : continueResolution(decision);
|
|
297
302
|
}
|
|
298
|
-
function resolveRouteRequest(target, options
|
|
303
|
+
function resolveRouteRequest(target, options) {
|
|
304
|
+
if (!options?.registry) throw new TypeError("resolveRouteRequest requires options.registry.");
|
|
299
305
|
return withTelemetry(options.telemetry?.routeMatch, {}, () => {
|
|
300
|
-
const
|
|
306
|
+
const records = options.registry.manifest.records;
|
|
307
|
+
const match = getMatchingRouteRecord(target, records);
|
|
301
308
|
if (!match) return null;
|
|
302
309
|
const mode = options.mode ?? getDefaultRouteMode();
|
|
303
310
|
const signal = options.signal ?? getActiveRenderContext()?.signal ?? new AbortController().signal;
|
|
304
|
-
const authOptions = getActiveRouteAuthOptions(options.auth ?? options.manifest
|
|
311
|
+
const authOptions = getActiveRouteAuthOptions(options.auth ?? options.registry.manifest.auth);
|
|
305
312
|
const base = buildRouteContextBase(target, match.params, {
|
|
306
313
|
mode,
|
|
307
314
|
signal
|
|
@@ -321,6 +328,42 @@ function resolveRouteRequest(target, options = {}) {
|
|
|
321
328
|
});
|
|
322
329
|
}
|
|
323
330
|
//#endregion
|
|
331
|
+
//#region src/router/manifest.ts
|
|
332
|
+
function createRouteManifest() {
|
|
333
|
+
const auth = getDefaultRouteAuthOptions();
|
|
334
|
+
return {
|
|
335
|
+
records: [...getRouteRecords()],
|
|
336
|
+
...auth ? { auth } : {}
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
function _applyManifest(manifest) {
|
|
340
|
+
setDefaultRouteAuthOptions(manifest.auth);
|
|
341
|
+
for (const record of manifest.records) {
|
|
342
|
+
insertRecordSorted(record);
|
|
343
|
+
addRouteToStores({
|
|
344
|
+
path: record.path,
|
|
345
|
+
handler: record.handler,
|
|
346
|
+
namespace: record.options.namespace,
|
|
347
|
+
..."fallbackPrefix" in record && typeof record.fallbackPrefix === "string" ? { fallbackPrefix: record.fallbackPrefix } : {}
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
function createRouteRegistry(definition, options = {}) {
|
|
352
|
+
const previous = snapshotRouteState();
|
|
353
|
+
clearRouteState();
|
|
354
|
+
try {
|
|
355
|
+
setDefaultRouteAuthOptions(options.auth);
|
|
356
|
+
definition();
|
|
357
|
+
const manifest = createRouteManifest();
|
|
358
|
+
return Object.freeze({
|
|
359
|
+
manifest,
|
|
360
|
+
routes: getRouteList()
|
|
361
|
+
});
|
|
362
|
+
} finally {
|
|
363
|
+
restoreRouteState(previous);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
//#endregion
|
|
324
367
|
//#region src/router/deferred.tsx
|
|
325
368
|
const DEFERRED_VALUE = Symbol.for("@askrjs/askr/deferred-value");
|
|
326
369
|
const DEFERRED_BOUNDARY = Symbol.for("@askrjs/askr/deferred-boundary");
|
|
@@ -450,4 +493,4 @@ function Resolve(props) {
|
|
|
450
493
|
return props.children(result.value);
|
|
451
494
|
}
|
|
452
495
|
//#endregion
|
|
453
|
-
export { lazy as S,
|
|
496
|
+
export { lazy as C, getRenderHandler as S, getExecutionModel as T, isPageRenderEnvelope as _, resolveDeferredValues as a, Outlet as b, _applyManifest as c, getRouteRenderContext as d, getRouteRenderData as f, isEmptyPageRenderEnvelope as g, createPageRenderEnvelope as h, isDeferred as i, createRouteRegistry as l, resolveRouteRequest as m, Resolve as n, reviveDeferredValue as o, hasRouteRenderData as p, defer as r, routeData as s, DEFERRED_BOUNDARY as t, bindResolvedRouteData as u, pageRenderEnvelope as v, assertExecutionModel as w, createRouteHandler as x, currentAuth as y };
|