@askrjs/askr 0.3.1 → 0.4.1
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/CHANGELOG.md +898 -1
- package/README.md +61 -29
- package/capabilities.json +19 -9
- package/dist/actions/index.js +56 -12
- package/dist/benchmark.js +10 -15
- package/dist/boot/index.d.ts +3 -2
- package/dist/boot/index.js +1 -1
- package/dist/boot-C5HnQZio.js +4032 -0
- package/dist/browser-activity-2r5gA1Cb.js +222 -0
- package/dist/components/index.d.ts +8 -1
- package/dist/components/index.js +1 -43
- package/dist/{aria-hKjThHcp.js → compose-ref-ccRU59BM.js} +48 -5
- package/dist/control/index.d.ts +2 -0
- package/dist/control/index.js +1 -1
- package/dist/control-hr3B0QaD.js +211 -0
- package/dist/core.d.ts +5 -9
- package/dist/csp-nonce-D-XM_uuJ.js +35 -0
- package/dist/data/index.js +3 -3
- package/dist/{data-2edW0jhF.js → data-BsnOUpQ6.js} +294 -95
- package/dist/data-runtime-i-RD0Ffm.js +242 -0
- package/dist/{deferred-CbgNhf67.js → deferred-nGZglpj6.js} +42 -20
- package/dist/domains/actions.d.ts +8 -3
- package/dist/domains/component.d.ts +1 -107
- package/dist/domains/context.d.ts +0 -15
- package/dist/domains/control.d.ts +36 -249
- package/dist/domains/data.d.ts +31 -13
- package/dist/domains/diagnostics.d.ts +1 -2
- package/dist/domains/lifecycle.d.ts +2 -5
- package/dist/domains/refs.d.ts +0 -2
- package/dist/domains/routing.d.ts +43 -8
- package/dist/domains/scheduler.d.ts +1 -76
- package/dist/domains/server.d.ts +5 -2
- package/dist/domains/state.d.ts +2 -5
- package/dist/domains/telemetry.d.ts +0 -2
- package/dist/elements.d.ts +214 -51
- package/dist/error-boundary-ChXoIFVl.js +293 -0
- package/dist/foundations/icon/index.d.ts +0 -58
- package/dist/foundations/icon/index.js +2 -2
- package/dist/foundations/index.js +2 -1
- package/dist/foundations/interactions/index.js +1 -1
- package/dist/foundations/state/index.d.ts +3 -3
- package/dist/foundations/state/index.js +5 -3
- package/dist/foundations/structures/index.js +2 -1
- package/dist/foundations/utilities/index.d.ts +2 -0
- package/dist/foundations/utilities/index.js +1 -2
- package/dist/fx/index.d.ts +31 -58
- package/dist/fx/index.js +313 -195
- package/dist/hooks-kjLigwDJ.js +202 -0
- package/dist/index.d.ts +11 -74
- package/dist/index.js +8 -447
- package/dist/instance-DL-YKrj6.js +992 -0
- package/dist/{interactions-CTeACdT_.js → interactions-BAa2xuJ-.js} +3 -2
- package/dist/jsx-dev-runtime.d.ts +41 -18
- package/dist/jsx-dev-runtime.js +1 -6
- package/dist/{jsx-runtime-CPJXAOBF.js → jsx-runtime-C4idqHY0.js} +18 -7
- package/dist/jsx-runtime.js +1 -1
- package/dist/jsx.d.ts +61 -37
- package/dist/{manifest-D1P3UNWV.js → manifest-BNupb86f.js} +3 -3
- package/dist/{navigate-prC5V8XV.js → navigate-DsfO12hB.js} +468 -85
- package/dist/navigation.d.ts +12 -3
- package/dist/peer-types.d.ts +8 -0
- package/dist/portal-LYf4cbHd.js +349 -0
- package/dist/promise-Dz8iIHAm.js +16 -0
- package/dist/query-registry-tzSiVrV_.js +325 -0
- package/dist/readable-DFWOsTac.js +21 -0
- package/dist/{render-context-B7ohlh6t.js → render-context-DGHnEveC.js} +27 -11
- package/dist/render-sync-CUxvoVab.js +1256 -0
- package/dist/resolution-CF1BqHLm.js +1258 -0
- package/dist/resource-BhcaFqdB.js +377 -0
- package/dist/resources/index.d.ts +21 -11
- package/dist/resources/index.js +65 -266
- package/dist/router/index.d.ts +14 -5
- package/dist/router/index.js +22 -53
- package/dist/scope-ila_zaId.js +55 -0
- package/dist/{shared-kU8yvryp.js → shared-O2ZnmYAf.js} +1 -7
- package/dist/{snapshot-source-An4iKajG.js → snapshot-Cjx4N988.js} +6 -1
- package/dist/ssg/index.d.ts +1 -2
- package/dist/ssg/index.js +43 -22
- package/dist/ssr/index.d.ts +26 -28
- package/dist/ssr/index.js +4 -4
- package/dist/{ssr-CiNm6Vzl.js → ssr-9LOx_bEu.js} +133 -122
- package/dist/ssr-DPfdcq0e.js +229 -0
- package/dist/state-i9D4EtY6.js +250 -0
- package/dist/structures-CkehlOT3.js +231 -0
- package/dist/testing/index.d.ts +8 -4
- package/dist/testing/index.js +29 -34
- package/dist/url-C9xMpMMv.js +185 -0
- package/dist/utilities.d.ts +48 -2
- package/dist/verify-hydration-D_cyn1OX.js +152 -0
- package/package.json +19 -9
- package/dist/access-CEA2pOry.js +0 -1046
- package/dist/activity-oy2YFTFF.js +0 -201
- package/dist/boot-DLplW9h4.js +0 -1104
- package/dist/capabilities-Bbf0mlp9.js +0 -42
- package/dist/compose-ref-v0qGlHoN.js +0 -26
- package/dist/control-BR_mrX__.js +0 -173
- package/dist/control-C-ApnFs1.js +0 -20
- package/dist/csp-nonce-BfFWcd0w.js +0 -173
- package/dist/data-runtime-CxoY98lX.js +0 -149
- package/dist/default-portal-runtime-DvMvWJVp.js +0 -20
- package/dist/dom-renderer.d.ts +0 -77
- package/dist/domains/renderer.d.ts +0 -127
- package/dist/eager-control.d.ts +0 -8
- package/dist/effect-C3Zsc1k6.js +0 -292
- package/dist/for-state-BDykeOJL.js +0 -1753
- package/dist/instance-VAFYVJOa.js +0 -1027
- package/dist/notify-DxxuDPeY.js +0 -266
- package/dist/portal-w3oQ6oQU.js +0 -29
- package/dist/query-registry-LrYpQsDJ.js +0 -83
- package/dist/render-resolved-BC0W7_5b.js +0 -1123
- package/dist/renderer-CT0HeY58.js +0 -8623
- package/dist/resolution-3DskUkjG.js +0 -577
- package/dist/resource-18-sFRl9.js +0 -344
- package/dist/route-matching-CUzZ31gT.js +0 -551
- package/dist/router-internal-7nuNEHfG.js +0 -36
- package/dist/scope-Cq_iYbTR.js +0 -403
- package/dist/selector-BSPVkjKP.js +0 -311
- package/dist/ssr-EPjwJH5R.js +0 -29
- package/dist/state-BXJfudq1.js +0 -91
- package/dist/structures-CjEzfj4h.js +0 -654
- package/dist/timer-C7uqnJVt.js +0 -85
- package/dist/verify-hydration-ikaxotf7.js +0 -27
- package/dist/vnode-D24p6IW2.js +0 -7
package/dist/ssg/index.d.ts
CHANGED
|
@@ -9,8 +9,7 @@ import {
|
|
|
9
9
|
DocumentRenderArgs,
|
|
10
10
|
RoutePolicy,
|
|
11
11
|
} from '../core.js';
|
|
12
|
-
import { AuthRequirement } from '
|
|
13
|
-
import 'node:fs/promises';
|
|
12
|
+
import { AuthRequirement } from '../peer-types.js';
|
|
14
13
|
/** Whether an SSG run rebuilds every route (`full`) or only changed ones (`incremental`). */
|
|
15
14
|
type SSGMode = 'full' | 'incremental';
|
|
16
15
|
/** Outcome of generating a single route's static HTML. */
|
package/dist/ssg/index.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import "../
|
|
2
|
-
import { n as createRouteRegistry } from "../manifest-
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { n as renderDocument } from "../ssr-EPjwJH5R.js";
|
|
7
|
-
import { n as renderToString } from "../ssr-CiNm6Vzl.js";
|
|
1
|
+
import { ct as encodePathSegment, mt as prepareRouteHydrationData, nt as getRenderHandler, ot as RouteDataLoadError, rt as _preloadRouteHandler, t as bindResolvedRouteData, x as addRouteBasePath } from "../resolution-CF1BqHLm.js";
|
|
2
|
+
import { n as createRouteRegistry } from "../manifest-BNupb86f.js";
|
|
3
|
+
import { c as route, n as resolveDeferredValues } from "../deferred-nGZglpj6.js";
|
|
4
|
+
import { n as renderDocument } from "../ssr-DPfdcq0e.js";
|
|
5
|
+
import { n as renderToString } from "../ssr-9LOx_bEu.js";
|
|
8
6
|
import * as fs$1 from "node:fs/promises";
|
|
9
7
|
import * as os from "node:os";
|
|
10
8
|
import * as pathModule from "node:path";
|
|
@@ -130,9 +128,33 @@ function getOutputFilePath(pathStr) {
|
|
|
130
128
|
if (pathStr === "/") return "index.html";
|
|
131
129
|
return `${pathStr.replace(/^\/|\/$/g, "")}/index.html`;
|
|
132
130
|
}
|
|
131
|
+
/** Prefer normalized names while accepting legacy raw template keys. */
|
|
132
|
+
function readRouteParameter(params, key) {
|
|
133
|
+
const normalized = key.trim();
|
|
134
|
+
const splat = normalized.startsWith("*");
|
|
135
|
+
const name = splat ? normalized.slice(1).trim() : normalized;
|
|
136
|
+
const canonical = params[name];
|
|
137
|
+
return {
|
|
138
|
+
name,
|
|
139
|
+
splat,
|
|
140
|
+
value: key === name || typeof canonical === "string" || Object.prototype.hasOwnProperty.call(params, name) ? canonical : params[key]
|
|
141
|
+
};
|
|
142
|
+
}
|
|
133
143
|
function interpolateRoutePath(routePath, params) {
|
|
134
144
|
if (!params) return routePath;
|
|
135
|
-
return routePath.replace(/\{([^}]+)\}/g, (_, key) =>
|
|
145
|
+
return routePath.replace(/\{([^}]+)\}/g, (_, key) => {
|
|
146
|
+
return readRouteParameter(params, key).value ?? "";
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
/** Encode the render URL while retaining the existing concrete output path. */
|
|
150
|
+
function interpolateRouteUrl(routePath, params) {
|
|
151
|
+
assertSafeRoutePath(interpolateRoutePath(routePath, params));
|
|
152
|
+
if (!params) return routePath;
|
|
153
|
+
return routePath.replace(/\{([^}]+)\}/g, (_, key) => {
|
|
154
|
+
const { splat, value: parameterValue } = readRouteParameter(params, key);
|
|
155
|
+
const value = parameterValue ?? "";
|
|
156
|
+
return splat ? value.split("/").map(encodePathSegment).join("/") : encodePathSegment(value);
|
|
157
|
+
});
|
|
136
158
|
}
|
|
137
159
|
function resolveRouteDescriptor(route) {
|
|
138
160
|
const path = interpolateRoutePath(route.path, route.params);
|
|
@@ -240,9 +262,12 @@ function validateRoutes(routes) {
|
|
|
240
262
|
if (seen.has(key)) throw new Error(`duplicate route entry detected for path "${route.path}"`);
|
|
241
263
|
seen.add(key);
|
|
242
264
|
if (route.path.includes("{")) {
|
|
243
|
-
const
|
|
265
|
+
const paramKeys = route.path.split("/").filter((segment) => segment.startsWith("{") && segment.endsWith("}")).map((segment) => segment.slice(1, -1));
|
|
244
266
|
if (!route.params) throw new Error(`route "${route.path}" uses path parameters and requires params`);
|
|
245
|
-
for (const
|
|
267
|
+
for (const paramKey of paramKeys) {
|
|
268
|
+
const { name, value } = readRouteParameter(route.params, paramKey);
|
|
269
|
+
if (typeof value !== "string") throw new Error(`route "${route.path}" missing required param "${name}"`);
|
|
270
|
+
}
|
|
246
271
|
}
|
|
247
272
|
}
|
|
248
273
|
}
|
|
@@ -268,10 +293,11 @@ async function batchRenderRoutes(routes, options = {}) {
|
|
|
268
293
|
}, { basePath: sharedBasePath }) : void 0;
|
|
269
294
|
const renderOne = async (route$2) => {
|
|
270
295
|
const startTime = performance.now();
|
|
271
|
-
const
|
|
296
|
+
const path = interpolateRoutePath(route$2.path, route$2.params);
|
|
297
|
+
const url = interpolateRouteUrl(route$2.path, route$2.params);
|
|
272
298
|
const publicUrl = addRouteBasePath(url, route$2.basePath ?? "");
|
|
273
299
|
const requestUrl = new URL(url, "http://localhost");
|
|
274
|
-
const resolvedData = resolveSsgRouteData(dataMap, route$2.path,
|
|
300
|
+
const resolvedData = resolveSsgRouteData(dataMap, route$2.path, path);
|
|
275
301
|
const baseData = resolvedData.hasData ? resolvedData.data : void 0;
|
|
276
302
|
const resourceCount = resolvedData.hasData && baseData ? Object.keys(baseData).length : 0;
|
|
277
303
|
const mergedHandler = route$2.handler;
|
|
@@ -344,8 +370,8 @@ async function batchRenderRoutes(routes, options = {}) {
|
|
|
344
370
|
});
|
|
345
371
|
const duration = performance.now() - startTime;
|
|
346
372
|
return {
|
|
347
|
-
path
|
|
348
|
-
filePath: getOutputFilePath(
|
|
373
|
+
path,
|
|
374
|
+
filePath: getOutputFilePath(path),
|
|
349
375
|
html,
|
|
350
376
|
fileSize: Buffer.byteLength(html, "utf8"),
|
|
351
377
|
renderDuration: Math.round(duration),
|
|
@@ -357,8 +383,8 @@ async function batchRenderRoutes(routes, options = {}) {
|
|
|
357
383
|
} catch (error) {
|
|
358
384
|
const duration = performance.now() - startTime;
|
|
359
385
|
return {
|
|
360
|
-
path
|
|
361
|
-
filePath: getOutputFilePath(
|
|
386
|
+
path,
|
|
387
|
+
filePath: getOutputFilePath(path),
|
|
362
388
|
html: "",
|
|
363
389
|
fileSize: 0,
|
|
364
390
|
renderDuration: Math.round(duration),
|
|
@@ -771,8 +797,6 @@ function routeRegistryToRouteConfigs(registry) {
|
|
|
771
797
|
routeConfigs.push({
|
|
772
798
|
path: record.path,
|
|
773
799
|
handler: getRenderHandler(record),
|
|
774
|
-
params: options.params,
|
|
775
|
-
props: options.props,
|
|
776
800
|
invalidationKeys: options.invalidationKeys ? [...options.invalidationKeys] : void 0,
|
|
777
801
|
namespace: options.namespace,
|
|
778
802
|
auth: options.auth,
|
|
@@ -873,9 +897,7 @@ function assembleRouteResults({ selected, renderedByRouteId, routeResultsById, d
|
|
|
873
897
|
async function publishGeneration({ routeResults, nextManifestRoutes, effectiveMode, seed, concurrency, outputDir, assets, metadata }) {
|
|
874
898
|
const targetOutputDir = effectiveMode === "full" ? await createStagingDirectory(outputDir) : outputDir;
|
|
875
899
|
try {
|
|
876
|
-
const writeStartTime = performance.now();
|
|
877
900
|
await writeStaticFiles(routeResults, targetOutputDir, { concurrency });
|
|
878
|
-
performance.now() - writeStartTime;
|
|
879
901
|
await writeMetadata(metadata, targetOutputDir);
|
|
880
902
|
await writeIncrementalManifest({
|
|
881
903
|
schemaVersion: 1,
|
|
@@ -933,10 +955,10 @@ function createStaticGen(options) {
|
|
|
933
955
|
for (let index = 0; index < eligibleDescriptors.length; index += 1) {
|
|
934
956
|
const descriptor = eligibleDescriptors[index];
|
|
935
957
|
const entry = selectRouteForGeneration(descriptor, previousEntries.get(descriptor.routeId) ?? null, effectiveMode, changedKeys, changedRoutes);
|
|
958
|
+
if (entry.reason === "unchanged" && !outputFileExists(options.outputDir, descriptor.filePath)) entry.reason = "new-route";
|
|
936
959
|
selected.push(entry);
|
|
937
960
|
if (entry.reason !== "unchanged") routesToRender.push(entry);
|
|
938
961
|
}
|
|
939
|
-
const renderStartTime = performance.now();
|
|
940
962
|
const renderedResults = routesToRender.length > 0 ? await batchRenderRoutes(getRoutesToRender(routesToRender), {
|
|
941
963
|
seed,
|
|
942
964
|
dataMap,
|
|
@@ -944,7 +966,6 @@ function createStaticGen(options) {
|
|
|
944
966
|
document: options.document,
|
|
945
967
|
styleRegistrationValidation: options.styleRegistrationValidation
|
|
946
968
|
}) : [];
|
|
947
|
-
performance.now() - renderStartTime;
|
|
948
969
|
const renderedByRouteId = /* @__PURE__ */ new Map();
|
|
949
970
|
for (let index = 0; index < renderedResults.length; index += 1) renderedByRouteId.set(routesToRender[index].descriptor.routeId, renderedResults[index]);
|
|
950
971
|
const { cacheHits, nextManifestRoutes } = assembleRouteResults({
|
package/dist/ssr/index.d.ts
CHANGED
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
DocumentRenderArgs,
|
|
12
12
|
Route,
|
|
13
13
|
RouteRecord,
|
|
14
|
-
RouteRequestResult,
|
|
15
14
|
ServerQueryRegistry,
|
|
16
15
|
CoreTelemetry,
|
|
17
16
|
AccessDenyDecision,
|
|
@@ -21,7 +20,7 @@ import {
|
|
|
21
20
|
AccessRedirectDecision,
|
|
22
21
|
} from '../core.js';
|
|
23
22
|
import { JSXElement, Props } from '../elements.js';
|
|
24
|
-
import { AuthContext } from '
|
|
23
|
+
import { AuthContext } from '../peer-types.js';
|
|
25
24
|
declare const PAGE_RENDER_ENVELOPE_VERSION: 1;
|
|
26
25
|
interface PageRenderEnvelope {
|
|
27
26
|
readonly version: typeof PAGE_RENDER_ENVELOPE_VERSION;
|
|
@@ -36,6 +35,17 @@ declare class SSRDataMissingError extends Error {
|
|
|
36
35
|
readonly code = 'SSR_DATA_MISSING';
|
|
37
36
|
constructor(message?: string);
|
|
38
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Thrown by synchronous route SSR (`renderToString()`/`renderToStream()`) when
|
|
40
|
+
* route auth or a policy redirects or denies the request. Nothing is rendered;
|
|
41
|
+
* send `decision` as the HTTP response (for example a 302 to `decision.to`, or
|
|
42
|
+
* `decision.status`).
|
|
43
|
+
*/
|
|
44
|
+
declare class SSRAccessDecisionError extends Error {
|
|
45
|
+
readonly code = 'SSR_ACCESS_DECISION';
|
|
46
|
+
readonly decision: AccessRedirectDecision | AccessDenyDecision;
|
|
47
|
+
constructor(decision: AccessRedirectDecision | AccessDenyDecision);
|
|
48
|
+
}
|
|
39
49
|
interface RenderContext {
|
|
40
50
|
url: string;
|
|
41
51
|
seed: number;
|
|
@@ -126,15 +136,6 @@ type RouteStreamOptions = RouteRenderOptions & {
|
|
|
126
136
|
onChunk(html: string): void;
|
|
127
137
|
onComplete(): void;
|
|
128
138
|
};
|
|
129
|
-
/** Resolve a URL against a route registry for SSR, applying auth/policies before render. */
|
|
130
|
-
declare function resolveRequest(opts: {
|
|
131
|
-
url: string;
|
|
132
|
-
registry: RouteRegistry;
|
|
133
|
-
auth?: RouteAuthOptions;
|
|
134
|
-
authContext?: AuthContext;
|
|
135
|
-
request?: Request;
|
|
136
|
-
signal?: AbortSignal;
|
|
137
|
-
}): Promise<RouteRequestResult>;
|
|
138
139
|
/** VNode representation for SSR rendering */
|
|
139
140
|
type VNode = {
|
|
140
141
|
type: string | SSRComponent | symbol;
|
|
@@ -166,25 +167,11 @@ declare function renderToStringSync(
|
|
|
166
167
|
envelope?: PageRenderEnvelope;
|
|
167
168
|
cspNonce?: string;
|
|
168
169
|
/** @internal Request-local authentication for deferred SSR passes. */
|
|
169
|
-
authContext?:
|
|
170
|
+
authContext?: AuthContext;
|
|
170
171
|
/** @internal Capture request-local registrations produced by this pass. */
|
|
171
172
|
onContext?: (ctx: RenderContext) => void;
|
|
172
173
|
}
|
|
173
174
|
): string;
|
|
174
|
-
/** Synchronously render an already-resolved route handler to an HTML string. */
|
|
175
|
-
declare function renderResolvedToStringSync(opts: {
|
|
176
|
-
url: string;
|
|
177
|
-
registry: RouteRegistry;
|
|
178
|
-
handler: RouteHandler;
|
|
179
|
-
params?: Record<string, string>;
|
|
180
|
-
options?: {
|
|
181
|
-
seed?: number;
|
|
182
|
-
data?: SSRData;
|
|
183
|
-
dataRuntime?: DataRuntime;
|
|
184
|
-
envelope?: PageRenderEnvelope;
|
|
185
|
-
cspNonce?: string;
|
|
186
|
-
};
|
|
187
|
-
}): string;
|
|
188
175
|
/** Options for {@link renderRouteRequest} and {@link renderRouteRequestToString}. */
|
|
189
176
|
interface RenderRouteRequestOptions {
|
|
190
177
|
url: string;
|
|
@@ -235,6 +222,17 @@ declare function renderToString(
|
|
|
235
222
|
) => VNode | JSXElement | string | number | null
|
|
236
223
|
): string;
|
|
237
224
|
declare function renderToString(opts: RouteRenderOptions): string;
|
|
225
|
+
/**
|
|
226
|
+
* Escape a string for interpolation into a hand-written HTML document template,
|
|
227
|
+
* either as text content or inside a quoted attribute value. Replaces `&`, `<`,
|
|
228
|
+
* `>`, `"` and `'` with character references. Use it for every request-derived
|
|
229
|
+
* value a `document` renderer interpolates, such as `context.pathname`,
|
|
230
|
+
* `context.params` or loader data. Do not use it for `appHtml`, which is
|
|
231
|
+
* already rendered markup, or inside `<script>`/`<style>` raw text.
|
|
232
|
+
* `null` and `undefined` render as an empty string; other values are
|
|
233
|
+
* converted with `String()`.
|
|
234
|
+
*/
|
|
235
|
+
declare function escapeHtml(value: unknown): string;
|
|
238
236
|
/** Stream a route request's rendered HTML to the response sink described by `opts`. */
|
|
239
237
|
declare function renderToStream(opts: RouteStreamOptions): void;
|
|
240
238
|
export {
|
|
@@ -243,6 +241,7 @@ export {
|
|
|
243
241
|
type DocumentRenderer,
|
|
244
242
|
type RenderRouteRequestOptions,
|
|
245
243
|
type RenderRouteRequestResult,
|
|
244
|
+
SSRAccessDecisionError,
|
|
246
245
|
type SSRComponent,
|
|
247
246
|
SSRDataMissingError,
|
|
248
247
|
type SSRRoute,
|
|
@@ -250,14 +249,13 @@ export {
|
|
|
250
249
|
type SSRStyleRegistrationValidation,
|
|
251
250
|
type VNode,
|
|
252
251
|
createRenderContext,
|
|
252
|
+
escapeHtml,
|
|
253
253
|
getRenderContext,
|
|
254
|
-
renderResolvedToStringSync,
|
|
255
254
|
renderRouteRequest,
|
|
256
255
|
renderRouteRequestToString,
|
|
257
256
|
renderToStream,
|
|
258
257
|
renderToString,
|
|
259
258
|
renderToStringSync,
|
|
260
|
-
resolveRequest,
|
|
261
259
|
withRenderContext,
|
|
262
260
|
withRenderContextAsync,
|
|
263
261
|
};
|
package/dist/ssr/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
export { SSRDataMissingError, createRenderContext,
|
|
1
|
+
import { T as SSRDataMissingError } from "../render-context-DGHnEveC.js";
|
|
2
|
+
import { a as SSRAccessDecisionError, i as renderRouteRequestToString, n as renderToString, r as renderRouteRequest, t as renderToStream } from "../ssr-9LOx_bEu.js";
|
|
3
|
+
import { c as getRenderContext, d as escapeHtml, l as withRenderContext, n as renderToStringSync, s as createRenderContext, u as withRenderContextAsync } from "../render-sync-CUxvoVab.js";
|
|
4
|
+
export { SSRAccessDecisionError, SSRDataMissingError, createRenderContext, escapeHtml, getRenderContext, renderRouteRequest, renderRouteRequestToString, renderToStream, renderToString, renderToStringSync, withRenderContext, withRenderContextAsync };
|
|
@@ -1,87 +1,64 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { c as withTelemetry, t as createQueryPrefetchContext } from "./query-registry-
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { n as renderDocument } from "./ssr-
|
|
8
|
-
import { a as serializeHydrationRenderData,
|
|
1
|
+
import { r as validateCspNonce } from "./csp-nonce-D-XM_uuJ.js";
|
|
2
|
+
import { T as SSRDataMissingError, n as getActiveRenderContext, u as setDeferredBoundaryParent } from "./render-context-DGHnEveC.js";
|
|
3
|
+
import { n as isPromiseLike } from "./promise-Dz8iIHAm.js";
|
|
4
|
+
import { c as withTelemetry, i as dehydrateDataRuntime, t as createQueryPrefetchContext } from "./query-registry-tzSiVrV_.js";
|
|
5
|
+
import { C as removeRouteBasePath, S as normalizeRouteBasePath, _ as getMatchingRouteRecord, a as resolveRouteRequest, n as getRouteRenderContext } from "./resolution-CF1BqHLm.js";
|
|
6
|
+
import { t as createDataRuntime } from "./data-runtime-i-RD0Ffm.js";
|
|
7
|
+
import { n as renderDocument } from "./ssr-DPfdcq0e.js";
|
|
8
|
+
import { a as serializeHydrationRenderData, i as StringSink, n as renderToStringSync, o as withHydratedAuth, r as StreamSink, s as createRenderContext, t as renderSSRRouteAppToSink, u as withRenderContextAsync } from "./render-sync-CUxvoVab.js";
|
|
9
|
+
//#region src/ssr/errors.ts
|
|
10
|
+
/**
|
|
11
|
+
* Thrown by synchronous route SSR (`renderToString()`/`renderToStream()`) when
|
|
12
|
+
* route auth or a policy redirects or denies the request. Nothing is rendered;
|
|
13
|
+
* send `decision` as the HTTP response (for example a 302 to `decision.to`, or
|
|
14
|
+
* `decision.status`).
|
|
15
|
+
*/
|
|
16
|
+
var SSRAccessDecisionError = class SSRAccessDecisionError extends Error {
|
|
17
|
+
constructor(decision) {
|
|
18
|
+
super(decision.kind === "redirect" ? `SSR: route access redirected to ${decision.to}.` : `SSR: route access denied with status ${decision.status}.`);
|
|
19
|
+
this.code = "SSR_ACCESS_DECISION";
|
|
20
|
+
this.name = "SSRAccessDecisionError";
|
|
21
|
+
this.decision = decision;
|
|
22
|
+
Object.setPrototypeOf(this, SSRAccessDecisionError.prototype);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
//#endregion
|
|
9
26
|
//#region src/ssr/route-policy-resolution.ts
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
27
|
+
function getRouteRequestResultSync(result, href) {
|
|
28
|
+
if (isPromiseLike(result)) {
|
|
29
|
+
Promise.resolve(result).catch(() => void 0);
|
|
30
|
+
throw new SSRDataMissingError(`SSR: route resolution for ${href} is asynchronous (async auth, policy, lazy route, or preload). renderToString()/renderToStream() only resolve routes synchronously; use renderRouteRequest() to await route resolution.`);
|
|
31
|
+
}
|
|
13
32
|
return result;
|
|
14
33
|
}
|
|
15
|
-
function
|
|
34
|
+
function resolvePolicyAwareSSRRoute(opts, routeTable) {
|
|
35
|
+
const href = opts.url;
|
|
36
|
+
const logicalHref = removeRouteBasePath(href, normalizeRouteBasePath(opts.registry.manifest.basePath));
|
|
37
|
+
if (logicalHref === void 0) throw new Error(`SSR: no route found for url: ${href}`);
|
|
38
|
+
const matched = getMatchingRouteRecord(logicalHref, opts.registry.manifest.records);
|
|
39
|
+
const route = matched && routeTable.find((entry) => entry.handler === matched.record.handler);
|
|
40
|
+
if (!matched || !route) throw new Error(`SSR: no route found for url: ${href}`);
|
|
41
|
+
const { record } = matched;
|
|
42
|
+
if (typeof record.options.loader === "function") throw new SSRDataMissingError(`SSR: route ${record.path} declares a loader, which renderToString()/renderToStream() do not run. Use renderRouteRequest() to await route loaders before rendering.`);
|
|
43
|
+
const resolved = getRouteRequestResultSync(resolveRouteRequest(href, {
|
|
44
|
+
registry: opts.registry,
|
|
45
|
+
mode: "ssr",
|
|
46
|
+
auth: opts.auth,
|
|
47
|
+
authContext: opts.authContext,
|
|
48
|
+
request: opts.request,
|
|
49
|
+
signal: opts.signal,
|
|
50
|
+
load: false
|
|
51
|
+
}), href);
|
|
52
|
+
if (resolved === null) throw new Error(`SSR: no route found for url: ${href}`);
|
|
53
|
+
if (resolved.kind === "redirect" || resolved.kind === "deny") throw new SSRAccessDecisionError(resolved);
|
|
16
54
|
return {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
props: { "data-route-denied": String(status) },
|
|
21
|
-
children: [String(status)]
|
|
22
|
-
})
|
|
55
|
+
route,
|
|
56
|
+
params: matched.params,
|
|
57
|
+
authContext: getRouteRenderContext(resolved)?.auth
|
|
23
58
|
};
|
|
24
59
|
}
|
|
25
|
-
function resolvePolicyAwareSSRRoute(opts, routeTable) {
|
|
26
|
-
const manifest = opts.registry.manifest;
|
|
27
|
-
let href = opts.url;
|
|
28
|
-
const visited = /* @__PURE__ */ new Set();
|
|
29
|
-
for (let redirects = 0; redirects <= MAX_SSR_REDIRECTS; redirects += 1) {
|
|
30
|
-
const logicalHref = removeRouteBasePath(href, normalizeRouteBasePath(manifest.basePath));
|
|
31
|
-
if (logicalHref === void 0) throw new Error(`SSR: no route found for url: ${href}`);
|
|
32
|
-
const logicalUrl = new URL(logicalHref, "http://localhost");
|
|
33
|
-
const matched = _resolveRouteMatchFromRoutes(logicalUrl.pathname, routeTable);
|
|
34
|
-
if (!matched) throw new Error(`SSR: no route found for url: ${href}`);
|
|
35
|
-
const resolved = getRouteRequestResultSync(resolveRouteRequest(href, {
|
|
36
|
-
registry: opts.registry,
|
|
37
|
-
mode: "ssr",
|
|
38
|
-
auth: opts.auth,
|
|
39
|
-
authContext: opts.authContext,
|
|
40
|
-
request: opts.request,
|
|
41
|
-
signal: opts.signal
|
|
42
|
-
}));
|
|
43
|
-
if (resolved === null) throw new Error(`SSR: no route found for url: ${href}`);
|
|
44
|
-
if (resolved.kind === "redirect") {
|
|
45
|
-
const redirectTarget = new URL(resolved.to, "http://localhost");
|
|
46
|
-
const redirectHref = `${redirectTarget.pathname}${redirectTarget.search}${redirectTarget.hash}`;
|
|
47
|
-
if (redirectHref === href || visited.has(redirectHref)) throw new Error(`[Askr] SSR redirect cycle detected at ${redirectHref}.`);
|
|
48
|
-
if (redirects >= MAX_SSR_REDIRECTS) throw new Error(`[Askr] SSR redirect limit exceeded (${MAX_SSR_REDIRECTS}).`);
|
|
49
|
-
visited.add(redirectHref);
|
|
50
|
-
href = redirectHref;
|
|
51
|
-
continue;
|
|
52
|
-
}
|
|
53
|
-
if (resolved.kind === "deny") return {
|
|
54
|
-
url: href,
|
|
55
|
-
route: buildDeniedRoute(matched.route, resolved.status),
|
|
56
|
-
params: matched.params
|
|
57
|
-
};
|
|
58
|
-
return {
|
|
59
|
-
url: href,
|
|
60
|
-
route: {
|
|
61
|
-
...matched.route,
|
|
62
|
-
handler: resolvedRouteHasLoader(resolved) ? resolved.handler : matched.route.handler
|
|
63
|
-
},
|
|
64
|
-
params: matched.params,
|
|
65
|
-
authContext: getRouteRenderContext(resolved)?.auth
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
throw new Error(`[Askr] SSR redirect limit exceeded (${MAX_SSR_REDIRECTS}).`);
|
|
69
|
-
}
|
|
70
60
|
//#endregion
|
|
71
61
|
//#region src/ssr/route-render.ts
|
|
72
|
-
/** Resolve a URL against a route registry for SSR, applying auth/policies before render. */
|
|
73
|
-
async function resolveRequest(opts) {
|
|
74
|
-
if (!opts?.registry) throw new TypeError("resolveRequest requires opts.registry.");
|
|
75
|
-
const { url, auth, authContext, request, signal } = opts;
|
|
76
|
-
return await resolveRouteRequest(url, {
|
|
77
|
-
registry: opts.registry,
|
|
78
|
-
mode: "ssr",
|
|
79
|
-
auth,
|
|
80
|
-
authContext,
|
|
81
|
-
request,
|
|
82
|
-
signal
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
62
|
function resolveSSRRouteSource(source) {
|
|
86
63
|
if (source.registry.routes.length === 0) throw new Error("SSR requires a non-empty route registry.");
|
|
87
64
|
return source.registry.routes.map((route) => ({
|
|
@@ -96,9 +73,9 @@ function resolveSSRRouteRender(opts) {
|
|
|
96
73
|
const cspNonce = validateCspNonce(opts.cspNonce);
|
|
97
74
|
const routeTable = resolveSSRRouteSource(opts);
|
|
98
75
|
const resolvedRoute = resolvePolicyAwareSSRRoute(opts, routeTable);
|
|
99
|
-
const requestUrl = new URL(
|
|
76
|
+
const requestUrl = new URL(opts.url, "http://localhost");
|
|
100
77
|
const ctx = createRenderContext(seed, {
|
|
101
|
-
url:
|
|
78
|
+
url: opts.url,
|
|
102
79
|
basePath: opts.registry.manifest.basePath,
|
|
103
80
|
data,
|
|
104
81
|
params: resolvedRoute.params,
|
|
@@ -111,8 +88,9 @@ function resolveSSRRouteRender(opts) {
|
|
|
111
88
|
envelope: opts.envelope,
|
|
112
89
|
cspNonce
|
|
113
90
|
});
|
|
91
|
+
if (!opts.envelope && ctx.hydrationData) ctx.hydrationData = withHydratedAuth(ctx.hydrationData, opts.auth ?? opts.registry.manifest.auth, resolvedRoute.authContext);
|
|
114
92
|
return {
|
|
115
|
-
url:
|
|
93
|
+
url: opts.url,
|
|
116
94
|
requestUrl,
|
|
117
95
|
route: resolvedRoute.route,
|
|
118
96
|
params: resolvedRoute.params,
|
|
@@ -209,17 +187,27 @@ function settleWithSignal(promise, signal) {
|
|
|
209
187
|
function stripHydrationPayload(html) {
|
|
210
188
|
return html.replace(/<script type="application\/json" data-askr-render-data="true">[\s\S]*?<\/script>$/, "");
|
|
211
189
|
}
|
|
212
|
-
function renderBoundary(boundary, state, payload,
|
|
190
|
+
function renderBoundary(boundary, state, payload, pass) {
|
|
213
191
|
const styles = [];
|
|
192
|
+
const boundaries = [];
|
|
214
193
|
return {
|
|
215
|
-
html: stripHydrationPayload(renderToStringSync(() =>
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
194
|
+
html: stripHydrationPayload(renderToStringSync(() => {
|
|
195
|
+
const context = getActiveRenderContext();
|
|
196
|
+
if (context) setDeferredBoundaryParent(context, boundary.id);
|
|
197
|
+
return state === "fulfilled" ? boundary.fulfilled(payload) : boundary.rejected(payload);
|
|
198
|
+
}, {}, {
|
|
199
|
+
seed: pass.seed,
|
|
200
|
+
envelope: pass.data ?? void 0,
|
|
201
|
+
cspNonce: pass.cspNonce,
|
|
202
|
+
authContext: pass.authContext,
|
|
203
|
+
route: pass.route,
|
|
204
|
+
onContext: (context) => {
|
|
205
|
+
styles.push(...context.ssrStyles.values());
|
|
206
|
+
boundaries.push(...context.deferredBoundaries);
|
|
207
|
+
}
|
|
221
208
|
})),
|
|
222
|
-
styles
|
|
209
|
+
styles,
|
|
210
|
+
boundaries
|
|
223
211
|
};
|
|
224
212
|
}
|
|
225
213
|
function escapeHtmlAttribute(value) {
|
|
@@ -232,13 +220,18 @@ function patchChunk(id, html, styles, cspNonce) {
|
|
|
232
220
|
const selector = `askr-resolve[data-askr-deferred=${JSON.stringify(id)}]`;
|
|
233
221
|
return `${styles.length === 0 ? "" : `<style data-askr-style-registry="true" data-askr-deferred-style="true"${cspNonce ? ` nonce="${escapeHtmlAttribute(cspNonce)}"` : ""}>${escapeStyleRawText(styles.map((style) => style.cssText).join("\n"))}\n</style>`}<template data-askr-deferred-patch="${escapeHtmlAttribute(id)}">${html}</template><script${cspNonce ? ` nonce="${escapeHtmlAttribute(cspNonce)}"` : ""} data-askr-deferred-apply="${escapeHtmlAttribute(id)}">(function(s){var t=s.previousElementSibling;var c=t&&t.previousElementSibling;var r=document.querySelector('style[data-askr-style-registry="true"]:not([data-askr-deferred-style])');if(c&&c.tagName==='STYLE'&&r&&r!==c){r.append(c.textContent||'');c.remove();}else if(c&&c.tagName==='STYLE'){c.removeAttribute('data-askr-deferred-style');}var b=document.querySelector(${JSON.stringify(selector)});if(b&&t){b.replaceWith(t.content.cloneNode(true));}if(t)t.remove();s.remove();})(document.currentScript)<\/script>`;
|
|
234
222
|
}
|
|
235
|
-
|
|
223
|
+
/**
|
|
224
|
+
* Stream the shell, then one patch per boundary in the order boundaries
|
|
225
|
+
* settle. A boundary whose content renders another pending `Resolve` queues
|
|
226
|
+
* that nested boundary with the same request state; hydration data follows
|
|
227
|
+
* once every boundary, nested ones included, has been patched.
|
|
228
|
+
*/
|
|
229
|
+
function createDeferredRenderStream(initialHtml, boundaries, signal, pass, runtime) {
|
|
236
230
|
const encoder = new TextEncoder();
|
|
237
231
|
const local = new AbortController();
|
|
238
232
|
let cancelled = false;
|
|
239
233
|
let closed = false;
|
|
240
|
-
let
|
|
241
|
-
let boundaryIndex = 0;
|
|
234
|
+
let unsettled = 0;
|
|
242
235
|
let controllerRef;
|
|
243
236
|
const cleanup = () => signal.removeEventListener("abort", abort);
|
|
244
237
|
const close = (controller) => {
|
|
@@ -247,46 +240,51 @@ function createDeferredRenderStream(initialHtml, boundaries, signal, seed, data,
|
|
|
247
240
|
cleanup();
|
|
248
241
|
controller.close();
|
|
249
242
|
};
|
|
243
|
+
const fail = (controller, error) => {
|
|
244
|
+
if (closed) return;
|
|
245
|
+
closed = true;
|
|
246
|
+
cleanup();
|
|
247
|
+
local.abort(error);
|
|
248
|
+
controller.error(error);
|
|
249
|
+
};
|
|
250
250
|
const abort = () => {
|
|
251
251
|
local.abort(signal.reason);
|
|
252
252
|
if (controllerRef) close(controllerRef);
|
|
253
253
|
};
|
|
254
254
|
signal.addEventListener("abort", abort, { once: true });
|
|
255
|
+
const streamBoundary = (controller, boundary) => {
|
|
256
|
+
unsettled += 1;
|
|
257
|
+
settleWithSignal(boundary.promise, local.signal).then((value) => ({
|
|
258
|
+
state: "fulfilled",
|
|
259
|
+
payload: value
|
|
260
|
+
}), (error) => ({
|
|
261
|
+
state: "rejected",
|
|
262
|
+
payload: error
|
|
263
|
+
})).then(({ state, payload }) => {
|
|
264
|
+
if (cancelled || closed || local.signal.aborted) return;
|
|
265
|
+
try {
|
|
266
|
+
const rendered = renderBoundary(boundary, state, payload, pass);
|
|
267
|
+
controller.enqueue(encoder.encode(patchChunk(boundary.id, rendered.html, rendered.styles, pass.cspNonce)));
|
|
268
|
+
for (const nested of rendered.boundaries) streamBoundary(controller, nested);
|
|
269
|
+
unsettled -= 1;
|
|
270
|
+
if (unsettled > 0) return;
|
|
271
|
+
const hydration = serializeHydrationRenderData(pass.data ?? void 0, runtime);
|
|
272
|
+
if (hydration) controller.enqueue(encoder.encode(hydration));
|
|
273
|
+
close(controller);
|
|
274
|
+
} catch (error) {
|
|
275
|
+
fail(controller, error);
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
};
|
|
255
279
|
return new ReadableStream({
|
|
256
280
|
start(controller) {
|
|
257
281
|
controllerRef = controller;
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
if (cancelled || closed) return;
|
|
261
|
-
if (initialPending) {
|
|
262
|
-
initialPending = false;
|
|
263
|
-
controller.enqueue(encoder.encode(stripHydrationPayload(initialHtml)));
|
|
282
|
+
if (signal.aborted) {
|
|
283
|
+
abort();
|
|
264
284
|
return;
|
|
265
285
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
boundaryIndex += 1;
|
|
269
|
-
try {
|
|
270
|
-
const value = await settleWithSignal(boundary.promise, local.signal);
|
|
271
|
-
if (!cancelled && !closed) {
|
|
272
|
-
const rendered = renderBoundary(boundary, "fulfilled", value, seed, data, cspNonce, authContext);
|
|
273
|
-
controller.enqueue(encoder.encode(patchChunk(boundary.id, rendered.html, rendered.styles, cspNonce)));
|
|
274
|
-
}
|
|
275
|
-
} catch (error) {
|
|
276
|
-
if (local.signal.aborted) {
|
|
277
|
-
if (!cancelled && !closed) close(controller);
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
if (!cancelled && !closed) {
|
|
281
|
-
const rendered = renderBoundary(boundary, "rejected", error, seed, data, cspNonce, authContext);
|
|
282
|
-
controller.enqueue(encoder.encode(patchChunk(boundary.id, rendered.html, rendered.styles, cspNonce)));
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
const hydration = serializeHydrationRenderData(data ?? void 0, runtime);
|
|
288
|
-
if (hydration) controller.enqueue(encoder.encode(hydration));
|
|
289
|
-
close(controller);
|
|
286
|
+
controller.enqueue(encoder.encode(stripHydrationPayload(initialHtml)));
|
|
287
|
+
for (const boundary of boundaries) streamBoundary(controller, boundary);
|
|
290
288
|
},
|
|
291
289
|
cancel(reason) {
|
|
292
290
|
cancelled = true;
|
|
@@ -350,6 +348,7 @@ async function renderRouteRequestInternal(options) {
|
|
|
350
348
|
if (!resolved) return { kind: "no-match" };
|
|
351
349
|
if (resolved.kind !== "render") return resolved;
|
|
352
350
|
context.params = resolved.params;
|
|
351
|
+
if (context.hydrationData) context.hydrationData = withHydratedAuth(context.hydrationData, options.auth ?? manifest.auth, context.authContext);
|
|
353
352
|
const sink = new StringSink();
|
|
354
353
|
renderSSRRouteAppToSink({
|
|
355
354
|
route: {
|
|
@@ -364,11 +363,23 @@ async function renderRouteRequestInternal(options) {
|
|
|
364
363
|
sink.end();
|
|
365
364
|
const html = sink.toString();
|
|
366
365
|
const boundaries = [...context.deferredBoundaries];
|
|
366
|
+
if (boundaries.length > 0) dehydrateDataRuntime(runtime);
|
|
367
367
|
const result = {
|
|
368
368
|
kind: "render",
|
|
369
369
|
html,
|
|
370
370
|
styles: Array.from(context.ssrStyles.values()),
|
|
371
|
-
...boundaries.length > 0 ? { stream: createDeferredRenderStream(html, boundaries, signal,
|
|
371
|
+
...boundaries.length > 0 ? { stream: createDeferredRenderStream(html, boundaries, signal, {
|
|
372
|
+
seed: options.seed,
|
|
373
|
+
data: context.hydrationData,
|
|
374
|
+
cspNonce,
|
|
375
|
+
authContext: context.authContext,
|
|
376
|
+
route: {
|
|
377
|
+
url: context.url,
|
|
378
|
+
routes: context.routes,
|
|
379
|
+
basePath: context.basePath,
|
|
380
|
+
params: context.params
|
|
381
|
+
}
|
|
382
|
+
}, runtime) } : {},
|
|
372
383
|
params: resolved.params
|
|
373
384
|
};
|
|
374
385
|
if (resolved.record) Object.defineProperty(result, "record", {
|
|
@@ -390,4 +401,4 @@ function renderToStream(opts) {
|
|
|
390
401
|
renderRouteToStream(opts, routeRenderHost);
|
|
391
402
|
}
|
|
392
403
|
//#endregion
|
|
393
|
-
export {
|
|
404
|
+
export { SSRAccessDecisionError as a, renderRouteRequestToString as i, renderToString as n, renderRouteRequest as r, renderToStream as t };
|