@reckona/mreact-server 0.0.81 → 0.0.83

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.
@@ -0,0 +1,39 @@
1
+ import type { HtmlSink } from "@reckona/mreact-shared/compiler-contract";
2
+ export interface AsyncBoundaryOptions {
3
+ catch?: (sink: HtmlSink, error: unknown) => void | PromiseLike<void>;
4
+ hydrationAwaitId?: string;
5
+ }
6
+ export interface OutOfOrderBoundaryOptions extends AsyncBoundaryOptions {
7
+ hydration?: boolean;
8
+ placeholder?: (sink: HtmlSink) => void | PromiseLike<void>;
9
+ placeholderTag?: string;
10
+ }
11
+ export interface OutOfOrderReorderScriptOptions {
12
+ nonce?: string;
13
+ src?: string;
14
+ }
15
+ export interface HydrationScriptOptions {
16
+ nonce?: string;
17
+ }
18
+ export interface ReactSuspenseScriptOptions {
19
+ nonce?: string;
20
+ src?: string;
21
+ }
22
+ export interface ReactSuspenseBoundaryOptions extends AsyncBoundaryOptions {
23
+ fallback?: (sink: HtmlSink) => void | PromiseLike<void>;
24
+ nonce?: string;
25
+ }
26
+ export interface ReactSuspenseClientRenderOptions {
27
+ message?: string;
28
+ stack?: string;
29
+ }
30
+ export type AsyncBoundaryRender<T> = (sink: HtmlSink, value: Awaited<T>) => void | PromiseLike<void>;
31
+ export declare function renderAsyncBoundary<T>(sink: HtmlSink, value: T, render: AsyncBoundaryRender<T>, options?: AsyncBoundaryOptions): Promise<void>;
32
+ export declare function renderOutOfOrderBoundary<T>(sink: HtmlSink, id: string, value: T, render: AsyncBoundaryRender<T>, options?: OutOfOrderBoundaryOptions): void;
33
+ export declare function renderOutOfOrderReorderScript(sink: HtmlSink, options?: OutOfOrderReorderScriptOptions): void;
34
+ export declare function renderReactSuspenseBoundary(sink: HtmlSink, render: (sink: HtmlSink) => void | PromiseLike<void>): void | PromiseLike<void>;
35
+ export declare function renderReactSuspenseOutOfOrderBoundary<T>(sink: HtmlSink, boundaryId: string, segmentId: string, value: T, render: AsyncBoundaryRender<T>, options?: ReactSuspenseBoundaryOptions): void;
36
+ export declare function renderReactSuspenseClientRenderBoundary(sink: HtmlSink, fallback: (sink: HtmlSink) => void | PromiseLike<void>, options?: ReactSuspenseClientRenderOptions): void | PromiseLike<void>;
37
+ export declare function renderHydrationBoundary(sink: HtmlSink, id: string, render: (sink: HtmlSink) => void | PromiseLike<void>): void | PromiseLike<void>;
38
+ export declare const reactSuspenseRevealExternalScript = "(()=>{(self.$RC=self.$RC||function(bid,sid){var b=document.getElementById(bid);var s=document.getElementById(sid);if(!b||!s)return;var p=b.parentNode;var e=b.nextSibling;var d=0;var r=[];for(var n=e;n;n=n.nextSibling){if(n.nodeType===8){if(n.data===\"$\"||n.data===\"$?\"||n.data===\"$!\")d++;else if(n.data===\"/$\"){if(d===0){e=n;break;}d--;}}r.push(n);}for(var i=0;r[i];i++)p.removeChild(r[i]);while(s.firstChild)p.insertBefore(s.firstChild,e);s.remove();b.data=\"$\";});var s=document.currentScript;if(!s)return;var b=s.getAttribute(\"data-boundary-id\");var seg=s.getAttribute(\"data-segment-id\");if(b!==null&&seg!==null)self.$RC(b,seg);})();";
39
+ //# sourceMappingURL=boundary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"boundary.d.ts","sourceRoot":"","sources":["../src/boundary.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,249 @@
1
+ import { escapeAttribute, isPromiseLikeUnknown, renderNonceAttribute, serializeScriptJson, } from "./html-internals.js";
2
+ import { createStringSink } from "./sink.js";
3
+ const outOfOrderBoundaryInstances = new WeakMap();
4
+ export async function renderAsyncBoundary(sink, value, render, options = {}) {
5
+ try {
6
+ const resolved = await value;
7
+ await sink.backpressure?.();
8
+ await render(sink, resolved);
9
+ if (options.hydrationAwaitId !== undefined) {
10
+ appendAwaitHydrationData(sink, options.hydrationAwaitId, resolved);
11
+ }
12
+ }
13
+ catch (error) {
14
+ if (options.catch === undefined) {
15
+ throw error;
16
+ }
17
+ await sink.backpressure?.();
18
+ await options.catch(sink, error);
19
+ }
20
+ }
21
+ // Threshold for `<Await>` payload size warnings (UTF-8 byte length of
22
+ // JSON-serialized representation). 100KB warn / 1MB error follow the
23
+ // "you're sending a lot of data" hint pattern used by other frameworks.
24
+ const AWAIT_PAYLOAD_WARN_BYTES = 100 * 1024;
25
+ const AWAIT_PAYLOAD_ERROR_BYTES = 1024 * 1024;
26
+ function isProductionMode() {
27
+ // `process` may not exist in cross-runtime environments (Cloudflare/Deno).
28
+ // The server tsconfig does not include `@types/node`, so we look it up
29
+ // through `globalThis` with a minimal local typing.
30
+ const globalProcess = globalThis
31
+ .process;
32
+ try {
33
+ return globalProcess?.env?.["NODE_ENV"] === "production";
34
+ }
35
+ catch {
36
+ return false;
37
+ }
38
+ }
39
+ function appendAwaitHydrationData(sink, awaitId, resolved) {
40
+ const serialized = serializeAwaitHydrationValue(resolved, awaitId);
41
+ if (serialized === undefined) {
42
+ return;
43
+ }
44
+ const idLiteral = JSON.stringify(awaitId).replaceAll("<", "\\u003c");
45
+ sink.append(`<script data-mreact-await=${idLiteral}>(self.__mreactAwaitData||(self.__mreactAwaitData={}))[${idLiteral}]={value:${serialized}}</script>`);
46
+ }
47
+ // Emits a single console.warn in dev when `resolved` contains shapes the
48
+ // wire format will silently drop or coerce (Date / Map / Set / RegExp /
49
+ // class instance / function / Symbol / nested non-POJO).
50
+ function warnIfNonSerializableAwaitValue(value, awaitId) {
51
+ if (isProductionMode()) {
52
+ return;
53
+ }
54
+ if (!containsNonSerializableSurface(value)) {
55
+ return;
56
+ }
57
+ console.warn(`[mreact] <Await value={...}> for "${awaitId}" includes non-serializable ` +
58
+ `data (Date / Map / Set / RegExp / class instance / function / Symbol). ` +
59
+ `The wire format uses JSON.stringify, so the client-side renderer may ` +
60
+ `receive a different shape after the JSON round-trip. Convert to plain ` +
61
+ `JSON-serializable data before it reaches <Await>. See ` +
62
+ `https://github.com/t-k/mreact#streaming-loading-and-await.`);
63
+ }
64
+ function containsNonSerializableSurface(value) {
65
+ if (value === null || value === undefined || typeof value !== "object") {
66
+ return false;
67
+ }
68
+ if (value instanceof Date || value instanceof Map || value instanceof Set || value instanceof RegExp) {
69
+ return true;
70
+ }
71
+ if (Array.isArray(value)) {
72
+ return value.some((entry) => containsNonSerializableSurface(entry));
73
+ }
74
+ const proto = Object.getPrototypeOf(value);
75
+ if (proto !== Object.prototype && proto !== null) {
76
+ return true;
77
+ }
78
+ for (const entry of Object.values(value)) {
79
+ if (containsNonSerializableSurface(entry)) {
80
+ return true;
81
+ }
82
+ }
83
+ return false;
84
+ }
85
+ function reportAwaitPayloadSize(json, awaitId) {
86
+ const byteLength = typeof TextEncoder !== "undefined" ? new TextEncoder().encode(json).length : json.length;
87
+ if (byteLength > AWAIT_PAYLOAD_ERROR_BYTES) {
88
+ console.error(`[mreact] <Await> payload for "${awaitId}" is ${(byteLength / 1024 / 1024).toFixed(2)}MB, ` +
89
+ `exceeding the 1MB error threshold. Large payloads inflate HTML response size and ` +
90
+ `client memory pressure. Stream the data via loader or split into smaller boundaries.`);
91
+ return;
92
+ }
93
+ if (byteLength > AWAIT_PAYLOAD_WARN_BYTES) {
94
+ console.warn(`[mreact] large await payload for "${awaitId}": ${(byteLength / 1024).toFixed(1)}KB ` +
95
+ `(over the 100KB warning threshold). Consider streaming the data via loader or ` +
96
+ `splitting into smaller boundaries.`);
97
+ }
98
+ }
99
+ function serializeAwaitHydrationValue(value, awaitId) {
100
+ try {
101
+ const json = JSON.stringify(value);
102
+ if (json === undefined) {
103
+ return undefined;
104
+ }
105
+ warnIfNonSerializableAwaitValue(value, awaitId);
106
+ reportAwaitPayloadSize(json, awaitId);
107
+ // Defuse `</script>` and runaway control characters so the data can be
108
+ // safely embedded inside a `<script>` element.
109
+ return json
110
+ .replaceAll("<", "\\u003c")
111
+ .replaceAll("\u2028", "\\u2028")
112
+ .replaceAll("\u2029", "\\u2029");
113
+ }
114
+ catch {
115
+ return undefined;
116
+ }
117
+ }
118
+ function inheritBackpressure(childSink, parentSink) {
119
+ if (parentSink.backpressure === undefined) {
120
+ return childSink;
121
+ }
122
+ return {
123
+ ...childSink,
124
+ backpressure: parentSink.backpressure,
125
+ };
126
+ }
127
+ export function renderOutOfOrderBoundary(sink, id, value, render, options = {}) {
128
+ const boundaryId = nextOutOfOrderBoundaryInstanceId(sink, id);
129
+ const placeholderSink = createStringSink();
130
+ void options.placeholder?.(placeholderSink);
131
+ const placeholderTag = normalizeOutOfOrderPlaceholderTag(options.placeholderTag);
132
+ const hydrationStart = options.hydration === true ? `<!--mreact-h:start:${encodeURIComponent(boundaryId)}-->` : "";
133
+ const hydrationEnd = options.hydration === true ? `<!--mreact-h:end:${encodeURIComponent(boundaryId)}-->` : "";
134
+ sink.append(`${hydrationStart}<${placeholderTag} data-mreact-oob-placeholder="${escapeAttribute(boundaryId)}">${placeholderSink.toString()}</${placeholderTag}>${hydrationEnd}`);
135
+ const task = renderOutOfOrderFragment(sink, boundaryId, value, render, options);
136
+ if (sink.defer === undefined) {
137
+ void task;
138
+ return;
139
+ }
140
+ sink.defer(task);
141
+ }
142
+ function normalizeOutOfOrderPlaceholderTag(tag) {
143
+ if (typeof tag !== "string") {
144
+ return "span";
145
+ }
146
+ const normalized = tag.trim().toLowerCase();
147
+ return /^[a-z][a-z0-9-]*$/.test(normalized) ? normalized : "span";
148
+ }
149
+ async function renderOutOfOrderFragment(sink, id, value, render, options) {
150
+ const fragmentSink = inheritBackpressure(createStringSink(), sink);
151
+ let resolvedValue;
152
+ let hasResolvedValue = false;
153
+ await renderAsyncBoundary(fragmentSink, value, async (childSink, resolved) => {
154
+ resolvedValue = resolved;
155
+ hasResolvedValue = true;
156
+ await render(childSink, resolved);
157
+ }, options.catch === undefined ? {} : { catch: options.catch });
158
+ await fragmentSink.drain();
159
+ sink.append(`<template data-mreact-oob-fragment="${escapeAttribute(id)}">${fragmentSink.toString()}</template>`);
160
+ if (hasResolvedValue && options.hydrationAwaitId !== undefined) {
161
+ appendAwaitHydrationData(sink, options.hydrationAwaitId, resolvedValue);
162
+ }
163
+ }
164
+ export function renderOutOfOrderReorderScript(sink, options = {}) {
165
+ const nonceAttribute = options.nonce === undefined ? "" : ` nonce="${escapeAttribute(options.nonce)}"`;
166
+ if (options.src !== undefined) {
167
+ sink.append(`<script data-mreact-oob-reorder${nonceAttribute} src="${escapeAttribute(options.src)}"></script>`);
168
+ return;
169
+ }
170
+ sink.append(`<script data-mreact-oob-reorder${nonceAttribute}>${outOfOrderReorderScript}</script>`);
171
+ }
172
+ export function renderReactSuspenseBoundary(sink, render) {
173
+ sink.append("<!--$-->");
174
+ const result = render(sink);
175
+ if (isPromiseLike(result)) {
176
+ return result.then(() => {
177
+ sink.append("<!--/$-->");
178
+ });
179
+ }
180
+ sink.append("<!--/$-->");
181
+ }
182
+ export function renderReactSuspenseOutOfOrderBoundary(sink, boundaryId, segmentId, value, render, options = {}) {
183
+ const actualBoundaryId = nextOutOfOrderBoundaryInstanceId(sink, boundaryId);
184
+ const suffix = actualBoundaryId.slice(boundaryId.length);
185
+ const actualSegmentId = `${segmentId}${suffix}`;
186
+ const fallbackSink = createStringSink();
187
+ void options.fallback?.(fallbackSink);
188
+ sink.append(`<!--$?--><template id="${escapeAttribute(actualBoundaryId)}"></template>${fallbackSink.toString()}<!--/$-->`);
189
+ const task = renderReactSuspenseSegment(sink, actualBoundaryId, actualSegmentId, value, render, options);
190
+ if (sink.defer === undefined) {
191
+ void task;
192
+ return;
193
+ }
194
+ sink.defer(task);
195
+ }
196
+ function nextOutOfOrderBoundaryInstanceId(sink, id) {
197
+ let instances = outOfOrderBoundaryInstances.get(sink);
198
+ if (instances === undefined) {
199
+ instances = new Map();
200
+ outOfOrderBoundaryInstances.set(sink, instances);
201
+ }
202
+ const count = instances.get(id) ?? 0;
203
+ instances.set(id, count + 1);
204
+ return count === 0 ? id : `${id}-${count.toString(36)}`;
205
+ }
206
+ export function renderReactSuspenseClientRenderBoundary(sink, fallback, options = {}) {
207
+ sink.append(`<!--$!--><template${renderReactSuspenseErrorAttributes(options)}></template>`);
208
+ const result = fallback(sink);
209
+ if (isPromiseLike(result)) {
210
+ return result.then(() => {
211
+ sink.append("<!--/$-->");
212
+ });
213
+ }
214
+ sink.append("<!--/$-->");
215
+ }
216
+ async function renderReactSuspenseSegment(sink, boundaryId, segmentId, value, render, options) {
217
+ const segmentSink = inheritBackpressure(createStringSink(), sink);
218
+ await renderAsyncBoundary(segmentSink, value, render, options.catch === undefined ? {} : { catch: options.catch });
219
+ sink.append(`<div hidden id="${escapeAttribute(segmentId)}">${segmentSink.toString()}</div>${renderReactSuspenseRevealScript(boundaryId, segmentId, options)}`);
220
+ }
221
+ function renderReactSuspenseErrorAttributes(options) {
222
+ const message = options.message === undefined ? "" : ` data-msg="${escapeAttribute(options.message)}"`;
223
+ const stack = options.stack === undefined ? "" : ` data-stck="${escapeAttribute(options.stack)}"`;
224
+ return `${message}${stack}`;
225
+ }
226
+ function renderReactSuspenseRevealScript(boundaryId, segmentId, options = {}) {
227
+ if (options.src !== undefined) {
228
+ return `<script data-mreact-react-suspense-reveal${renderNonceAttribute(options.nonce)} src="${escapeAttribute(options.src)}" data-boundary-id="${escapeAttribute(boundaryId)}" data-segment-id="${escapeAttribute(segmentId)}"></script>`;
229
+ }
230
+ return `<script${renderNonceAttribute(options.nonce)}>${reactSuspenseRevealScriptBody};$RC(${serializeScriptJson(boundaryId)},${serializeScriptJson(segmentId)})</script>`;
231
+ }
232
+ export function renderHydrationBoundary(sink, id, render) {
233
+ const markerId = encodeURIComponent(id);
234
+ sink.append(`<!--mreact-h:start:${markerId}-->`);
235
+ const result = render(sink);
236
+ if (isPromiseLike(result)) {
237
+ return result.then(() => {
238
+ sink.append(`<!--mreact-h:end:${markerId}-->`);
239
+ });
240
+ }
241
+ sink.append(`<!--mreact-h:end:${markerId}-->`);
242
+ }
243
+ function isPromiseLike(value) {
244
+ return isPromiseLikeUnknown(value);
245
+ }
246
+ const outOfOrderReorderScript = `(()=>{function apply(root){const fragments=Array.from(root.querySelectorAll("template[data-mreact-oob-fragment]"));for(const fragment of fragments){const id=fragment.getAttribute("data-mreact-oob-fragment");if(id===null)continue;const placeholders=Array.from(root.querySelectorAll("[data-mreact-oob-placeholder]"));const placeholder=placeholders.find((candidate)=>candidate.getAttribute("data-mreact-oob-placeholder")===id);if(placeholder===undefined)continue;placeholder.replaceWith(fragment.content.cloneNode(true));fragment.remove();}}apply(document);new MutationObserver(()=>apply(document)).observe(document.documentElement,{childList:true,subtree:true});})();`;
247
+ const reactSuspenseRevealScriptBody = `(self.$RC=self.$RC||function(bid,sid){var b=document.getElementById(bid);var s=document.getElementById(sid);if(!b||!s)return;var p=b.parentNode;var e=b.nextSibling;var d=0;var r=[];for(var n=e;n;n=n.nextSibling){if(n.nodeType===8){if(n.data==="$"||n.data==="$?"||n.data==="$!")d++;else if(n.data==="/$"){if(d===0){e=n;break;}d--;}}r.push(n);}for(var i=0;r[i];i++)p.removeChild(r[i]);while(s.firstChild)p.insertBefore(s.firstChild,e);s.remove();b.data="$";})`;
248
+ export const reactSuspenseRevealExternalScript = `(()=>{${reactSuspenseRevealScriptBody};var s=document.currentScript;if(!s)return;var b=s.getAttribute("data-boundary-id");var seg=s.getAttribute("data-segment-id");if(b!==null&&seg!==null)self.$RC(b,seg);})();`;
249
+ //# sourceMappingURL=boundary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"boundary.js","sourceRoot":"","sources":["../src/boundary.ts"],"names":[],"mappings":"AACA,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,gBAAgB,EAAuB,MAAM,WAAW,CAAC;AA0ClE,MAAM,2BAA2B,GAAG,IAAI,OAAO,EAAiC,CAAC;AAEjF,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,IAAc,EACd,KAAQ,EACR,MAA8B,EAC9B,UAAgC,EAAE;IAElC,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC;QAC7B,MAAM,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC;QAC5B,MAAM,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAC7B,IAAI,OAAO,CAAC,gBAAgB,KAAK,SAAS,EAAE,CAAC;YAC3C,wBAAwB,CAAC,IAAI,EAAE,OAAO,CAAC,gBAAgB,EAAE,QAAQ,CAAC,CAAC;QACrE,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;YAChC,MAAM,KAAK,CAAC;QACd,CAAC;QAED,MAAM,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC;QAC5B,MAAM,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACnC,CAAC;AACH,CAAC;AAED,sEAAsE;AACtE,qEAAqE;AACrE,wEAAwE;AACxE,MAAM,wBAAwB,GAAG,GAAG,GAAG,IAAI,CAAC;AAC5C,MAAM,yBAAyB,GAAG,IAAI,GAAG,IAAI,CAAC;AAE9C,SAAS,gBAAgB;IACvB,2EAA2E;IAC3E,uEAAuE;IACvE,oDAAoD;IACpD,MAAM,aAAa,GAAI,UAAyE;SAC7F,OAAO,CAAC;IAEX,IAAI,CAAC;QACH,OAAO,aAAa,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,YAAY,CAAC;IAC3D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,SAAS,wBAAwB,CAC/B,IAAc,EACd,OAAe,EACf,QAAiB;IAEjB,MAAM,UAAU,GAAG,4BAA4B,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAEnE,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC7B,OAAO;IACT,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IACrE,IAAI,CAAC,MAAM,CACT,6BAA6B,SAAS,0DAA0D,SAAS,YAAY,UAAU,YAAY,CAC5I,CAAC;AACJ,CAAC;AAED,yEAAyE;AACzE,wEAAwE;AACxE,yDAAyD;AACzD,SAAS,+BAA+B,CAAC,KAAc,EAAE,OAAe;IACtE,IAAI,gBAAgB,EAAE,EAAE,CAAC;QACvB,OAAO;IACT,CAAC;IAED,IAAI,CAAC,8BAA8B,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3C,OAAO;IACT,CAAC;IAED,OAAO,CAAC,IAAI,CACV,qCAAqC,OAAO,8BAA8B;QACxE,yEAAyE;QACzE,uEAAuE;QACvE,wEAAwE;QACxE,wDAAwD;QACxD,4DAA4D,CAC/D,CAAC;AACJ,CAAC;AAED,SAAS,8BAA8B,CAAC,KAAc;IACpD,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACvE,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,KAAK,YAAY,IAAI,IAAI,KAAK,YAAY,GAAG,IAAI,KAAK,YAAY,GAAG,IAAI,KAAK,YAAY,MAAM,EAAE,CAAC;QACrG,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,8BAA8B,CAAC,KAAK,CAAC,CAAC,CAAC;IACtE,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IAE3C,IAAI,KAAK,KAAK,MAAM,CAAC,SAAS,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACjD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,KAAgC,CAAC,EAAE,CAAC;QACpE,IAAI,8BAA8B,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1C,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,sBAAsB,CAAC,IAAY,EAAE,OAAe;IAC3D,MAAM,UAAU,GACd,OAAO,WAAW,KAAK,WAAW,CAAC,CAAC,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC;IAE3F,IAAI,UAAU,GAAG,yBAAyB,EAAE,CAAC;QAC3C,OAAO,CAAC,KAAK,CACX,iCAAiC,OAAO,QAAQ,CAAC,UAAU,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM;YACzF,mFAAmF;YACnF,sFAAsF,CACzF,CAAC;QACF,OAAO;IACT,CAAC;IAED,IAAI,UAAU,GAAG,wBAAwB,EAAE,CAAC;QAC1C,OAAO,CAAC,IAAI,CACV,qCAAqC,OAAO,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK;YACnF,gFAAgF;YAChF,oCAAoC,CACvC,CAAC;IACJ,CAAC;AACH,CAAC;AAED,SAAS,4BAA4B,CAAC,KAAc,EAAE,OAAe;IACnE,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACnC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,+BAA+B,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAChD,sBAAsB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAEtC,uEAAuE;QACvE,+CAA+C;QAC/C,OAAO,IAAI;aACR,UAAU,CAAC,GAAG,EAAE,SAAS,CAAC;aAC1B,UAAU,CAAC,QAAQ,EAAE,SAAS,CAAC;aAC/B,UAAU,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,SAAS,mBAAmB,CAAC,SAAyB,EAAE,UAAoB;IAC1E,IAAI,UAAU,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;QAC1C,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO;QACL,GAAG,SAAS;QACZ,YAAY,EAAE,UAAU,CAAC,YAAY;KACtC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,wBAAwB,CACtC,IAAc,EACd,EAAU,EACV,KAAQ,EACR,MAA8B,EAC9B,UAAqC,EAAE;IAEvC,MAAM,UAAU,GAAG,gCAAgC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC9D,MAAM,eAAe,GAAG,gBAAgB,EAAE,CAAC;IAC3C,KAAK,OAAO,CAAC,WAAW,EAAE,CAAC,eAAe,CAAC,CAAC;IAC5C,MAAM,cAAc,GAAG,iCAAiC,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;IACjF,MAAM,cAAc,GAClB,OAAO,CAAC,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC,sBAAsB,kBAAkB,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9F,MAAM,YAAY,GAChB,OAAO,CAAC,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC,oBAAoB,kBAAkB,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IAC5F,IAAI,CAAC,MAAM,CACT,GAAG,cAAc,IAAI,cAAc,iCAAiC,eAAe,CAAC,UAAU,CAAC,KAAK,eAAe,CAAC,QAAQ,EAAE,KAAK,cAAc,IAAI,YAAY,EAAE,CACpK,CAAC;IAEF,MAAM,IAAI,GAAG,wBAAwB,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IAEhF,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QAC7B,KAAK,IAAI,CAAC;QACV,OAAO;IACT,CAAC;IAED,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AACnB,CAAC;AAED,SAAS,iCAAiC,CAAC,GAAY;IACrD,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5B,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,MAAM,UAAU,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC5C,OAAO,mBAAmB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC;AACpE,CAAC;AAED,KAAK,UAAU,wBAAwB,CACrC,IAAc,EACd,EAAU,EACV,KAAQ,EACR,MAA8B,EAC9B,OAAkC;IAElC,MAAM,YAAY,GAAG,mBAAmB,CAAC,gBAAgB,EAAE,EAAE,IAAI,CAAC,CAAC;IACnE,IAAI,aAAsB,CAAC;IAC3B,IAAI,gBAAgB,GAAG,KAAK,CAAC;IAE7B,MAAM,mBAAmB,CACvB,YAAY,EACZ,KAAK,EACL,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE;QAC5B,aAAa,GAAG,QAAQ,CAAC;QACzB,gBAAgB,GAAG,IAAI,CAAC;QACxB,MAAM,MAAM,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IACpC,CAAC,EACD,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAC5D,CAAC;IACF,MAAM,YAAY,CAAC,KAAK,EAAE,CAAC;IAE3B,IAAI,CAAC,MAAM,CACT,uCAAuC,eAAe,CAAC,EAAE,CAAC,KAAK,YAAY,CAAC,QAAQ,EAAE,aAAa,CACpG,CAAC;IAEF,IAAI,gBAAgB,IAAI,OAAO,CAAC,gBAAgB,KAAK,SAAS,EAAE,CAAC;QAC/D,wBAAwB,CAAC,IAAI,EAAE,OAAO,CAAC,gBAAgB,EAAE,aAAa,CAAC,CAAC;IAC1E,CAAC;AACH,CAAC;AAED,MAAM,UAAU,6BAA6B,CAC3C,IAAc,EACd,UAA0C,EAAE;IAE5C,MAAM,cAAc,GAClB,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,eAAe,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC;IAElF,IAAI,OAAO,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;QAC9B,IAAI,CAAC,MAAM,CACT,kCAAkC,cAAc,SAAS,eAAe,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CACnG,CAAC;QACF,OAAO;IACT,CAAC;IAED,IAAI,CAAC,MAAM,CACT,kCAAkC,cAAc,IAAI,uBAAuB,WAAW,CACvF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,2BAA2B,CACzC,IAAc,EACd,MAAoD;IAEpD,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IACxB,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAE5B,IAAI,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1B,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE;YACtB,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QAC3B,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,qCAAqC,CACnD,IAAc,EACd,UAAkB,EAClB,SAAiB,EACjB,KAAQ,EACR,MAA8B,EAC9B,UAAwC,EAAE;IAE1C,MAAM,gBAAgB,GAAG,gCAAgC,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IAC5E,MAAM,MAAM,GAAG,gBAAgB,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IACzD,MAAM,eAAe,GAAG,GAAG,SAAS,GAAG,MAAM,EAAE,CAAC;IAChD,MAAM,YAAY,GAAG,gBAAgB,EAAE,CAAC;IACxC,KAAK,OAAO,CAAC,QAAQ,EAAE,CAAC,YAAY,CAAC,CAAC;IACtC,IAAI,CAAC,MAAM,CACT,0BAA0B,eAAe,CAAC,gBAAgB,CAAC,gBAAgB,YAAY,CAAC,QAAQ,EAAE,WAAW,CAC9G,CAAC;IAEF,MAAM,IAAI,GAAG,0BAA0B,CACrC,IAAI,EACJ,gBAAgB,EAChB,eAAe,EACf,KAAK,EACL,MAAM,EACN,OAAO,CACR,CAAC;IAEF,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QAC7B,KAAK,IAAI,CAAC;QACV,OAAO;IACT,CAAC;IAED,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AACnB,CAAC;AAED,SAAS,gCAAgC,CAAC,IAAc,EAAE,EAAU;IAClE,IAAI,SAAS,GAAG,2BAA2B,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAEtD,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,SAAS,GAAG,IAAI,GAAG,EAAE,CAAC;QACtB,2BAA2B,CAAC,GAAG,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IACnD,CAAC;IAED,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;IACrC,SAAS,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;IAE7B,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,IAAI,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,uCAAuC,CACrD,IAAc,EACd,QAAsD,EACtD,UAA4C,EAAE;IAE9C,IAAI,CAAC,MAAM,CAAC,qBAAqB,kCAAkC,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;IAC5F,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAE9B,IAAI,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1B,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE;YACtB,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QAC3B,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;AAC3B,CAAC;AAED,KAAK,UAAU,0BAA0B,CACvC,IAAc,EACd,UAAkB,EAClB,SAAiB,EACjB,KAAQ,EACR,MAA8B,EAC9B,OAAqC;IAErC,MAAM,WAAW,GAAG,mBAAmB,CAAC,gBAAgB,EAAE,EAAE,IAAI,CAAC,CAAC;IAElE,MAAM,mBAAmB,CACvB,WAAW,EACX,KAAK,EACL,MAAM,EACN,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAC5D,CAAC;IAEF,IAAI,CAAC,MAAM,CACT,mBAAmB,eAAe,CAAC,SAAS,CAAC,KAAK,WAAW,CAAC,QAAQ,EAAE,SAAS,+BAA+B,CAAC,UAAU,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,CACnJ,CAAC;AACJ,CAAC;AAED,SAAS,kCAAkC,CAAC,OAAyC;IACnF,MAAM,OAAO,GACX,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,cAAc,eAAe,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC;IACzF,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,eAAe,eAAe,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC;IAElG,OAAO,GAAG,OAAO,GAAG,KAAK,EAAE,CAAC;AAC9B,CAAC;AAED,SAAS,+BAA+B,CACtC,UAAkB,EAClB,SAAiB,EACjB,UAAsC,EAAE;IAExC,IAAI,OAAO,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;QAC9B,OAAO,4CAA4C,oBAAoB,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,eAAe,CAAC,OAAO,CAAC,GAAG,CAAC,uBAAuB,eAAe,CAAC,UAAU,CAAC,sBAAsB,eAAe,CAAC,SAAS,CAAC,aAAa,CAAC;IAC7O,CAAC;IAED,OAAO,UAAU,oBAAoB,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,6BAA6B,QAAQ,mBAAmB,CAAC,UAAU,CAAC,IAAI,mBAAmB,CAAC,SAAS,CAAC,YAAY,CAAC;AAC7K,CAAC;AAED,MAAM,UAAU,uBAAuB,CACrC,IAAc,EACd,EAAU,EACV,MAAoD;IAEpD,MAAM,QAAQ,GAAG,kBAAkB,CAAC,EAAE,CAAC,CAAC;IACxC,IAAI,CAAC,MAAM,CAAC,sBAAsB,QAAQ,KAAK,CAAC,CAAC;IACjD,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAE5B,IAAI,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1B,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE;YACtB,IAAI,CAAC,MAAM,CAAC,oBAAoB,QAAQ,KAAK,CAAC,CAAC;QACjD,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,CAAC,MAAM,CAAC,oBAAoB,QAAQ,KAAK,CAAC,CAAC;AACjD,CAAC;AAED,SAAS,aAAa,CAAC,KAAc;IACnC,OAAO,oBAAoB,CAAC,KAAK,CAAC,CAAC;AACrC,CAAC;AAED,MAAM,uBAAuB,GAAG,2pBAA2pB,CAAC;AAE5rB,MAAM,6BAA6B,GAAG,2cAA2c,CAAC;AAElf,MAAM,CAAC,MAAM,iCAAiC,GAAG,SAAS,6BAA6B,6KAA6K,CAAC","sourcesContent":["import type { HtmlSink } from \"@reckona/mreact-shared/compiler-contract\";\nimport {\n escapeAttribute,\n isPromiseLikeUnknown,\n renderNonceAttribute,\n serializeScriptJson,\n} from \"./html-internals.js\";\nimport { createStringSink, type StringHtmlSink } from \"./sink.js\";\n\nexport interface AsyncBoundaryOptions {\n catch?: (sink: HtmlSink, error: unknown) => void | PromiseLike<void>;\n hydrationAwaitId?: string;\n}\n\nexport interface OutOfOrderBoundaryOptions extends AsyncBoundaryOptions {\n hydration?: boolean;\n placeholder?: (sink: HtmlSink) => void | PromiseLike<void>;\n placeholderTag?: string;\n}\n\nexport interface OutOfOrderReorderScriptOptions {\n nonce?: string;\n src?: string;\n}\n\nexport interface HydrationScriptOptions {\n nonce?: string;\n}\n\nexport interface ReactSuspenseScriptOptions {\n nonce?: string;\n src?: string;\n}\n\nexport interface ReactSuspenseBoundaryOptions extends AsyncBoundaryOptions {\n fallback?: (sink: HtmlSink) => void | PromiseLike<void>;\n nonce?: string;\n}\n\nexport interface ReactSuspenseClientRenderOptions {\n message?: string;\n stack?: string;\n}\n\nexport type AsyncBoundaryRender<T> = (\n sink: HtmlSink,\n value: Awaited<T>,\n) => void | PromiseLike<void>;\n\nconst outOfOrderBoundaryInstances = new WeakMap<HtmlSink, Map<string, number>>();\n\nexport async function renderAsyncBoundary<T>(\n sink: HtmlSink,\n value: T,\n render: AsyncBoundaryRender<T>,\n options: AsyncBoundaryOptions = {},\n): Promise<void> {\n try {\n const resolved = await value;\n await sink.backpressure?.();\n await render(sink, resolved);\n if (options.hydrationAwaitId !== undefined) {\n appendAwaitHydrationData(sink, options.hydrationAwaitId, resolved);\n }\n } catch (error) {\n if (options.catch === undefined) {\n throw error;\n }\n\n await sink.backpressure?.();\n await options.catch(sink, error);\n }\n}\n\n// Threshold for `<Await>` payload size warnings (UTF-8 byte length of\n// JSON-serialized representation). 100KB warn / 1MB error follow the\n// \"you're sending a lot of data\" hint pattern used by other frameworks.\nconst AWAIT_PAYLOAD_WARN_BYTES = 100 * 1024;\nconst AWAIT_PAYLOAD_ERROR_BYTES = 1024 * 1024;\n\nfunction isProductionMode(): boolean {\n // `process` may not exist in cross-runtime environments (Cloudflare/Deno).\n // The server tsconfig does not include `@types/node`, so we look it up\n // through `globalThis` with a minimal local typing.\n const globalProcess = (globalThis as { process?: { env?: Record<string, string | undefined> } })\n .process;\n\n try {\n return globalProcess?.env?.[\"NODE_ENV\"] === \"production\";\n } catch {\n return false;\n }\n}\n\nfunction appendAwaitHydrationData(\n sink: HtmlSink,\n awaitId: string,\n resolved: unknown,\n): void {\n const serialized = serializeAwaitHydrationValue(resolved, awaitId);\n\n if (serialized === undefined) {\n return;\n }\n\n const idLiteral = JSON.stringify(awaitId).replaceAll(\"<\", \"\\\\u003c\");\n sink.append(\n `<script data-mreact-await=${idLiteral}>(self.__mreactAwaitData||(self.__mreactAwaitData={}))[${idLiteral}]={value:${serialized}}</script>`,\n );\n}\n\n// Emits a single console.warn in dev when `resolved` contains shapes the\n// wire format will silently drop or coerce (Date / Map / Set / RegExp /\n// class instance / function / Symbol / nested non-POJO).\nfunction warnIfNonSerializableAwaitValue(value: unknown, awaitId: string): void {\n if (isProductionMode()) {\n return;\n }\n\n if (!containsNonSerializableSurface(value)) {\n return;\n }\n\n console.warn(\n `[mreact] <Await value={...}> for \"${awaitId}\" includes non-serializable ` +\n `data (Date / Map / Set / RegExp / class instance / function / Symbol). ` +\n `The wire format uses JSON.stringify, so the client-side renderer may ` +\n `receive a different shape after the JSON round-trip. Convert to plain ` +\n `JSON-serializable data before it reaches <Await>. See ` +\n `https://github.com/t-k/mreact#streaming-loading-and-await.`,\n );\n}\n\nfunction containsNonSerializableSurface(value: unknown): boolean {\n if (value === null || value === undefined || typeof value !== \"object\") {\n return false;\n }\n\n if (value instanceof Date || value instanceof Map || value instanceof Set || value instanceof RegExp) {\n return true;\n }\n\n if (Array.isArray(value)) {\n return value.some((entry) => containsNonSerializableSurface(entry));\n }\n\n const proto = Object.getPrototypeOf(value);\n\n if (proto !== Object.prototype && proto !== null) {\n return true;\n }\n\n for (const entry of Object.values(value as Record<string, unknown>)) {\n if (containsNonSerializableSurface(entry)) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction reportAwaitPayloadSize(json: string, awaitId: string): void {\n const byteLength =\n typeof TextEncoder !== \"undefined\" ? new TextEncoder().encode(json).length : json.length;\n\n if (byteLength > AWAIT_PAYLOAD_ERROR_BYTES) {\n console.error(\n `[mreact] <Await> payload for \"${awaitId}\" is ${(byteLength / 1024 / 1024).toFixed(2)}MB, ` +\n `exceeding the 1MB error threshold. Large payloads inflate HTML response size and ` +\n `client memory pressure. Stream the data via loader or split into smaller boundaries.`,\n );\n return;\n }\n\n if (byteLength > AWAIT_PAYLOAD_WARN_BYTES) {\n console.warn(\n `[mreact] large await payload for \"${awaitId}\": ${(byteLength / 1024).toFixed(1)}KB ` +\n `(over the 100KB warning threshold). Consider streaming the data via loader or ` +\n `splitting into smaller boundaries.`,\n );\n }\n}\n\nfunction serializeAwaitHydrationValue(value: unknown, awaitId: string): string | undefined {\n try {\n const json = JSON.stringify(value);\n if (json === undefined) {\n return undefined;\n }\n\n warnIfNonSerializableAwaitValue(value, awaitId);\n reportAwaitPayloadSize(json, awaitId);\n\n // Defuse `</script>` and runaway control characters so the data can be\n // safely embedded inside a `<script>` element.\n return json\n .replaceAll(\"<\", \"\\\\u003c\")\n .replaceAll(\"\\u2028\", \"\\\\u2028\")\n .replaceAll(\"\\u2029\", \"\\\\u2029\");\n } catch {\n return undefined;\n }\n}\n\nfunction inheritBackpressure(childSink: StringHtmlSink, parentSink: HtmlSink): StringHtmlSink {\n if (parentSink.backpressure === undefined) {\n return childSink;\n }\n\n return {\n ...childSink,\n backpressure: parentSink.backpressure,\n };\n}\n\nexport function renderOutOfOrderBoundary<T>(\n sink: HtmlSink,\n id: string,\n value: T,\n render: AsyncBoundaryRender<T>,\n options: OutOfOrderBoundaryOptions = {},\n): void {\n const boundaryId = nextOutOfOrderBoundaryInstanceId(sink, id);\n const placeholderSink = createStringSink();\n void options.placeholder?.(placeholderSink);\n const placeholderTag = normalizeOutOfOrderPlaceholderTag(options.placeholderTag);\n const hydrationStart =\n options.hydration === true ? `<!--mreact-h:start:${encodeURIComponent(boundaryId)}-->` : \"\";\n const hydrationEnd =\n options.hydration === true ? `<!--mreact-h:end:${encodeURIComponent(boundaryId)}-->` : \"\";\n sink.append(\n `${hydrationStart}<${placeholderTag} data-mreact-oob-placeholder=\"${escapeAttribute(boundaryId)}\">${placeholderSink.toString()}</${placeholderTag}>${hydrationEnd}`,\n );\n\n const task = renderOutOfOrderFragment(sink, boundaryId, value, render, options);\n\n if (sink.defer === undefined) {\n void task;\n return;\n }\n\n sink.defer(task);\n}\n\nfunction normalizeOutOfOrderPlaceholderTag(tag: unknown): string {\n if (typeof tag !== \"string\") {\n return \"span\";\n }\n\n const normalized = tag.trim().toLowerCase();\n return /^[a-z][a-z0-9-]*$/.test(normalized) ? normalized : \"span\";\n}\n\nasync function renderOutOfOrderFragment<T>(\n sink: HtmlSink,\n id: string,\n value: T,\n render: AsyncBoundaryRender<T>,\n options: OutOfOrderBoundaryOptions,\n): Promise<void> {\n const fragmentSink = inheritBackpressure(createStringSink(), sink);\n let resolvedValue: unknown;\n let hasResolvedValue = false;\n\n await renderAsyncBoundary(\n fragmentSink,\n value,\n async (childSink, resolved) => {\n resolvedValue = resolved;\n hasResolvedValue = true;\n await render(childSink, resolved);\n },\n options.catch === undefined ? {} : { catch: options.catch },\n );\n await fragmentSink.drain();\n\n sink.append(\n `<template data-mreact-oob-fragment=\"${escapeAttribute(id)}\">${fragmentSink.toString()}</template>`,\n );\n\n if (hasResolvedValue && options.hydrationAwaitId !== undefined) {\n appendAwaitHydrationData(sink, options.hydrationAwaitId, resolvedValue);\n }\n}\n\nexport function renderOutOfOrderReorderScript(\n sink: HtmlSink,\n options: OutOfOrderReorderScriptOptions = {},\n): void {\n const nonceAttribute =\n options.nonce === undefined ? \"\" : ` nonce=\"${escapeAttribute(options.nonce)}\"`;\n\n if (options.src !== undefined) {\n sink.append(\n `<script data-mreact-oob-reorder${nonceAttribute} src=\"${escapeAttribute(options.src)}\"></script>`,\n );\n return;\n }\n\n sink.append(\n `<script data-mreact-oob-reorder${nonceAttribute}>${outOfOrderReorderScript}</script>`,\n );\n}\n\nexport function renderReactSuspenseBoundary(\n sink: HtmlSink,\n render: (sink: HtmlSink) => void | PromiseLike<void>,\n): void | PromiseLike<void> {\n sink.append(\"<!--$-->\");\n const result = render(sink);\n\n if (isPromiseLike(result)) {\n return result.then(() => {\n sink.append(\"<!--/$-->\");\n });\n }\n\n sink.append(\"<!--/$-->\");\n}\n\nexport function renderReactSuspenseOutOfOrderBoundary<T>(\n sink: HtmlSink,\n boundaryId: string,\n segmentId: string,\n value: T,\n render: AsyncBoundaryRender<T>,\n options: ReactSuspenseBoundaryOptions = {},\n): void {\n const actualBoundaryId = nextOutOfOrderBoundaryInstanceId(sink, boundaryId);\n const suffix = actualBoundaryId.slice(boundaryId.length);\n const actualSegmentId = `${segmentId}${suffix}`;\n const fallbackSink = createStringSink();\n void options.fallback?.(fallbackSink);\n sink.append(\n `<!--$?--><template id=\"${escapeAttribute(actualBoundaryId)}\"></template>${fallbackSink.toString()}<!--/$-->`,\n );\n\n const task = renderReactSuspenseSegment(\n sink,\n actualBoundaryId,\n actualSegmentId,\n value,\n render,\n options,\n );\n\n if (sink.defer === undefined) {\n void task;\n return;\n }\n\n sink.defer(task);\n}\n\nfunction nextOutOfOrderBoundaryInstanceId(sink: HtmlSink, id: string): string {\n let instances = outOfOrderBoundaryInstances.get(sink);\n\n if (instances === undefined) {\n instances = new Map();\n outOfOrderBoundaryInstances.set(sink, instances);\n }\n\n const count = instances.get(id) ?? 0;\n instances.set(id, count + 1);\n\n return count === 0 ? id : `${id}-${count.toString(36)}`;\n}\n\nexport function renderReactSuspenseClientRenderBoundary(\n sink: HtmlSink,\n fallback: (sink: HtmlSink) => void | PromiseLike<void>,\n options: ReactSuspenseClientRenderOptions = {},\n): void | PromiseLike<void> {\n sink.append(`<!--$!--><template${renderReactSuspenseErrorAttributes(options)}></template>`);\n const result = fallback(sink);\n\n if (isPromiseLike(result)) {\n return result.then(() => {\n sink.append(\"<!--/$-->\");\n });\n }\n\n sink.append(\"<!--/$-->\");\n}\n\nasync function renderReactSuspenseSegment<T>(\n sink: HtmlSink,\n boundaryId: string,\n segmentId: string,\n value: T,\n render: AsyncBoundaryRender<T>,\n options: ReactSuspenseBoundaryOptions,\n): Promise<void> {\n const segmentSink = inheritBackpressure(createStringSink(), sink);\n\n await renderAsyncBoundary(\n segmentSink,\n value,\n render,\n options.catch === undefined ? {} : { catch: options.catch },\n );\n\n sink.append(\n `<div hidden id=\"${escapeAttribute(segmentId)}\">${segmentSink.toString()}</div>${renderReactSuspenseRevealScript(boundaryId, segmentId, options)}`,\n );\n}\n\nfunction renderReactSuspenseErrorAttributes(options: ReactSuspenseClientRenderOptions): string {\n const message =\n options.message === undefined ? \"\" : ` data-msg=\"${escapeAttribute(options.message)}\"`;\n const stack = options.stack === undefined ? \"\" : ` data-stck=\"${escapeAttribute(options.stack)}\"`;\n\n return `${message}${stack}`;\n}\n\nfunction renderReactSuspenseRevealScript(\n boundaryId: string,\n segmentId: string,\n options: ReactSuspenseScriptOptions = {},\n): string {\n if (options.src !== undefined) {\n return `<script data-mreact-react-suspense-reveal${renderNonceAttribute(options.nonce)} src=\"${escapeAttribute(options.src)}\" data-boundary-id=\"${escapeAttribute(boundaryId)}\" data-segment-id=\"${escapeAttribute(segmentId)}\"></script>`;\n }\n\n return `<script${renderNonceAttribute(options.nonce)}>${reactSuspenseRevealScriptBody};$RC(${serializeScriptJson(boundaryId)},${serializeScriptJson(segmentId)})</script>`;\n}\n\nexport function renderHydrationBoundary(\n sink: HtmlSink,\n id: string,\n render: (sink: HtmlSink) => void | PromiseLike<void>,\n): void | PromiseLike<void> {\n const markerId = encodeURIComponent(id);\n sink.append(`<!--mreact-h:start:${markerId}-->`);\n const result = render(sink);\n\n if (isPromiseLike(result)) {\n return result.then(() => {\n sink.append(`<!--mreact-h:end:${markerId}-->`);\n });\n }\n\n sink.append(`<!--mreact-h:end:${markerId}-->`);\n}\n\nfunction isPromiseLike(value: unknown): value is PromiseLike<void> {\n return isPromiseLikeUnknown(value);\n}\n\nconst outOfOrderReorderScript = `(()=>{function apply(root){const fragments=Array.from(root.querySelectorAll(\"template[data-mreact-oob-fragment]\"));for(const fragment of fragments){const id=fragment.getAttribute(\"data-mreact-oob-fragment\");if(id===null)continue;const placeholders=Array.from(root.querySelectorAll(\"[data-mreact-oob-placeholder]\"));const placeholder=placeholders.find((candidate)=>candidate.getAttribute(\"data-mreact-oob-placeholder\")===id);if(placeholder===undefined)continue;placeholder.replaceWith(fragment.content.cloneNode(true));fragment.remove();}}apply(document);new MutationObserver(()=>apply(document)).observe(document.documentElement,{childList:true,subtree:true});})();`;\n\nconst reactSuspenseRevealScriptBody = `(self.$RC=self.$RC||function(bid,sid){var b=document.getElementById(bid);var s=document.getElementById(sid);if(!b||!s)return;var p=b.parentNode;var e=b.nextSibling;var d=0;var r=[];for(var n=e;n;n=n.nextSibling){if(n.nodeType===8){if(n.data===\"$\"||n.data===\"$?\"||n.data===\"$!\")d++;else if(n.data===\"/$\"){if(d===0){e=n;break;}d--;}}r.push(n);}for(var i=0;r[i];i++)p.removeChild(r[i]);while(s.firstChild)p.insertBefore(s.firstChild,e);s.remove();b.data=\"$\";})`;\n\nexport const reactSuspenseRevealExternalScript = `(()=>{${reactSuspenseRevealScriptBody};var s=document.currentScript;if(!s)return;var b=s.getAttribute(\"data-boundary-id\");var seg=s.getAttribute(\"data-segment-id\");if(b!==null&&seg!==null)self.$RC(b,seg);})();`;\n"]}
@@ -0,0 +1,31 @@
1
+ import type { HtmlSink } from "@reckona/mreact-shared/compiler-contract";
2
+ import { type HydrationScriptOptions } from "./boundary.js";
3
+ import { type StreamRender } from "./sink.js";
4
+ export interface ScriptAssetOptions {
5
+ src: string;
6
+ nonce?: string;
7
+ integrity?: string;
8
+ crossOrigin?: "anonymous" | "use-credentials";
9
+ }
10
+ export interface EventHydrationEntry {
11
+ id: string;
12
+ event: string;
13
+ handler: string;
14
+ }
15
+ export interface EventHydrationManifest {
16
+ version: 1;
17
+ events: EventHydrationEntry[];
18
+ }
19
+ export interface HtmlResponseOptions {
20
+ headers?: HeadersInit;
21
+ status?: number;
22
+ statusText?: string;
23
+ }
24
+ export declare function serializeSsrState(value: unknown): string;
25
+ export declare function renderSsrState(sink: HtmlSink, value: unknown, options?: HydrationScriptOptions): void;
26
+ export declare function createEventHydrationManifest(events: readonly EventHydrationEntry[]): EventHydrationManifest;
27
+ export declare function renderEventHydrationManifest(sink: HtmlSink, manifest: EventHydrationManifest, options?: HydrationScriptOptions): void;
28
+ export declare function renderScriptAsset(sink: HtmlSink, options: ScriptAssetOptions): void;
29
+ export declare function html(node: unknown, options?: HtmlResponseOptions): Response;
30
+ export declare function renderToString(render: StreamRender): Promise<string>;
31
+ //# sourceMappingURL=html-helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html-helpers.d.ts","sourceRoot":"","sources":["../src/html-helpers.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0CAA0C,CAAC;AAEzE,OAAO,EAGL,KAAK,sBAAsB,EAC5B,MAAM,eAAe,CAAC;AAavB,OAAO,EAAoB,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AAGhE,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,WAAW,GAAG,iBAAiB,CAAC;CAC/C;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,CAAC,CAAC;IACX,MAAM,EAAE,mBAAmB,EAAE,CAAC;CAC/B;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAExD;AAED,wBAAgB,cAAc,CAC5B,IAAI,EAAE,QAAQ,EACd,KAAK,EAAE,OAAO,EACd,OAAO,GAAE,sBAA2B,GACnC,IAAI,CAIN;AAED,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,SAAS,mBAAmB,EAAE,GACrC,sBAAsB,CAKxB;AAED,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,QAAQ,EACd,QAAQ,EAAE,sBAAsB,EAChC,OAAO,GAAE,sBAA2B,GACnC,IAAI,CAIN;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,kBAAkB,GAAG,IAAI,CAWnF;AAED,wBAAgB,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,GAAE,mBAAwB,GAAG,QAAQ,CAuB/E;AAED,wBAAsB,cAAc,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAO1E"}
@@ -0,0 +1,259 @@
1
+ import { Fragment, Suspense as ReactCompatSuspense, isValidElement, } from "@reckona/mreact-compat";
2
+ import { escapeHtmlText as escapeHtml } from "@reckona/mreact-shared/html-escape";
3
+ import { renderReactSuspenseBoundary, renderReactSuspenseOutOfOrderBoundary, } from "./boundary.js";
4
+ import { isDangerousHtmlAttribute, isDangerousHtmlOptIn, isUnsafeMetaRefreshContent, isUnsafeUrlAttribute, } from "./url-safety.js";
5
+ import { escapeAttribute, isPromiseLikeUnknown, renderNonceAttribute, serializeScriptJson, } from "./html-internals.js";
6
+ import { createStringSink } from "./sink.js";
7
+ import { renderToReadableStream } from "./stream.js";
8
+ export function serializeSsrState(value) {
9
+ return serializeScriptJson(value);
10
+ }
11
+ export function renderSsrState(sink, value, options = {}) {
12
+ sink.append(`<script type="application/json" data-mreact-ssr-state${renderNonceAttribute(options.nonce)}>${serializeSsrState(value)}</script>`);
13
+ }
14
+ export function createEventHydrationManifest(events) {
15
+ return {
16
+ version: 1,
17
+ events: events.map((event) => ({ ...event })),
18
+ };
19
+ }
20
+ export function renderEventHydrationManifest(sink, manifest, options = {}) {
21
+ sink.append(`<script type="application/json" data-mreact-event-manifest${renderNonceAttribute(options.nonce)}>${serializeSsrState(manifest)}</script>`);
22
+ }
23
+ export function renderScriptAsset(sink, options) {
24
+ const integrityAttribute = options.integrity === undefined ? "" : ` integrity="${escapeAttribute(options.integrity)}"`;
25
+ const crossOrigin = options.integrity === undefined ? options.crossOrigin : (options.crossOrigin ?? "anonymous");
26
+ const crossOriginAttribute = crossOrigin === undefined ? "" : ` crossorigin="${escapeAttribute(crossOrigin)}"`;
27
+ sink.append(`<script src="${escapeAttribute(options.src)}"${renderNonceAttribute(options.nonce)}${integrityAttribute}${crossOriginAttribute}></script>`);
28
+ }
29
+ export function html(node, options = {}) {
30
+ const headers = new Headers(options.headers);
31
+ const responseOptions = { headers };
32
+ if (!headers.has("content-type")) {
33
+ headers.set("content-type", "text/html; charset=utf-8");
34
+ }
35
+ if (options.status !== undefined) {
36
+ responseOptions.status = options.status;
37
+ }
38
+ if (options.statusText !== undefined) {
39
+ responseOptions.statusText = options.statusText;
40
+ }
41
+ return new Response(renderToReadableStream((sink) => {
42
+ const state = { suspenseId: 0 };
43
+ return appendReactNode(sink, node, state);
44
+ }), responseOptions);
45
+ }
46
+ export async function renderToString(render) {
47
+ const sink = createStringSink();
48
+ await render(sink);
49
+ await sink.drain();
50
+ return sink.toString();
51
+ }
52
+ function appendReactNode(sink, node, state) {
53
+ if (isPromiseLikeNode(node)) {
54
+ return node.then((resolved) => appendReactNode(sink, resolved, state));
55
+ }
56
+ if (node === null || node === undefined || typeof node === "boolean") {
57
+ return;
58
+ }
59
+ if (typeof node === "string" || typeof node === "number") {
60
+ sink.append(escapeHtml(node));
61
+ return;
62
+ }
63
+ if (Array.isArray(node)) {
64
+ return appendReactNodeList(sink, node, state);
65
+ }
66
+ if (!isValidElement(node)) {
67
+ return;
68
+ }
69
+ return appendReactElement(sink, node, state);
70
+ }
71
+ function appendReactNodeList(sink, nodes, state) {
72
+ let chain;
73
+ for (const node of nodes) {
74
+ if (chain !== undefined) {
75
+ chain = chain.then(() => appendReactNode(sink, node, state));
76
+ continue;
77
+ }
78
+ const result = appendReactNode(sink, node, state);
79
+ if (isPromiseLike(result)) {
80
+ chain = result;
81
+ }
82
+ }
83
+ return chain;
84
+ }
85
+ function appendReactElement(sink, element, state) {
86
+ if (typeof element.type === "string") {
87
+ return appendHostElement(sink, element, state);
88
+ }
89
+ if (element.type === Fragment) {
90
+ return appendReactNode(sink, element.props.children, state);
91
+ }
92
+ if (element.type === ReactCompatSuspense) {
93
+ return appendSuspenseElement(sink, element, state);
94
+ }
95
+ if (isClassComponentType(element.type)) {
96
+ const instance = new element.type(element.props);
97
+ return appendReactNode(sink, instance.render(), state);
98
+ }
99
+ if (typeof element.type === "function") {
100
+ return appendReactNode(sink, element.type(element.props), state);
101
+ }
102
+ }
103
+ function isClassComponentType(value) {
104
+ return typeof value === "function" && value.prototype?.render !== undefined;
105
+ }
106
+ function appendHostElement(sink, element, state) {
107
+ const tagName = element.type;
108
+ const innerHtml = element.props
109
+ .dangerouslySetInnerHTML;
110
+ sink.append(`<${tagName}${renderHtmlAttributes(element.props)}>`);
111
+ if (innerHtml !== undefined) {
112
+ sink.append(String(innerHtml.__html ?? ""));
113
+ sink.append(`</${tagName}>`);
114
+ return;
115
+ }
116
+ const result = appendReactNode(sink, element.props.children, state);
117
+ if (isPromiseLike(result)) {
118
+ return result.then(() => {
119
+ sink.append(`</${tagName}>`);
120
+ });
121
+ }
122
+ sink.append(`</${tagName}>`);
123
+ }
124
+ function appendSuspenseElement(sink, element, state) {
125
+ const rendered = renderReactNodeToString(element.props.children, state);
126
+ if (!isPromiseLikeString(rendered)) {
127
+ renderReactSuspenseBoundary(sink, (boundarySink) => {
128
+ boundarySink.append(rendered);
129
+ });
130
+ return;
131
+ }
132
+ const id = state.suspenseId;
133
+ state.suspenseId += 1;
134
+ renderReactSuspenseOutOfOrderBoundary(sink, `B:${id}`, `S:${id}`, rendered, (boundarySink, renderedHtml) => {
135
+ boundarySink.append(renderedHtml);
136
+ }, {
137
+ fallback(boundarySink) {
138
+ const fallback = renderReactNodeToString(element.props.fallback, state);
139
+ if (isPromiseLikeString(fallback)) {
140
+ return fallback.then((html) => {
141
+ boundarySink.append(html);
142
+ });
143
+ }
144
+ boundarySink.append(fallback);
145
+ },
146
+ });
147
+ }
148
+ function renderReactNodeToString(node, state) {
149
+ const sink = createStringSink();
150
+ const result = appendReactNode(sink, node, state);
151
+ if (isPromiseLike(result)) {
152
+ return result.then(() => sink.toString());
153
+ }
154
+ return sink.toString();
155
+ }
156
+ function renderHtmlAttributes(props) {
157
+ // Issue 078: <meta http-equiv="refresh" content="0;url=javascript:...">
158
+ // is URL-bearing only when http-equiv is "refresh", so we need both
159
+ // attributes in scope to make the call. Strip the unsafe content
160
+ // before per-attribute rendering.
161
+ const sanitizedProps = sanitizeMetaRefreshProps(props);
162
+ return Object.entries(sanitizedProps)
163
+ .map(([name, value]) => renderHtmlAttribute(name, value))
164
+ .filter((attribute) => attribute !== "")
165
+ .join("");
166
+ }
167
+ function sanitizeMetaRefreshProps(props) {
168
+ const httpEquiv = props["http-equiv"] ?? props.httpEquiv;
169
+ const content = props["content"];
170
+ if (typeof httpEquiv !== "string" || typeof content !== "string")
171
+ return props;
172
+ if (!isUnsafeMetaRefreshContent(httpEquiv, content))
173
+ return props;
174
+ // Drop only `content`; keep http-equiv so the developer's intent is
175
+ // still visible in the rendered HTML (debugging hint).
176
+ const sanitized = { ...props };
177
+ delete sanitized["content"];
178
+ return sanitized;
179
+ }
180
+ // Mirrors `isAttributeNameSafe` in react-dom: an attribute name must start with
181
+ // an ASCII letter (or underscore) and contain only word chars, dot, hyphen, or
182
+ // colon. Anything else is dropped to prevent SSR XSS via spread props
183
+ // (`<div {...userControlled} />`). See docs/issues/resolved Issue 060.
184
+ const VALID_ATTRIBUTE_NAME = /^[A-Za-z_][\w.\-:]*$/;
185
+ // URL scheme allow/block list is shared with the compiler emit paths
186
+ // (Issues 062 / 073). See packages/server/src/url-safety.ts.
187
+ function renderHtmlAttribute(name, value) {
188
+ if (name === "children" ||
189
+ name === "dangerouslySetInnerHTML" ||
190
+ name === "key" ||
191
+ name === "ref" ||
192
+ value === false ||
193
+ value === null ||
194
+ value === undefined ||
195
+ typeof value === "function") {
196
+ return "";
197
+ }
198
+ const attributeName = toHtmlAttributeName(name);
199
+ if (!VALID_ATTRIBUTE_NAME.test(attributeName)) {
200
+ return "";
201
+ }
202
+ // Issue 077: HTML-bearing attributes (`srcdoc`) require the explicit
203
+ // `{ __html: "..." }` opt-in. A plain string value -- even if escaped
204
+ // for the attribute syntax -- decodes back to executable HTML inside
205
+ // the iframe document with the parent's origin.
206
+ if (isDangerousHtmlAttribute(attributeName)) {
207
+ if (!isDangerousHtmlOptIn(value)) {
208
+ return "";
209
+ }
210
+ return ` ${attributeName}="${escapeAttribute(value.__html)}"`;
211
+ }
212
+ if (value === true) {
213
+ return ` ${attributeName}`;
214
+ }
215
+ const stringValue = String(value);
216
+ if (isUnsafeUrlAttribute(attributeName, stringValue)) {
217
+ return "";
218
+ }
219
+ return ` ${attributeName}="${escapeAttribute(stringValue)}"`;
220
+ }
221
+ function toHtmlAttributeName(name) {
222
+ return HTML_ATTRIBUTE_ALIASES[name] ?? name;
223
+ }
224
+ const HTML_ATTRIBUTE_ALIASES = {
225
+ acceptCharset: "accept-charset",
226
+ autoFocus: "autofocus",
227
+ autoPlay: "autoplay",
228
+ charSet: "charset",
229
+ className: "class",
230
+ colSpan: "colspan",
231
+ contentEditable: "contenteditable",
232
+ crossOrigin: "crossorigin",
233
+ encType: "enctype",
234
+ formAction: "formaction",
235
+ frameBorder: "frameborder",
236
+ htmlFor: "for",
237
+ httpEquiv: "http-equiv",
238
+ maxLength: "maxlength",
239
+ minLength: "minlength",
240
+ noValidate: "novalidate",
241
+ playsInline: "playsinline",
242
+ readOnly: "readonly",
243
+ rowSpan: "rowspan",
244
+ spellCheck: "spellcheck",
245
+ srcDoc: "srcdoc",
246
+ srcSet: "srcset",
247
+ tabIndex: "tabindex",
248
+ useMap: "usemap",
249
+ };
250
+ function isPromiseLikeNode(value) {
251
+ return isPromiseLikeUnknown(value);
252
+ }
253
+ function isPromiseLikeString(value) {
254
+ return isPromiseLikeUnknown(value);
255
+ }
256
+ function isPromiseLike(value) {
257
+ return isPromiseLikeUnknown(value);
258
+ }
259
+ //# sourceMappingURL=html-helpers.js.map