/** * Async / streaming render entry points. * * Builds on top of the synchronous `renderToString()` and adds: * - `renderToStringAsync()` — awaits Promise/`defer()` values in the context. * - `renderToStream()` — emits the HTML as a Web `ReadableStream`. * - `renderToResponse()` — wraps the stream in a `Response` with sensible * defaults (`Content-Type`, `Cache-Control`, ETag, head injection, store * state injection). * * All three run on Bun, Deno and Node ≥ 24 without external dependencies. * * @module bquery/ssr */ import type { BindingContext } from '../view/types'; import { resolveContext } from './async'; import { createSSRContext, type SSRContext } from './context'; import { renderToString } from './render'; import { serializeStoreState } from './serialize'; import type { RenderOptions, SSRResult } from './types'; const escapeAttr = (value: string): string => value.replace(/&/g, '&').replace(/"/g, '"').replace(//g, '>'); /** * HTML ASCII whitespace used between tag names and attributes. Includes form * feed (`\f`) because the HTML tokenizer treats it as whitespace alongside * spaces, tabs, CR and LF. */ const isHtmlWhitespace = (ch: string | undefined): boolean => ch === ' ' || ch === '\n' || ch === '\t' || ch === '\r' || ch === '\f'; const injectScriptNonce = (scriptTag: string, nonce: string): string => { const scriptPrefix = '' && !isHtmlWhitespace(next)) { return scriptTag; } return `