@askrjs/askr 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +874 -0
- 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-DaoTYoCw.js +3989 -0
- package/dist/browser-activity-CB8e-V4f.js +220 -0
- package/dist/components/index.d.ts +8 -1
- package/dist/components/index.js +1 -43
- package/dist/{aria-hKjThHcp.js → compose-ref-DWzdbxsD.js} +41 -4
- package/dist/control/index.d.ts +2 -0
- package/dist/control/index.js +1 -1
- package/dist/control-nfpT2fbK.js +209 -0
- package/dist/core.d.ts +5 -9
- package/dist/csp-nonce-DwGw1A6Q.js +35 -0
- package/dist/data/index.js +3 -3
- package/dist/{data-2edW0jhF.js → data-BCwClJiF.js} +283 -94
- package/dist/data-runtime-C1bFAlba.js +242 -0
- package/dist/{deferred-CbgNhf67.js → deferred-bEEHHvic.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-Bpwofwps.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-BimKStr4.js +202 -0
- package/dist/index.d.ts +11 -74
- package/dist/index.js +8 -447
- package/dist/instance-BAnfmXOR.js +988 -0
- package/dist/{interactions-CTeACdT_.js → interactions-D2BoREb-.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-CW9gyNBY.js} +3 -3
- package/dist/{navigate-prC5V8XV.js → navigate-CP_0Zy5U.js} +454 -87
- package/dist/navigation.d.ts +12 -3
- package/dist/peer-types.d.ts +8 -0
- package/dist/portal-DuOKfnLa.js +349 -0
- package/dist/promise-Dz8iIHAm.js +16 -0
- package/dist/query-registry-WMCN0BJL.js +320 -0
- package/dist/readable-DFWOsTac.js +21 -0
- package/dist/{render-context-B7ohlh6t.js → render-context-DGHnEveC.js} +27 -11
- package/dist/render-sync-IC5lMFpq.js +1240 -0
- package/dist/resolution-Cx82-xjW.js +1244 -0
- package/dist/resource-CLjB7RBQ.js +377 -0
- package/dist/resources/index.d.ts +21 -11
- package/dist/resources/index.js +45 -244
- package/dist/router/index.d.ts +14 -5
- package/dist/router/index.js +22 -53
- package/dist/scope-CwpVSHVf.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 +5 -13
- package/dist/ssr/index.d.ts +26 -28
- package/dist/ssr/index.js +4 -4
- package/dist/{ssr-CiNm6Vzl.js → ssr-BJ8ug7zH.js} +133 -122
- package/dist/ssr-DoV-vJ-5.js +229 -0
- package/dist/state-DWO1KzkX.js +232 -0
- package/dist/structures-D4dDMqHX.js +231 -0
- package/dist/testing/index.d.ts +8 -4
- package/dist/testing/index.js +29 -34
- package/dist/url-CnbfpX0K.js +185 -0
- package/dist/utilities.d.ts +48 -2
- package/dist/verify-hydration-DpVaDnqz.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-BMBMWUGI.js +0 -1084
- 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.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 { mt as prepareRouteHydrationData, nt as getRenderHandler, ot as RouteDataLoadError, rt as _preloadRouteHandler, t as bindResolvedRouteData, x as addRouteBasePath } from "../resolution-Cx82-xjW.js";
|
|
2
|
+
import { n as createRouteRegistry } from "../manifest-CW9gyNBY.js";
|
|
3
|
+
import { c as route, n as resolveDeferredValues } from "../deferred-bEEHHvic.js";
|
|
4
|
+
import { n as renderDocument } from "../ssr-DoV-vJ-5.js";
|
|
5
|
+
import { n as renderToString } from "../ssr-BJ8ug7zH.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";
|
|
@@ -771,8 +769,6 @@ function routeRegistryToRouteConfigs(registry) {
|
|
|
771
769
|
routeConfigs.push({
|
|
772
770
|
path: record.path,
|
|
773
771
|
handler: getRenderHandler(record),
|
|
774
|
-
params: options.params,
|
|
775
|
-
props: options.props,
|
|
776
772
|
invalidationKeys: options.invalidationKeys ? [...options.invalidationKeys] : void 0,
|
|
777
773
|
namespace: options.namespace,
|
|
778
774
|
auth: options.auth,
|
|
@@ -873,9 +869,7 @@ function assembleRouteResults({ selected, renderedByRouteId, routeResultsById, d
|
|
|
873
869
|
async function publishGeneration({ routeResults, nextManifestRoutes, effectiveMode, seed, concurrency, outputDir, assets, metadata }) {
|
|
874
870
|
const targetOutputDir = effectiveMode === "full" ? await createStagingDirectory(outputDir) : outputDir;
|
|
875
871
|
try {
|
|
876
|
-
const writeStartTime = performance.now();
|
|
877
872
|
await writeStaticFiles(routeResults, targetOutputDir, { concurrency });
|
|
878
|
-
performance.now() - writeStartTime;
|
|
879
873
|
await writeMetadata(metadata, targetOutputDir);
|
|
880
874
|
await writeIncrementalManifest({
|
|
881
875
|
schemaVersion: 1,
|
|
@@ -936,7 +930,6 @@ function createStaticGen(options) {
|
|
|
936
930
|
selected.push(entry);
|
|
937
931
|
if (entry.reason !== "unchanged") routesToRender.push(entry);
|
|
938
932
|
}
|
|
939
|
-
const renderStartTime = performance.now();
|
|
940
933
|
const renderedResults = routesToRender.length > 0 ? await batchRenderRoutes(getRoutesToRender(routesToRender), {
|
|
941
934
|
seed,
|
|
942
935
|
dataMap,
|
|
@@ -944,7 +937,6 @@ function createStaticGen(options) {
|
|
|
944
937
|
document: options.document,
|
|
945
938
|
styleRegistrationValidation: options.styleRegistrationValidation
|
|
946
939
|
}) : [];
|
|
947
|
-
performance.now() - renderStartTime;
|
|
948
940
|
const renderedByRouteId = /* @__PURE__ */ new Map();
|
|
949
941
|
for (let index = 0; index < renderedResults.length; index += 1) renderedByRouteId.set(routesToRender[index].descriptor.routeId, renderedResults[index]);
|
|
950
942
|
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-BJ8ug7zH.js";
|
|
3
|
+
import { c as getRenderContext, d as escapeHtml, l as withRenderContext, n as renderToStringSync, s as createRenderContext, u as withRenderContextAsync } from "../render-sync-IC5lMFpq.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-DwGw1A6Q.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-WMCN0BJL.js";
|
|
5
|
+
import { C as removeRouteBasePath, S as normalizeRouteBasePath, _ as getMatchingRouteRecord, a as resolveRouteRequest, n as getRouteRenderContext } from "./resolution-Cx82-xjW.js";
|
|
6
|
+
import { t as createDataRuntime } from "./data-runtime-C1bFAlba.js";
|
|
7
|
+
import { n as renderDocument } from "./ssr-DoV-vJ-5.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-IC5lMFpq.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 };
|