/** * `@sparkletree/core/server` — server-side reads for pages rendered on the * server. * * Text a visitor can see is never replaced, so live copy is decided before a * slot is shown. A page rendered on the server can ask, at request time, for * the live copy already cached for this visitor's context and render it as * final text. When there is none, the slot keeps its reservation and the * browser's stream and hold take over as usual. * * Server only. Nothing here belongs in a browser bundle, and calling it in a * browser returns empty results without a request. */ import { type InitialCopy, type ServerContext } from "./visitorContext.js"; import { type DeclaredContext } from "./context.js"; import type { CopyField } from "./state.js"; export type { InitialCopy, ServerContext } from "./visitorContext.js"; /** The islands a server read can return copy for. */ export type ServerIsland = "hero" | "cta"; /** * Request headers, as a `Headers` object (App Router `headers()`, a Fetch * `Request`) or a plain record (Pages Router `req.headers`). Only a short * allow-list is ever read: see `deriveServerContext`. */ export type HeadersLike = Headers | { get(name: string): string | null; } | Record; /** Default total budget for a server read, in milliseconds. */ export declare const SERVER_COPY_BUDGET_MS = 250; /** The largest budget a server read accepts, in milliseconds. */ export declare const SERVER_COPY_MAX_BUDGET_MS = 1000; /** An island's fallback copy, as its `fallback` prop gives it. */ export type IslandFallback = Partial>; export interface DeriveServerContextOptions { /** * The time zone to use when the request carries none (no * `x-vercel-ip-timezone` or `cf-timezone` header), e.g. your audience's * main zone. Without it, the time of day is read in UTC and no time zone * is sent. */ fallbackTimezone?: string; /** Test seam: the current time. */ now?: () => Date; } /** * The visitor context for one request, from its headers only. * * Reads exactly these headers and nothing else: * * - `Accept-Language`: the language; * - `Sec-CH-UA-Mobile`, then `User-Agent`: the device bucket (`mobile` for * phones, `desktop` for everything else, tablets included: the browser * buckets by viewport width, and tablets are 768 CSS pixels or wider); * - `x-vercel-ip-timezone`, then `cf-timezone`: the time zone, from which * the time of day is bucketed exactly as the browser does. * * The visitor's IP, cookies, full user agent, referrer and location are * never read or forwarded. Never throws. */ export declare function deriveServerContext(headers: HeadersLike | null | undefined, options?: DeriveServerContextOptions): ServerContext; export interface GetIslandCopyOptions { /** Your publishable key: live (`st_pk_live_`) in production, test (`st_pk_test_`) in development. */ publishableKey: string; /** Defaults to the organization in the key. */ organizationId?: string; /** Defaults to the API base in the key. */ apiBase?: string; /** The campaign the islands show. Give this or `surfaceId`. */ campaignId?: string; /** The surface the islands show, when they are surface-addressed. */ surfaceId?: string; /** The islands to read copy for. One read decides one variant for all of them. */ islands: readonly ServerIsland[]; /** * Each island's fallback: the same `fallback` the island gets. The * fallback sets the size of each field's slot, so its length is sent as * the field's length budget, exactly as the island's stream sends it, and * the read finds the copy the stream cached. A field the fallback does * not have is never shown, so it is left out of the result. */ fallbacks?: Partial>; /** A variant to read for, when the page already pinned one. */ variantId?: string; /** * The page's declared placement: the same values you pass in the * provider's `context`, so the server reads the copy the browser's stream * would replay. */ declared?: DeclaredContext; /** The incoming request's headers. Only an allow-list is read. */ headers?: HeadersLike | null; /** * Total time to wait, in milliseconds, network included. Defaults to * `SERVER_COPY_BUDGET_MS` (250) and is capped at * `SERVER_COPY_MAX_BUDGET_MS` (1000). When it runs out the read is * aborted and every island is a miss. */ budgetMs?: number; /** See `DeriveServerContextOptions.fallbackTimezone`. */ fallbackTimezone?: string; /** Test seam; defaults to the platform `fetch`. */ fetchImpl?: typeof fetch; /** Test seam: the current time. */ now?: () => Date; } /** Why a read returned no copy. Informational; for logs and metrics. */ export type IslandCopyMissReason = "miss" | "conditioned" | "deadline" | "unresolved" | "variant_not_found" | "cache_disabled" | "suppressed" | "unsupported_island" | "too_large" | "live_key_required" | "identity_mismatch" | "browser" | "config" | "error" | (string & {}); export interface IslandCopyResult { /** * Per requested island: copy ready for its `initialCopy` option, or `null` * when there is none (render the island with its fallback; it holds and * the stream fills it). */ islands: Partial>; /** The campaign the read resolved, when it got that far. */ campaignId: string | null; /** The variant the read decided, when it got that far. Pinned in `context`. */ variantId: string | null; /** * Pass to `SparkletreeProvider`'s `serverContext`: the visitor context * this read used, and the variant pin, so the browser's streams and cache * fills use the same key the server read. */ context: ServerContext; /** Why some or all islands are `null`, when the read says. */ reasons: Partial>; /** How long the read took, in milliseconds. */ elapsedMs: number; } /** * Read the cached live copy for this request's visitor, to render as final * text on the server. * * Strictly a read: nothing is generated, and no impression is recorded (the * browser records it when the island is seen). One call decides one variant * for every island it names; call it once per request (for example through * React's `cache`) and share the result between the islands. * * Never throws and never waits past `budgetMs`. A miss, an error, a timeout, * a refused key or a call in a browser all return `null` islands, with the * visitor context still derived. */ export declare function getIslandCopy(options: GetIslandCopyOptions): Promise; //# sourceMappingURL=server.d.ts.map