/** * Deferred loader data (`defer()`). A loader may mark slow values as deferred: the critical data * renders in the shell, the deferred data streams in behind a `` (the adapter's `` * consumes it) and resolves on the client **without a re-fetch**. This module owns the agnostic * protocol - the marker, the resolved/deferred split, and the tiny client registry that streamed * resolution scripts settle. The per-adapter `` lives in `@nifrajs/web-{solid,react}/client`. */ import { type TransportCodec } from "@nifrajs/core/transport-codec"; /** * A loader value marked to stream in after the shell. The component consumes it with the adapter's * ``; until the promise settles the shell shows the `` fallback. * `id` is assigned by the server at serialization time - the streamed resolve script keys off it. */ export interface Deferred { readonly __nifra_deferred: true; readonly id: number; readonly promise: Promise; } /** * Mark a loader value as deferred - it streams in after the shell instead of blocking it. Works * **anywhere** in the loader's returned data - a top-level key, or nested in objects/arrays: * * return { user: await api.user.get(), feed: defer(api.user.feed()), * panels: [{ chart: defer(api.metrics()) }] } * * `LoaderData` surfaces each deferred value as `Deferred<…>` (not awaited), so the * component knows to `` it. The `id` is a placeholder until the server assigns the real one. */ export declare function defer(promise: Promise): Deferred; /** * Split a loader (or action) result into the **component-facing** data (deferred values carry their * assigned `id` + promise, for ``) and the **client-serializable** data (deferred values * replaced with a `{__nifra_deferred: id}` placeholder - promises don't serialize). Walks the tree * **recursively** - `defer()` works at any depth, inside nested objects and arrays - assigning ids in * walk order. `idOffset` continues the id space when a page splits two results (loader data **and** * action data) into one shared client registry, so their ids don't collide. `deferred` lists the * promises for callers that await them. Recurses plain objects + arrays only; the data is expected to * be plain JSON-serializable values. */ export declare function prepareDeferred(data: unknown, idOffset?: number): { readonly forComponent: unknown; readonly forClient: unknown; readonly deferred: ReadonlyArray<{ readonly id: number; readonly promise: Promise; }>; }; /** * Stable, non-leaking payload streamed to the client when a deferred value rejects - never the raw * error text. The real reason is logged server-side; ``'s `errorFallback` receives * this code. (A future typed `DeferredError` could opt into a public, intentional message.) Shared by * the NDJSON soft-nav transport and the full-document SSR path (`renderPage`'s `streamDocument`). */ export declare const DEFERRED_ERROR_CODE = "deferred_error"; /** * Stream a loader result as NDJSON for a client (soft) navigation: line 1 is the critical data with * `{__nifra_deferred: id}` placeholders (`forClient`), then one line per deferred as its promise * settles - `{"i": id, "v": value}` on resolve, `{"i": id, "e": "deferred_error"}` on reject (a * rejection is data, not a stream error; the opaque code never leaks the raw reason). Closes when all * settle. The client (`defaultFetchData`) returns the * data after line 1 and settles ``'s markers as the resolution lines arrive. `JSON.stringify` * escapes newlines in values, so each line is NDJSON-safe; no HTML escaping (this is a fetch body, * parsed with `JSON.parse`, never injected into markup). */ export declare function ndjsonStream(forClient: unknown, deferred: ReadonlyArray<{ readonly id: number; readonly promise: Promise; }>, codec?: TransportCodec): ReadableStream; /** * Inline client runtime injected into the shell `` when a page has deferred data: a per-id * promise registry that streamed `__nifraResolve(id, value)` / `__nifraReject(id, err)` scripts settle * (created lazily, so a resolve script that arrives before the client maps the placeholder still * works), plus `__nifraDeferred(id)` to read the promise. A plain inline script (not the deferred * module entry), so it runs before any streamed resolve script. */ export declare const DEFERRED_RUNTIME = "(() => {\n const reg = new Map();\n const get = (id) => {\n let e = reg.get(id);\n if (!e) {\n let r, j;\n const p = new Promise((a, b) => { r = a; j = b; });\n // reads the tagged status/reason synchronously (it never .then()s a rejected promise),\n // so attach a no-op catch to keep a rejection from surfacing as an unhandled rejection.\n p.catch(() => {});\n e = { p, r, j };\n reg.set(id, e);\n }\n return e;\n };\n window.__nifraDeferred = (id) => get(id).p;\n // Tag the promise (status/value/reason) - React's use() reads them and returns SYNCHRONOUSLY at\n // hydration, so it renders the resolved content directly into the boundary the server\n // streamed (no re-suspend, no fallback flash). r/j also wake awaiters (Solid uses its own _$HY).\n window.__nifraResolve = (id, v) => { const e = get(id); e.p.status = \"fulfilled\"; e.p.value = v; e.r(v); };\n window.__nifraReject = (id, x) => { const e = get(id); e.p.status = \"rejected\"; e.p.reason = x; e.j(x); };\n})();"; /** * Source emitted into the generated client entry: maps serialized `{__nifra_deferred: id}` placeholders * (at any depth - nested objects/arrays) to the registry's promises, so the component receives real * promises to ``. A no-op for data without placeholders (so non-deferred pages are unchanged). */ export declare const MAP_DEFERRED_SOURCE = "const mapDeferred = (d) => {\n if (!d || typeof d !== \"object\") return d\n if (typeof d.__nifra_deferred === \"number\") return { __nifra_deferred: true, id: d.__nifra_deferred, promise: window.__nifraDeferred(d.__nifra_deferred) }\n if (Array.isArray(d)) return d.map(mapDeferred)\n const out = {}\n for (const k of Object.keys(d)) Object.defineProperty(out, k, { value: mapDeferred(d[k]), enumerable: true, writable: true, configurable: true })\n return out\n}"; /** * Client side of {@link ndjsonStream} (used by the router's `defaultFetchData` on a soft nav): read * the NDJSON body into a data object whose deferred markers settle as resolution lines arrive. * Returns after **line 1** so the router can apply the critical data + render immediately; the * markers' promises settle/reject in the background. If the stream ends (or `signal` aborts) with * markers unsettled, they reject - so `` never hangs. */ export declare function parseNdjsonData(stream: ReadableStream, signal?: AbortSignal, codec?: TransportCodec): Promise; //# sourceMappingURL=deferred.d.ts.map