@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
|
@@ -0,0 +1,1240 @@
|
|
|
1
|
+
import { A as untrack, F as logger, j as Owner, t as ComponentInstance } from "./instance-BAnfmXOR.js";
|
|
2
|
+
import { n as readValue } from "./readable-DFWOsTac.js";
|
|
3
|
+
import { n as Fragment, t as ELEMENT_TYPE } from "./jsx-DRS-qjiN.js";
|
|
4
|
+
import { v as clarifyRenderOverflow } from "./hooks-BimKStr4.js";
|
|
5
|
+
import { r as validateCspNonce, t as CspNonceScope } from "./csp-nonce-DwGw1A6Q.js";
|
|
6
|
+
import { C as withHydrationRenderUrl, T as SSRDataMissingError, _ as createPageRenderEnvelope, b as isPageRenderEnvelope, f as startRenderPhase$1, m as stopRenderPhase$1, r as getCurrentRenderData, t as configureRenderContextProvider, w as withPageFramework, y as isEmptyPageRenderEnvelope } from "./render-context-DGHnEveC.js";
|
|
7
|
+
import { o as functionChildOutput, s as normalizeChildren } from "./error-boundary-Bpwofwps.js";
|
|
8
|
+
import { n as isPromiseLike } from "./promise-Dz8iIHAm.js";
|
|
9
|
+
import { d as DEFERRED_BOUNDARY, i as dehydrateDataRuntime, p as isDeferred } from "./query-registry-WMCN0BJL.js";
|
|
10
|
+
import { ft as getRouteHydrationMetadata, ht as validateRouteHydrationData } from "./resolution-Cx82-xjW.js";
|
|
11
|
+
import { a as SSR_PORTAL_HOST, n as Portal, o as comparePortalWriterOrder, s as createSSRPortalAnchorToken, t as DefaultPortal } from "./portal-DuOKfnLa.js";
|
|
12
|
+
import { t as createDataRuntime } from "./data-runtime-C1bFAlba.js";
|
|
13
|
+
import { d as isCustomElementName, o as isUnsafeUrlAttribute, s as rejectUnsafeUrlAttribute, u as getPublicAttributeName } from "./url-CnbfpX0K.js";
|
|
14
|
+
import { d as booleanAttributeValue, f as isSkippedProp, h as styleValueText, l as isPropertyOnlyProp, m as normalizeStylePropertyName, p as keepsFalseValue, r as sanitizeCssValue, t as SSR_RENDER_DATA_ATTR } from "./ssr-DoV-vJ-5.js";
|
|
15
|
+
//#region src/ssr/escape.ts
|
|
16
|
+
/**
|
|
17
|
+
* HTML escaping utilities for SSR
|
|
18
|
+
*
|
|
19
|
+
* Centralizes text and attribute escaping to avoid duplication
|
|
20
|
+
* between sync and streaming SSR renderers.
|
|
21
|
+
*/
|
|
22
|
+
const VOID_ELEMENTS = /* @__PURE__ */ new Set([
|
|
23
|
+
"area",
|
|
24
|
+
"base",
|
|
25
|
+
"br",
|
|
26
|
+
"col",
|
|
27
|
+
"embed",
|
|
28
|
+
"hr",
|
|
29
|
+
"img",
|
|
30
|
+
"input",
|
|
31
|
+
"link",
|
|
32
|
+
"meta",
|
|
33
|
+
"param",
|
|
34
|
+
"source",
|
|
35
|
+
"track",
|
|
36
|
+
"wbr"
|
|
37
|
+
]);
|
|
38
|
+
const escapeCache = /* @__PURE__ */ new Map();
|
|
39
|
+
const MAX_CACHE_SIZE = 256;
|
|
40
|
+
const STYLE_PROP_CACHE = /* @__PURE__ */ new Map();
|
|
41
|
+
const MAX_STYLE_PROP_CACHE_SIZE = 512;
|
|
42
|
+
function toKebabCached(prop) {
|
|
43
|
+
const cached = STYLE_PROP_CACHE.get(prop);
|
|
44
|
+
if (cached !== void 0) return cached;
|
|
45
|
+
const kebab = normalizeStylePropertyName(prop);
|
|
46
|
+
if (STYLE_PROP_CACHE.size < MAX_STYLE_PROP_CACHE_SIZE) STYLE_PROP_CACHE.set(prop, kebab);
|
|
47
|
+
return kebab;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Clear the escape cache. Call between SSR requests in long-running servers
|
|
51
|
+
* to prevent memory buildup from unique strings.
|
|
52
|
+
*/
|
|
53
|
+
function clearEscapeCache() {
|
|
54
|
+
escapeCache.clear();
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Fast check if a string needs text escaping.
|
|
58
|
+
* Used to skip the full escape call when not needed.
|
|
59
|
+
*/
|
|
60
|
+
function needsEscapeText(text) {
|
|
61
|
+
for (let i = 0; i < text.length; i++) {
|
|
62
|
+
const ch = text.charCodeAt(i);
|
|
63
|
+
if (ch === 38 || ch === 60 || ch === 62) return true;
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Fast check if a string needs attribute escaping.
|
|
69
|
+
* Checks for & " ' < > characters.
|
|
70
|
+
*/
|
|
71
|
+
function needsEscapeAttr(value) {
|
|
72
|
+
for (let i = 0; i < value.length; i++) {
|
|
73
|
+
const ch = value.charCodeAt(i);
|
|
74
|
+
if (ch === 38 || ch === 34 || ch === 39 || ch === 60 || ch === 62) return true;
|
|
75
|
+
}
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Escape HTML special characters in text content
|
|
80
|
+
* Fast-path: cache first, then scan-then-escape for new strings
|
|
81
|
+
*/
|
|
82
|
+
function escapeText(text) {
|
|
83
|
+
const useCache = text.length <= 64;
|
|
84
|
+
if (useCache) {
|
|
85
|
+
const cached = escapeCache.get(text);
|
|
86
|
+
if (cached !== void 0) return cached;
|
|
87
|
+
}
|
|
88
|
+
if (!needsEscapeText(text)) {
|
|
89
|
+
if (useCache && escapeCache.size < MAX_CACHE_SIZE) escapeCache.set(text, text);
|
|
90
|
+
return text;
|
|
91
|
+
}
|
|
92
|
+
let escaped = "";
|
|
93
|
+
let lastIndex = 0;
|
|
94
|
+
for (let i = 0; i < text.length; i++) {
|
|
95
|
+
const ch = text.charCodeAt(i);
|
|
96
|
+
let entity;
|
|
97
|
+
if (ch === 38) entity = "&";
|
|
98
|
+
else if (ch === 60) entity = "<";
|
|
99
|
+
else if (ch === 62) entity = ">";
|
|
100
|
+
else continue;
|
|
101
|
+
escaped += text.slice(lastIndex, i) + entity;
|
|
102
|
+
lastIndex = i + 1;
|
|
103
|
+
}
|
|
104
|
+
const result = escaped + text.slice(lastIndex);
|
|
105
|
+
if (useCache && escapeCache.size < MAX_CACHE_SIZE) escapeCache.set(text, result);
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
/** Return the raw text element kind for a lower-case HTML tag name. */
|
|
109
|
+
function getRawTextElement(tag) {
|
|
110
|
+
return tag === "script" || tag === "style" ? tag : null;
|
|
111
|
+
}
|
|
112
|
+
const SCRIPT_RAW_TEXT_RE = /<(\/|!--|script)/gi;
|
|
113
|
+
/**
|
|
114
|
+
* Make text safe to emit verbatim inside an HTML raw text element.
|
|
115
|
+
*
|
|
116
|
+
* `<style>`: every `<` becomes the CSS escape `\3c ` (the space ends the
|
|
117
|
+
* escape and is consumed), so no markup can form whatever context the parser
|
|
118
|
+
* reads the text in. Inside CSS strings, `url()` and comments this denotes the
|
|
119
|
+
* original `<`; elsewhere (custom property values, range media queries) it
|
|
120
|
+
* reads back as an escaped identifier, since no escape can represent a bare
|
|
121
|
+
* `<` there.
|
|
122
|
+
*
|
|
123
|
+
* `<script>`: every `</` becomes `<\/`, so no end tag of this element or of
|
|
124
|
+
* any ancestor can form, and the `<` of `<!--` and `<script` is written as
|
|
125
|
+
* `\u003C`, which keeps the parser out of the script data (double) escaped
|
|
126
|
+
* states. Inside JS string, template and regex literals, and inside JSON
|
|
127
|
+
* strings (`application/json`, `importmap`, `application/ld+json`), each
|
|
128
|
+
* rewrite denotes the original characters; outside literals these sequences
|
|
129
|
+
* cannot be represented and may change meaning or fail to parse.
|
|
130
|
+
*
|
|
131
|
+
* Matching is case-insensitive. The whole text must be escaped at once: a
|
|
132
|
+
* sequence split across children only forms once they are concatenated.
|
|
133
|
+
*/
|
|
134
|
+
function escapeRawText(text, element) {
|
|
135
|
+
if (!text.includes("<")) return text;
|
|
136
|
+
if (element === "style") return text.replaceAll("<", "\\3c ");
|
|
137
|
+
return text.replace(SCRIPT_RAW_TEXT_RE, (_match, rest) => rest === "/" ? "<\\/" : `\\u003C${rest}`);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Escape HTML special characters in attribute values.
|
|
141
|
+
* Single-pass scan: returns the original string unchanged when no special
|
|
142
|
+
* characters are found, so callers can drop separate needsEscapeAttr checks.
|
|
143
|
+
*/
|
|
144
|
+
function escapeAttr(value) {
|
|
145
|
+
let escaped = "";
|
|
146
|
+
let lastIndex = 0;
|
|
147
|
+
for (let i = 0; i < value.length; i++) {
|
|
148
|
+
const ch = value.charCodeAt(i);
|
|
149
|
+
let entity;
|
|
150
|
+
if (ch === 38) entity = "&";
|
|
151
|
+
else if (ch === 34) entity = """;
|
|
152
|
+
else if (ch === 39) entity = "'";
|
|
153
|
+
else if (ch === 60) entity = "<";
|
|
154
|
+
else if (ch === 62) entity = ">";
|
|
155
|
+
else continue;
|
|
156
|
+
escaped += value.slice(lastIndex, i) + entity;
|
|
157
|
+
lastIndex = i + 1;
|
|
158
|
+
}
|
|
159
|
+
return lastIndex === 0 ? value : escaped + value.slice(lastIndex);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Escape a string for interpolation into a hand-written HTML document template,
|
|
163
|
+
* either as text content or inside a quoted attribute value. Replaces `&`, `<`,
|
|
164
|
+
* `>`, `"` and `'` with character references. Use it for every request-derived
|
|
165
|
+
* value a `document` renderer interpolates, such as `context.pathname`,
|
|
166
|
+
* `context.params` or loader data. Do not use it for `appHtml`, which is
|
|
167
|
+
* already rendered markup, or inside `<script>`/`<style>` raw text.
|
|
168
|
+
* `null` and `undefined` render as an empty string; other values are
|
|
169
|
+
* converted with `String()`.
|
|
170
|
+
*/
|
|
171
|
+
function escapeHtml(value) {
|
|
172
|
+
return value == null ? "" : escapeAttr(String(value));
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Convert a style object to a CSS string, omitting unsafe values.
|
|
176
|
+
* Optimized to avoid Object.entries allocation
|
|
177
|
+
*/
|
|
178
|
+
function styleObjToCss(value) {
|
|
179
|
+
if (!value || typeof value !== "object") return null;
|
|
180
|
+
const styleObj = value;
|
|
181
|
+
let result = "";
|
|
182
|
+
for (const k in styleObj) {
|
|
183
|
+
const v = styleObj[k];
|
|
184
|
+
if (v === null || v === void 0 || v === false) continue;
|
|
185
|
+
const prop = toKebabCached(k);
|
|
186
|
+
const safeValue = sanitizeCssValue(styleValueText(prop, v));
|
|
187
|
+
if (safeValue) result += `${prop}:${safeValue};`;
|
|
188
|
+
}
|
|
189
|
+
return result || null;
|
|
190
|
+
}
|
|
191
|
+
//#endregion
|
|
192
|
+
//#region src/ssr/context.ts
|
|
193
|
+
/**
|
|
194
|
+
* SSR Context Management
|
|
195
|
+
*
|
|
196
|
+
* Provides render-context storage for server-side rendering.
|
|
197
|
+
* The first render installs AsyncLocalStorage from `globalThis` or, on Node and
|
|
198
|
+
* Node-compatible runtimes (Deno, Bun, Workers with `nodejs_compat`), from
|
|
199
|
+
* `process.getBuiltinModule('node:async_hooks')`. Neither is a static import,
|
|
200
|
+
* so browser bundles never pull in the Node builtin. Runtimes without either
|
|
201
|
+
* use a synchronous fallback stack and reject async render contexts.
|
|
202
|
+
*/
|
|
203
|
+
const FALLBACK_ASYNC_CONTEXT_ERROR = "[Askr] async SSR render context fallback is unsupported in this environment. Use synchronous SSR rendering or a runtime with AsyncLocalStorage (globalThis.AsyncLocalStorage or process.getBuiltinModule('node:async_hooks')).";
|
|
204
|
+
let renderContextAccessor = null;
|
|
205
|
+
let renderContextAccessorInitialized = false;
|
|
206
|
+
let fallbackStack = null;
|
|
207
|
+
function resolveAsyncLocalStorage() {
|
|
208
|
+
const host = globalThis;
|
|
209
|
+
return host.AsyncLocalStorage ?? host.process?.getBuiltinModule?.("node:async_hooks")?.AsyncLocalStorage;
|
|
210
|
+
}
|
|
211
|
+
function ensureRenderContextAccessor() {
|
|
212
|
+
if (renderContextAccessorInitialized) return;
|
|
213
|
+
renderContextAccessorInitialized = true;
|
|
214
|
+
const AsyncLocalStorage = resolveAsyncLocalStorage();
|
|
215
|
+
if (AsyncLocalStorage) renderContextAccessor = new AsyncLocalStorage();
|
|
216
|
+
}
|
|
217
|
+
function createRenderContext(seed = 12345, opts = {}) {
|
|
218
|
+
clearEscapeCache();
|
|
219
|
+
const queryCache = /* @__PURE__ */ new Map();
|
|
220
|
+
const envelope = opts.envelope ?? createPageRenderEnvelope({
|
|
221
|
+
resources: opts.data,
|
|
222
|
+
framework: opts.framework
|
|
223
|
+
});
|
|
224
|
+
const hydrationEnvelope = opts.url ? withHydrationRenderUrl(envelope, opts.url) : envelope;
|
|
225
|
+
return {
|
|
226
|
+
url: opts.url ?? "",
|
|
227
|
+
seed,
|
|
228
|
+
data: opts.data,
|
|
229
|
+
params: opts.params,
|
|
230
|
+
routes: opts.routes,
|
|
231
|
+
routeAuth: opts.routeAuth,
|
|
232
|
+
authContext: opts.authContext,
|
|
233
|
+
basePath: opts.basePath,
|
|
234
|
+
signal: opts.signal,
|
|
235
|
+
dataRuntime: opts.dataRuntime ?? createDataRuntime({ queryCache }),
|
|
236
|
+
mode: opts.mode ?? "ssr",
|
|
237
|
+
queryPrefetch: opts.queryPrefetch,
|
|
238
|
+
queryCache,
|
|
239
|
+
resourceDataProvided: opts.envelope ? Object.keys(envelope.resources).some((key) => key.startsWith("r:")) : opts.data !== void 0,
|
|
240
|
+
ssrCleanupFns: [],
|
|
241
|
+
keyCounter: 0,
|
|
242
|
+
renderData: envelope,
|
|
243
|
+
hydrationData: hydrationEnvelope,
|
|
244
|
+
deferredBoundaries: [],
|
|
245
|
+
ssrStyles: /* @__PURE__ */ new Map(),
|
|
246
|
+
ssrPortals: {
|
|
247
|
+
slots: /* @__PURE__ */ new Map(),
|
|
248
|
+
nextHostId: 0,
|
|
249
|
+
nextWriteOrder: 0
|
|
250
|
+
},
|
|
251
|
+
cspNonce: opts.cspNonce
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Run a function with the given render context.
|
|
256
|
+
* Concurrency-safe on runtimes with AsyncLocalStorage (Node.js, Deno, Bun,
|
|
257
|
+
* Workers with `nodejs_compat`); elsewhere only synchronous work is accepted.
|
|
258
|
+
*/
|
|
259
|
+
function withRenderContext(ctx, fn) {
|
|
260
|
+
ensureRenderContextProvider();
|
|
261
|
+
ensureRenderContextAccessor();
|
|
262
|
+
if (renderContextAccessor) return renderContextAccessor.run(ctx, fn);
|
|
263
|
+
const prev = fallbackStack;
|
|
264
|
+
fallbackStack = ctx;
|
|
265
|
+
try {
|
|
266
|
+
const result = fn();
|
|
267
|
+
if (isPromiseLike(result)) throw new Error(FALLBACK_ASYNC_CONTEXT_ERROR);
|
|
268
|
+
return result;
|
|
269
|
+
} finally {
|
|
270
|
+
fallbackStack = prev;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
/** Run `fn` with `ctx` as the active SSR render context, using async-local storage. */
|
|
274
|
+
async function withRenderContextAsync(ctx, fn) {
|
|
275
|
+
ensureRenderContextProvider();
|
|
276
|
+
ensureRenderContextAccessor();
|
|
277
|
+
if (renderContextAccessor) return renderContextAccessor.run(ctx, () => Promise.resolve(fn()));
|
|
278
|
+
throw new Error(FALLBACK_ASYNC_CONTEXT_ERROR);
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Get the current render context.
|
|
282
|
+
* Returns null if not inside a render.
|
|
283
|
+
*/
|
|
284
|
+
function getRenderContext() {
|
|
285
|
+
ensureRenderContextAccessor();
|
|
286
|
+
if (renderContextAccessor) return renderContextAccessor.getStore() ?? null;
|
|
287
|
+
return fallbackStack;
|
|
288
|
+
}
|
|
289
|
+
let renderContextProvided = false;
|
|
290
|
+
/**
|
|
291
|
+
* Publish this module's render-context accessor to `common/render-context`.
|
|
292
|
+
*
|
|
293
|
+
* Runtime code (resources, portals, deferred routes) asks that module for the
|
|
294
|
+
* active SSR context, and it cannot import SSR. Installing the accessor at
|
|
295
|
+
* module scope meant the answer depended on whether anything had imported this
|
|
296
|
+
* module; entering a render context composes it instead. Consumers only ever
|
|
297
|
+
* observe a context from inside one, so this covers every reader.
|
|
298
|
+
*/
|
|
299
|
+
function ensureRenderContextProvider() {
|
|
300
|
+
if (renderContextProvided) return;
|
|
301
|
+
renderContextProvided = true;
|
|
302
|
+
configureRenderContextProvider({ getRenderContext });
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Centralized SSR enforcement helper — throws a consistent error when async
|
|
306
|
+
* data is encountered during synchronous SSR.
|
|
307
|
+
*/
|
|
308
|
+
function throwSSRDataMissing() {
|
|
309
|
+
throw new SSRDataMissingError();
|
|
310
|
+
}
|
|
311
|
+
//#endregion
|
|
312
|
+
//#region src/ssr/hydration-data.ts
|
|
313
|
+
const DEFERRED_PAYLOAD = "__askr_deferred__";
|
|
314
|
+
/** Client-visible reason for a rejected deferred value that did not opt in to exposure. */
|
|
315
|
+
const REDACTED_DEFERRED_ERROR = "Deferred value rejected.";
|
|
316
|
+
/**
|
|
317
|
+
* Rejection reasons reach the page only when the error opts in with
|
|
318
|
+
* `expose: true`. Anything else can carry server internals (connection
|
|
319
|
+
* strings, hosts, SQL) into public HTML.
|
|
320
|
+
*/
|
|
321
|
+
function exposedDeferredError(error) {
|
|
322
|
+
return error instanceof Error && error.expose === true ? error.message : REDACTED_DEFERRED_ERROR;
|
|
323
|
+
}
|
|
324
|
+
function hydrationReplacer(_key, value) {
|
|
325
|
+
if (!isDeferred(value)) return value;
|
|
326
|
+
if (value.state === "fulfilled") return {
|
|
327
|
+
[DEFERRED_PAYLOAD]: "fulfilled",
|
|
328
|
+
value: value.value
|
|
329
|
+
};
|
|
330
|
+
if (value.state === "rejected") return {
|
|
331
|
+
[DEFERRED_PAYLOAD]: "rejected",
|
|
332
|
+
error: exposedDeferredError(value.error)
|
|
333
|
+
};
|
|
334
|
+
return { [DEFERRED_PAYLOAD]: "pending" };
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Add the app's opted-in identity projection to a hydration envelope. Without
|
|
338
|
+
* `auth.dehydrate`, nothing about the identity reaches the page. The session is
|
|
339
|
+
* never sent; `authenticated`, `principal`, `tenant`, and `scopes` are sent
|
|
340
|
+
* verbatim.
|
|
341
|
+
*/
|
|
342
|
+
function withHydratedAuth(envelope, options, context) {
|
|
343
|
+
if (!options?.dehydrate || !context) return envelope;
|
|
344
|
+
const { authenticated, principal, tenant, scopes } = options.dehydrate(context);
|
|
345
|
+
return withPageFramework(envelope, {
|
|
346
|
+
...envelope.framework,
|
|
347
|
+
["au"]: {
|
|
348
|
+
authenticated,
|
|
349
|
+
principal,
|
|
350
|
+
tenant,
|
|
351
|
+
...scopes ? { scopes } : {}
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
function serializeHydrationRenderData(data, runtime) {
|
|
356
|
+
const queryCache = runtime ? dehydrateDataRuntime(runtime) : void 0;
|
|
357
|
+
const current = isPageRenderEnvelope(data) ? data : createPageRenderEnvelope({ resources: data });
|
|
358
|
+
const queries = {
|
|
359
|
+
...current.queries,
|
|
360
|
+
...queryCache
|
|
361
|
+
};
|
|
362
|
+
const payload = createPageRenderEnvelope({
|
|
363
|
+
resources: current.resources,
|
|
364
|
+
queries: Object.keys(queries).length > 0 ? queries : void 0,
|
|
365
|
+
route: current.route,
|
|
366
|
+
framework: current.framework
|
|
367
|
+
});
|
|
368
|
+
const routeHydration = getRouteHydrationMetadata(payload.framework);
|
|
369
|
+
if (routeHydration) validateRouteHydrationData(payload.route, routeHydration.r);
|
|
370
|
+
if (isEmptyPageRenderEnvelope(payload)) return "";
|
|
371
|
+
return `<script type="application/json" ${SSR_RENDER_DATA_ATTR}="true">${JSON.stringify(payload, hydrationReplacer).replace(/</g, "\\u003C").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029")}<\/script>`;
|
|
372
|
+
}
|
|
373
|
+
//#endregion
|
|
374
|
+
//#region src/ssr/sink.ts
|
|
375
|
+
var StringSink = class StringSink {
|
|
376
|
+
constructor() {
|
|
377
|
+
this.chunks = [];
|
|
378
|
+
this.bufferChunks = [];
|
|
379
|
+
this.bufferLen = 0;
|
|
380
|
+
}
|
|
381
|
+
static {
|
|
382
|
+
this.FLUSH_THRESHOLD = 32768;
|
|
383
|
+
}
|
|
384
|
+
write(html) {
|
|
385
|
+
if (!html) return;
|
|
386
|
+
this.bufferChunks.push(html);
|
|
387
|
+
this.bufferLen += html.length;
|
|
388
|
+
if (this.bufferLen >= StringSink.FLUSH_THRESHOLD) {
|
|
389
|
+
this.chunks.push(this.bufferChunks.join(""));
|
|
390
|
+
this.bufferChunks.length = 0;
|
|
391
|
+
this.bufferLen = 0;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
write2(a, b) {
|
|
395
|
+
if (a) {
|
|
396
|
+
this.bufferChunks.push(a);
|
|
397
|
+
this.bufferLen += a.length;
|
|
398
|
+
}
|
|
399
|
+
if (b) {
|
|
400
|
+
this.bufferChunks.push(b);
|
|
401
|
+
this.bufferLen += b.length;
|
|
402
|
+
}
|
|
403
|
+
if (this.bufferLen >= StringSink.FLUSH_THRESHOLD) {
|
|
404
|
+
this.chunks.push(this.bufferChunks.join(""));
|
|
405
|
+
this.bufferChunks.length = 0;
|
|
406
|
+
this.bufferLen = 0;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
write3(a, b, c) {
|
|
410
|
+
if (a) {
|
|
411
|
+
this.bufferChunks.push(a);
|
|
412
|
+
this.bufferLen += a.length;
|
|
413
|
+
}
|
|
414
|
+
if (b) {
|
|
415
|
+
this.bufferChunks.push(b);
|
|
416
|
+
this.bufferLen += b.length;
|
|
417
|
+
}
|
|
418
|
+
if (c) {
|
|
419
|
+
this.bufferChunks.push(c);
|
|
420
|
+
this.bufferLen += c.length;
|
|
421
|
+
}
|
|
422
|
+
if (this.bufferLen >= StringSink.FLUSH_THRESHOLD) {
|
|
423
|
+
this.chunks.push(this.bufferChunks.join(""));
|
|
424
|
+
this.bufferChunks.length = 0;
|
|
425
|
+
this.bufferLen = 0;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
end() {
|
|
429
|
+
if (this.bufferLen) {
|
|
430
|
+
this.chunks.push(this.bufferChunks.join(""));
|
|
431
|
+
this.bufferChunks.length = 0;
|
|
432
|
+
this.bufferLen = 0;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
toString() {
|
|
436
|
+
if (this.bufferLen) {
|
|
437
|
+
this.chunks.push(this.bufferChunks.join(""));
|
|
438
|
+
this.bufferChunks.length = 0;
|
|
439
|
+
this.bufferLen = 0;
|
|
440
|
+
}
|
|
441
|
+
return this.chunks.join("");
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
var StreamSink = class {
|
|
445
|
+
constructor(onChunk, onComplete) {
|
|
446
|
+
this.onChunk = onChunk;
|
|
447
|
+
this.onComplete = onComplete;
|
|
448
|
+
}
|
|
449
|
+
write(html) {
|
|
450
|
+
if (html) this.onChunk(html);
|
|
451
|
+
}
|
|
452
|
+
end() {
|
|
453
|
+
this.onComplete();
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
//#endregion
|
|
457
|
+
//#region src/ssr/attrs.ts
|
|
458
|
+
const ESCAPED_ATTR_VALUE_CACHE_LIMIT = 512;
|
|
459
|
+
const escapedAttrValueCache = /* @__PURE__ */ new Map();
|
|
460
|
+
function isEventHandler(key) {
|
|
461
|
+
return key.length >= 2 && key.slice(0, 2).toLowerCase() === "on";
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* A function or readable prop is reactive on the client; the server renders
|
|
465
|
+
* its current value once, without subscribing to it. A function that returns
|
|
466
|
+
* a readable renders the readable's value, as on the client.
|
|
467
|
+
*/
|
|
468
|
+
function resolvePropValue(value) {
|
|
469
|
+
return typeof value === "function" ? untrack(() => readValue(value)) : value;
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* `props` with every function or readable attribute value read once, for an
|
|
473
|
+
* element whose attributes are inspected before they are written. Returns
|
|
474
|
+
* `props` itself when nothing needs reading.
|
|
475
|
+
*/
|
|
476
|
+
function resolveReactiveAttributeProps(props) {
|
|
477
|
+
if (!props || typeof props !== "object") return props;
|
|
478
|
+
let resolved = null;
|
|
479
|
+
const propsObj = props;
|
|
480
|
+
for (const key in propsObj) {
|
|
481
|
+
const value = propsObj[key];
|
|
482
|
+
if (typeof value !== "function" || isSkippedProp(key) || isEventHandler(key)) continue;
|
|
483
|
+
resolved ??= { ...propsObj };
|
|
484
|
+
resolved[key] = resolvePropValue(value);
|
|
485
|
+
}
|
|
486
|
+
return resolved ?? props;
|
|
487
|
+
}
|
|
488
|
+
function assertAttributeName(name) {
|
|
489
|
+
if (!/^[A-Za-z_:][A-Za-z0-9_.:-]*$/.test(name)) throw new TypeError(`Invalid SSR attribute name: ${JSON.stringify(name)}`);
|
|
490
|
+
}
|
|
491
|
+
function getEscapedAttrValue(value) {
|
|
492
|
+
if (value.length > 64) return escapeAttr(value);
|
|
493
|
+
const cached = escapedAttrValueCache.get(value);
|
|
494
|
+
if (cached !== void 0) return cached;
|
|
495
|
+
const escaped = escapeAttr(value);
|
|
496
|
+
if (escaped === value) return value;
|
|
497
|
+
if (escapedAttrValueCache.size >= ESCAPED_ATTR_VALUE_CACHE_LIMIT) escapedAttrValueCache.clear();
|
|
498
|
+
escapedAttrValueCache.set(value, escaped);
|
|
499
|
+
return escaped;
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Render attributes directly to a sink without intermediate string allocations.
|
|
503
|
+
* This is the hot path for streaming SSR.
|
|
504
|
+
*
|
|
505
|
+
* Props the DOM renderer writes only as properties (`prop:*`, `indeterminate`,
|
|
506
|
+
* object values on custom elements) have no markup and are left out; the
|
|
507
|
+
* client applies them when it hydrates.
|
|
508
|
+
*/
|
|
509
|
+
function renderAttrsDirect(props, sink, tagName = "") {
|
|
510
|
+
if (!props || typeof props !== "object") return;
|
|
511
|
+
const customElement = isCustomElementName(tagName);
|
|
512
|
+
const propsObj = props;
|
|
513
|
+
for (const key in propsObj) {
|
|
514
|
+
if (isSkippedProp(key) || key === "dangerouslySetInnerHTML") continue;
|
|
515
|
+
if (isEventHandler(key)) continue;
|
|
516
|
+
if (key.charCodeAt(0) === 95) continue;
|
|
517
|
+
const value = resolvePropValue(propsObj[key]);
|
|
518
|
+
if (isPropertyOnlyProp(tagName, key, value)) continue;
|
|
519
|
+
if (value !== null && typeof value === "object" && key.startsWith("attr:")) continue;
|
|
520
|
+
const attrName = getPublicAttributeName(key, customElement);
|
|
521
|
+
if (attrName !== key && isEventHandler(attrName)) continue;
|
|
522
|
+
assertAttributeName(attrName);
|
|
523
|
+
if (attrName === "style") {
|
|
524
|
+
const css = typeof value === "string" ? value : styleObjToCss(value);
|
|
525
|
+
if (!css) continue;
|
|
526
|
+
sink.write(" style=\"");
|
|
527
|
+
if (needsEscapeAttr(css)) sink.write(getEscapedAttrValue(css));
|
|
528
|
+
else sink.write(css);
|
|
529
|
+
sink.write("\"");
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
532
|
+
if (value === true) {
|
|
533
|
+
sink.write(" ");
|
|
534
|
+
sink.write(attrName);
|
|
535
|
+
const booleanValue = booleanAttributeValue(attrName);
|
|
536
|
+
if (booleanValue) {
|
|
537
|
+
sink.write("=\"");
|
|
538
|
+
sink.write(booleanValue);
|
|
539
|
+
sink.write("\"");
|
|
540
|
+
}
|
|
541
|
+
continue;
|
|
542
|
+
}
|
|
543
|
+
if (value === null || value === void 0 || value === false && !keepsFalseValue(attrName)) continue;
|
|
544
|
+
const strValue = String(value);
|
|
545
|
+
if (rejectUnsafeUrlAttribute(attrName, strValue)) continue;
|
|
546
|
+
sink.write(" ");
|
|
547
|
+
sink.write(attrName);
|
|
548
|
+
sink.write("=\"");
|
|
549
|
+
sink.write(getEscapedAttrValue(strValue));
|
|
550
|
+
sink.write("\"");
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* The value the HTML parser sees for attribute `name` (lower-case) on an
|
|
555
|
+
* element whose attributes `renderAttrsDirect` writes, or `null` when it is
|
|
556
|
+
* not written. Mirrors the emitted attributes: names compare
|
|
557
|
+
* case-insensitively and the first written occurrence wins, as the parser
|
|
558
|
+
* drops later duplicates.
|
|
559
|
+
*/
|
|
560
|
+
function getRenderedAttributeValue(props, name) {
|
|
561
|
+
if (!props || typeof props !== "object") return null;
|
|
562
|
+
const propsObj = props;
|
|
563
|
+
for (const key in propsObj) {
|
|
564
|
+
if (isSkippedProp(key) || key === "dangerouslySetInnerHTML") continue;
|
|
565
|
+
if (isEventHandler(key)) continue;
|
|
566
|
+
if (key.charCodeAt(0) === 95) continue;
|
|
567
|
+
const attrName = getPublicAttributeName(key);
|
|
568
|
+
if (attrName.toLowerCase() !== name) continue;
|
|
569
|
+
const value = resolvePropValue(propsObj[key]);
|
|
570
|
+
if (attrName === "style") {
|
|
571
|
+
const css = typeof value === "string" ? value : styleObjToCss(value);
|
|
572
|
+
if (!css) continue;
|
|
573
|
+
return css;
|
|
574
|
+
}
|
|
575
|
+
if (value === true) return booleanAttributeValue(attrName);
|
|
576
|
+
if (value === null || value === void 0 || value === false && !keepsFalseValue(attrName)) continue;
|
|
577
|
+
const strValue = String(value);
|
|
578
|
+
if (isUnsafeUrlAttribute(attrName, strValue)) continue;
|
|
579
|
+
return strValue;
|
|
580
|
+
}
|
|
581
|
+
return null;
|
|
582
|
+
}
|
|
583
|
+
//#endregion
|
|
584
|
+
//#region src/ssr/namespace.ts
|
|
585
|
+
/** HTML elements whose content the parser reads as text, not markup. */
|
|
586
|
+
const TEXT_CONTENT_ELEMENTS = /* @__PURE__ */ new Set([
|
|
587
|
+
"iframe",
|
|
588
|
+
"noembed",
|
|
589
|
+
"noframes",
|
|
590
|
+
"noscript",
|
|
591
|
+
"plaintext",
|
|
592
|
+
"script",
|
|
593
|
+
"style",
|
|
594
|
+
"textarea",
|
|
595
|
+
"title",
|
|
596
|
+
"xmp"
|
|
597
|
+
]);
|
|
598
|
+
/** The namespace an element with lower-case tag `tag` gets in `context`. */
|
|
599
|
+
function getElementNamespace(context, tag) {
|
|
600
|
+
switch (context) {
|
|
601
|
+
case "text": return "text";
|
|
602
|
+
case "svg": return "svg";
|
|
603
|
+
case "math": return "math";
|
|
604
|
+
case "math-annotation": return tag === "svg" ? "svg" : "math";
|
|
605
|
+
case "math-text": if (tag === "mglyph" || tag === "malignmark") return "math";
|
|
606
|
+
}
|
|
607
|
+
if (tag === "svg") return "svg";
|
|
608
|
+
if (tag === "math") return "math";
|
|
609
|
+
return "html";
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* The context the children of an element in `namespace`, itself written in
|
|
613
|
+
* `context`, are parsed in.
|
|
614
|
+
*/
|
|
615
|
+
function getChildNamespace(context, namespace, tag, props) {
|
|
616
|
+
if (namespace === "text") return "text";
|
|
617
|
+
if (namespace === "html") {
|
|
618
|
+
if (TEXT_CONTENT_ELEMENTS.has(tag)) return "text";
|
|
619
|
+
if (tag === "template") return "html";
|
|
620
|
+
return tag === "select" || context === "select" ? "select" : "html";
|
|
621
|
+
}
|
|
622
|
+
if (namespace === "svg") return tag === "foreignobject" || tag === "desc" || tag === "title" ? "html" : "svg";
|
|
623
|
+
switch (tag) {
|
|
624
|
+
case "mi":
|
|
625
|
+
case "mo":
|
|
626
|
+
case "mn":
|
|
627
|
+
case "ms":
|
|
628
|
+
case "mtext": return "math-text";
|
|
629
|
+
case "annotation-xml": return isHtmlAnnotationEncoding(getRenderedAttributeValue(props, "encoding")) ? "html" : "math-annotation";
|
|
630
|
+
default: return "math";
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Whether an element with lower-case tag `tag`, in `namespace` and written in
|
|
635
|
+
* `context`, is parsed as an HTML raw text element whose text may be written
|
|
636
|
+
* verbatim.
|
|
637
|
+
*/
|
|
638
|
+
function getRawTextElementInContext(context, namespace, tag) {
|
|
639
|
+
if (namespace !== "html") return null;
|
|
640
|
+
const element = getRawTextElement(tag);
|
|
641
|
+
return element === "style" && context === "select" ? null : element;
|
|
642
|
+
}
|
|
643
|
+
function isHtmlAnnotationEncoding(encoding) {
|
|
644
|
+
if (encoding === null) return false;
|
|
645
|
+
const value = encoding.toLowerCase();
|
|
646
|
+
return value === "text/html" || value === "application/xhtml+xml";
|
|
647
|
+
}
|
|
648
|
+
//#endregion
|
|
649
|
+
//#region src/ssr/render-keys.ts
|
|
650
|
+
/**
|
|
651
|
+
* SSR Data Management
|
|
652
|
+
*
|
|
653
|
+
* Manages render-phase keying for deterministic SSR data lookup.
|
|
654
|
+
* All state is stored in the per-render context for concurrency safety.
|
|
655
|
+
*/
|
|
656
|
+
function startRenderPhase(data) {
|
|
657
|
+
startRenderPhase$1(data === null ? null : isPageRenderEnvelope(data) ? data : createPageRenderEnvelope({ resources: data }));
|
|
658
|
+
}
|
|
659
|
+
function stopRenderPhase() {
|
|
660
|
+
stopRenderPhase$1();
|
|
661
|
+
}
|
|
662
|
+
//#endregion
|
|
663
|
+
//#region src/ssr/render-sync.ts
|
|
664
|
+
/**
|
|
665
|
+
* Synchronous server rendering.
|
|
666
|
+
*
|
|
667
|
+
* Components run on the same execution path as in the browser (a component
|
|
668
|
+
* instance per element, positional hooks, scopes through the owner tree) and
|
|
669
|
+
* their output is interpreted with the same child descriptors as the DOM
|
|
670
|
+
* renderer. The server reads function children and props once, never
|
|
671
|
+
* subscribes, and ends every lifetime when the render finishes.
|
|
672
|
+
*
|
|
673
|
+
* The output is plain HTML apart from private hydration markers when the
|
|
674
|
+
* render contains preloaded resource slots. Portal hosts write tokens that
|
|
675
|
+
* are replaced with their final content once the render root completes.
|
|
676
|
+
*/
|
|
677
|
+
/**
|
|
678
|
+
* Collects writes so they can be published or dropped. An ErrorBoundary's
|
|
679
|
+
* subtree renders into one first, so markup from a subtree that then throws
|
|
680
|
+
* never reaches the response.
|
|
681
|
+
*/
|
|
682
|
+
var BufferedSink = class {
|
|
683
|
+
constructor() {
|
|
684
|
+
this.operations = [];
|
|
685
|
+
}
|
|
686
|
+
write(html) {
|
|
687
|
+
if (html) this.operations.push({
|
|
688
|
+
portalHost: false,
|
|
689
|
+
text: html
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
writePortalHost(token) {
|
|
693
|
+
this.operations.push({
|
|
694
|
+
portalHost: true,
|
|
695
|
+
text: token
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
/** The buffered markup, without portal host tokens. */
|
|
699
|
+
html() {
|
|
700
|
+
let html = "";
|
|
701
|
+
for (const operation of this.operations) if (!operation.portalHost) html += operation.text;
|
|
702
|
+
return html;
|
|
703
|
+
}
|
|
704
|
+
publishTo(sink) {
|
|
705
|
+
for (const operation of this.operations) if (operation.portalHost && sink.writePortalHost) sink.writePortalHost(operation.text);
|
|
706
|
+
else sink.write(operation.text);
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
/** Buffers output once a portal host appears so tokens can be resolved. */
|
|
710
|
+
var PortalSink = class {
|
|
711
|
+
constructor(sink) {
|
|
712
|
+
this.sink = sink;
|
|
713
|
+
this.buffered = null;
|
|
714
|
+
}
|
|
715
|
+
write(html) {
|
|
716
|
+
if (!html) return;
|
|
717
|
+
if (this.buffered) this.buffered.push(html);
|
|
718
|
+
else this.sink.write(html);
|
|
719
|
+
}
|
|
720
|
+
writePortalHost(token) {
|
|
721
|
+
(this.buffered ??= []).push(token);
|
|
722
|
+
}
|
|
723
|
+
flush(ctx) {
|
|
724
|
+
if (this.buffered) this.sink.write(resolvePortals(this.buffered.join(""), ctx));
|
|
725
|
+
}
|
|
726
|
+
};
|
|
727
|
+
let current = null;
|
|
728
|
+
function state() {
|
|
729
|
+
if (!current) throw new Error("[Askr] No server render is active.");
|
|
730
|
+
return current;
|
|
731
|
+
}
|
|
732
|
+
function withOwner(owner, fn) {
|
|
733
|
+
const render = state();
|
|
734
|
+
const previous = render.owner;
|
|
735
|
+
render.owner = owner;
|
|
736
|
+
try {
|
|
737
|
+
return fn();
|
|
738
|
+
} finally {
|
|
739
|
+
render.owner = previous;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
function withNamespace(namespace, fn) {
|
|
743
|
+
const render = state();
|
|
744
|
+
const previous = render.namespace;
|
|
745
|
+
render.namespace = namespace;
|
|
746
|
+
try {
|
|
747
|
+
return fn();
|
|
748
|
+
} finally {
|
|
749
|
+
render.namespace = previous;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
function runComponent(instance) {
|
|
753
|
+
try {
|
|
754
|
+
const output = instance.render();
|
|
755
|
+
if (isPromiseLike(output)) throwSSRDataMissing();
|
|
756
|
+
return output;
|
|
757
|
+
} finally {}
|
|
758
|
+
}
|
|
759
|
+
function componentOutput(value) {
|
|
760
|
+
return typeof value === "function" ? null : value;
|
|
761
|
+
}
|
|
762
|
+
function renderComponent(fn, props, sink) {
|
|
763
|
+
const render = state();
|
|
764
|
+
let owner = render.owner;
|
|
765
|
+
for (;;) {
|
|
766
|
+
const instance = new ComponentInstance(owner, fn, props);
|
|
767
|
+
instance.server = true;
|
|
768
|
+
instance.serverContext = render.ctx;
|
|
769
|
+
const output = runComponent(instance);
|
|
770
|
+
const resources = getCurrentRenderData()?.resources;
|
|
771
|
+
if (instance.serverResourceKeys.length && resources && Object.keys(resources).some((key) => /^r:\d+$/.test(key))) sink.write(`<!--askr-resource:${instance.serverResourceKeys.join(",")}-->`);
|
|
772
|
+
if (fn === Portal) sink.write(createSSRPortalAnchorToken(render.ctx.ssrPortals.nextHostId++));
|
|
773
|
+
const next = instance.hooks.length === 0 && !instance.boundary ? selfComponentChild(output, fn) : null;
|
|
774
|
+
if (next) {
|
|
775
|
+
owner = instance;
|
|
776
|
+
fn = next.fn;
|
|
777
|
+
props = next.props;
|
|
778
|
+
continue;
|
|
779
|
+
}
|
|
780
|
+
if (!instance.boundary) {
|
|
781
|
+
withOwner(instance, () => renderValue(componentOutput(output), sink));
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
const buffer = new BufferedSink();
|
|
785
|
+
const restorePortals = capturePortalWrites(render.ctx);
|
|
786
|
+
try {
|
|
787
|
+
withOwner(instance, () => renderValue(componentOutput(output), buffer));
|
|
788
|
+
} catch (caught) {
|
|
789
|
+
const error = clarifyRenderOverflow(caught);
|
|
790
|
+
restorePortals();
|
|
791
|
+
const cleanupErrors = disposeFailedSubtree(instance);
|
|
792
|
+
try {
|
|
793
|
+
if (!instance.boundary(error)) throw error;
|
|
794
|
+
const fallback = runComponent(instance);
|
|
795
|
+
withOwner(instance, () => renderValue(componentOutput(fallback), sink));
|
|
796
|
+
} finally {
|
|
797
|
+
reportBoundaryCleanupErrors(cleanupErrors);
|
|
798
|
+
}
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
buffer.publishTo(sink);
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* End the lifetimes a failed boundary subtree started, newest first as in
|
|
807
|
+
* `Owner.dispose()`, keeping the boundary's own render computation.
|
|
808
|
+
*/
|
|
809
|
+
function disposeFailedSubtree(instance) {
|
|
810
|
+
const errors = [];
|
|
811
|
+
const owned = instance.owned ? [...instance.owned] : [];
|
|
812
|
+
for (let index = owned.length - 1; index >= 0; index--) {
|
|
813
|
+
const child = owned[index];
|
|
814
|
+
if (child && child !== instance.computation) errors.push(...child.dispose());
|
|
815
|
+
}
|
|
816
|
+
return errors;
|
|
817
|
+
}
|
|
818
|
+
function reportBoundaryCleanupErrors(errors) {
|
|
819
|
+
if (errors.length === 0) return;
|
|
820
|
+
const failure = errors.length === 1 ? errors[0] : new AggregateError(errors, "SSR ErrorBoundary cleanup failed");
|
|
821
|
+
logger.error("[Askr] SSR ErrorBoundary cleanup failed:", failure);
|
|
822
|
+
}
|
|
823
|
+
function selfComponentChild(output, fn) {
|
|
824
|
+
if (!output || typeof output !== "object" || Array.isArray(output) || output.type !== fn) return null;
|
|
825
|
+
const children = normalizeChildren(output);
|
|
826
|
+
return children.length === 1 && children[0].kind === 2 ? children[0] : null;
|
|
827
|
+
}
|
|
828
|
+
function capturePortalWrites(ctx) {
|
|
829
|
+
const saved = /* @__PURE__ */ new Map();
|
|
830
|
+
for (const [key, slot] of ctx.ssrPortals.slots) saved.set(key, new Map(slot.writers));
|
|
831
|
+
return () => {
|
|
832
|
+
for (const [key, slot] of ctx.ssrPortals.slots) {
|
|
833
|
+
slot.writers = new Map(saved.get(key) ?? []);
|
|
834
|
+
const ordered = [...slot.writers.values()].sort((left, right) => comparePortalWriterOrder(left.owner, right.owner, left.order, right.order));
|
|
835
|
+
const latest = ordered[ordered.length - 1];
|
|
836
|
+
slot.hasValue = latest !== void 0;
|
|
837
|
+
slot.value = latest?.value;
|
|
838
|
+
slot.owner = latest?.owner;
|
|
839
|
+
}
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
function isVNodeOf(value, type) {
|
|
843
|
+
return typeof value === "object" && value !== null && value.type === type;
|
|
844
|
+
}
|
|
845
|
+
/** Render any renderable value. */
|
|
846
|
+
function renderValue(value, sink) {
|
|
847
|
+
if (Array.isArray(value)) {
|
|
848
|
+
for (const item of value) renderValue(item, sink);
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
if (isVNodeOf(value, SSR_PORTAL_HOST)) {
|
|
852
|
+
const token = String(value.props?.token ?? "");
|
|
853
|
+
state().portalNamespaces.set(token, state().namespace);
|
|
854
|
+
if (sink.writePortalHost) sink.writePortalHost(token);
|
|
855
|
+
else sink.write(token);
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
if (isVNodeOf(value, DEFERRED_BOUNDARY)) {
|
|
859
|
+
const id = String(value.props?.id ?? "");
|
|
860
|
+
sink.write(`<askr-resolve data-askr-deferred="${id}">`);
|
|
861
|
+
renderValue(value.props?.pending, sink);
|
|
862
|
+
sink.write("</askr-resolve>");
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
for (const child of normalizeChildren(value)) renderChild(child, sink);
|
|
866
|
+
}
|
|
867
|
+
function renderChild(child, sink) {
|
|
868
|
+
switch (child.kind) {
|
|
869
|
+
case 1:
|
|
870
|
+
sink.write(escapeText(child.text));
|
|
871
|
+
return;
|
|
872
|
+
case 0:
|
|
873
|
+
renderElement(child.tag, child.props, sink);
|
|
874
|
+
return;
|
|
875
|
+
case 2:
|
|
876
|
+
renderComponent(child.fn, child.props, sink);
|
|
877
|
+
return;
|
|
878
|
+
case 3: {
|
|
879
|
+
const owner = child.owner;
|
|
880
|
+
if (owner) withOwner(owner, () => renderValue(child.children, sink));
|
|
881
|
+
else renderValue(child.children, sink);
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
case 4:
|
|
885
|
+
renderComponent(() => functionChildOutput(readValue(child.fn)), {}, sink);
|
|
886
|
+
return;
|
|
887
|
+
case 7: return;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
function assertElementName(name) {
|
|
891
|
+
if (!/^[A-Za-z][A-Za-z0-9._:-]*$/.test(name)) throw new TypeError(`Invalid SSR element name: ${JSON.stringify(name)}`);
|
|
892
|
+
}
|
|
893
|
+
function renderElement(tag, props, sink) {
|
|
894
|
+
assertElementName(tag);
|
|
895
|
+
if (VOID_ELEMENTS.has(tag)) {
|
|
896
|
+
sink.write("<" + tag);
|
|
897
|
+
renderAttrsDirect(props, sink, tag);
|
|
898
|
+
sink.write(" />");
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
const render = state();
|
|
902
|
+
const parentNamespace = render.namespace;
|
|
903
|
+
const lower = tag.toLowerCase();
|
|
904
|
+
const namespace = getElementNamespace(parentNamespace, lower);
|
|
905
|
+
let elementProps = lower === "annotation-xml" || namespace === "html" && (lower === "select" || lower === "option" && parentNamespace === "select") ? resolveReactiveAttributeProps(props) ?? props : props;
|
|
906
|
+
const selection = render.selectSelections[render.selectSelections.length - 1];
|
|
907
|
+
let renderedChildren = null;
|
|
908
|
+
if (lower === "option" && parentNamespace === "select" && selection) {
|
|
909
|
+
let value;
|
|
910
|
+
const own = elementProps.value;
|
|
911
|
+
if (own === void 0 || own === null || own === false) {
|
|
912
|
+
const buffer = new BufferedSink();
|
|
913
|
+
const props = elementProps;
|
|
914
|
+
withNamespace(getChildNamespace(parentNamespace, namespace, lower, props), () => writeContent(props, null, buffer));
|
|
915
|
+
renderedChildren = buffer;
|
|
916
|
+
value = optionTextValue(buffer.html());
|
|
917
|
+
} else value = String(own);
|
|
918
|
+
const selected = selection.values.has(value) && (selection.multiple || !selection.matched);
|
|
919
|
+
if (selected) selection.matched = true;
|
|
920
|
+
elementProps = {
|
|
921
|
+
...elementProps,
|
|
922
|
+
selected
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
if (renderedChildren) {
|
|
926
|
+
sink.write("<" + tag);
|
|
927
|
+
renderAttrsDirect(elementProps, sink, tag);
|
|
928
|
+
sink.write(">");
|
|
929
|
+
renderedChildren.publishTo(sink);
|
|
930
|
+
sink.write("</" + tag + ">");
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
let nextSelection = null;
|
|
934
|
+
if (lower === "select" && namespace === "html" && elementProps.value != null) {
|
|
935
|
+
const multiple = Boolean(elementProps.multiple);
|
|
936
|
+
const value = elementProps.value;
|
|
937
|
+
nextSelection = {
|
|
938
|
+
values: new Set(multiple && Array.isArray(value) ? value.map(String) : [String(value)]),
|
|
939
|
+
multiple,
|
|
940
|
+
matched: false
|
|
941
|
+
};
|
|
942
|
+
render.selectSelections.push(nextSelection);
|
|
943
|
+
}
|
|
944
|
+
const rawText = getRawTextElementInContext(parentNamespace, namespace, lower);
|
|
945
|
+
try {
|
|
946
|
+
withNamespace(getChildNamespace(parentNamespace, namespace, lower, elementProps), () => writeElement(tag, elementProps, rawText, sink));
|
|
947
|
+
} finally {
|
|
948
|
+
if (nextSelection) render.selectSelections.pop();
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
const NAMED_ENTITIES = {
|
|
952
|
+
amp: "&",
|
|
953
|
+
lt: "<",
|
|
954
|
+
gt: ">",
|
|
955
|
+
quot: "\"",
|
|
956
|
+
apos: "'",
|
|
957
|
+
nbsp: "\xA0"
|
|
958
|
+
};
|
|
959
|
+
/** Decode the entity starting at `start` (`&...;`), or null if unknown. */
|
|
960
|
+
function decodeEntity(html, start) {
|
|
961
|
+
const end = html.indexOf(";", start + 1);
|
|
962
|
+
if (end < 0 || end - start > 12) return null;
|
|
963
|
+
const name = html.slice(start + 1, end);
|
|
964
|
+
if (name.startsWith("#")) {
|
|
965
|
+
const hex = name[1] === "x" || name[1] === "X";
|
|
966
|
+
const code = Number.parseInt(name.slice(hex ? 2 : 1), hex ? 16 : 10);
|
|
967
|
+
if (!Number.isFinite(code) || code < 0 || code > 1114111) return null;
|
|
968
|
+
return {
|
|
969
|
+
text: String.fromCodePoint(code),
|
|
970
|
+
end
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
const text = NAMED_ENTITIES[name];
|
|
974
|
+
return text === void 0 ? null : {
|
|
975
|
+
text,
|
|
976
|
+
end
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
function isAsciiWhitespace(code) {
|
|
980
|
+
return code === 32 || code === 9 || code === 10 || code === 12 || code === 13;
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
983
|
+
* The value a browser gives a value-less `<option>` rendered as `html`: its
|
|
984
|
+
* text content with ASCII whitespace stripped and collapsed. The markup is
|
|
985
|
+
* this renderer's own output, so tags, comments, and the three entities the
|
|
986
|
+
* text escaper emits are all there is to undo.
|
|
987
|
+
*/
|
|
988
|
+
function optionTextValue(html) {
|
|
989
|
+
let text = "";
|
|
990
|
+
let pendingSpace = false;
|
|
991
|
+
let i = 0;
|
|
992
|
+
while (i < html.length) {
|
|
993
|
+
const ch = html[i];
|
|
994
|
+
if (ch === "<") {
|
|
995
|
+
if (html.startsWith("<!--", i)) {
|
|
996
|
+
const end = html.indexOf("-->", i + 4);
|
|
997
|
+
i = end < 0 ? html.length : end + 3;
|
|
998
|
+
continue;
|
|
999
|
+
}
|
|
1000
|
+
const end = html.indexOf(">", i + 1);
|
|
1001
|
+
if (end < 0) break;
|
|
1002
|
+
if (html.slice(i + 1, i + 7).toLowerCase() === "script") {
|
|
1003
|
+
const close = html.toLowerCase().indexOf("<\/script", end);
|
|
1004
|
+
const closeEnd = close < 0 ? -1 : html.indexOf(">", close);
|
|
1005
|
+
i = closeEnd < 0 ? html.length : closeEnd + 1;
|
|
1006
|
+
continue;
|
|
1007
|
+
}
|
|
1008
|
+
i = end + 1;
|
|
1009
|
+
continue;
|
|
1010
|
+
}
|
|
1011
|
+
let decoded = ch;
|
|
1012
|
+
if (ch === "&") {
|
|
1013
|
+
const entity = decodeEntity(html, i);
|
|
1014
|
+
if (entity) {
|
|
1015
|
+
decoded = entity.text;
|
|
1016
|
+
i = entity.end;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
i++;
|
|
1020
|
+
for (const part of decoded) {
|
|
1021
|
+
if (isAsciiWhitespace(part.charCodeAt(0))) {
|
|
1022
|
+
pendingSpace = text.length > 0;
|
|
1023
|
+
continue;
|
|
1024
|
+
}
|
|
1025
|
+
if (pendingSpace) text += " ";
|
|
1026
|
+
pendingSpace = false;
|
|
1027
|
+
text += part;
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
return text;
|
|
1031
|
+
}
|
|
1032
|
+
function writeElement(tag, props, rawText, sink) {
|
|
1033
|
+
sink.write("<" + tag);
|
|
1034
|
+
renderAttrsDirect(props, sink, rawText === null ? tag : void 0);
|
|
1035
|
+
sink.write(">");
|
|
1036
|
+
writeContent(props, rawText, sink);
|
|
1037
|
+
sink.write("</" + tag + ">");
|
|
1038
|
+
}
|
|
1039
|
+
/** An element's content: raw HTML, raw text, or rendered children. */
|
|
1040
|
+
function writeContent(props, rawText, sink) {
|
|
1041
|
+
const dangerous = props.dangerouslySetInnerHTML;
|
|
1042
|
+
if (dangerous !== void 0 && dangerous !== null) {
|
|
1043
|
+
if (typeof dangerous === "object" && "__html" in dangerous) {
|
|
1044
|
+
const html = dangerous.__html;
|
|
1045
|
+
if (html !== null && html !== void 0) sink.write(String(html));
|
|
1046
|
+
} else renderValue(props.children, sink);
|
|
1047
|
+
} else if (rawText !== null) sink.write(escapeRawText(collectRawText(props.children, rawText), rawText));
|
|
1048
|
+
else if (!props.imperativeChildren) renderValue(props.children, sink);
|
|
1049
|
+
}
|
|
1050
|
+
/**
|
|
1051
|
+
* The text of a `<script>` or `<style>`: collected unescaped and neutralized
|
|
1052
|
+
* as a whole. Element children are rejected rather than serialized as markup
|
|
1053
|
+
* the parser would read back as literal text.
|
|
1054
|
+
*/
|
|
1055
|
+
function collectRawText(value, element) {
|
|
1056
|
+
return flattenText(value, element).join("");
|
|
1057
|
+
}
|
|
1058
|
+
function flattenText(value, element) {
|
|
1059
|
+
const out = [];
|
|
1060
|
+
const visit = (item) => {
|
|
1061
|
+
if (Array.isArray(item)) {
|
|
1062
|
+
for (const entry of item) visit(entry);
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
for (const child of normalizeChildren(item)) switch (child.kind) {
|
|
1066
|
+
case 1:
|
|
1067
|
+
out.push(child.text);
|
|
1068
|
+
break;
|
|
1069
|
+
case 3:
|
|
1070
|
+
visit(child.children);
|
|
1071
|
+
break;
|
|
1072
|
+
case 4:
|
|
1073
|
+
visit(functionChildOutput(untrack(() => readValue(child.fn))));
|
|
1074
|
+
break;
|
|
1075
|
+
case 2: {
|
|
1076
|
+
const render = state();
|
|
1077
|
+
const instance = new ComponentInstance(render.owner, child.fn, child.props);
|
|
1078
|
+
instance.server = true;
|
|
1079
|
+
instance.serverContext = render.ctx;
|
|
1080
|
+
const output = runComponent(instance);
|
|
1081
|
+
if (!instance.boundary) {
|
|
1082
|
+
withOwner(instance, () => visit(componentOutput(output)));
|
|
1083
|
+
break;
|
|
1084
|
+
}
|
|
1085
|
+
const start = out.length;
|
|
1086
|
+
const restorePortals = capturePortalWrites(render.ctx);
|
|
1087
|
+
try {
|
|
1088
|
+
withOwner(instance, () => visit(componentOutput(output)));
|
|
1089
|
+
} catch (caught) {
|
|
1090
|
+
const error = clarifyRenderOverflow(caught);
|
|
1091
|
+
out.length = start;
|
|
1092
|
+
restorePortals();
|
|
1093
|
+
const cleanupErrors = disposeFailedSubtree(instance);
|
|
1094
|
+
try {
|
|
1095
|
+
if (!instance.boundary(error)) throw error;
|
|
1096
|
+
const fallback = runComponent(instance);
|
|
1097
|
+
withOwner(instance, () => visit(componentOutput(fallback)));
|
|
1098
|
+
} finally {
|
|
1099
|
+
reportBoundaryCleanupErrors(cleanupErrors);
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
break;
|
|
1103
|
+
}
|
|
1104
|
+
case 0: throw new Error(`SSR: <${element}> children must be text, but received an element <${child.tag}>.`);
|
|
1105
|
+
default: throw new Error(`SSR: <${element}> children must be text, but received a non-text node.`);
|
|
1106
|
+
}
|
|
1107
|
+
};
|
|
1108
|
+
visit(value);
|
|
1109
|
+
return out;
|
|
1110
|
+
}
|
|
1111
|
+
function renderToString(value, namespace) {
|
|
1112
|
+
const sink = new StringSink();
|
|
1113
|
+
withNamespace(namespace, () => renderValue(value, sink));
|
|
1114
|
+
sink.end();
|
|
1115
|
+
return sink.toString();
|
|
1116
|
+
}
|
|
1117
|
+
/** Replace portal host tokens with their portal's final content. */
|
|
1118
|
+
function resolvePortals(html, ctx) {
|
|
1119
|
+
let resolved = html;
|
|
1120
|
+
const rendered = /* @__PURE__ */ new Set();
|
|
1121
|
+
for (;;) {
|
|
1122
|
+
let found = false;
|
|
1123
|
+
for (const slot of ctx.ssrPortals.slots.values()) {
|
|
1124
|
+
const explicit = slot.hosts.filter((host) => !host.automatic);
|
|
1125
|
+
for (const host of slot.hosts) {
|
|
1126
|
+
if (rendered.has(host.token)) continue;
|
|
1127
|
+
found = true;
|
|
1128
|
+
rendered.add(host.token);
|
|
1129
|
+
const active = host.automatic ? explicit.length === 0 : true;
|
|
1130
|
+
const content = active ? [...slot.writers.values()].sort((left, right) => comparePortalWriterOrder(left.owner, right.owner, left.order, right.order)).map((write) => withOwner(write.owner ?? state().owner, () => renderToString(write.value, state().portalNamespaces.get(host.token) ?? "html"))).join("") : "";
|
|
1131
|
+
const hostContent = host.automatic && (!active || !slot.hasValue) ? "" : host.automatic && content === "" ? host.token : host.defaultPortal && active && slot.hasValue ? `<!--askr-range-start-->${content}<!--askr-range-end-->` : content;
|
|
1132
|
+
resolved = resolved.replace(host.token, () => hostContent);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
if (!found) return resolved;
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
function withServerRender(ctx, fn) {
|
|
1139
|
+
const previous = current;
|
|
1140
|
+
const owner = new Owner(null);
|
|
1141
|
+
current = {
|
|
1142
|
+
ctx,
|
|
1143
|
+
owner,
|
|
1144
|
+
namespace: "html",
|
|
1145
|
+
portalNamespaces: /* @__PURE__ */ new Map(),
|
|
1146
|
+
selectSelections: []
|
|
1147
|
+
};
|
|
1148
|
+
let result;
|
|
1149
|
+
try {
|
|
1150
|
+
result = fn();
|
|
1151
|
+
} catch (error) {
|
|
1152
|
+
current = previous;
|
|
1153
|
+
const cleanupErrors = owner.dispose();
|
|
1154
|
+
const failure = clarifyRenderOverflow(error);
|
|
1155
|
+
if (cleanupErrors.length > 0) throw new AggregateError([failure, ...cleanupErrors], "SSR render failed and temporary owner cleanup also failed");
|
|
1156
|
+
throw failure;
|
|
1157
|
+
}
|
|
1158
|
+
current = previous;
|
|
1159
|
+
const errors = owner.dispose();
|
|
1160
|
+
if (errors.length === 1) throw errors[0];
|
|
1161
|
+
if (errors.length > 1) throw new AggregateError(errors, "SSR temporary owner cleanup failed");
|
|
1162
|
+
return result;
|
|
1163
|
+
}
|
|
1164
|
+
const AUTO_PORTAL = {
|
|
1165
|
+
$$typeof: ELEMENT_TYPE,
|
|
1166
|
+
type: DefaultPortal,
|
|
1167
|
+
props: { __askrAutoDefaultPortal: true },
|
|
1168
|
+
key: "__default_portal"
|
|
1169
|
+
};
|
|
1170
|
+
/** A root's output followed by the automatic default portal host. */
|
|
1171
|
+
function withDefaultPortal(output) {
|
|
1172
|
+
return {
|
|
1173
|
+
$$typeof: ELEMENT_TYPE,
|
|
1174
|
+
type: Fragment,
|
|
1175
|
+
props: { children: output == null ? [AUTO_PORTAL] : [output, AUTO_PORTAL] },
|
|
1176
|
+
key: null
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
1179
|
+
function rootElement(component, props, nonce) {
|
|
1180
|
+
const Root = (_props, context) => {
|
|
1181
|
+
const output = component(props, context);
|
|
1182
|
+
if (isPromiseLike(output)) throwSSRDataMissing();
|
|
1183
|
+
return withDefaultPortal(output);
|
|
1184
|
+
};
|
|
1185
|
+
Object.defineProperty(Root, "name", { value: component.name || "Component" });
|
|
1186
|
+
const element = {
|
|
1187
|
+
$$typeof: ELEMENT_TYPE,
|
|
1188
|
+
type: Root,
|
|
1189
|
+
props: {},
|
|
1190
|
+
key: null
|
|
1191
|
+
};
|
|
1192
|
+
return nonce === void 0 ? element : CspNonceScope({
|
|
1193
|
+
value: nonce,
|
|
1194
|
+
children: element
|
|
1195
|
+
});
|
|
1196
|
+
}
|
|
1197
|
+
/** Synchronously render a component to an HTML string, without route resolution. */
|
|
1198
|
+
function renderToStringSync(component, props, options) {
|
|
1199
|
+
const nonce = validateCspNonce(options?.cspNonce);
|
|
1200
|
+
const ctx = createRenderContext(options?.seed ?? 12345, {
|
|
1201
|
+
data: options?.data,
|
|
1202
|
+
envelope: options?.envelope,
|
|
1203
|
+
cspNonce: nonce,
|
|
1204
|
+
authContext: options?.authContext,
|
|
1205
|
+
...options?.route
|
|
1206
|
+
});
|
|
1207
|
+
return withRenderContext(ctx, () => {
|
|
1208
|
+
startRenderPhase(ctx.renderData);
|
|
1209
|
+
try {
|
|
1210
|
+
return withServerRender(ctx, () => {
|
|
1211
|
+
const sink = new StringSink();
|
|
1212
|
+
renderValue(rootElement(component, props ?? {}, nonce), sink);
|
|
1213
|
+
sink.end();
|
|
1214
|
+
const html = resolvePortals(sink.toString(), ctx);
|
|
1215
|
+
options?.onContext?.(ctx);
|
|
1216
|
+
return html + serializeHydrationRenderData(ctx.hydrationData ?? void 0, ctx.dataRuntime);
|
|
1217
|
+
});
|
|
1218
|
+
} finally {
|
|
1219
|
+
stopRenderPhase();
|
|
1220
|
+
}
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1223
|
+
function renderSSRRouteAppToSink(input) {
|
|
1224
|
+
const { ctx, data, route, params, sink } = input;
|
|
1225
|
+
withRenderContext(ctx, () => {
|
|
1226
|
+
startRenderPhase(ctx.renderData);
|
|
1227
|
+
try {
|
|
1228
|
+
withServerRender(ctx, () => {
|
|
1229
|
+
const appSink = new PortalSink(sink);
|
|
1230
|
+
renderValue(rootElement(((routeParams) => route.handler(routeParams)), params, ctx.cspNonce), appSink);
|
|
1231
|
+
appSink.flush(ctx);
|
|
1232
|
+
});
|
|
1233
|
+
if (ctx.deferredBoundaries.length === 0) sink.write(serializeHydrationRenderData(ctx.hydrationData ?? data, ctx.dataRuntime));
|
|
1234
|
+
} finally {
|
|
1235
|
+
stopRenderPhase();
|
|
1236
|
+
}
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1239
|
+
//#endregion
|
|
1240
|
+
export { serializeHydrationRenderData as a, getRenderContext as c, escapeHtml as d, StringSink as i, withRenderContext as l, renderToStringSync as n, withHydratedAuth as o, StreamSink as r, createRenderContext as s, renderSSRRouteAppToSink as t, withRenderContextAsync as u };
|