@bettercms-ai/sdk 1.35.0 → 1.37.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/README.md +27 -1
- package/dist/index.cjs +27 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +51 -6
- package/dist/index.d.ts +51 -6
- package/dist/index.js +25 -6
- package/dist/index.js.map +1 -1
- package/dist/read.cjs +1 -1
- package/dist/read.cjs.map +1 -1
- package/dist/read.js +1 -1
- package/dist/read.js.map +1 -1
- package/dist/sections.cjs +1 -1
- package/dist/sections.cjs.map +1 -1
- package/dist/sections.js +1 -1
- package/dist/sections.js.map +1 -1
- package/package.json +2 -2
package/dist/sections.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/sections.ts","../src/errors.ts","../src/retry.ts","../src/read-client.ts","../src/config.ts","../src/draft.ts"],"sourcesContent":["/// <reference lib=\"dom\" />\n/**\n * A published page's sections as server-rendered HTML, for a front end that renders its own\n * document (SvelteKit, Nuxt, any server): the hosted renderer's markup, its scoped stylesheet and\n * the runtime its forms, sliders and tabs need (`GET /api/v1/sites/fragment/:projectId/:slug`).\n * One renderer for every site, so the markup is the hosted site's and the editor's.\n */\nimport { BetterCMSError, ErrorCodes } from \"./errors.js\";\nimport { createClient, type BetterCMSReadClient } from \"./read-client.js\";\nimport { resolveBcmsConfig, type BcmsSiteConfig, type BcmsSiteOptions } from \"./config.js\";\nimport { draftDisableResponse, draftEnableResponse, draftTokenFrom } from \"./draft.js\";\n\nexport interface SectionsFragment {\n html: string;\n css: string;\n scripts: string;\n}\n\n/** Fetch a page's sections (`home` is `/`). Null when the page is not published. */\nexport async function fetchSections(\n opts: { apiUrl: string; projectId: string; slug: string; fetch?: typeof fetch },\n): Promise<SectionsFragment | null> {\n const doFetch = opts.fetch ?? fetch;\n const url = `${opts.apiUrl.replace(/\\/+$/, \"\")}/api/v1/sites/fragment/${encodeURIComponent(opts.projectId)}/${encodeURIComponent(opts.slug || \"home\")}`;\n const res = await doFetch(url, { headers: { accept: \"application/json\" } });\n if (res.status === 404) return null;\n if (!res.ok) throw new BetterCMSError(`Sections for \"${opts.slug}\" could not be read (${res.status})`, res.status, res.status === 403 ? ErrorCodes.FORBIDDEN : ErrorCodes.INTERNAL_ERROR);\n return ((await res.json()) as { data: SectionsFragment }).data;\n}\n\n/** The fragment as one string for `{@html}` / `v-html`: its stylesheet, its markup, its scripts. */\nexport function sectionsMarkup(fragment: SectionsFragment | null | undefined): string {\n if (!fragment) return \"\";\n return `<style>${fragment.css}</style>${fragment.html}${fragment.scripts}`;\n}\n\n/**\n * Run the scripts inside `root`. A server-rendered first load runs them itself; a client-side\n * navigation inserts the markup with innerHTML, where a <script> never runs, so the forms,\n * sliders and tabs of the new page would be dead. Each script runs once per element.\n */\nexport function runSectionScripts(root: Element): void {\n for (const old of Array.from(root.querySelectorAll(\"script\"))) {\n if (old.hasAttribute(\"data-bcms-ran\")) continue;\n const fresh = document.createElement(\"script\");\n for (const attr of Array.from(old.attributes)) fresh.setAttribute(attr.name, attr.value);\n fresh.setAttribute(\"data-bcms-ran\", \"\");\n fresh.textContent = old.textContent;\n old.replaceWith(fresh);\n }\n}\n\n\nexport interface BcmsSite {\n config: BcmsSiteConfig;\n /** The read client for a request: drafts while its draft cookie holds a valid token. */\n client(request?: Request): Promise<BetterCMSReadClient>;\n /** A published page's sections (`home` is `/`); pass the framework's `fetch` on the server. */\n sections(slug: string, fetchImpl?: typeof fetch): Promise<SectionsFragment | null>;\n /** GET handler for the draft path the editor opens (`/api/draft`). */\n draftEnable(request: Request): Promise<Response>;\n /** GET handler that leaves draft mode. */\n draftDisable(request: Request): Response;\n}\n\n/**\n * Everything a server-rendered site needs from BetterCMS, built once from its settings: the\n * precedence every adapter shares (options, `<PREFIX>BCMS_*`, `BCMS_*`), the read client per\n * request, the sections fragment and the draft routes. The SvelteKit and Nuxt adapters are this\n * with their env prefix.\n */\nexport function createBcmsSite(\n options: BcmsSiteOptions & { env?: Record<string, string | undefined>; draftSecret?: string; prefixes: string[]; adapter: string },\n): BcmsSite {\n const env = options.env ?? {};\n const { config, missing } = resolveBcmsConfig({ options, env, prefixes: [...options.prefixes, \"\"] });\n if (missing.length > 0) {\n const names = missing.map((m) => `${options.prefixes[0]}BCMS_${m === \"apiUrl\" ? \"API_URL\" : \"WORKSPACE\"}`).join(\" and \");\n throw new Error(`[${options.adapter}] Missing ${names}: set them, or pass them to createBcms().`);\n }\n const secret = options.draftSecret ?? env.BCMS_DRAFT_SECRET;\n return {\n config,\n async client(request) {\n const token = request ? await draftTokenFrom(request.headers.get(\"cookie\"), secret) : null;\n return createClient({\n apiUrl: config.apiUrl,\n workspace: config.workspace,\n projectId: config.projectId,\n ...(token ? { perspective: \"drafts\" as const, previewToken: token } : {}),\n });\n },\n sections(slug, fetchImpl) {\n if (!config.projectId) throw new Error(`[${options.adapter}] Sections need the project: set ${options.prefixes[0]}BCMS_PROJECT_ID.`);\n return fetchSections({ apiUrl: config.apiUrl, projectId: config.projectId, slug, fetch: fetchImpl });\n },\n draftEnable: (request) => draftEnableResponse(request, { apiUrl: config.apiUrl, secret }),\n draftDisable: draftDisableResponse,\n };\n}\n","import type { BetterCMSErrorCode } from \"@bettercms-ai/types\";\n\n/** Semantic error codes used by the SDK. */\nexport const ErrorCodes = {\n CONTENT_NOT_FOUND: \"CONTENT_NOT_FOUND\",\n UNAUTHORIZED: \"UNAUTHORIZED\",\n FORBIDDEN: \"FORBIDDEN\",\n RATE_LIMITED: \"RATE_LIMITED\",\n INTERNAL_ERROR: \"INTERNAL_ERROR\",\n VALIDATION_ERROR: \"VALIDATION_ERROR\",\n NETWORK_ERROR: \"NETWORK_ERROR\",\n ADMIN_NOT_IMPLEMENTED: \"ADMIN_NOT_IMPLEMENTED\",\n} as const;\n\nexport type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];\n\n/** Maps HTTP status codes to semantic error codes. */\nfunction statusToCode(status: number): BetterCMSErrorCode {\n if (status === 404) return ErrorCodes.CONTENT_NOT_FOUND;\n if (status === 401) return ErrorCodes.UNAUTHORIZED;\n if (status === 403) return ErrorCodes.FORBIDDEN;\n if (status === 429) return ErrorCodes.RATE_LIMITED;\n if (status >= 500) return ErrorCodes.INTERNAL_ERROR;\n if (status === 422) return ErrorCodes.VALIDATION_ERROR;\n return ErrorCodes.INTERNAL_ERROR;\n}\n\n/**\n * BetterCMSError — normalized SDK error with a semantic code.\n */\nexport class BetterCMSError extends Error {\n readonly status: number;\n readonly code: BetterCMSErrorCode;\n /**\n * Raw machine-readable `code` from the response body (e.g. \"PROJECT_DELETED\"),\n * when the API supplies one. Distinct from `code` (which is derived from the\n * HTTP status), this lets callers branch on a specific server condition that\n * shares a status with others (e.g. 409 slug-conflict vs 409 project-deleted).\n */\n readonly bodyCode?: string;\n /**\n * Per-field validation messages from a 400 `{ errors: { field: message } }` body.\n * The backend returns validation failures in this shape (not `message`/`error`), so\n * without this the SDK collapsed every validation 400 to a bare \"Bad Request\" with no\n * hint which field or rule failed (FLO-474). Mirrors the forms client's `fieldErrors`.\n */\n readonly fieldErrors?: Record<string, string>;\n /**\n * The parsed response body, when there was one.\n *\n * Several refusals carry STRUCTURED detail that neither `message` nor `fieldErrors` can hold,\n * and which is the whole point of the refusal: a component publish 409s with the full\n * `readiness` tuple (what evidence is missing, whether a human still has to approve it) and\n * 422s with the `issues` naming the Layouts it would break. Collapsing those to a sentence\n * leaves a caller able to report that something failed and not what to do about it.\n */\n readonly body?: unknown;\n\n constructor(\n message: string,\n status: number,\n code: BetterCMSErrorCode,\n bodyCode?: string,\n fieldErrors?: Record<string, string>,\n body?: unknown,\n ) {\n super(message);\n this.name = \"BetterCMSError\";\n this.status = status;\n this.code = code;\n this.bodyCode = bodyCode;\n this.fieldErrors = fieldErrors;\n this.body = body;\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, BetterCMSError);\n }\n }\n\n toJSON() {\n return {\n name: this.name,\n message: this.message,\n status: this.status,\n code: this.code,\n bodyCode: this.bodyCode,\n fieldErrors: this.fieldErrors,\n };\n }\n\n /**\n * Factory — creates a BetterCMSError from a failed fetch Response. Reads the body's\n * `message` (or `error`) for the human message and `code` for a machine-readable\n * condition surfaced as `bodyCode`. Validation failures arrive as `{ errors: {...} }`\n * (no `message`/`error`), so fall back to the first field error for the message and\n * expose the full map as `fieldErrors` (FLO-474).\n */\n static async from(res: Response): Promise<BetterCMSError> {\n let message = res.statusText || \"An error occurred\";\n let bodyCode: string | undefined;\n let fieldErrors: Record<string, string> | undefined;\n let code = statusToCode(res.status);\n let raw: unknown;\n try {\n const body = (await res.json()) as {\n message?: string;\n error?: string;\n code?: string;\n errors?: Record<string, string>;\n };\n if (body?.errors && typeof body.errors === \"object\") {\n fieldErrors = body.errors;\n code = ErrorCodes.VALIDATION_ERROR;\n }\n if (body?.message) message = body.message;\n else if (body?.error) message = body.error;\n else if (fieldErrors) {\n const [field, detail] = Object.entries(fieldErrors).find(\n ([, v]) => typeof v === \"string\" && v.length > 0,\n ) ?? [];\n if (field) message = `${field}: ${detail}`;\n }\n if (body?.code) bodyCode = body.code;\n raw = body;\n } catch {\n // ignore parse errors\n }\n return new BetterCMSError(message, res.status, code, bodyCode, fieldErrors, raw);\n }\n}\n","/**\n * Shared 429/503 backoff for every SDK client.\n *\n * ── Why this exists ─────────────────────────────────────────────────────────\n * The backend limiter (src/lib/ratelimit.ts, windowHit) is a FIXED 60-SECOND\n * WINDOW: 100 requests per workspace per endpoint, and a denial answers\n * `Retry-After: <seconds until the window resets>` — anything up to 60.\n *\n * Every client here used to climb a private ladder that topped out at 7 seconds\n * ([250,750,1500] in read-client, [1000,2000,4000] in admin/management) and none\n * of them read Retry-After. So all three retries landed INSIDE the same exhausted\n * window and a 429 was fatal roughly whenever it was thrown. The retry code looked\n * like resilience but could only ever recover from a denial raised in the final\n * seconds of a window.\n *\n * That is what failed customer builds: `action/fetch-content.mjs` spends ~65 of the\n * 100 `delivery-content-entries-list` slots pulling a large workspace, `npm run build`\n * starts seconds later in the SAME window, and the first per-route delivery read past\n * the ceiling killed the deploy with \"Too many requests. Please retry later.\"\n *\n * ── The rule ────────────────────────────────────────────────────────────────\n * Obey Retry-After when the server sends one, and let a single request outlast a\n * whole window. A build waiting 60s is slow; a build that dies is a broken product.\n */\n\n/** Statuses worth retrying. 429 = rate limited, 503 = limiter/backend unavailable. */\nexport const RETRY_STATUSES = [429, 503] as const;\n\n/** Retries after the first attempt. 3 retries × up to ~65s spans three full windows. */\nexport const MAX_RETRIES = 3;\n\n/**\n * Hard ceiling on a single wait, jitter included. Must exceed the limiter's 60s\n * window, or a denial raised at the very start of a window still cannot be waited\n * out — and must be finite, so a hostile or mis-set Retry-After cannot hang a build.\n */\nexport const MAX_DELAY_MS = 65_000;\n\n/** Fallback ladder when the server sends no Retry-After: 1s, 2s, 4s, 8s. */\nconst BASE_DELAY_MS = 1_000;\n\n/**\n * De-sync jitter, added to every wait.\n *\n * Deliberately a small FLAT amount, not a percentage. Requests denied by one window\n * only need to stop firing in the same millisecond — the server's counter serialises\n * them after that. A proportional jitter would add up to 15 seconds to a 60-second\n * wait for no benefit, and would push the total past MAX_DELAY_MS.\n */\nconst JITTER_MS = 250;\n\nexport function isRetryableStatus(status: number): boolean {\n return (RETRY_STATUSES as readonly number[]).includes(status);\n}\n\n/**\n * Parse `Retry-After`, which RFC 9110 allows in either form: delay-seconds\n * (\"47\") or an HTTP-date (\"Wed, 21 Oct 2015 07:28:00 GMT\"). Returns ms to wait,\n * or null when the header is absent or unparseable.\n */\nexport function retryAfterMs(res: Pick<Response, \"headers\">): number | null {\n const raw = res.headers?.get?.(\"retry-after\");\n if (!raw) return null;\n\n const seconds = Number(raw);\n if (Number.isFinite(seconds)) {\n return Math.min(Math.max(seconds, 0) * 1000, MAX_DELAY_MS);\n }\n\n const at = Date.parse(raw);\n if (!Number.isNaN(at)) {\n return Math.min(Math.max(0, at - Date.now()), MAX_DELAY_MS);\n }\n return null;\n}\n\n/**\n * How long to wait before retry `attempt` (0-based).\n *\n * Server instruction wins; otherwise exponential. Jitter is added in both cases:\n * when a build's requests are all denied by the same window they would otherwise\n * wake in lockstep at the reset and re-exhaust it immediately (thundering herd).\n */\nexport function backoffMs(attempt: number, res?: Pick<Response, \"headers\">): number {\n const fromServer = res ? retryAfterMs(res) : null;\n // Base is capped with the jitter's headroom already reserved, so the total never\n // exceeds MAX_DELAY_MS AND the jitter is never clamped away at the ceiling — which\n // would put every capped caller back in lockstep, the exact thing jitter prevents.\n const base = Math.min(fromServer ?? BASE_DELAY_MS * 2 ** attempt, MAX_DELAY_MS - JITTER_MS);\n // Jitter is ADDED, never subtracted: waking before the server's Retry-After elapses\n // just burns a retry on a window that has not reset.\n return base + Math.random() * JITTER_MS;\n}\n\nexport const sleep = (ms: number): Promise<void> =>\n new Promise((resolve) => setTimeout(resolve, ms));\n","/**\n * createClient — framework-agnostic typed read client for the Delivery API.\n *\n * Shared by @bettercms-ai/astro and @bettercms-ai/next so the fetch/typing/retry\n * logic lives in one place. Read-only; for schema authoring use the\n * Management client, for sessions use the Admin client.\n *\n * const client = createClient({ apiUrl, workspace, apiKey });\n * await client.getPage(\"home\");\n * await client.listEntries({ model: \"post\", perPage: 50 });\n */\nimport type {\n DeliveryComponent,\n DeliveryEntry,\n DeliveryList,\n DeliveryPage,\n DeliveredLayout,\n Perspective,\n} from \"@bettercms-ai/types\";\nimport { BetterCMSError } from \"./errors.js\";\nimport { MAX_RETRIES, backoffMs, isRetryableStatus, sleep } from \"./retry.js\";\nimport type { DeliveryForm } from \"./forms.js\";\n\nconst DEFAULT_TIMEOUT = 10_000;\n\nexport interface CreateClientOptions {\n /** Backend base, e.g. `https://api.bettercms.ai` (no trailing slash needed). */\n apiUrl: string;\n /** Workspace slug. */\n workspace: string;\n /** Delivery API key. Use a `content:read:draft` key for drafts. */\n apiKey?: string;\n /**\n * The project every read is addressed to. A workspace holds many projects whose slugs\n * collide (each has a `blog` model and a `home` page), so a site names its own; published\n * reads need no key for it. BetterCMS hosting supplies `PUBLIC_BCMS_PROJECT_ID`.\n */\n projectId?: string;\n /** `published` (default) or `drafts`. */\n perspective?: Perspective;\n /**\n * Preview-token JWT for token-based single-document draft reads via the\n * `/preview/:slug` endpoint. Only used when `perspective === \"drafts\"`.\n */\n previewToken?: string;\n /**\n * Embed invisible visual-editing provenance in draft reads, so a preview build can\n * deep-link each rendered value back to its editor. Requires `perspective: \"drafts\"`,\n * and then either a `previewToken` or a `content:read:draft` key. Ignored on published\n * reads — the payload must never reach a production build.\n *\n * **Defaults to `true` on `perspective: \"drafts\"`** (since 1.15.0). Provenance is the whole\n * reason the drafts perspective exists in a preview build, and an option nobody knew to pass\n * meant every such build rendered anonymous strings: the editor could bind them only by\n * guessing at their text. It stays OFF on every other perspective, unconditionally, so a\n * published build cannot acquire a payload by accident — pass `stega: false` to opt a draft\n * read out (a diff or a checksum over draft content is the case that wants it).\n */\n stega?: boolean;\n /** Per-attempt timeout in ms. Defaults to 10000. */\n timeout?: number;\n /**\n * Collapse identical GETs to one network round-trip for the life of this client.\n * Default false.\n *\n * Only correct where the content is a POINT-IN-TIME SNAPSHOT — i.e. a static build,\n * where every route renders from the same published dataset. `@bettercms-ai/astro`\n * turns this on automatically when Astro's `command === \"build\"` and never for `dev`,\n * SSR or the drafts perspective, where a repeat read must see the current row.\n *\n * A static build without it re-fetches the same nav/settings/collection once PER ROUTE.\n * That is what made a large site exhaust the delivery rate limit: hundreds of identical\n * requests, and `getPage()` re-scanning every page of the paginated list each time it is\n * called.\n */\n cache?: boolean;\n}\n\nexport interface ListEntriesOptions {\n /** Filter by content-model slug. */\n model?: string;\n page?: number;\n perPage?: number;\n /** Reference hydration depth (lists cap at 1). */\n depth?: 0 | 1;\n /** Projection — field names to return. */\n select?: string[];\n}\n\nexport interface GetEntryOptions {\n depth?: 0 | 1 | 2;\n select?: string[];\n}\n\nexport interface ListPagesOptions {\n page?: number;\n perPage?: number;\n}\n\nexport interface BetterCMSReadClient {\n readonly perspective: Perspective;\n getEntry<T = Record<string, unknown>>(\n slug: string,\n opts?: GetEntryOptions,\n ): Promise<DeliveryEntry<T>>;\n listEntries<T = Record<string, unknown>>(\n opts?: ListEntriesOptions,\n ): Promise<DeliveryList<DeliveryEntry<T>>>;\n getPage(slug: string): Promise<DeliveryPage | null>;\n listPages(opts?: ListPagesOptions): Promise<DeliveryList<DeliveryPage>>;\n /**\n * The workspace's reusable component definitions, for resolving `component` blocks.\n * Under `perspective: \"drafts\"` this is the DRAFT definition — what makes an SSR render\n * of a draft page look like the page the editor is showing.\n */\n listComponents(): Promise<DeliveryComponent[]>;\n /** Fetch the project-scoped Global Layout for the active perspective. */\n getLayout(): Promise<DeliveredLayout | null>;\n listForms(opts?: {\n projectId?: string;\n }): Promise<{ items: DeliveryForm[]; turnstileSiteKey: string | null }>;\n /** Build a deterministic Next/cache tag for a resource. */\n tag(resource: \"entry\" | \"page\" | \"forms\", id?: string): string;\n}\n\n/** Drop undefined values, stringify the rest into a query string. */\nfunction qs(params: Record<string, string | number | undefined>): string {\n const sp = new URLSearchParams();\n for (const [k, v] of Object.entries(params)) {\n if (v !== undefined && v !== \"\") sp.set(k, String(v));\n }\n const s = sp.toString();\n return s ? `?${s}` : \"\";\n}\n\n/**\n * Unwrap a `{ data: ... }` list/legacy envelope when present. A bare entry is\n * already the payload (it has `id`/`slug` and its own `data` field), so we only\n * unwrap when no payload markers are at the top level.\n */\nfunction unwrap<T>(json: unknown): T {\n if (json && typeof json === \"object\") {\n const o = json as Record<string, unknown>;\n const isPayload = \"id\" in o || \"slug\" in o || \"items\" in o;\n if (!isPayload && \"data\" in o) return o.data as T;\n }\n return json as T;\n}\n\n/**\n * The `entrySlug` claim of a preview-token JWT, UNVERIFIED.\n *\n * The backend is the signature authority — this read only decides WHICH url to call, and a\n * forged claim buys nothing: `/preview/:slug` re-verifies and 401s. Kept private to this file\n * (`@bettercms-ai/astro` has its own copy for the same reason: neither package may depend on\n * the other).\n */\nfunction tokenSlug(token: string): string | null {\n try {\n const part = token.split(\".\")[1];\n if (!part) return null;\n const json = atob(part.replace(/-/g, \"+\").replace(/_/g, \"/\"));\n return (JSON.parse(json) as { entrySlug?: string }).entrySlug ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Per-URL request collapsing.\n *\n * Two jobs, both needed: coalesce requests that are IN FLIGHT (Astro renders routes\n * concurrently, so identical GETs overlap) and retain the ones that have COMPLETED\n * (routes render in batches, so the same GET recurs long after the first settled).\n *\n * Values are cloned on the way out. Callers get a fresh object graph, so a template that\n * sorts `items` in place — an easy thing to write and a brutal thing to debug — cannot\n * corrupt what every other route sees. Cloning costs memory bandwidth; the request it\n * replaces costs a network round-trip and a slot in the rate-limit window.\n *\n * A rejected request is never retained, so a transient failure does not become permanent.\n */\nfunction createRequestCache() {\n const inflight = new Map<string, Promise<unknown>>();\n const settled = new Map<string, unknown>();\n\n const copy = <T>(value: T): T =>\n typeof structuredClone === \"function\" ? structuredClone(value) : (JSON.parse(JSON.stringify(value)) as T);\n\n return async function collapse<T>(url: string, fetcher: () => Promise<T>): Promise<T> {\n if (settled.has(url)) return copy(settled.get(url) as T);\n\n const pending = inflight.get(url) as Promise<T> | undefined;\n if (pending) return copy(await pending);\n\n const run = (async () => {\n try {\n const value = await fetcher();\n settled.set(url, value);\n return value;\n } finally {\n inflight.delete(url);\n }\n })();\n inflight.set(url, run);\n return copy(await run);\n };\n}\n\nexport function createClient(options: CreateClientOptions): BetterCMSReadClient {\n const apiUrl = options.apiUrl.replace(/\\/+$/, \"\");\n const { workspace, apiKey, perspective = \"published\", previewToken } = options;\n const defaultProjectId = options.projectId;\n const timeout = options.timeout ?? DEFAULT_TIMEOUT;\n const base = `${apiUrl}/api/v1/delivery/${workspace}`;\n\n const headers = (): Record<string, string> => {\n const h: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (apiKey) h[\"X-API-Key\"] = apiKey;\n return h;\n };\n\n // Drafts must never be collapsed even if a caller asks: a preview surface re-reads to\n // see the edit that just happened, and serving it a retained copy would show stale content.\n const collapse =\n options.cache && perspective === \"published\" ? createRequestCache() : undefined;\n\n const fetchJSON = <T>(url: string): Promise<T> =>\n collapse ? collapse(url, () => request<T>(url)) : request<T>(url);\n\n async function request<T>(url: string): Promise<T> {\n let lastError: unknown;\n for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeout);\n try {\n const res = await globalThis.fetch(url, {\n headers: headers(),\n signal: controller.signal,\n });\n clearTimeout(timer);\n if (res.ok) return (await res.json()) as T;\n\n const retryable = attempt < MAX_RETRIES && isRetryableStatus(res.status);\n if (!retryable) throw await BetterCMSError.from(res);\n // Honour Retry-After: the limiter's window is up to 60s, far longer than\n // any fixed ladder, so the server's own number is the only one that works.\n await sleep(backoffMs(attempt, res));\n } catch (err) {\n clearTimeout(timer);\n if (err instanceof Error && err.name === \"AbortError\") {\n throw new BetterCMSError(\"Request timeout\", 408, \"NETWORK_ERROR\");\n }\n if (err instanceof BetterCMSError) throw err;\n lastError = err;\n if (attempt < MAX_RETRIES) {\n await sleep(backoffMs(attempt));\n continue;\n }\n throw new BetterCMSError(\n `Network error: ${err instanceof Error ? err.message : String(err)}`,\n 0,\n \"NETWORK_ERROR\",\n );\n }\n }\n throw lastError ?? new BetterCMSError(\"Unreachable\", 0, \"NETWORK_ERROR\");\n }\n\n const draft = perspective === \"drafts\";\n /**\n * Resolved ONCE, here, so the three call sites below cannot drift apart — they did exactly\n * that before (`options.stega` alone on the preview branch, `draft && options.stega` on the\n * other two), and a default expressed three times is a default that will eventually be two.\n * `??` and not `||`: an explicit `stega: false` on a draft read must win.\n */\n const stega = options.stega ?? draft;\n\n return {\n perspective,\n\n async getEntry<T = Record<string, unknown>>(slug: string, opts?: GetEntryOptions) {\n // Token-based draft reads go through the dedicated preview endpoint.\n // Without a token the drafts perspective is served by delivery itself, which needs\n // to be TOLD — `?perspective=draft` (honoured only for a `content:read:draft` key).\n // Until this branch sent it, `perspective: \"drafts\"` was silently inert here, and\n // with it `stega`, so a static build could never obtain provenance without minting\n // a preview token per entry.\n const url =\n draft && previewToken\n ? `${apiUrl}/api/v1/preview/${encodeURIComponent(slug)}${qs({\n token: previewToken,\n depth: opts?.depth,\n select: opts?.select?.join(\",\"),\n stega: stega ? \"1\" : undefined,\n })}`\n : `${base}/content-entries/${encodeURIComponent(slug)}${qs({\n depth: opts?.depth,\n select: opts?.select?.join(\",\"),\n perspective: draft ? \"draft\" : undefined,\n stega: draft && stega ? \"1\" : undefined,\n projectId: defaultProjectId,\n })}`;\n return unwrap<DeliveryEntry<T>>(await fetchJSON(url));\n },\n\n async listEntries<T = Record<string, unknown>>(opts?: ListEntriesOptions) {\n const url = `${base}/content-entries${qs({\n model: opts?.model,\n page: opts?.page,\n perPage: opts?.perPage,\n depth: opts?.depth,\n select: opts?.select?.join(\",\"),\n perspective: draft ? \"draft\" : undefined,\n stega: draft && stega ? \"1\" : undefined,\n projectId: defaultProjectId,\n })}`;\n return unwrap<DeliveryList<DeliveryEntry<T>>>(await fetchJSON(url));\n },\n\n async getPage(slug: string) {\n // The token names ONE page, and for that page the preview endpoint is the only read\n // that returns the DRAFT block tree — the paginated delivery list is published-only, so\n // an SSR draft render would otherwise be the live structure with draft field values.\n // Any other slug falls through to the published scan, which is what a token for /about\n // rendering the site's nav or footer pages must keep doing.\n if (draft && previewToken && tokenSlug(previewToken) === slug) {\n const url = `${apiUrl}/api/v1/preview/${encodeURIComponent(slug)}${qs({\n token: previewToken,\n stega: stega ? \"1\" : undefined,\n })}`;\n const d = unwrap<Record<string, unknown>>(await fetchJSON(url));\n return {\n slug: d.slug as string,\n title: d.title as string,\n metaTitle: (d.metaTitle as string | null) ?? null,\n metaDescription: (d.metaDescription as string | null) ?? null,\n metaJson: (d.metaJson as DeliveryPage[\"metaJson\"]) ?? null,\n blocks: (d.blockJson as DeliveryPage[\"blocks\"]) ?? [],\n // C3: the DRAFT field styles, present only when the page styles a field.\n ...(d.fieldStyles ? { fieldStyles: d.fieldStyles as NonNullable<DeliveryPage[\"fieldStyles\"]> } : {}),\n updatedAt: (d.updatedAt as string | null) ?? null,\n publishedAt: (d.publishedAt as string | null) ?? null,\n } satisfies DeliveryPage;\n }\n // No dedicated single-page delivery endpoint — scan the paginated list.\n let page = 1;\n for (;;) {\n const list = await this.listPages({ page, perPage: 100 });\n const found = list.items.find((p) => p.slug === slug);\n if (found) return found;\n if (!list.hasNextPage) return null;\n page += 1;\n }\n },\n\n async listPages(opts?: ListPagesOptions) {\n const url = `${base}/pages${qs({\n page: opts?.page,\n perPage: opts?.perPage,\n projectId: defaultProjectId,\n })}`;\n return unwrap<DeliveryList<DeliveryPage>>(await fetchJSON(url));\n },\n\n async listComponents() {\n // Same shape as getLayout: `?perspective=draft` alone is the key-scoped path (the server\n // ignores it for a published key), and a preview token is the page-scoped one.\n const url = `${base}/components${qs({\n perspective: draft ? \"draft\" : undefined,\n token: draft ? previewToken : undefined,\n projectId: defaultProjectId,\n })}`;\n return unwrap<DeliveryComponent[]>(await fetchJSON(url));\n },\n\n async getLayout() {\n const url = `${base}/layout${qs({ perspective: draft ? \"draft\" : undefined, projectId: defaultProjectId })}`;\n try {\n return unwrap<DeliveredLayout>(await fetchJSON(url));\n } catch (error) {\n if (error instanceof BetterCMSError && error.status === 404) return null;\n throw error;\n }\n },\n\n async listForms(opts?: { projectId?: string }) {\n const url = `${base}/forms${qs({\n projectId: opts?.projectId ?? defaultProjectId,\n })}`;\n return unwrap<{ items: DeliveryForm[]; turnstileSiteKey: string | null }>(\n await fetchJSON(url),\n );\n },\n\n tag(resource, id) {\n return id ? `bcms:${workspace}:${resource}:${id}` : `bcms:${workspace}:${resource}`;\n },\n };\n}\n","/**\n * Where a site's BetterCMS settings come from, the same way on every framework: explicit options,\n * then `<PREFIX>BCMS_*` env (each framework exposes env to the browser under its own prefix:\n * `PUBLIC_` for Astro and SvelteKit, `NEXT_PUBLIC_` for Next, `VITE_` for Vite React,\n * `NUXT_PUBLIC_` for Nuxt), then the build snapshot `bettercms pull-content` writes.\n *\n * Pure: the adapter passes its env and reads the snapshot (this module touches no filesystem).\n */\n\nexport const DEFAULT_MEDIA_URL = \"https://cdn.bettercms.ai\";\n\nexport interface BcmsSiteOptions {\n apiUrl?: string;\n workspace?: string;\n projectId?: string;\n mediaUrl?: string;\n studioUrl?: string;\n}\n\nexport interface BcmsSiteConfig {\n apiUrl: string;\n workspace: string;\n projectId?: string;\n mediaUrl: string;\n studioUrl?: string;\n}\n\nexport interface ResolveBcmsConfigInput {\n options?: BcmsSiteOptions;\n env?: Record<string, string | undefined>;\n /** Env prefixes, first match wins: `[\"NEXT_PUBLIC_\", \"\"]` reads NEXT_PUBLIC_BCMS_API_URL, then BCMS_API_URL. */\n prefixes: string[];\n /** The build snapshot's identity, read only when options and env both miss. */\n snapshot?: () => { apiUrl?: string; workspace?: string; projectId?: string };\n}\n\n/** The resolved settings, and which essentials are missing (the adapter words the error). */\nexport function resolveBcmsConfig(input: ResolveBcmsConfigInput): { config: BcmsSiteConfig; missing: (\"apiUrl\" | \"workspace\")[] } {\n const env = input.env ?? {};\n const fromEnv = (name: string) => {\n for (const p of input.prefixes) {\n const v = env[`${p}BCMS_${name}`];\n if (v) return v;\n }\n return undefined;\n };\n let snap: ReturnType<NonNullable<ResolveBcmsConfigInput[\"snapshot\"]>> | undefined;\n const snapshot = () => (snap ??= input.snapshot?.() ?? {});\n const o = input.options ?? {};\n const config: BcmsSiteConfig = {\n apiUrl: (o.apiUrl ?? fromEnv(\"API_URL\") ?? snapshot().apiUrl ?? \"\").replace(/\\/+$/, \"\"),\n workspace: o.workspace ?? fromEnv(\"WORKSPACE\") ?? snapshot().workspace ?? \"\",\n projectId: o.projectId ?? fromEnv(\"PROJECT_ID\") ?? snapshot().projectId,\n mediaUrl: (o.mediaUrl ?? fromEnv(\"MEDIA_URL\") ?? DEFAULT_MEDIA_URL).replace(/\\/+$/, \"\"),\n studioUrl: o.studioUrl ?? fromEnv(\"STUDIO_URL\"),\n };\n const missing = ([\"apiUrl\", \"workspace\"] as const).filter((k) => !config[k]);\n return { config, missing };\n}\n","/**\n * Draft mode, shared by every framework adapter: the signed cookie that says \"this visitor is\n * previewing\", the preview token's slug, and the redirect a draft route may follow.\n *\n * Web Crypto and `atob` only, so it runs in Node, edge runtimes and workers alike. Each adapter\n * mounts its own routes around these; the rules live here once.\n */\n\n/** The draft cookie every adapter sets: one name, so an editor link works on any framework. */\nexport const DRAFT_COOKIE = \"bcms-draft\";\n\nconst enc = new TextEncoder();\n\nfunction base64url(bytes: ArrayBuffer): string {\n const b = btoa(String.fromCharCode(...new Uint8Array(bytes)));\n return b.replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\nasync function hmac(value: string, secret: string): Promise<string> {\n const key = await crypto.subtle.importKey(\"raw\", enc.encode(secret), { name: \"HMAC\", hash: \"SHA-256\" }, false, [\"sign\"]);\n return base64url(await crypto.subtle.sign(\"HMAC\", key, enc.encode(value)));\n}\n\n/** Sign a value as `value.sig` for tamper-evident cookie storage (HMAC-SHA256, base64url). */\nexport async function signValue(value: string, secret: string): Promise<string> {\n return `${value}.${await hmac(value, secret)}`;\n}\n\n/** The original value of a signed one, or null when it was tampered with. */\nexport async function verifyValue(signed: string, secret: string): Promise<string | null> {\n const idx = signed.lastIndexOf(\".\");\n if (idx < 0) return null;\n const value = signed.slice(0, idx);\n const sig = signed.slice(idx + 1);\n const expected = await hmac(value, secret);\n if (sig.length !== expected.length) return null;\n let diff = 0;\n for (let i = 0; i < sig.length; i++) diff |= sig.charCodeAt(i) ^ expected.charCodeAt(i);\n return diff === 0 ? value : null;\n}\n\n/** Only same-origin relative paths: blocks \"//evil.com\", \"https://evil.com\" and \"/\\\\evil.com\". */\nexport function safeRedirectPath(to: string): string {\n return to.startsWith(\"/\") && !to.startsWith(\"//\") && !to.startsWith(\"/\\\\\") ? to : \"/\";\n}\n\n/** A preview token's `entrySlug`, read from its payload UNVERIFIED (the backend verifies it). */\nexport function decodeTokenSlug(token: string): string | null {\n try {\n const part = token.split(\".\")[1];\n if (!part) return null;\n // atob gives bytes as Latin-1: decoded as UTF-8, a slug like `über-uns` survives.\n const bytes = Uint8Array.from(atob(part.replace(/-/g, \"+\").replace(/_/g, \"/\")), (c) => c.charCodeAt(0));\n const json = JSON.parse(new TextDecoder().decode(bytes)) as { entrySlug?: string };\n return json.entrySlug ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Ask the backend whether a preview token is good: the backend is the signature authority, so a\n * draft route enables draft mode only on its yes.\n */\nexport async function previewTokenValid(apiUrl: string, token: string, fetchImpl: typeof fetch = fetch): Promise<boolean> {\n const slug = decodeTokenSlug(token);\n if (!slug) return false;\n const res = await fetchImpl(`${apiUrl.replace(/\\/+$/, \"\")}/api/v1/preview/${encodeURIComponent(slug)}?token=${encodeURIComponent(token)}`);\n await res.body?.cancel().catch(() => {});\n return res.ok;\n}\n\n/** The draft cookie's preview token from a `Cookie` header, or null when absent or tampered with. */\nexport async function draftTokenFrom(cookieHeader: string | null | undefined, secret: string | undefined): Promise<string | null> {\n if (!cookieHeader || !secret) return null;\n const m = new RegExp(`(?:^|;\\\\s*)${DRAFT_COOKIE}=([^;]+)`).exec(cookieHeader);\n if (!m) return null;\n try {\n return await verifyValue(decodeURIComponent(m[1]!), secret);\n } catch {\n return null;\n }\n}\n\n/**\n * The draft-enable route for any server that speaks Web `Request`/`Response` (SvelteKit `+server.ts`,\n * Nuxt via `toWebRequest`, Remix, Hono): `GET ?token=…&redirect=/x` checks the token with the\n * backend, sets the signed draft cookie and redirects. 401 on a refused token; 500 without a secret.\n */\nexport async function draftEnableResponse(request: Request, opts: { apiUrl: string; secret: string | undefined; fetchImpl?: typeof fetch }): Promise<Response> {\n const url = new URL(request.url);\n const token = url.searchParams.get(\"token\");\n if (!opts.secret) return new Response(\"BCMS_DRAFT_SECRET is not configured\", { status: 500 });\n if (!token) return new Response(\"Missing token\", { status: 400 });\n if (!(await previewTokenValid(opts.apiUrl, token, opts.fetchImpl))) return new Response(\"Invalid or expired token\", { status: 401 });\n const cookie = `${DRAFT_COOKIE}=${encodeURIComponent(await signValue(token, opts.secret))}; Path=/; HttpOnly; SameSite=Lax; Max-Age=86400${url.protocol === \"https:\" ? \"; Secure\" : \"\"}`;\n return new Response(null, { status: 302, headers: { Location: safeRedirectPath(url.searchParams.get(\"redirect\") ?? \"/\"), \"Set-Cookie\": cookie } });\n}\n\n/** Leave draft mode: clear the cookie and redirect (`?redirect=/x`, same-origin only). */\nexport function draftDisableResponse(request: Request): Response {\n const url = new URL(request.url);\n return new Response(null, {\n status: 302,\n headers: { Location: safeRedirectPath(url.searchParams.get(\"redirect\") ?? \"/\"), \"Set-Cookie\": `${DRAFT_COOKIE}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0` },\n });\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/sections.ts","../src/errors.ts","../src/retry.ts","../src/read-client.ts","../src/config.ts","../src/draft.ts"],"sourcesContent":["/// <reference lib=\"dom\" />\n/**\n * A published page's sections as server-rendered HTML, for a front end that renders its own\n * document (SvelteKit, Nuxt, any server): the hosted renderer's markup, its scoped stylesheet and\n * the runtime its forms, sliders and tabs need (`GET /api/v1/sites/fragment/:projectId/:slug`).\n * One renderer for every site, so the markup is the hosted site's and the editor's.\n */\nimport { BetterCMSError, ErrorCodes } from \"./errors.js\";\nimport { createClient, type BetterCMSReadClient } from \"./read-client.js\";\nimport { resolveBcmsConfig, type BcmsSiteConfig, type BcmsSiteOptions } from \"./config.js\";\nimport { draftDisableResponse, draftEnableResponse, draftTokenFrom } from \"./draft.js\";\n\nexport interface SectionsFragment {\n html: string;\n css: string;\n scripts: string;\n}\n\n/** Fetch a page's sections (`home` is `/`). Null when the page is not published. */\nexport async function fetchSections(\n opts: { apiUrl: string; projectId: string; slug: string; fetch?: typeof fetch },\n): Promise<SectionsFragment | null> {\n const doFetch = opts.fetch ?? fetch;\n const url = `${opts.apiUrl.replace(/\\/+$/, \"\")}/api/v1/sites/fragment/${encodeURIComponent(opts.projectId)}/${encodeURIComponent(opts.slug || \"home\")}`;\n const res = await doFetch(url, { headers: { accept: \"application/json\" } });\n if (res.status === 404) return null;\n if (!res.ok) throw new BetterCMSError(`Sections for \"${opts.slug}\" could not be read (${res.status})`, res.status, res.status === 403 ? ErrorCodes.FORBIDDEN : ErrorCodes.INTERNAL_ERROR);\n return ((await res.json()) as { data: SectionsFragment }).data;\n}\n\n/** The fragment as one string for `{@html}` / `v-html`: its stylesheet, its markup, its scripts. */\nexport function sectionsMarkup(fragment: SectionsFragment | null | undefined): string {\n if (!fragment) return \"\";\n return `<style>${fragment.css}</style>${fragment.html}${fragment.scripts}`;\n}\n\n/**\n * Run the scripts inside `root`. A server-rendered first load runs them itself; a client-side\n * navigation inserts the markup with innerHTML, where a <script> never runs, so the forms,\n * sliders and tabs of the new page would be dead. Each script runs once per element.\n */\nexport function runSectionScripts(root: Element): void {\n for (const old of Array.from(root.querySelectorAll(\"script\"))) {\n if (old.hasAttribute(\"data-bcms-ran\")) continue;\n const fresh = document.createElement(\"script\");\n for (const attr of Array.from(old.attributes)) fresh.setAttribute(attr.name, attr.value);\n fresh.setAttribute(\"data-bcms-ran\", \"\");\n fresh.textContent = old.textContent;\n old.replaceWith(fresh);\n }\n}\n\n\nexport interface BcmsSite {\n config: BcmsSiteConfig;\n /** The read client for a request: drafts while its draft cookie holds a valid token. */\n client(request?: Request): Promise<BetterCMSReadClient>;\n /** A published page's sections (`home` is `/`); pass the framework's `fetch` on the server. */\n sections(slug: string, fetchImpl?: typeof fetch): Promise<SectionsFragment | null>;\n /** GET handler for the draft path the editor opens (`/api/draft`). */\n draftEnable(request: Request): Promise<Response>;\n /** GET handler that leaves draft mode. */\n draftDisable(request: Request): Response;\n}\n\n/**\n * Everything a server-rendered site needs from BetterCMS, built once from its settings: the\n * precedence every adapter shares (options, `<PREFIX>BCMS_*`, `BCMS_*`), the read client per\n * request, the sections fragment and the draft routes. The SvelteKit and Nuxt adapters are this\n * with their env prefix.\n */\nexport function createBcmsSite(\n options: BcmsSiteOptions & { env?: Record<string, string | undefined>; draftSecret?: string; prefixes: string[]; adapter: string },\n): BcmsSite {\n const env = options.env ?? {};\n const { config, missing } = resolveBcmsConfig({ options, env, prefixes: [...options.prefixes, \"\"] });\n if (missing.length > 0) {\n const names = missing.map((m) => `${options.prefixes[0]}BCMS_${m === \"apiUrl\" ? \"API_URL\" : \"WORKSPACE\"}`).join(\" and \");\n throw new Error(`[${options.adapter}] Missing ${names}: set them, or pass them to createBcms().`);\n }\n const secret = options.draftSecret ?? env.BCMS_DRAFT_SECRET;\n return {\n config,\n async client(request) {\n const token = request ? await draftTokenFrom(request.headers.get(\"cookie\"), secret) : null;\n return createClient({\n apiUrl: config.apiUrl,\n workspace: config.workspace,\n projectId: config.projectId,\n ...(token ? { perspective: \"drafts\" as const, previewToken: token } : {}),\n });\n },\n sections(slug, fetchImpl) {\n if (!config.projectId) throw new Error(`[${options.adapter}] Sections need the project: set ${options.prefixes[0]}BCMS_PROJECT_ID.`);\n return fetchSections({ apiUrl: config.apiUrl, projectId: config.projectId, slug, fetch: fetchImpl });\n },\n draftEnable: (request) => draftEnableResponse(request, { apiUrl: config.apiUrl, secret }),\n draftDisable: draftDisableResponse,\n };\n}\n","import type { BetterCMSErrorCode } from \"@bettercms-ai/types\";\n\n/** Semantic error codes used by the SDK. */\nexport const ErrorCodes = {\n CONTENT_NOT_FOUND: \"CONTENT_NOT_FOUND\",\n UNAUTHORIZED: \"UNAUTHORIZED\",\n FORBIDDEN: \"FORBIDDEN\",\n RATE_LIMITED: \"RATE_LIMITED\",\n INTERNAL_ERROR: \"INTERNAL_ERROR\",\n VALIDATION_ERROR: \"VALIDATION_ERROR\",\n NETWORK_ERROR: \"NETWORK_ERROR\",\n ADMIN_NOT_IMPLEMENTED: \"ADMIN_NOT_IMPLEMENTED\",\n} as const;\n\nexport type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];\n\n/** Maps HTTP status codes to semantic error codes. */\nfunction statusToCode(status: number): BetterCMSErrorCode {\n if (status === 404) return ErrorCodes.CONTENT_NOT_FOUND;\n if (status === 401) return ErrorCodes.UNAUTHORIZED;\n if (status === 403) return ErrorCodes.FORBIDDEN;\n if (status === 429) return ErrorCodes.RATE_LIMITED;\n if (status >= 500) return ErrorCodes.INTERNAL_ERROR;\n if (status === 422) return ErrorCodes.VALIDATION_ERROR;\n return ErrorCodes.INTERNAL_ERROR;\n}\n\n/**\n * BetterCMSError — normalized SDK error with a semantic code.\n */\nexport class BetterCMSError extends Error {\n readonly status: number;\n readonly code: BetterCMSErrorCode;\n /**\n * Raw machine-readable `code` from the response body (e.g. \"PROJECT_DELETED\"),\n * when the API supplies one. Distinct from `code` (which is derived from the\n * HTTP status), this lets callers branch on a specific server condition that\n * shares a status with others (e.g. 409 slug-conflict vs 409 project-deleted).\n */\n readonly bodyCode?: string;\n /**\n * Per-field validation messages from a 400 `{ errors: { field: message } }` body.\n * The backend returns validation failures in this shape (not `message`/`error`), so\n * without this the SDK collapsed every validation 400 to a bare \"Bad Request\" with no\n * hint which field or rule failed (FLO-474). Mirrors the forms client's `fieldErrors`.\n */\n readonly fieldErrors?: Record<string, string>;\n /**\n * The parsed response body, when there was one.\n *\n * Several refusals carry STRUCTURED detail that neither `message` nor `fieldErrors` can hold,\n * and which is the whole point of the refusal: a component publish 409s with the full\n * `readiness` tuple (what evidence is missing, whether a human still has to approve it) and\n * 422s with the `issues` naming the Layouts it would break. Collapsing those to a sentence\n * leaves a caller able to report that something failed and not what to do about it.\n */\n readonly body?: unknown;\n\n constructor(\n message: string,\n status: number,\n code: BetterCMSErrorCode,\n bodyCode?: string,\n fieldErrors?: Record<string, string>,\n body?: unknown,\n ) {\n super(message);\n this.name = \"BetterCMSError\";\n this.status = status;\n this.code = code;\n this.bodyCode = bodyCode;\n this.fieldErrors = fieldErrors;\n this.body = body;\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, BetterCMSError);\n }\n }\n\n toJSON() {\n return {\n name: this.name,\n message: this.message,\n status: this.status,\n code: this.code,\n bodyCode: this.bodyCode,\n fieldErrors: this.fieldErrors,\n };\n }\n\n /**\n * Factory — creates a BetterCMSError from a failed fetch Response. Reads the body's\n * `message` (or `error`) for the human message and `code` for a machine-readable\n * condition surfaced as `bodyCode`. Validation failures arrive as `{ errors: {...} }`\n * (no `message`/`error`), so fall back to the first field error for the message and\n * expose the full map as `fieldErrors` (FLO-474).\n */\n static async from(res: Response): Promise<BetterCMSError> {\n let message = res.statusText || \"An error occurred\";\n let bodyCode: string | undefined;\n let fieldErrors: Record<string, string> | undefined;\n let code = statusToCode(res.status);\n let raw: unknown;\n try {\n const body = (await res.json()) as {\n message?: string;\n error?: string;\n code?: string;\n errors?: Record<string, string>;\n };\n if (body?.errors && typeof body.errors === \"object\") {\n fieldErrors = body.errors;\n code = ErrorCodes.VALIDATION_ERROR;\n }\n if (body?.message) message = body.message;\n else if (body?.error) message = body.error;\n else if (fieldErrors) {\n const [field, detail] = Object.entries(fieldErrors).find(\n ([, v]) => typeof v === \"string\" && v.length > 0,\n ) ?? [];\n if (field) message = `${field}: ${detail}`;\n }\n if (body?.code) bodyCode = body.code;\n raw = body;\n } catch {\n // ignore parse errors\n }\n return new BetterCMSError(message, res.status, code, bodyCode, fieldErrors, raw);\n }\n}\n","/**\n * Shared 429/503 backoff for every SDK client.\n *\n * ── Why this exists ─────────────────────────────────────────────────────────\n * The backend limiter (src/lib/ratelimit.ts, windowHit) is a FIXED 60-SECOND\n * WINDOW: 100 requests per workspace per endpoint, and a denial answers\n * `Retry-After: <seconds until the window resets>` — anything up to 60.\n *\n * Every client here used to climb a private ladder that topped out at 7 seconds\n * ([250,750,1500] in read-client, [1000,2000,4000] in admin/management) and none\n * of them read Retry-After. So all three retries landed INSIDE the same exhausted\n * window and a 429 was fatal roughly whenever it was thrown. The retry code looked\n * like resilience but could only ever recover from a denial raised in the final\n * seconds of a window.\n *\n * That is what failed customer builds: `action/fetch-content.mjs` spends ~65 of the\n * 100 `delivery-content-entries-list` slots pulling a large workspace, `npm run build`\n * starts seconds later in the SAME window, and the first per-route delivery read past\n * the ceiling killed the deploy with \"Too many requests. Please retry later.\"\n *\n * ── The rule ────────────────────────────────────────────────────────────────\n * Obey Retry-After when the server sends one, and let a single request outlast a\n * whole window. A build waiting 60s is slow; a build that dies is a broken product.\n */\n\n/** Statuses worth retrying. 429 = rate limited, 503 = limiter/backend unavailable. */\nexport const RETRY_STATUSES = [429, 503] as const;\n\n/** Retries after the first attempt. 3 retries × up to ~65s spans three full windows. */\nexport const MAX_RETRIES = 3;\n\n/**\n * Hard ceiling on a single wait, jitter included. Must exceed the limiter's 60s\n * window, or a denial raised at the very start of a window still cannot be waited\n * out — and must be finite, so a hostile or mis-set Retry-After cannot hang a build.\n */\nexport const MAX_DELAY_MS = 65_000;\n\n/** Fallback ladder when the server sends no Retry-After: 1s, 2s, 4s, 8s. */\nconst BASE_DELAY_MS = 1_000;\n\n/**\n * De-sync jitter, added to every wait.\n *\n * Deliberately a small FLAT amount, not a percentage. Requests denied by one window\n * only need to stop firing in the same millisecond — the server's counter serialises\n * them after that. A proportional jitter would add up to 15 seconds to a 60-second\n * wait for no benefit, and would push the total past MAX_DELAY_MS.\n */\nconst JITTER_MS = 250;\n\nexport function isRetryableStatus(status: number): boolean {\n return (RETRY_STATUSES as readonly number[]).includes(status);\n}\n\n/**\n * Parse `Retry-After`, which RFC 9110 allows in either form: delay-seconds\n * (\"47\") or an HTTP-date (\"Wed, 21 Oct 2015 07:28:00 GMT\"). Returns ms to wait,\n * or null when the header is absent or unparseable.\n */\nexport function retryAfterMs(res: Pick<Response, \"headers\">): number | null {\n const raw = res.headers?.get?.(\"retry-after\");\n if (!raw) return null;\n\n const seconds = Number(raw);\n if (Number.isFinite(seconds)) {\n return Math.min(Math.max(seconds, 0) * 1000, MAX_DELAY_MS);\n }\n\n const at = Date.parse(raw);\n if (!Number.isNaN(at)) {\n return Math.min(Math.max(0, at - Date.now()), MAX_DELAY_MS);\n }\n return null;\n}\n\n/**\n * How long to wait before retry `attempt` (0-based).\n *\n * Server instruction wins; otherwise exponential. Jitter is added in both cases:\n * when a build's requests are all denied by the same window they would otherwise\n * wake in lockstep at the reset and re-exhaust it immediately (thundering herd).\n */\nexport function backoffMs(attempt: number, res?: Pick<Response, \"headers\">): number {\n const fromServer = res ? retryAfterMs(res) : null;\n // Base is capped with the jitter's headroom already reserved, so the total never\n // exceeds MAX_DELAY_MS AND the jitter is never clamped away at the ceiling — which\n // would put every capped caller back in lockstep, the exact thing jitter prevents.\n const base = Math.min(fromServer ?? BASE_DELAY_MS * 2 ** attempt, MAX_DELAY_MS - JITTER_MS);\n // Jitter is ADDED, never subtracted: waking before the server's Retry-After elapses\n // just burns a retry on a window that has not reset.\n return base + Math.random() * JITTER_MS;\n}\n\nexport const sleep = (ms: number): Promise<void> =>\n new Promise((resolve) => setTimeout(resolve, ms));\n","/**\n * createClient — framework-agnostic typed read client for the Delivery API.\n *\n * Shared by @bettercms-ai/astro and @bettercms-ai/next so the fetch/typing/retry\n * logic lives in one place. Read-only; for schema authoring use the\n * Management client, for sessions use the Admin client.\n *\n * const client = createClient({ apiUrl, workspace, apiKey });\n * await client.getPage(\"home\");\n * await client.listEntries({ model: \"post\", perPage: 50 });\n */\nimport type {\n DeliveryComponent,\n DeliveryEntry,\n DeliveryList,\n DeliveryPage,\n DeliveredLayout,\n Perspective,\n} from \"@bettercms-ai/types\";\nimport { BetterCMSError } from \"./errors.js\";\nimport { MAX_RETRIES, backoffMs, isRetryableStatus, sleep } from \"./retry.js\";\nimport type { DeliveryForm } from \"./forms.js\";\n\nconst DEFAULT_TIMEOUT = 10_000;\n\nexport interface CreateClientOptions {\n /** Backend base, e.g. `https://api.bettercms.ai` (no trailing slash needed). */\n apiUrl: string;\n /** Workspace slug. */\n workspace: string;\n /** Delivery API key. Use a `content:read:draft` key for drafts. */\n apiKey?: string;\n /**\n * The project every read is addressed to. A workspace holds many projects whose slugs\n * collide (each has a `blog` model and a `home` page), so a site names its own; published\n * reads need no key for it. BetterCMS hosting supplies `PUBLIC_BCMS_PROJECT_ID`.\n */\n projectId?: string;\n /** `published` (default) or `drafts`. */\n perspective?: Perspective;\n /**\n * Preview-token JWT for token-based single-document draft reads via the\n * `/preview/:slug` endpoint. Only used when `perspective === \"drafts\"`.\n */\n previewToken?: string;\n /**\n * Embed invisible visual-editing provenance in draft reads, so a preview build can\n * deep-link each rendered value back to its editor. Requires `perspective: \"drafts\"`,\n * and then either a `previewToken` or a `content:read:draft` key. Ignored on published\n * reads — the payload must never reach a production build.\n *\n * **Defaults to `true` on `perspective: \"drafts\"`** (since 1.15.0). Provenance is the whole\n * reason the drafts perspective exists in a preview build, and an option nobody knew to pass\n * meant every such build rendered anonymous strings: the editor could bind them only by\n * guessing at their text. It stays OFF on every other perspective, unconditionally, so a\n * published build cannot acquire a payload by accident — pass `stega: false` to opt a draft\n * read out (a diff or a checksum over draft content is the case that wants it).\n */\n stega?: boolean;\n /** Per-attempt timeout in ms. Defaults to 10000. */\n timeout?: number;\n /**\n * Collapse identical GETs to one network round-trip for the life of this client.\n * Default false.\n *\n * Only correct where the content is a POINT-IN-TIME SNAPSHOT — i.e. a static build,\n * where every route renders from the same published dataset. `@bettercms-ai/astro`\n * turns this on automatically when Astro's `command === \"build\"` and never for `dev`,\n * SSR or the drafts perspective, where a repeat read must see the current row.\n *\n * A static build without it re-fetches the same nav/settings/collection once PER ROUTE.\n * That is what made a large site exhaust the delivery rate limit: hundreds of identical\n * requests, and `getPage()` re-scanning every page of the paginated list each time it is\n * called.\n */\n cache?: boolean;\n}\n\nexport interface ListEntriesOptions {\n /** Filter by content-model slug. */\n model?: string;\n page?: number;\n perPage?: number;\n /** Reference hydration depth (lists cap at 1). */\n depth?: 0 | 1;\n /** Projection — field names to return. */\n select?: string[];\n}\n\nexport interface GetEntryOptions {\n depth?: 0 | 1 | 2;\n select?: string[];\n}\n\nexport interface ListPagesOptions {\n page?: number;\n perPage?: number;\n}\n\nexport interface BetterCMSReadClient {\n readonly perspective: Perspective;\n getEntry<T = Record<string, unknown>>(\n slug: string,\n opts?: GetEntryOptions,\n ): Promise<DeliveryEntry<T>>;\n listEntries<T = Record<string, unknown>>(\n opts?: ListEntriesOptions,\n ): Promise<DeliveryList<DeliveryEntry<T>>>;\n getPage(slug: string): Promise<DeliveryPage | null>;\n listPages(opts?: ListPagesOptions): Promise<DeliveryList<DeliveryPage>>;\n /**\n * The workspace's reusable component definitions, for resolving `component` blocks.\n * Under `perspective: \"drafts\"` this is the DRAFT definition — what makes an SSR render\n * of a draft page look like the page the editor is showing.\n */\n listComponents(): Promise<DeliveryComponent[]>;\n /** Fetch the project-scoped Global Layout for the active perspective. */\n getLayout(): Promise<DeliveredLayout | null>;\n listForms(opts?: {\n projectId?: string;\n }): Promise<{ items: DeliveryForm[]; turnstileSiteKey: string | null }>;\n /** Build a deterministic Next/cache tag for a resource. */\n tag(resource: \"entry\" | \"page\" | \"forms\", id?: string): string;\n}\n\n/** Drop undefined values, stringify the rest into a query string. */\nfunction qs(params: Record<string, string | number | undefined>): string {\n const sp = new URLSearchParams();\n for (const [k, v] of Object.entries(params)) {\n if (v !== undefined && v !== \"\") sp.set(k, String(v));\n }\n const s = sp.toString();\n return s ? `?${s}` : \"\";\n}\n\n/**\n * Unwrap a `{ data: ... }` list/legacy envelope when present. A bare entry is\n * already the payload (it has `id`/`slug` and its own `data` field), so we only\n * unwrap when no payload markers are at the top level.\n */\nfunction unwrap<T>(json: unknown): T {\n if (json && typeof json === \"object\") {\n const o = json as Record<string, unknown>;\n const isPayload = \"id\" in o || \"slug\" in o || \"items\" in o;\n if (!isPayload && \"data\" in o) return o.data as T;\n }\n return json as T;\n}\n\n/**\n * The `entrySlug` claim of a preview-token JWT, UNVERIFIED.\n *\n * The backend is the signature authority — this read only decides WHICH url to call, and a\n * forged claim buys nothing: `/preview/:slug` re-verifies and 401s. Kept private to this file\n * (`@bettercms-ai/astro` has its own copy for the same reason: neither package may depend on\n * the other).\n */\nfunction tokenSlug(token: string): string | null {\n try {\n const part = token.split(\".\")[1];\n if (!part) return null;\n const json = atob(part.replace(/-/g, \"+\").replace(/_/g, \"/\"));\n return (JSON.parse(json) as { entrySlug?: string }).entrySlug ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Per-URL request collapsing.\n *\n * Two jobs, both needed: coalesce requests that are IN FLIGHT (Astro renders routes\n * concurrently, so identical GETs overlap) and retain the ones that have COMPLETED\n * (routes render in batches, so the same GET recurs long after the first settled).\n *\n * Values are cloned on the way out. Callers get a fresh object graph, so a template that\n * sorts `items` in place — an easy thing to write and a brutal thing to debug — cannot\n * corrupt what every other route sees. Cloning costs memory bandwidth; the request it\n * replaces costs a network round-trip and a slot in the rate-limit window.\n *\n * A rejected request is never retained, so a transient failure does not become permanent.\n */\nfunction createRequestCache() {\n const inflight = new Map<string, Promise<unknown>>();\n const settled = new Map<string, unknown>();\n\n const copy = <T>(value: T): T =>\n typeof structuredClone === \"function\" ? structuredClone(value) : (JSON.parse(JSON.stringify(value)) as T);\n\n return async function collapse<T>(url: string, fetcher: () => Promise<T>): Promise<T> {\n if (settled.has(url)) return copy(settled.get(url) as T);\n\n const pending = inflight.get(url) as Promise<T> | undefined;\n if (pending) return copy(await pending);\n\n const run = (async () => {\n try {\n const value = await fetcher();\n settled.set(url, value);\n return value;\n } finally {\n inflight.delete(url);\n }\n })();\n inflight.set(url, run);\n return copy(await run);\n };\n}\n\nexport function createClient(options: CreateClientOptions): BetterCMSReadClient {\n const apiUrl = options.apiUrl.replace(/\\/+$/, \"\");\n const { workspace, apiKey, perspective = \"published\", previewToken } = options;\n const defaultProjectId = options.projectId;\n const timeout = options.timeout ?? DEFAULT_TIMEOUT;\n const base = `${apiUrl}/api/v1/delivery/${workspace}`;\n\n const headers = (): Record<string, string> => {\n const h: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (apiKey) h[\"X-API-Key\"] = apiKey;\n return h;\n };\n\n // Drafts must never be collapsed even if a caller asks: a preview surface re-reads to\n // see the edit that just happened, and serving it a retained copy would show stale content.\n const collapse =\n options.cache && perspective === \"published\" ? createRequestCache() : undefined;\n\n const fetchJSON = <T>(url: string): Promise<T> =>\n collapse ? collapse(url, () => request<T>(url)) : request<T>(url);\n\n async function request<T>(url: string): Promise<T> {\n let lastError: unknown;\n for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeout);\n try {\n const res = await globalThis.fetch(url, {\n headers: headers(),\n signal: controller.signal,\n });\n clearTimeout(timer);\n if (res.ok) return (await res.json()) as T;\n\n const retryable = attempt < MAX_RETRIES && isRetryableStatus(res.status);\n if (!retryable) throw await BetterCMSError.from(res);\n // Honour Retry-After: the limiter's window is up to 60s, far longer than\n // any fixed ladder, so the server's own number is the only one that works.\n await sleep(backoffMs(attempt, res));\n } catch (err) {\n clearTimeout(timer);\n if (err instanceof Error && err.name === \"AbortError\") {\n throw new BetterCMSError(\"Request timeout\", 408, \"NETWORK_ERROR\");\n }\n if (err instanceof BetterCMSError) throw err;\n lastError = err;\n if (attempt < MAX_RETRIES) {\n await sleep(backoffMs(attempt));\n continue;\n }\n throw new BetterCMSError(\n `Network error: ${err instanceof Error ? err.message : String(err)}`,\n 0,\n \"NETWORK_ERROR\",\n );\n }\n }\n throw lastError ?? new BetterCMSError(\"Unreachable\", 0, \"NETWORK_ERROR\");\n }\n\n const draft = perspective === \"drafts\";\n /**\n * Resolved ONCE, here, so the three call sites below cannot drift apart — they did exactly\n * that before (`options.stega` alone on the preview branch, `draft && options.stega` on the\n * other two), and a default expressed three times is a default that will eventually be two.\n * `??` and not `||`: an explicit `stega: false` on a draft read must win.\n */\n const stega = options.stega ?? draft;\n\n return {\n perspective,\n\n async getEntry<T = Record<string, unknown>>(slug: string, opts?: GetEntryOptions) {\n // Token-based draft reads go through the dedicated preview endpoint.\n // Without a token the drafts perspective is served by delivery itself, which needs\n // to be TOLD — `?perspective=draft` (honoured only for a `content:read:draft` key).\n // Until this branch sent it, `perspective: \"drafts\"` was silently inert here, and\n // with it `stega`, so a static build could never obtain provenance without minting\n // a preview token per entry.\n // The token names ONE entry (as on getPage below): any other slug reads delivery, or the\n // preview endpoint 404s the post list, related posts and nav a draft page also renders.\n const url =\n draft && previewToken && tokenSlug(previewToken) === slug\n ? `${apiUrl}/api/v1/preview/${encodeURIComponent(slug)}${qs({\n token: previewToken,\n depth: opts?.depth,\n select: opts?.select?.join(\",\"),\n stega: stega ? \"1\" : undefined,\n })}`\n : `${base}/content-entries/${encodeURIComponent(slug)}${qs({\n depth: opts?.depth,\n select: opts?.select?.join(\",\"),\n perspective: draft ? \"draft\" : undefined,\n stega: draft && stega ? \"1\" : undefined,\n projectId: defaultProjectId,\n })}`;\n return unwrap<DeliveryEntry<T>>(await fetchJSON(url));\n },\n\n async listEntries<T = Record<string, unknown>>(opts?: ListEntriesOptions) {\n const url = `${base}/content-entries${qs({\n model: opts?.model,\n page: opts?.page,\n perPage: opts?.perPage,\n depth: opts?.depth,\n select: opts?.select?.join(\",\"),\n perspective: draft ? \"draft\" : undefined,\n stega: draft && stega ? \"1\" : undefined,\n projectId: defaultProjectId,\n })}`;\n return unwrap<DeliveryList<DeliveryEntry<T>>>(await fetchJSON(url));\n },\n\n async getPage(slug: string) {\n // The token names ONE page, and for that page the preview endpoint is the only read\n // that returns the DRAFT block tree — the paginated delivery list is published-only, so\n // an SSR draft render would otherwise be the live structure with draft field values.\n // Any other slug falls through to the published scan, which is what a token for /about\n // rendering the site's nav or footer pages must keep doing.\n if (draft && previewToken && tokenSlug(previewToken) === slug) {\n const url = `${apiUrl}/api/v1/preview/${encodeURIComponent(slug)}${qs({\n token: previewToken,\n stega: stega ? \"1\" : undefined,\n })}`;\n const d = unwrap<Record<string, unknown>>(await fetchJSON(url));\n return {\n slug: d.slug as string,\n title: d.title as string,\n metaTitle: (d.metaTitle as string | null) ?? null,\n metaDescription: (d.metaDescription as string | null) ?? null,\n metaJson: (d.metaJson as DeliveryPage[\"metaJson\"]) ?? null,\n blocks: (d.blockJson as DeliveryPage[\"blocks\"]) ?? [],\n // C3: the DRAFT field styles, present only when the page styles a field.\n ...(d.fieldStyles ? { fieldStyles: d.fieldStyles as NonNullable<DeliveryPage[\"fieldStyles\"]> } : {}),\n updatedAt: (d.updatedAt as string | null) ?? null,\n publishedAt: (d.publishedAt as string | null) ?? null,\n } satisfies DeliveryPage;\n }\n // No dedicated single-page delivery endpoint — scan the paginated list.\n let page = 1;\n for (;;) {\n const list = await this.listPages({ page, perPage: 100 });\n const found = list.items.find((p) => p.slug === slug);\n if (found) return found;\n if (!list.hasNextPage) return null;\n page += 1;\n }\n },\n\n async listPages(opts?: ListPagesOptions) {\n const url = `${base}/pages${qs({\n page: opts?.page,\n perPage: opts?.perPage,\n projectId: defaultProjectId,\n })}`;\n return unwrap<DeliveryList<DeliveryPage>>(await fetchJSON(url));\n },\n\n async listComponents() {\n // Same shape as getLayout: `?perspective=draft` alone is the key-scoped path (the server\n // ignores it for a published key), and a preview token is the page-scoped one.\n const url = `${base}/components${qs({\n perspective: draft ? \"draft\" : undefined,\n token: draft ? previewToken : undefined,\n projectId: defaultProjectId,\n })}`;\n return unwrap<DeliveryComponent[]>(await fetchJSON(url));\n },\n\n async getLayout() {\n const url = `${base}/layout${qs({ perspective: draft ? \"draft\" : undefined, projectId: defaultProjectId })}`;\n try {\n return unwrap<DeliveredLayout>(await fetchJSON(url));\n } catch (error) {\n if (error instanceof BetterCMSError && error.status === 404) return null;\n throw error;\n }\n },\n\n async listForms(opts?: { projectId?: string }) {\n const url = `${base}/forms${qs({\n projectId: opts?.projectId ?? defaultProjectId,\n })}`;\n return unwrap<{ items: DeliveryForm[]; turnstileSiteKey: string | null }>(\n await fetchJSON(url),\n );\n },\n\n tag(resource, id) {\n return id ? `bcms:${workspace}:${resource}:${id}` : `bcms:${workspace}:${resource}`;\n },\n };\n}\n","/**\n * Where a site's BetterCMS settings come from, the same way on every framework: explicit options,\n * then `<PREFIX>BCMS_*` env (each framework exposes env to the browser under its own prefix:\n * `PUBLIC_` for Astro and SvelteKit, `NEXT_PUBLIC_` for Next, `VITE_` for Vite React,\n * `NUXT_PUBLIC_` for Nuxt), then the build snapshot `bettercms pull-content` writes.\n *\n * Pure: the adapter passes its env and reads the snapshot (this module touches no filesystem).\n */\n\nexport const DEFAULT_MEDIA_URL = \"https://cdn.bettercms.ai\";\n\nexport interface BcmsSiteOptions {\n apiUrl?: string;\n workspace?: string;\n projectId?: string;\n mediaUrl?: string;\n studioUrl?: string;\n}\n\nexport interface BcmsSiteConfig {\n apiUrl: string;\n workspace: string;\n projectId?: string;\n mediaUrl: string;\n studioUrl?: string;\n}\n\nexport interface ResolveBcmsConfigInput {\n options?: BcmsSiteOptions;\n env?: Record<string, string | undefined>;\n /** Env prefixes, first match wins: `[\"NEXT_PUBLIC_\", \"\"]` reads NEXT_PUBLIC_BCMS_API_URL, then BCMS_API_URL. */\n prefixes: string[];\n /** The build snapshot's identity, read only when options and env both miss. */\n snapshot?: () => { apiUrl?: string; workspace?: string; projectId?: string };\n}\n\n/** The resolved settings, and which essentials are missing (the adapter words the error). */\nexport function resolveBcmsConfig(input: ResolveBcmsConfigInput): { config: BcmsSiteConfig; missing: (\"apiUrl\" | \"workspace\")[] } {\n const env = input.env ?? {};\n const fromEnv = (name: string) => {\n for (const p of input.prefixes) {\n const v = env[`${p}BCMS_${name}`];\n if (v) return v;\n }\n return undefined;\n };\n let snap: ReturnType<NonNullable<ResolveBcmsConfigInput[\"snapshot\"]>> | undefined;\n const snapshot = () => (snap ??= input.snapshot?.() ?? {});\n const o = input.options ?? {};\n const config: BcmsSiteConfig = {\n apiUrl: (o.apiUrl ?? fromEnv(\"API_URL\") ?? snapshot().apiUrl ?? \"\").replace(/\\/+$/, \"\"),\n workspace: o.workspace ?? fromEnv(\"WORKSPACE\") ?? snapshot().workspace ?? \"\",\n projectId: o.projectId ?? fromEnv(\"PROJECT_ID\") ?? snapshot().projectId,\n mediaUrl: (o.mediaUrl ?? fromEnv(\"MEDIA_URL\") ?? DEFAULT_MEDIA_URL).replace(/\\/+$/, \"\"),\n studioUrl: o.studioUrl ?? fromEnv(\"STUDIO_URL\"),\n };\n const missing = ([\"apiUrl\", \"workspace\"] as const).filter((k) => !config[k]);\n return { config, missing };\n}\n","/**\n * Draft mode, shared by every framework adapter: the signed cookie that says \"this visitor is\n * previewing\", the preview token's slug, and the redirect a draft route may follow.\n *\n * Web Crypto and `atob` only, so it runs in Node, edge runtimes and workers alike. Each adapter\n * mounts its own routes around these; the rules live here once.\n */\n\n/** The draft cookie every adapter sets: one name, so an editor link works on any framework. */\nexport const DRAFT_COOKIE = \"bcms-draft\";\n\nconst enc = new TextEncoder();\n\nfunction base64url(bytes: ArrayBuffer): string {\n const b = btoa(String.fromCharCode(...new Uint8Array(bytes)));\n return b.replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\nasync function hmac(value: string, secret: string): Promise<string> {\n const key = await crypto.subtle.importKey(\"raw\", enc.encode(secret), { name: \"HMAC\", hash: \"SHA-256\" }, false, [\"sign\"]);\n return base64url(await crypto.subtle.sign(\"HMAC\", key, enc.encode(value)));\n}\n\n/** Sign a value as `value.sig` for tamper-evident cookie storage (HMAC-SHA256, base64url). */\nexport async function signValue(value: string, secret: string): Promise<string> {\n return `${value}.${await hmac(value, secret)}`;\n}\n\n/** The original value of a signed one, or null when it was tampered with. */\nexport async function verifyValue(signed: string, secret: string): Promise<string | null> {\n const idx = signed.lastIndexOf(\".\");\n if (idx < 0) return null;\n const value = signed.slice(0, idx);\n const sig = signed.slice(idx + 1);\n const expected = await hmac(value, secret);\n if (sig.length !== expected.length) return null;\n let diff = 0;\n for (let i = 0; i < sig.length; i++) diff |= sig.charCodeAt(i) ^ expected.charCodeAt(i);\n return diff === 0 ? value : null;\n}\n\n/** Only same-origin relative paths: blocks \"//evil.com\", \"https://evil.com\" and \"/\\\\evil.com\". */\nexport function safeRedirectPath(to: string): string {\n return to.startsWith(\"/\") && !to.startsWith(\"//\") && !to.startsWith(\"/\\\\\") ? to : \"/\";\n}\n\n/** A preview token's `entrySlug`, read from its payload UNVERIFIED (the backend verifies it). */\nexport function decodeTokenSlug(token: string): string | null {\n try {\n const part = token.split(\".\")[1];\n if (!part) return null;\n // atob gives bytes as Latin-1: decoded as UTF-8, a slug like `über-uns` survives.\n const bytes = Uint8Array.from(atob(part.replace(/-/g, \"+\").replace(/_/g, \"/\")), (c) => c.charCodeAt(0));\n const json = JSON.parse(new TextDecoder().decode(bytes)) as { entrySlug?: string };\n return json.entrySlug ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Ask the backend whether a preview token is good: the backend is the signature authority, so a\n * draft route enables draft mode only on its yes.\n */\nexport async function previewTokenValid(apiUrl: string, token: string, fetchImpl: typeof fetch = fetch): Promise<boolean> {\n const slug = decodeTokenSlug(token);\n if (!slug) return false;\n const res = await fetchImpl(`${apiUrl.replace(/\\/+$/, \"\")}/api/v1/preview/${encodeURIComponent(slug)}?token=${encodeURIComponent(token)}`);\n await res.body?.cancel().catch(() => {});\n return res.ok;\n}\n\n/** The draft cookie's preview token from a `Cookie` header, or null when absent or tampered with. */\nexport async function draftTokenFrom(cookieHeader: string | null | undefined, secret: string | undefined): Promise<string | null> {\n if (!cookieHeader || !secret) return null;\n const m = new RegExp(`(?:^|;\\\\s*)${DRAFT_COOKIE}=([^;]+)`).exec(cookieHeader);\n if (!m) return null;\n try {\n return await verifyValue(decodeURIComponent(m[1]!), secret);\n } catch {\n return null;\n }\n}\n\n/**\n * The draft-enable route for any server that speaks Web `Request`/`Response` (SvelteKit `+server.ts`,\n * Nuxt via `toWebRequest`, Remix, Hono): `GET ?token=…&redirect=/x` checks the token with the\n * backend, sets the signed draft cookie and redirects. 401 on a refused token; 500 without a secret.\n */\nexport async function draftEnableResponse(request: Request, opts: { apiUrl: string; secret: string | undefined; fetchImpl?: typeof fetch }): Promise<Response> {\n const url = new URL(request.url);\n const token = url.searchParams.get(\"token\");\n if (!opts.secret) return new Response(\"BCMS_DRAFT_SECRET is not configured\", { status: 500 });\n if (!token) return new Response(\"Missing token\", { status: 400 });\n if (!(await previewTokenValid(opts.apiUrl, token, opts.fetchImpl))) return new Response(\"Invalid or expired token\", { status: 401 });\n const cookie = `${DRAFT_COOKIE}=${encodeURIComponent(await signValue(token, opts.secret))}; Path=/; HttpOnly; SameSite=Lax; Max-Age=86400${url.protocol === \"https:\" ? \"; Secure\" : \"\"}`;\n return new Response(null, { status: 302, headers: { Location: safeRedirectPath(url.searchParams.get(\"redirect\") ?? \"/\"), \"Set-Cookie\": cookie } });\n}\n\n/** Leave draft mode: clear the cookie and redirect (`?redirect=/x`, same-origin only). */\nexport function draftDisableResponse(request: Request): Response {\n const url = new URL(request.url);\n return new Response(null, {\n status: 302,\n headers: { Location: safeRedirectPath(url.searchParams.get(\"redirect\") ?? \"/\"), \"Set-Cookie\": `${DRAFT_COOKIE}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0` },\n });\n}\n"],"mappings":"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","names":[]}
|
package/dist/sections.js
CHANGED
|
@@ -233,7 +233,7 @@ function createClient(options) {
|
|
|
233
233
|
return {
|
|
234
234
|
perspective,
|
|
235
235
|
async getEntry(slug, opts) {
|
|
236
|
-
const url = draft && previewToken ? `${apiUrl}/api/v1/preview/${encodeURIComponent(slug)}${qs({
|
|
236
|
+
const url = draft && previewToken && tokenSlug(previewToken) === slug ? `${apiUrl}/api/v1/preview/${encodeURIComponent(slug)}${qs({
|
|
237
237
|
token: previewToken,
|
|
238
238
|
depth: opts?.depth,
|
|
239
239
|
select: opts?.select?.join(","),
|
package/dist/sections.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/retry.ts","../src/read-client.ts","../src/config.ts","../src/draft.ts","../src/sections.ts"],"sourcesContent":["import type { BetterCMSErrorCode } from \"@bettercms-ai/types\";\n\n/** Semantic error codes used by the SDK. */\nexport const ErrorCodes = {\n CONTENT_NOT_FOUND: \"CONTENT_NOT_FOUND\",\n UNAUTHORIZED: \"UNAUTHORIZED\",\n FORBIDDEN: \"FORBIDDEN\",\n RATE_LIMITED: \"RATE_LIMITED\",\n INTERNAL_ERROR: \"INTERNAL_ERROR\",\n VALIDATION_ERROR: \"VALIDATION_ERROR\",\n NETWORK_ERROR: \"NETWORK_ERROR\",\n ADMIN_NOT_IMPLEMENTED: \"ADMIN_NOT_IMPLEMENTED\",\n} as const;\n\nexport type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];\n\n/** Maps HTTP status codes to semantic error codes. */\nfunction statusToCode(status: number): BetterCMSErrorCode {\n if (status === 404) return ErrorCodes.CONTENT_NOT_FOUND;\n if (status === 401) return ErrorCodes.UNAUTHORIZED;\n if (status === 403) return ErrorCodes.FORBIDDEN;\n if (status === 429) return ErrorCodes.RATE_LIMITED;\n if (status >= 500) return ErrorCodes.INTERNAL_ERROR;\n if (status === 422) return ErrorCodes.VALIDATION_ERROR;\n return ErrorCodes.INTERNAL_ERROR;\n}\n\n/**\n * BetterCMSError — normalized SDK error with a semantic code.\n */\nexport class BetterCMSError extends Error {\n readonly status: number;\n readonly code: BetterCMSErrorCode;\n /**\n * Raw machine-readable `code` from the response body (e.g. \"PROJECT_DELETED\"),\n * when the API supplies one. Distinct from `code` (which is derived from the\n * HTTP status), this lets callers branch on a specific server condition that\n * shares a status with others (e.g. 409 slug-conflict vs 409 project-deleted).\n */\n readonly bodyCode?: string;\n /**\n * Per-field validation messages from a 400 `{ errors: { field: message } }` body.\n * The backend returns validation failures in this shape (not `message`/`error`), so\n * without this the SDK collapsed every validation 400 to a bare \"Bad Request\" with no\n * hint which field or rule failed (FLO-474). Mirrors the forms client's `fieldErrors`.\n */\n readonly fieldErrors?: Record<string, string>;\n /**\n * The parsed response body, when there was one.\n *\n * Several refusals carry STRUCTURED detail that neither `message` nor `fieldErrors` can hold,\n * and which is the whole point of the refusal: a component publish 409s with the full\n * `readiness` tuple (what evidence is missing, whether a human still has to approve it) and\n * 422s with the `issues` naming the Layouts it would break. Collapsing those to a sentence\n * leaves a caller able to report that something failed and not what to do about it.\n */\n readonly body?: unknown;\n\n constructor(\n message: string,\n status: number,\n code: BetterCMSErrorCode,\n bodyCode?: string,\n fieldErrors?: Record<string, string>,\n body?: unknown,\n ) {\n super(message);\n this.name = \"BetterCMSError\";\n this.status = status;\n this.code = code;\n this.bodyCode = bodyCode;\n this.fieldErrors = fieldErrors;\n this.body = body;\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, BetterCMSError);\n }\n }\n\n toJSON() {\n return {\n name: this.name,\n message: this.message,\n status: this.status,\n code: this.code,\n bodyCode: this.bodyCode,\n fieldErrors: this.fieldErrors,\n };\n }\n\n /**\n * Factory — creates a BetterCMSError from a failed fetch Response. Reads the body's\n * `message` (or `error`) for the human message and `code` for a machine-readable\n * condition surfaced as `bodyCode`. Validation failures arrive as `{ errors: {...} }`\n * (no `message`/`error`), so fall back to the first field error for the message and\n * expose the full map as `fieldErrors` (FLO-474).\n */\n static async from(res: Response): Promise<BetterCMSError> {\n let message = res.statusText || \"An error occurred\";\n let bodyCode: string | undefined;\n let fieldErrors: Record<string, string> | undefined;\n let code = statusToCode(res.status);\n let raw: unknown;\n try {\n const body = (await res.json()) as {\n message?: string;\n error?: string;\n code?: string;\n errors?: Record<string, string>;\n };\n if (body?.errors && typeof body.errors === \"object\") {\n fieldErrors = body.errors;\n code = ErrorCodes.VALIDATION_ERROR;\n }\n if (body?.message) message = body.message;\n else if (body?.error) message = body.error;\n else if (fieldErrors) {\n const [field, detail] = Object.entries(fieldErrors).find(\n ([, v]) => typeof v === \"string\" && v.length > 0,\n ) ?? [];\n if (field) message = `${field}: ${detail}`;\n }\n if (body?.code) bodyCode = body.code;\n raw = body;\n } catch {\n // ignore parse errors\n }\n return new BetterCMSError(message, res.status, code, bodyCode, fieldErrors, raw);\n }\n}\n","/**\n * Shared 429/503 backoff for every SDK client.\n *\n * ── Why this exists ─────────────────────────────────────────────────────────\n * The backend limiter (src/lib/ratelimit.ts, windowHit) is a FIXED 60-SECOND\n * WINDOW: 100 requests per workspace per endpoint, and a denial answers\n * `Retry-After: <seconds until the window resets>` — anything up to 60.\n *\n * Every client here used to climb a private ladder that topped out at 7 seconds\n * ([250,750,1500] in read-client, [1000,2000,4000] in admin/management) and none\n * of them read Retry-After. So all three retries landed INSIDE the same exhausted\n * window and a 429 was fatal roughly whenever it was thrown. The retry code looked\n * like resilience but could only ever recover from a denial raised in the final\n * seconds of a window.\n *\n * That is what failed customer builds: `action/fetch-content.mjs` spends ~65 of the\n * 100 `delivery-content-entries-list` slots pulling a large workspace, `npm run build`\n * starts seconds later in the SAME window, and the first per-route delivery read past\n * the ceiling killed the deploy with \"Too many requests. Please retry later.\"\n *\n * ── The rule ────────────────────────────────────────────────────────────────\n * Obey Retry-After when the server sends one, and let a single request outlast a\n * whole window. A build waiting 60s is slow; a build that dies is a broken product.\n */\n\n/** Statuses worth retrying. 429 = rate limited, 503 = limiter/backend unavailable. */\nexport const RETRY_STATUSES = [429, 503] as const;\n\n/** Retries after the first attempt. 3 retries × up to ~65s spans three full windows. */\nexport const MAX_RETRIES = 3;\n\n/**\n * Hard ceiling on a single wait, jitter included. Must exceed the limiter's 60s\n * window, or a denial raised at the very start of a window still cannot be waited\n * out — and must be finite, so a hostile or mis-set Retry-After cannot hang a build.\n */\nexport const MAX_DELAY_MS = 65_000;\n\n/** Fallback ladder when the server sends no Retry-After: 1s, 2s, 4s, 8s. */\nconst BASE_DELAY_MS = 1_000;\n\n/**\n * De-sync jitter, added to every wait.\n *\n * Deliberately a small FLAT amount, not a percentage. Requests denied by one window\n * only need to stop firing in the same millisecond — the server's counter serialises\n * them after that. A proportional jitter would add up to 15 seconds to a 60-second\n * wait for no benefit, and would push the total past MAX_DELAY_MS.\n */\nconst JITTER_MS = 250;\n\nexport function isRetryableStatus(status: number): boolean {\n return (RETRY_STATUSES as readonly number[]).includes(status);\n}\n\n/**\n * Parse `Retry-After`, which RFC 9110 allows in either form: delay-seconds\n * (\"47\") or an HTTP-date (\"Wed, 21 Oct 2015 07:28:00 GMT\"). Returns ms to wait,\n * or null when the header is absent or unparseable.\n */\nexport function retryAfterMs(res: Pick<Response, \"headers\">): number | null {\n const raw = res.headers?.get?.(\"retry-after\");\n if (!raw) return null;\n\n const seconds = Number(raw);\n if (Number.isFinite(seconds)) {\n return Math.min(Math.max(seconds, 0) * 1000, MAX_DELAY_MS);\n }\n\n const at = Date.parse(raw);\n if (!Number.isNaN(at)) {\n return Math.min(Math.max(0, at - Date.now()), MAX_DELAY_MS);\n }\n return null;\n}\n\n/**\n * How long to wait before retry `attempt` (0-based).\n *\n * Server instruction wins; otherwise exponential. Jitter is added in both cases:\n * when a build's requests are all denied by the same window they would otherwise\n * wake in lockstep at the reset and re-exhaust it immediately (thundering herd).\n */\nexport function backoffMs(attempt: number, res?: Pick<Response, \"headers\">): number {\n const fromServer = res ? retryAfterMs(res) : null;\n // Base is capped with the jitter's headroom already reserved, so the total never\n // exceeds MAX_DELAY_MS AND the jitter is never clamped away at the ceiling — which\n // would put every capped caller back in lockstep, the exact thing jitter prevents.\n const base = Math.min(fromServer ?? BASE_DELAY_MS * 2 ** attempt, MAX_DELAY_MS - JITTER_MS);\n // Jitter is ADDED, never subtracted: waking before the server's Retry-After elapses\n // just burns a retry on a window that has not reset.\n return base + Math.random() * JITTER_MS;\n}\n\nexport const sleep = (ms: number): Promise<void> =>\n new Promise((resolve) => setTimeout(resolve, ms));\n","/**\n * createClient — framework-agnostic typed read client for the Delivery API.\n *\n * Shared by @bettercms-ai/astro and @bettercms-ai/next so the fetch/typing/retry\n * logic lives in one place. Read-only; for schema authoring use the\n * Management client, for sessions use the Admin client.\n *\n * const client = createClient({ apiUrl, workspace, apiKey });\n * await client.getPage(\"home\");\n * await client.listEntries({ model: \"post\", perPage: 50 });\n */\nimport type {\n DeliveryComponent,\n DeliveryEntry,\n DeliveryList,\n DeliveryPage,\n DeliveredLayout,\n Perspective,\n} from \"@bettercms-ai/types\";\nimport { BetterCMSError } from \"./errors.js\";\nimport { MAX_RETRIES, backoffMs, isRetryableStatus, sleep } from \"./retry.js\";\nimport type { DeliveryForm } from \"./forms.js\";\n\nconst DEFAULT_TIMEOUT = 10_000;\n\nexport interface CreateClientOptions {\n /** Backend base, e.g. `https://api.bettercms.ai` (no trailing slash needed). */\n apiUrl: string;\n /** Workspace slug. */\n workspace: string;\n /** Delivery API key. Use a `content:read:draft` key for drafts. */\n apiKey?: string;\n /**\n * The project every read is addressed to. A workspace holds many projects whose slugs\n * collide (each has a `blog` model and a `home` page), so a site names its own; published\n * reads need no key for it. BetterCMS hosting supplies `PUBLIC_BCMS_PROJECT_ID`.\n */\n projectId?: string;\n /** `published` (default) or `drafts`. */\n perspective?: Perspective;\n /**\n * Preview-token JWT for token-based single-document draft reads via the\n * `/preview/:slug` endpoint. Only used when `perspective === \"drafts\"`.\n */\n previewToken?: string;\n /**\n * Embed invisible visual-editing provenance in draft reads, so a preview build can\n * deep-link each rendered value back to its editor. Requires `perspective: \"drafts\"`,\n * and then either a `previewToken` or a `content:read:draft` key. Ignored on published\n * reads — the payload must never reach a production build.\n *\n * **Defaults to `true` on `perspective: \"drafts\"`** (since 1.15.0). Provenance is the whole\n * reason the drafts perspective exists in a preview build, and an option nobody knew to pass\n * meant every such build rendered anonymous strings: the editor could bind them only by\n * guessing at their text. It stays OFF on every other perspective, unconditionally, so a\n * published build cannot acquire a payload by accident — pass `stega: false` to opt a draft\n * read out (a diff or a checksum over draft content is the case that wants it).\n */\n stega?: boolean;\n /** Per-attempt timeout in ms. Defaults to 10000. */\n timeout?: number;\n /**\n * Collapse identical GETs to one network round-trip for the life of this client.\n * Default false.\n *\n * Only correct where the content is a POINT-IN-TIME SNAPSHOT — i.e. a static build,\n * where every route renders from the same published dataset. `@bettercms-ai/astro`\n * turns this on automatically when Astro's `command === \"build\"` and never for `dev`,\n * SSR or the drafts perspective, where a repeat read must see the current row.\n *\n * A static build without it re-fetches the same nav/settings/collection once PER ROUTE.\n * That is what made a large site exhaust the delivery rate limit: hundreds of identical\n * requests, and `getPage()` re-scanning every page of the paginated list each time it is\n * called.\n */\n cache?: boolean;\n}\n\nexport interface ListEntriesOptions {\n /** Filter by content-model slug. */\n model?: string;\n page?: number;\n perPage?: number;\n /** Reference hydration depth (lists cap at 1). */\n depth?: 0 | 1;\n /** Projection — field names to return. */\n select?: string[];\n}\n\nexport interface GetEntryOptions {\n depth?: 0 | 1 | 2;\n select?: string[];\n}\n\nexport interface ListPagesOptions {\n page?: number;\n perPage?: number;\n}\n\nexport interface BetterCMSReadClient {\n readonly perspective: Perspective;\n getEntry<T = Record<string, unknown>>(\n slug: string,\n opts?: GetEntryOptions,\n ): Promise<DeliveryEntry<T>>;\n listEntries<T = Record<string, unknown>>(\n opts?: ListEntriesOptions,\n ): Promise<DeliveryList<DeliveryEntry<T>>>;\n getPage(slug: string): Promise<DeliveryPage | null>;\n listPages(opts?: ListPagesOptions): Promise<DeliveryList<DeliveryPage>>;\n /**\n * The workspace's reusable component definitions, for resolving `component` blocks.\n * Under `perspective: \"drafts\"` this is the DRAFT definition — what makes an SSR render\n * of a draft page look like the page the editor is showing.\n */\n listComponents(): Promise<DeliveryComponent[]>;\n /** Fetch the project-scoped Global Layout for the active perspective. */\n getLayout(): Promise<DeliveredLayout | null>;\n listForms(opts?: {\n projectId?: string;\n }): Promise<{ items: DeliveryForm[]; turnstileSiteKey: string | null }>;\n /** Build a deterministic Next/cache tag for a resource. */\n tag(resource: \"entry\" | \"page\" | \"forms\", id?: string): string;\n}\n\n/** Drop undefined values, stringify the rest into a query string. */\nfunction qs(params: Record<string, string | number | undefined>): string {\n const sp = new URLSearchParams();\n for (const [k, v] of Object.entries(params)) {\n if (v !== undefined && v !== \"\") sp.set(k, String(v));\n }\n const s = sp.toString();\n return s ? `?${s}` : \"\";\n}\n\n/**\n * Unwrap a `{ data: ... }` list/legacy envelope when present. A bare entry is\n * already the payload (it has `id`/`slug` and its own `data` field), so we only\n * unwrap when no payload markers are at the top level.\n */\nfunction unwrap<T>(json: unknown): T {\n if (json && typeof json === \"object\") {\n const o = json as Record<string, unknown>;\n const isPayload = \"id\" in o || \"slug\" in o || \"items\" in o;\n if (!isPayload && \"data\" in o) return o.data as T;\n }\n return json as T;\n}\n\n/**\n * The `entrySlug` claim of a preview-token JWT, UNVERIFIED.\n *\n * The backend is the signature authority — this read only decides WHICH url to call, and a\n * forged claim buys nothing: `/preview/:slug` re-verifies and 401s. Kept private to this file\n * (`@bettercms-ai/astro` has its own copy for the same reason: neither package may depend on\n * the other).\n */\nfunction tokenSlug(token: string): string | null {\n try {\n const part = token.split(\".\")[1];\n if (!part) return null;\n const json = atob(part.replace(/-/g, \"+\").replace(/_/g, \"/\"));\n return (JSON.parse(json) as { entrySlug?: string }).entrySlug ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Per-URL request collapsing.\n *\n * Two jobs, both needed: coalesce requests that are IN FLIGHT (Astro renders routes\n * concurrently, so identical GETs overlap) and retain the ones that have COMPLETED\n * (routes render in batches, so the same GET recurs long after the first settled).\n *\n * Values are cloned on the way out. Callers get a fresh object graph, so a template that\n * sorts `items` in place — an easy thing to write and a brutal thing to debug — cannot\n * corrupt what every other route sees. Cloning costs memory bandwidth; the request it\n * replaces costs a network round-trip and a slot in the rate-limit window.\n *\n * A rejected request is never retained, so a transient failure does not become permanent.\n */\nfunction createRequestCache() {\n const inflight = new Map<string, Promise<unknown>>();\n const settled = new Map<string, unknown>();\n\n const copy = <T>(value: T): T =>\n typeof structuredClone === \"function\" ? structuredClone(value) : (JSON.parse(JSON.stringify(value)) as T);\n\n return async function collapse<T>(url: string, fetcher: () => Promise<T>): Promise<T> {\n if (settled.has(url)) return copy(settled.get(url) as T);\n\n const pending = inflight.get(url) as Promise<T> | undefined;\n if (pending) return copy(await pending);\n\n const run = (async () => {\n try {\n const value = await fetcher();\n settled.set(url, value);\n return value;\n } finally {\n inflight.delete(url);\n }\n })();\n inflight.set(url, run);\n return copy(await run);\n };\n}\n\nexport function createClient(options: CreateClientOptions): BetterCMSReadClient {\n const apiUrl = options.apiUrl.replace(/\\/+$/, \"\");\n const { workspace, apiKey, perspective = \"published\", previewToken } = options;\n const defaultProjectId = options.projectId;\n const timeout = options.timeout ?? DEFAULT_TIMEOUT;\n const base = `${apiUrl}/api/v1/delivery/${workspace}`;\n\n const headers = (): Record<string, string> => {\n const h: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (apiKey) h[\"X-API-Key\"] = apiKey;\n return h;\n };\n\n // Drafts must never be collapsed even if a caller asks: a preview surface re-reads to\n // see the edit that just happened, and serving it a retained copy would show stale content.\n const collapse =\n options.cache && perspective === \"published\" ? createRequestCache() : undefined;\n\n const fetchJSON = <T>(url: string): Promise<T> =>\n collapse ? collapse(url, () => request<T>(url)) : request<T>(url);\n\n async function request<T>(url: string): Promise<T> {\n let lastError: unknown;\n for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeout);\n try {\n const res = await globalThis.fetch(url, {\n headers: headers(),\n signal: controller.signal,\n });\n clearTimeout(timer);\n if (res.ok) return (await res.json()) as T;\n\n const retryable = attempt < MAX_RETRIES && isRetryableStatus(res.status);\n if (!retryable) throw await BetterCMSError.from(res);\n // Honour Retry-After: the limiter's window is up to 60s, far longer than\n // any fixed ladder, so the server's own number is the only one that works.\n await sleep(backoffMs(attempt, res));\n } catch (err) {\n clearTimeout(timer);\n if (err instanceof Error && err.name === \"AbortError\") {\n throw new BetterCMSError(\"Request timeout\", 408, \"NETWORK_ERROR\");\n }\n if (err instanceof BetterCMSError) throw err;\n lastError = err;\n if (attempt < MAX_RETRIES) {\n await sleep(backoffMs(attempt));\n continue;\n }\n throw new BetterCMSError(\n `Network error: ${err instanceof Error ? err.message : String(err)}`,\n 0,\n \"NETWORK_ERROR\",\n );\n }\n }\n throw lastError ?? new BetterCMSError(\"Unreachable\", 0, \"NETWORK_ERROR\");\n }\n\n const draft = perspective === \"drafts\";\n /**\n * Resolved ONCE, here, so the three call sites below cannot drift apart — they did exactly\n * that before (`options.stega` alone on the preview branch, `draft && options.stega` on the\n * other two), and a default expressed three times is a default that will eventually be two.\n * `??` and not `||`: an explicit `stega: false` on a draft read must win.\n */\n const stega = options.stega ?? draft;\n\n return {\n perspective,\n\n async getEntry<T = Record<string, unknown>>(slug: string, opts?: GetEntryOptions) {\n // Token-based draft reads go through the dedicated preview endpoint.\n // Without a token the drafts perspective is served by delivery itself, which needs\n // to be TOLD — `?perspective=draft` (honoured only for a `content:read:draft` key).\n // Until this branch sent it, `perspective: \"drafts\"` was silently inert here, and\n // with it `stega`, so a static build could never obtain provenance without minting\n // a preview token per entry.\n const url =\n draft && previewToken\n ? `${apiUrl}/api/v1/preview/${encodeURIComponent(slug)}${qs({\n token: previewToken,\n depth: opts?.depth,\n select: opts?.select?.join(\",\"),\n stega: stega ? \"1\" : undefined,\n })}`\n : `${base}/content-entries/${encodeURIComponent(slug)}${qs({\n depth: opts?.depth,\n select: opts?.select?.join(\",\"),\n perspective: draft ? \"draft\" : undefined,\n stega: draft && stega ? \"1\" : undefined,\n projectId: defaultProjectId,\n })}`;\n return unwrap<DeliveryEntry<T>>(await fetchJSON(url));\n },\n\n async listEntries<T = Record<string, unknown>>(opts?: ListEntriesOptions) {\n const url = `${base}/content-entries${qs({\n model: opts?.model,\n page: opts?.page,\n perPage: opts?.perPage,\n depth: opts?.depth,\n select: opts?.select?.join(\",\"),\n perspective: draft ? \"draft\" : undefined,\n stega: draft && stega ? \"1\" : undefined,\n projectId: defaultProjectId,\n })}`;\n return unwrap<DeliveryList<DeliveryEntry<T>>>(await fetchJSON(url));\n },\n\n async getPage(slug: string) {\n // The token names ONE page, and for that page the preview endpoint is the only read\n // that returns the DRAFT block tree — the paginated delivery list is published-only, so\n // an SSR draft render would otherwise be the live structure with draft field values.\n // Any other slug falls through to the published scan, which is what a token for /about\n // rendering the site's nav or footer pages must keep doing.\n if (draft && previewToken && tokenSlug(previewToken) === slug) {\n const url = `${apiUrl}/api/v1/preview/${encodeURIComponent(slug)}${qs({\n token: previewToken,\n stega: stega ? \"1\" : undefined,\n })}`;\n const d = unwrap<Record<string, unknown>>(await fetchJSON(url));\n return {\n slug: d.slug as string,\n title: d.title as string,\n metaTitle: (d.metaTitle as string | null) ?? null,\n metaDescription: (d.metaDescription as string | null) ?? null,\n metaJson: (d.metaJson as DeliveryPage[\"metaJson\"]) ?? null,\n blocks: (d.blockJson as DeliveryPage[\"blocks\"]) ?? [],\n // C3: the DRAFT field styles, present only when the page styles a field.\n ...(d.fieldStyles ? { fieldStyles: d.fieldStyles as NonNullable<DeliveryPage[\"fieldStyles\"]> } : {}),\n updatedAt: (d.updatedAt as string | null) ?? null,\n publishedAt: (d.publishedAt as string | null) ?? null,\n } satisfies DeliveryPage;\n }\n // No dedicated single-page delivery endpoint — scan the paginated list.\n let page = 1;\n for (;;) {\n const list = await this.listPages({ page, perPage: 100 });\n const found = list.items.find((p) => p.slug === slug);\n if (found) return found;\n if (!list.hasNextPage) return null;\n page += 1;\n }\n },\n\n async listPages(opts?: ListPagesOptions) {\n const url = `${base}/pages${qs({\n page: opts?.page,\n perPage: opts?.perPage,\n projectId: defaultProjectId,\n })}`;\n return unwrap<DeliveryList<DeliveryPage>>(await fetchJSON(url));\n },\n\n async listComponents() {\n // Same shape as getLayout: `?perspective=draft` alone is the key-scoped path (the server\n // ignores it for a published key), and a preview token is the page-scoped one.\n const url = `${base}/components${qs({\n perspective: draft ? \"draft\" : undefined,\n token: draft ? previewToken : undefined,\n projectId: defaultProjectId,\n })}`;\n return unwrap<DeliveryComponent[]>(await fetchJSON(url));\n },\n\n async getLayout() {\n const url = `${base}/layout${qs({ perspective: draft ? \"draft\" : undefined, projectId: defaultProjectId })}`;\n try {\n return unwrap<DeliveredLayout>(await fetchJSON(url));\n } catch (error) {\n if (error instanceof BetterCMSError && error.status === 404) return null;\n throw error;\n }\n },\n\n async listForms(opts?: { projectId?: string }) {\n const url = `${base}/forms${qs({\n projectId: opts?.projectId ?? defaultProjectId,\n })}`;\n return unwrap<{ items: DeliveryForm[]; turnstileSiteKey: string | null }>(\n await fetchJSON(url),\n );\n },\n\n tag(resource, id) {\n return id ? `bcms:${workspace}:${resource}:${id}` : `bcms:${workspace}:${resource}`;\n },\n };\n}\n","/**\n * Where a site's BetterCMS settings come from, the same way on every framework: explicit options,\n * then `<PREFIX>BCMS_*` env (each framework exposes env to the browser under its own prefix:\n * `PUBLIC_` for Astro and SvelteKit, `NEXT_PUBLIC_` for Next, `VITE_` for Vite React,\n * `NUXT_PUBLIC_` for Nuxt), then the build snapshot `bettercms pull-content` writes.\n *\n * Pure: the adapter passes its env and reads the snapshot (this module touches no filesystem).\n */\n\nexport const DEFAULT_MEDIA_URL = \"https://cdn.bettercms.ai\";\n\nexport interface BcmsSiteOptions {\n apiUrl?: string;\n workspace?: string;\n projectId?: string;\n mediaUrl?: string;\n studioUrl?: string;\n}\n\nexport interface BcmsSiteConfig {\n apiUrl: string;\n workspace: string;\n projectId?: string;\n mediaUrl: string;\n studioUrl?: string;\n}\n\nexport interface ResolveBcmsConfigInput {\n options?: BcmsSiteOptions;\n env?: Record<string, string | undefined>;\n /** Env prefixes, first match wins: `[\"NEXT_PUBLIC_\", \"\"]` reads NEXT_PUBLIC_BCMS_API_URL, then BCMS_API_URL. */\n prefixes: string[];\n /** The build snapshot's identity, read only when options and env both miss. */\n snapshot?: () => { apiUrl?: string; workspace?: string; projectId?: string };\n}\n\n/** The resolved settings, and which essentials are missing (the adapter words the error). */\nexport function resolveBcmsConfig(input: ResolveBcmsConfigInput): { config: BcmsSiteConfig; missing: (\"apiUrl\" | \"workspace\")[] } {\n const env = input.env ?? {};\n const fromEnv = (name: string) => {\n for (const p of input.prefixes) {\n const v = env[`${p}BCMS_${name}`];\n if (v) return v;\n }\n return undefined;\n };\n let snap: ReturnType<NonNullable<ResolveBcmsConfigInput[\"snapshot\"]>> | undefined;\n const snapshot = () => (snap ??= input.snapshot?.() ?? {});\n const o = input.options ?? {};\n const config: BcmsSiteConfig = {\n apiUrl: (o.apiUrl ?? fromEnv(\"API_URL\") ?? snapshot().apiUrl ?? \"\").replace(/\\/+$/, \"\"),\n workspace: o.workspace ?? fromEnv(\"WORKSPACE\") ?? snapshot().workspace ?? \"\",\n projectId: o.projectId ?? fromEnv(\"PROJECT_ID\") ?? snapshot().projectId,\n mediaUrl: (o.mediaUrl ?? fromEnv(\"MEDIA_URL\") ?? DEFAULT_MEDIA_URL).replace(/\\/+$/, \"\"),\n studioUrl: o.studioUrl ?? fromEnv(\"STUDIO_URL\"),\n };\n const missing = ([\"apiUrl\", \"workspace\"] as const).filter((k) => !config[k]);\n return { config, missing };\n}\n","/**\n * Draft mode, shared by every framework adapter: the signed cookie that says \"this visitor is\n * previewing\", the preview token's slug, and the redirect a draft route may follow.\n *\n * Web Crypto and `atob` only, so it runs in Node, edge runtimes and workers alike. Each adapter\n * mounts its own routes around these; the rules live here once.\n */\n\n/** The draft cookie every adapter sets: one name, so an editor link works on any framework. */\nexport const DRAFT_COOKIE = \"bcms-draft\";\n\nconst enc = new TextEncoder();\n\nfunction base64url(bytes: ArrayBuffer): string {\n const b = btoa(String.fromCharCode(...new Uint8Array(bytes)));\n return b.replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\nasync function hmac(value: string, secret: string): Promise<string> {\n const key = await crypto.subtle.importKey(\"raw\", enc.encode(secret), { name: \"HMAC\", hash: \"SHA-256\" }, false, [\"sign\"]);\n return base64url(await crypto.subtle.sign(\"HMAC\", key, enc.encode(value)));\n}\n\n/** Sign a value as `value.sig` for tamper-evident cookie storage (HMAC-SHA256, base64url). */\nexport async function signValue(value: string, secret: string): Promise<string> {\n return `${value}.${await hmac(value, secret)}`;\n}\n\n/** The original value of a signed one, or null when it was tampered with. */\nexport async function verifyValue(signed: string, secret: string): Promise<string | null> {\n const idx = signed.lastIndexOf(\".\");\n if (idx < 0) return null;\n const value = signed.slice(0, idx);\n const sig = signed.slice(idx + 1);\n const expected = await hmac(value, secret);\n if (sig.length !== expected.length) return null;\n let diff = 0;\n for (let i = 0; i < sig.length; i++) diff |= sig.charCodeAt(i) ^ expected.charCodeAt(i);\n return diff === 0 ? value : null;\n}\n\n/** Only same-origin relative paths: blocks \"//evil.com\", \"https://evil.com\" and \"/\\\\evil.com\". */\nexport function safeRedirectPath(to: string): string {\n return to.startsWith(\"/\") && !to.startsWith(\"//\") && !to.startsWith(\"/\\\\\") ? to : \"/\";\n}\n\n/** A preview token's `entrySlug`, read from its payload UNVERIFIED (the backend verifies it). */\nexport function decodeTokenSlug(token: string): string | null {\n try {\n const part = token.split(\".\")[1];\n if (!part) return null;\n // atob gives bytes as Latin-1: decoded as UTF-8, a slug like `über-uns` survives.\n const bytes = Uint8Array.from(atob(part.replace(/-/g, \"+\").replace(/_/g, \"/\")), (c) => c.charCodeAt(0));\n const json = JSON.parse(new TextDecoder().decode(bytes)) as { entrySlug?: string };\n return json.entrySlug ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Ask the backend whether a preview token is good: the backend is the signature authority, so a\n * draft route enables draft mode only on its yes.\n */\nexport async function previewTokenValid(apiUrl: string, token: string, fetchImpl: typeof fetch = fetch): Promise<boolean> {\n const slug = decodeTokenSlug(token);\n if (!slug) return false;\n const res = await fetchImpl(`${apiUrl.replace(/\\/+$/, \"\")}/api/v1/preview/${encodeURIComponent(slug)}?token=${encodeURIComponent(token)}`);\n await res.body?.cancel().catch(() => {});\n return res.ok;\n}\n\n/** The draft cookie's preview token from a `Cookie` header, or null when absent or tampered with. */\nexport async function draftTokenFrom(cookieHeader: string | null | undefined, secret: string | undefined): Promise<string | null> {\n if (!cookieHeader || !secret) return null;\n const m = new RegExp(`(?:^|;\\\\s*)${DRAFT_COOKIE}=([^;]+)`).exec(cookieHeader);\n if (!m) return null;\n try {\n return await verifyValue(decodeURIComponent(m[1]!), secret);\n } catch {\n return null;\n }\n}\n\n/**\n * The draft-enable route for any server that speaks Web `Request`/`Response` (SvelteKit `+server.ts`,\n * Nuxt via `toWebRequest`, Remix, Hono): `GET ?token=…&redirect=/x` checks the token with the\n * backend, sets the signed draft cookie and redirects. 401 on a refused token; 500 without a secret.\n */\nexport async function draftEnableResponse(request: Request, opts: { apiUrl: string; secret: string | undefined; fetchImpl?: typeof fetch }): Promise<Response> {\n const url = new URL(request.url);\n const token = url.searchParams.get(\"token\");\n if (!opts.secret) return new Response(\"BCMS_DRAFT_SECRET is not configured\", { status: 500 });\n if (!token) return new Response(\"Missing token\", { status: 400 });\n if (!(await previewTokenValid(opts.apiUrl, token, opts.fetchImpl))) return new Response(\"Invalid or expired token\", { status: 401 });\n const cookie = `${DRAFT_COOKIE}=${encodeURIComponent(await signValue(token, opts.secret))}; Path=/; HttpOnly; SameSite=Lax; Max-Age=86400${url.protocol === \"https:\" ? \"; Secure\" : \"\"}`;\n return new Response(null, { status: 302, headers: { Location: safeRedirectPath(url.searchParams.get(\"redirect\") ?? \"/\"), \"Set-Cookie\": cookie } });\n}\n\n/** Leave draft mode: clear the cookie and redirect (`?redirect=/x`, same-origin only). */\nexport function draftDisableResponse(request: Request): Response {\n const url = new URL(request.url);\n return new Response(null, {\n status: 302,\n headers: { Location: safeRedirectPath(url.searchParams.get(\"redirect\") ?? \"/\"), \"Set-Cookie\": `${DRAFT_COOKIE}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0` },\n });\n}\n","/// <reference lib=\"dom\" />\n/**\n * A published page's sections as server-rendered HTML, for a front end that renders its own\n * document (SvelteKit, Nuxt, any server): the hosted renderer's markup, its scoped stylesheet and\n * the runtime its forms, sliders and tabs need (`GET /api/v1/sites/fragment/:projectId/:slug`).\n * One renderer for every site, so the markup is the hosted site's and the editor's.\n */\nimport { BetterCMSError, ErrorCodes } from \"./errors.js\";\nimport { createClient, type BetterCMSReadClient } from \"./read-client.js\";\nimport { resolveBcmsConfig, type BcmsSiteConfig, type BcmsSiteOptions } from \"./config.js\";\nimport { draftDisableResponse, draftEnableResponse, draftTokenFrom } from \"./draft.js\";\n\nexport interface SectionsFragment {\n html: string;\n css: string;\n scripts: string;\n}\n\n/** Fetch a page's sections (`home` is `/`). Null when the page is not published. */\nexport async function fetchSections(\n opts: { apiUrl: string; projectId: string; slug: string; fetch?: typeof fetch },\n): Promise<SectionsFragment | null> {\n const doFetch = opts.fetch ?? fetch;\n const url = `${opts.apiUrl.replace(/\\/+$/, \"\")}/api/v1/sites/fragment/${encodeURIComponent(opts.projectId)}/${encodeURIComponent(opts.slug || \"home\")}`;\n const res = await doFetch(url, { headers: { accept: \"application/json\" } });\n if (res.status === 404) return null;\n if (!res.ok) throw new BetterCMSError(`Sections for \"${opts.slug}\" could not be read (${res.status})`, res.status, res.status === 403 ? ErrorCodes.FORBIDDEN : ErrorCodes.INTERNAL_ERROR);\n return ((await res.json()) as { data: SectionsFragment }).data;\n}\n\n/** The fragment as one string for `{@html}` / `v-html`: its stylesheet, its markup, its scripts. */\nexport function sectionsMarkup(fragment: SectionsFragment | null | undefined): string {\n if (!fragment) return \"\";\n return `<style>${fragment.css}</style>${fragment.html}${fragment.scripts}`;\n}\n\n/**\n * Run the scripts inside `root`. A server-rendered first load runs them itself; a client-side\n * navigation inserts the markup with innerHTML, where a <script> never runs, so the forms,\n * sliders and tabs of the new page would be dead. Each script runs once per element.\n */\nexport function runSectionScripts(root: Element): void {\n for (const old of Array.from(root.querySelectorAll(\"script\"))) {\n if (old.hasAttribute(\"data-bcms-ran\")) continue;\n const fresh = document.createElement(\"script\");\n for (const attr of Array.from(old.attributes)) fresh.setAttribute(attr.name, attr.value);\n fresh.setAttribute(\"data-bcms-ran\", \"\");\n fresh.textContent = old.textContent;\n old.replaceWith(fresh);\n }\n}\n\n\nexport interface BcmsSite {\n config: BcmsSiteConfig;\n /** The read client for a request: drafts while its draft cookie holds a valid token. */\n client(request?: Request): Promise<BetterCMSReadClient>;\n /** A published page's sections (`home` is `/`); pass the framework's `fetch` on the server. */\n sections(slug: string, fetchImpl?: typeof fetch): Promise<SectionsFragment | null>;\n /** GET handler for the draft path the editor opens (`/api/draft`). */\n draftEnable(request: Request): Promise<Response>;\n /** GET handler that leaves draft mode. */\n draftDisable(request: Request): Response;\n}\n\n/**\n * Everything a server-rendered site needs from BetterCMS, built once from its settings: the\n * precedence every adapter shares (options, `<PREFIX>BCMS_*`, `BCMS_*`), the read client per\n * request, the sections fragment and the draft routes. The SvelteKit and Nuxt adapters are this\n * with their env prefix.\n */\nexport function createBcmsSite(\n options: BcmsSiteOptions & { env?: Record<string, string | undefined>; draftSecret?: string; prefixes: string[]; adapter: string },\n): BcmsSite {\n const env = options.env ?? {};\n const { config, missing } = resolveBcmsConfig({ options, env, prefixes: [...options.prefixes, \"\"] });\n if (missing.length > 0) {\n const names = missing.map((m) => `${options.prefixes[0]}BCMS_${m === \"apiUrl\" ? \"API_URL\" : \"WORKSPACE\"}`).join(\" and \");\n throw new Error(`[${options.adapter}] Missing ${names}: set them, or pass them to createBcms().`);\n }\n const secret = options.draftSecret ?? env.BCMS_DRAFT_SECRET;\n return {\n config,\n async client(request) {\n const token = request ? await draftTokenFrom(request.headers.get(\"cookie\"), secret) : null;\n return createClient({\n apiUrl: config.apiUrl,\n workspace: config.workspace,\n projectId: config.projectId,\n ...(token ? { perspective: \"drafts\" as const, previewToken: token } : {}),\n });\n },\n sections(slug, fetchImpl) {\n if (!config.projectId) throw new Error(`[${options.adapter}] Sections need the project: set ${options.prefixes[0]}BCMS_PROJECT_ID.`);\n return fetchSections({ apiUrl: config.apiUrl, projectId: config.projectId, slug, fetch: fetchImpl });\n },\n draftEnable: (request) => draftEnableResponse(request, { apiUrl: config.apiUrl, secret }),\n draftDisable: draftDisableResponse,\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/retry.ts","../src/read-client.ts","../src/config.ts","../src/draft.ts","../src/sections.ts"],"sourcesContent":["import type { BetterCMSErrorCode } from \"@bettercms-ai/types\";\n\n/** Semantic error codes used by the SDK. */\nexport const ErrorCodes = {\n CONTENT_NOT_FOUND: \"CONTENT_NOT_FOUND\",\n UNAUTHORIZED: \"UNAUTHORIZED\",\n FORBIDDEN: \"FORBIDDEN\",\n RATE_LIMITED: \"RATE_LIMITED\",\n INTERNAL_ERROR: \"INTERNAL_ERROR\",\n VALIDATION_ERROR: \"VALIDATION_ERROR\",\n NETWORK_ERROR: \"NETWORK_ERROR\",\n ADMIN_NOT_IMPLEMENTED: \"ADMIN_NOT_IMPLEMENTED\",\n} as const;\n\nexport type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];\n\n/** Maps HTTP status codes to semantic error codes. */\nfunction statusToCode(status: number): BetterCMSErrorCode {\n if (status === 404) return ErrorCodes.CONTENT_NOT_FOUND;\n if (status === 401) return ErrorCodes.UNAUTHORIZED;\n if (status === 403) return ErrorCodes.FORBIDDEN;\n if (status === 429) return ErrorCodes.RATE_LIMITED;\n if (status >= 500) return ErrorCodes.INTERNAL_ERROR;\n if (status === 422) return ErrorCodes.VALIDATION_ERROR;\n return ErrorCodes.INTERNAL_ERROR;\n}\n\n/**\n * BetterCMSError — normalized SDK error with a semantic code.\n */\nexport class BetterCMSError extends Error {\n readonly status: number;\n readonly code: BetterCMSErrorCode;\n /**\n * Raw machine-readable `code` from the response body (e.g. \"PROJECT_DELETED\"),\n * when the API supplies one. Distinct from `code` (which is derived from the\n * HTTP status), this lets callers branch on a specific server condition that\n * shares a status with others (e.g. 409 slug-conflict vs 409 project-deleted).\n */\n readonly bodyCode?: string;\n /**\n * Per-field validation messages from a 400 `{ errors: { field: message } }` body.\n * The backend returns validation failures in this shape (not `message`/`error`), so\n * without this the SDK collapsed every validation 400 to a bare \"Bad Request\" with no\n * hint which field or rule failed (FLO-474). Mirrors the forms client's `fieldErrors`.\n */\n readonly fieldErrors?: Record<string, string>;\n /**\n * The parsed response body, when there was one.\n *\n * Several refusals carry STRUCTURED detail that neither `message` nor `fieldErrors` can hold,\n * and which is the whole point of the refusal: a component publish 409s with the full\n * `readiness` tuple (what evidence is missing, whether a human still has to approve it) and\n * 422s with the `issues` naming the Layouts it would break. Collapsing those to a sentence\n * leaves a caller able to report that something failed and not what to do about it.\n */\n readonly body?: unknown;\n\n constructor(\n message: string,\n status: number,\n code: BetterCMSErrorCode,\n bodyCode?: string,\n fieldErrors?: Record<string, string>,\n body?: unknown,\n ) {\n super(message);\n this.name = \"BetterCMSError\";\n this.status = status;\n this.code = code;\n this.bodyCode = bodyCode;\n this.fieldErrors = fieldErrors;\n this.body = body;\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, BetterCMSError);\n }\n }\n\n toJSON() {\n return {\n name: this.name,\n message: this.message,\n status: this.status,\n code: this.code,\n bodyCode: this.bodyCode,\n fieldErrors: this.fieldErrors,\n };\n }\n\n /**\n * Factory — creates a BetterCMSError from a failed fetch Response. Reads the body's\n * `message` (or `error`) for the human message and `code` for a machine-readable\n * condition surfaced as `bodyCode`. Validation failures arrive as `{ errors: {...} }`\n * (no `message`/`error`), so fall back to the first field error for the message and\n * expose the full map as `fieldErrors` (FLO-474).\n */\n static async from(res: Response): Promise<BetterCMSError> {\n let message = res.statusText || \"An error occurred\";\n let bodyCode: string | undefined;\n let fieldErrors: Record<string, string> | undefined;\n let code = statusToCode(res.status);\n let raw: unknown;\n try {\n const body = (await res.json()) as {\n message?: string;\n error?: string;\n code?: string;\n errors?: Record<string, string>;\n };\n if (body?.errors && typeof body.errors === \"object\") {\n fieldErrors = body.errors;\n code = ErrorCodes.VALIDATION_ERROR;\n }\n if (body?.message) message = body.message;\n else if (body?.error) message = body.error;\n else if (fieldErrors) {\n const [field, detail] = Object.entries(fieldErrors).find(\n ([, v]) => typeof v === \"string\" && v.length > 0,\n ) ?? [];\n if (field) message = `${field}: ${detail}`;\n }\n if (body?.code) bodyCode = body.code;\n raw = body;\n } catch {\n // ignore parse errors\n }\n return new BetterCMSError(message, res.status, code, bodyCode, fieldErrors, raw);\n }\n}\n","/**\n * Shared 429/503 backoff for every SDK client.\n *\n * ── Why this exists ─────────────────────────────────────────────────────────\n * The backend limiter (src/lib/ratelimit.ts, windowHit) is a FIXED 60-SECOND\n * WINDOW: 100 requests per workspace per endpoint, and a denial answers\n * `Retry-After: <seconds until the window resets>` — anything up to 60.\n *\n * Every client here used to climb a private ladder that topped out at 7 seconds\n * ([250,750,1500] in read-client, [1000,2000,4000] in admin/management) and none\n * of them read Retry-After. So all three retries landed INSIDE the same exhausted\n * window and a 429 was fatal roughly whenever it was thrown. The retry code looked\n * like resilience but could only ever recover from a denial raised in the final\n * seconds of a window.\n *\n * That is what failed customer builds: `action/fetch-content.mjs` spends ~65 of the\n * 100 `delivery-content-entries-list` slots pulling a large workspace, `npm run build`\n * starts seconds later in the SAME window, and the first per-route delivery read past\n * the ceiling killed the deploy with \"Too many requests. Please retry later.\"\n *\n * ── The rule ────────────────────────────────────────────────────────────────\n * Obey Retry-After when the server sends one, and let a single request outlast a\n * whole window. A build waiting 60s is slow; a build that dies is a broken product.\n */\n\n/** Statuses worth retrying. 429 = rate limited, 503 = limiter/backend unavailable. */\nexport const RETRY_STATUSES = [429, 503] as const;\n\n/** Retries after the first attempt. 3 retries × up to ~65s spans three full windows. */\nexport const MAX_RETRIES = 3;\n\n/**\n * Hard ceiling on a single wait, jitter included. Must exceed the limiter's 60s\n * window, or a denial raised at the very start of a window still cannot be waited\n * out — and must be finite, so a hostile or mis-set Retry-After cannot hang a build.\n */\nexport const MAX_DELAY_MS = 65_000;\n\n/** Fallback ladder when the server sends no Retry-After: 1s, 2s, 4s, 8s. */\nconst BASE_DELAY_MS = 1_000;\n\n/**\n * De-sync jitter, added to every wait.\n *\n * Deliberately a small FLAT amount, not a percentage. Requests denied by one window\n * only need to stop firing in the same millisecond — the server's counter serialises\n * them after that. A proportional jitter would add up to 15 seconds to a 60-second\n * wait for no benefit, and would push the total past MAX_DELAY_MS.\n */\nconst JITTER_MS = 250;\n\nexport function isRetryableStatus(status: number): boolean {\n return (RETRY_STATUSES as readonly number[]).includes(status);\n}\n\n/**\n * Parse `Retry-After`, which RFC 9110 allows in either form: delay-seconds\n * (\"47\") or an HTTP-date (\"Wed, 21 Oct 2015 07:28:00 GMT\"). Returns ms to wait,\n * or null when the header is absent or unparseable.\n */\nexport function retryAfterMs(res: Pick<Response, \"headers\">): number | null {\n const raw = res.headers?.get?.(\"retry-after\");\n if (!raw) return null;\n\n const seconds = Number(raw);\n if (Number.isFinite(seconds)) {\n return Math.min(Math.max(seconds, 0) * 1000, MAX_DELAY_MS);\n }\n\n const at = Date.parse(raw);\n if (!Number.isNaN(at)) {\n return Math.min(Math.max(0, at - Date.now()), MAX_DELAY_MS);\n }\n return null;\n}\n\n/**\n * How long to wait before retry `attempt` (0-based).\n *\n * Server instruction wins; otherwise exponential. Jitter is added in both cases:\n * when a build's requests are all denied by the same window they would otherwise\n * wake in lockstep at the reset and re-exhaust it immediately (thundering herd).\n */\nexport function backoffMs(attempt: number, res?: Pick<Response, \"headers\">): number {\n const fromServer = res ? retryAfterMs(res) : null;\n // Base is capped with the jitter's headroom already reserved, so the total never\n // exceeds MAX_DELAY_MS AND the jitter is never clamped away at the ceiling — which\n // would put every capped caller back in lockstep, the exact thing jitter prevents.\n const base = Math.min(fromServer ?? BASE_DELAY_MS * 2 ** attempt, MAX_DELAY_MS - JITTER_MS);\n // Jitter is ADDED, never subtracted: waking before the server's Retry-After elapses\n // just burns a retry on a window that has not reset.\n return base + Math.random() * JITTER_MS;\n}\n\nexport const sleep = (ms: number): Promise<void> =>\n new Promise((resolve) => setTimeout(resolve, ms));\n","/**\n * createClient — framework-agnostic typed read client for the Delivery API.\n *\n * Shared by @bettercms-ai/astro and @bettercms-ai/next so the fetch/typing/retry\n * logic lives in one place. Read-only; for schema authoring use the\n * Management client, for sessions use the Admin client.\n *\n * const client = createClient({ apiUrl, workspace, apiKey });\n * await client.getPage(\"home\");\n * await client.listEntries({ model: \"post\", perPage: 50 });\n */\nimport type {\n DeliveryComponent,\n DeliveryEntry,\n DeliveryList,\n DeliveryPage,\n DeliveredLayout,\n Perspective,\n} from \"@bettercms-ai/types\";\nimport { BetterCMSError } from \"./errors.js\";\nimport { MAX_RETRIES, backoffMs, isRetryableStatus, sleep } from \"./retry.js\";\nimport type { DeliveryForm } from \"./forms.js\";\n\nconst DEFAULT_TIMEOUT = 10_000;\n\nexport interface CreateClientOptions {\n /** Backend base, e.g. `https://api.bettercms.ai` (no trailing slash needed). */\n apiUrl: string;\n /** Workspace slug. */\n workspace: string;\n /** Delivery API key. Use a `content:read:draft` key for drafts. */\n apiKey?: string;\n /**\n * The project every read is addressed to. A workspace holds many projects whose slugs\n * collide (each has a `blog` model and a `home` page), so a site names its own; published\n * reads need no key for it. BetterCMS hosting supplies `PUBLIC_BCMS_PROJECT_ID`.\n */\n projectId?: string;\n /** `published` (default) or `drafts`. */\n perspective?: Perspective;\n /**\n * Preview-token JWT for token-based single-document draft reads via the\n * `/preview/:slug` endpoint. Only used when `perspective === \"drafts\"`.\n */\n previewToken?: string;\n /**\n * Embed invisible visual-editing provenance in draft reads, so a preview build can\n * deep-link each rendered value back to its editor. Requires `perspective: \"drafts\"`,\n * and then either a `previewToken` or a `content:read:draft` key. Ignored on published\n * reads — the payload must never reach a production build.\n *\n * **Defaults to `true` on `perspective: \"drafts\"`** (since 1.15.0). Provenance is the whole\n * reason the drafts perspective exists in a preview build, and an option nobody knew to pass\n * meant every such build rendered anonymous strings: the editor could bind them only by\n * guessing at their text. It stays OFF on every other perspective, unconditionally, so a\n * published build cannot acquire a payload by accident — pass `stega: false` to opt a draft\n * read out (a diff or a checksum over draft content is the case that wants it).\n */\n stega?: boolean;\n /** Per-attempt timeout in ms. Defaults to 10000. */\n timeout?: number;\n /**\n * Collapse identical GETs to one network round-trip for the life of this client.\n * Default false.\n *\n * Only correct where the content is a POINT-IN-TIME SNAPSHOT — i.e. a static build,\n * where every route renders from the same published dataset. `@bettercms-ai/astro`\n * turns this on automatically when Astro's `command === \"build\"` and never for `dev`,\n * SSR or the drafts perspective, where a repeat read must see the current row.\n *\n * A static build without it re-fetches the same nav/settings/collection once PER ROUTE.\n * That is what made a large site exhaust the delivery rate limit: hundreds of identical\n * requests, and `getPage()` re-scanning every page of the paginated list each time it is\n * called.\n */\n cache?: boolean;\n}\n\nexport interface ListEntriesOptions {\n /** Filter by content-model slug. */\n model?: string;\n page?: number;\n perPage?: number;\n /** Reference hydration depth (lists cap at 1). */\n depth?: 0 | 1;\n /** Projection — field names to return. */\n select?: string[];\n}\n\nexport interface GetEntryOptions {\n depth?: 0 | 1 | 2;\n select?: string[];\n}\n\nexport interface ListPagesOptions {\n page?: number;\n perPage?: number;\n}\n\nexport interface BetterCMSReadClient {\n readonly perspective: Perspective;\n getEntry<T = Record<string, unknown>>(\n slug: string,\n opts?: GetEntryOptions,\n ): Promise<DeliveryEntry<T>>;\n listEntries<T = Record<string, unknown>>(\n opts?: ListEntriesOptions,\n ): Promise<DeliveryList<DeliveryEntry<T>>>;\n getPage(slug: string): Promise<DeliveryPage | null>;\n listPages(opts?: ListPagesOptions): Promise<DeliveryList<DeliveryPage>>;\n /**\n * The workspace's reusable component definitions, for resolving `component` blocks.\n * Under `perspective: \"drafts\"` this is the DRAFT definition — what makes an SSR render\n * of a draft page look like the page the editor is showing.\n */\n listComponents(): Promise<DeliveryComponent[]>;\n /** Fetch the project-scoped Global Layout for the active perspective. */\n getLayout(): Promise<DeliveredLayout | null>;\n listForms(opts?: {\n projectId?: string;\n }): Promise<{ items: DeliveryForm[]; turnstileSiteKey: string | null }>;\n /** Build a deterministic Next/cache tag for a resource. */\n tag(resource: \"entry\" | \"page\" | \"forms\", id?: string): string;\n}\n\n/** Drop undefined values, stringify the rest into a query string. */\nfunction qs(params: Record<string, string | number | undefined>): string {\n const sp = new URLSearchParams();\n for (const [k, v] of Object.entries(params)) {\n if (v !== undefined && v !== \"\") sp.set(k, String(v));\n }\n const s = sp.toString();\n return s ? `?${s}` : \"\";\n}\n\n/**\n * Unwrap a `{ data: ... }` list/legacy envelope when present. A bare entry is\n * already the payload (it has `id`/`slug` and its own `data` field), so we only\n * unwrap when no payload markers are at the top level.\n */\nfunction unwrap<T>(json: unknown): T {\n if (json && typeof json === \"object\") {\n const o = json as Record<string, unknown>;\n const isPayload = \"id\" in o || \"slug\" in o || \"items\" in o;\n if (!isPayload && \"data\" in o) return o.data as T;\n }\n return json as T;\n}\n\n/**\n * The `entrySlug` claim of a preview-token JWT, UNVERIFIED.\n *\n * The backend is the signature authority — this read only decides WHICH url to call, and a\n * forged claim buys nothing: `/preview/:slug` re-verifies and 401s. Kept private to this file\n * (`@bettercms-ai/astro` has its own copy for the same reason: neither package may depend on\n * the other).\n */\nfunction tokenSlug(token: string): string | null {\n try {\n const part = token.split(\".\")[1];\n if (!part) return null;\n const json = atob(part.replace(/-/g, \"+\").replace(/_/g, \"/\"));\n return (JSON.parse(json) as { entrySlug?: string }).entrySlug ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Per-URL request collapsing.\n *\n * Two jobs, both needed: coalesce requests that are IN FLIGHT (Astro renders routes\n * concurrently, so identical GETs overlap) and retain the ones that have COMPLETED\n * (routes render in batches, so the same GET recurs long after the first settled).\n *\n * Values are cloned on the way out. Callers get a fresh object graph, so a template that\n * sorts `items` in place — an easy thing to write and a brutal thing to debug — cannot\n * corrupt what every other route sees. Cloning costs memory bandwidth; the request it\n * replaces costs a network round-trip and a slot in the rate-limit window.\n *\n * A rejected request is never retained, so a transient failure does not become permanent.\n */\nfunction createRequestCache() {\n const inflight = new Map<string, Promise<unknown>>();\n const settled = new Map<string, unknown>();\n\n const copy = <T>(value: T): T =>\n typeof structuredClone === \"function\" ? structuredClone(value) : (JSON.parse(JSON.stringify(value)) as T);\n\n return async function collapse<T>(url: string, fetcher: () => Promise<T>): Promise<T> {\n if (settled.has(url)) return copy(settled.get(url) as T);\n\n const pending = inflight.get(url) as Promise<T> | undefined;\n if (pending) return copy(await pending);\n\n const run = (async () => {\n try {\n const value = await fetcher();\n settled.set(url, value);\n return value;\n } finally {\n inflight.delete(url);\n }\n })();\n inflight.set(url, run);\n return copy(await run);\n };\n}\n\nexport function createClient(options: CreateClientOptions): BetterCMSReadClient {\n const apiUrl = options.apiUrl.replace(/\\/+$/, \"\");\n const { workspace, apiKey, perspective = \"published\", previewToken } = options;\n const defaultProjectId = options.projectId;\n const timeout = options.timeout ?? DEFAULT_TIMEOUT;\n const base = `${apiUrl}/api/v1/delivery/${workspace}`;\n\n const headers = (): Record<string, string> => {\n const h: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (apiKey) h[\"X-API-Key\"] = apiKey;\n return h;\n };\n\n // Drafts must never be collapsed even if a caller asks: a preview surface re-reads to\n // see the edit that just happened, and serving it a retained copy would show stale content.\n const collapse =\n options.cache && perspective === \"published\" ? createRequestCache() : undefined;\n\n const fetchJSON = <T>(url: string): Promise<T> =>\n collapse ? collapse(url, () => request<T>(url)) : request<T>(url);\n\n async function request<T>(url: string): Promise<T> {\n let lastError: unknown;\n for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeout);\n try {\n const res = await globalThis.fetch(url, {\n headers: headers(),\n signal: controller.signal,\n });\n clearTimeout(timer);\n if (res.ok) return (await res.json()) as T;\n\n const retryable = attempt < MAX_RETRIES && isRetryableStatus(res.status);\n if (!retryable) throw await BetterCMSError.from(res);\n // Honour Retry-After: the limiter's window is up to 60s, far longer than\n // any fixed ladder, so the server's own number is the only one that works.\n await sleep(backoffMs(attempt, res));\n } catch (err) {\n clearTimeout(timer);\n if (err instanceof Error && err.name === \"AbortError\") {\n throw new BetterCMSError(\"Request timeout\", 408, \"NETWORK_ERROR\");\n }\n if (err instanceof BetterCMSError) throw err;\n lastError = err;\n if (attempt < MAX_RETRIES) {\n await sleep(backoffMs(attempt));\n continue;\n }\n throw new BetterCMSError(\n `Network error: ${err instanceof Error ? err.message : String(err)}`,\n 0,\n \"NETWORK_ERROR\",\n );\n }\n }\n throw lastError ?? new BetterCMSError(\"Unreachable\", 0, \"NETWORK_ERROR\");\n }\n\n const draft = perspective === \"drafts\";\n /**\n * Resolved ONCE, here, so the three call sites below cannot drift apart — they did exactly\n * that before (`options.stega` alone on the preview branch, `draft && options.stega` on the\n * other two), and a default expressed three times is a default that will eventually be two.\n * `??` and not `||`: an explicit `stega: false` on a draft read must win.\n */\n const stega = options.stega ?? draft;\n\n return {\n perspective,\n\n async getEntry<T = Record<string, unknown>>(slug: string, opts?: GetEntryOptions) {\n // Token-based draft reads go through the dedicated preview endpoint.\n // Without a token the drafts perspective is served by delivery itself, which needs\n // to be TOLD — `?perspective=draft` (honoured only for a `content:read:draft` key).\n // Until this branch sent it, `perspective: \"drafts\"` was silently inert here, and\n // with it `stega`, so a static build could never obtain provenance without minting\n // a preview token per entry.\n // The token names ONE entry (as on getPage below): any other slug reads delivery, or the\n // preview endpoint 404s the post list, related posts and nav a draft page also renders.\n const url =\n draft && previewToken && tokenSlug(previewToken) === slug\n ? `${apiUrl}/api/v1/preview/${encodeURIComponent(slug)}${qs({\n token: previewToken,\n depth: opts?.depth,\n select: opts?.select?.join(\",\"),\n stega: stega ? \"1\" : undefined,\n })}`\n : `${base}/content-entries/${encodeURIComponent(slug)}${qs({\n depth: opts?.depth,\n select: opts?.select?.join(\",\"),\n perspective: draft ? \"draft\" : undefined,\n stega: draft && stega ? \"1\" : undefined,\n projectId: defaultProjectId,\n })}`;\n return unwrap<DeliveryEntry<T>>(await fetchJSON(url));\n },\n\n async listEntries<T = Record<string, unknown>>(opts?: ListEntriesOptions) {\n const url = `${base}/content-entries${qs({\n model: opts?.model,\n page: opts?.page,\n perPage: opts?.perPage,\n depth: opts?.depth,\n select: opts?.select?.join(\",\"),\n perspective: draft ? \"draft\" : undefined,\n stega: draft && stega ? \"1\" : undefined,\n projectId: defaultProjectId,\n })}`;\n return unwrap<DeliveryList<DeliveryEntry<T>>>(await fetchJSON(url));\n },\n\n async getPage(slug: string) {\n // The token names ONE page, and for that page the preview endpoint is the only read\n // that returns the DRAFT block tree — the paginated delivery list is published-only, so\n // an SSR draft render would otherwise be the live structure with draft field values.\n // Any other slug falls through to the published scan, which is what a token for /about\n // rendering the site's nav or footer pages must keep doing.\n if (draft && previewToken && tokenSlug(previewToken) === slug) {\n const url = `${apiUrl}/api/v1/preview/${encodeURIComponent(slug)}${qs({\n token: previewToken,\n stega: stega ? \"1\" : undefined,\n })}`;\n const d = unwrap<Record<string, unknown>>(await fetchJSON(url));\n return {\n slug: d.slug as string,\n title: d.title as string,\n metaTitle: (d.metaTitle as string | null) ?? null,\n metaDescription: (d.metaDescription as string | null) ?? null,\n metaJson: (d.metaJson as DeliveryPage[\"metaJson\"]) ?? null,\n blocks: (d.blockJson as DeliveryPage[\"blocks\"]) ?? [],\n // C3: the DRAFT field styles, present only when the page styles a field.\n ...(d.fieldStyles ? { fieldStyles: d.fieldStyles as NonNullable<DeliveryPage[\"fieldStyles\"]> } : {}),\n updatedAt: (d.updatedAt as string | null) ?? null,\n publishedAt: (d.publishedAt as string | null) ?? null,\n } satisfies DeliveryPage;\n }\n // No dedicated single-page delivery endpoint — scan the paginated list.\n let page = 1;\n for (;;) {\n const list = await this.listPages({ page, perPage: 100 });\n const found = list.items.find((p) => p.slug === slug);\n if (found) return found;\n if (!list.hasNextPage) return null;\n page += 1;\n }\n },\n\n async listPages(opts?: ListPagesOptions) {\n const url = `${base}/pages${qs({\n page: opts?.page,\n perPage: opts?.perPage,\n projectId: defaultProjectId,\n })}`;\n return unwrap<DeliveryList<DeliveryPage>>(await fetchJSON(url));\n },\n\n async listComponents() {\n // Same shape as getLayout: `?perspective=draft` alone is the key-scoped path (the server\n // ignores it for a published key), and a preview token is the page-scoped one.\n const url = `${base}/components${qs({\n perspective: draft ? \"draft\" : undefined,\n token: draft ? previewToken : undefined,\n projectId: defaultProjectId,\n })}`;\n return unwrap<DeliveryComponent[]>(await fetchJSON(url));\n },\n\n async getLayout() {\n const url = `${base}/layout${qs({ perspective: draft ? \"draft\" : undefined, projectId: defaultProjectId })}`;\n try {\n return unwrap<DeliveredLayout>(await fetchJSON(url));\n } catch (error) {\n if (error instanceof BetterCMSError && error.status === 404) return null;\n throw error;\n }\n },\n\n async listForms(opts?: { projectId?: string }) {\n const url = `${base}/forms${qs({\n projectId: opts?.projectId ?? defaultProjectId,\n })}`;\n return unwrap<{ items: DeliveryForm[]; turnstileSiteKey: string | null }>(\n await fetchJSON(url),\n );\n },\n\n tag(resource, id) {\n return id ? `bcms:${workspace}:${resource}:${id}` : `bcms:${workspace}:${resource}`;\n },\n };\n}\n","/**\n * Where a site's BetterCMS settings come from, the same way on every framework: explicit options,\n * then `<PREFIX>BCMS_*` env (each framework exposes env to the browser under its own prefix:\n * `PUBLIC_` for Astro and SvelteKit, `NEXT_PUBLIC_` for Next, `VITE_` for Vite React,\n * `NUXT_PUBLIC_` for Nuxt), then the build snapshot `bettercms pull-content` writes.\n *\n * Pure: the adapter passes its env and reads the snapshot (this module touches no filesystem).\n */\n\nexport const DEFAULT_MEDIA_URL = \"https://cdn.bettercms.ai\";\n\nexport interface BcmsSiteOptions {\n apiUrl?: string;\n workspace?: string;\n projectId?: string;\n mediaUrl?: string;\n studioUrl?: string;\n}\n\nexport interface BcmsSiteConfig {\n apiUrl: string;\n workspace: string;\n projectId?: string;\n mediaUrl: string;\n studioUrl?: string;\n}\n\nexport interface ResolveBcmsConfigInput {\n options?: BcmsSiteOptions;\n env?: Record<string, string | undefined>;\n /** Env prefixes, first match wins: `[\"NEXT_PUBLIC_\", \"\"]` reads NEXT_PUBLIC_BCMS_API_URL, then BCMS_API_URL. */\n prefixes: string[];\n /** The build snapshot's identity, read only when options and env both miss. */\n snapshot?: () => { apiUrl?: string; workspace?: string; projectId?: string };\n}\n\n/** The resolved settings, and which essentials are missing (the adapter words the error). */\nexport function resolveBcmsConfig(input: ResolveBcmsConfigInput): { config: BcmsSiteConfig; missing: (\"apiUrl\" | \"workspace\")[] } {\n const env = input.env ?? {};\n const fromEnv = (name: string) => {\n for (const p of input.prefixes) {\n const v = env[`${p}BCMS_${name}`];\n if (v) return v;\n }\n return undefined;\n };\n let snap: ReturnType<NonNullable<ResolveBcmsConfigInput[\"snapshot\"]>> | undefined;\n const snapshot = () => (snap ??= input.snapshot?.() ?? {});\n const o = input.options ?? {};\n const config: BcmsSiteConfig = {\n apiUrl: (o.apiUrl ?? fromEnv(\"API_URL\") ?? snapshot().apiUrl ?? \"\").replace(/\\/+$/, \"\"),\n workspace: o.workspace ?? fromEnv(\"WORKSPACE\") ?? snapshot().workspace ?? \"\",\n projectId: o.projectId ?? fromEnv(\"PROJECT_ID\") ?? snapshot().projectId,\n mediaUrl: (o.mediaUrl ?? fromEnv(\"MEDIA_URL\") ?? DEFAULT_MEDIA_URL).replace(/\\/+$/, \"\"),\n studioUrl: o.studioUrl ?? fromEnv(\"STUDIO_URL\"),\n };\n const missing = ([\"apiUrl\", \"workspace\"] as const).filter((k) => !config[k]);\n return { config, missing };\n}\n","/**\n * Draft mode, shared by every framework adapter: the signed cookie that says \"this visitor is\n * previewing\", the preview token's slug, and the redirect a draft route may follow.\n *\n * Web Crypto and `atob` only, so it runs in Node, edge runtimes and workers alike. Each adapter\n * mounts its own routes around these; the rules live here once.\n */\n\n/** The draft cookie every adapter sets: one name, so an editor link works on any framework. */\nexport const DRAFT_COOKIE = \"bcms-draft\";\n\nconst enc = new TextEncoder();\n\nfunction base64url(bytes: ArrayBuffer): string {\n const b = btoa(String.fromCharCode(...new Uint8Array(bytes)));\n return b.replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\nasync function hmac(value: string, secret: string): Promise<string> {\n const key = await crypto.subtle.importKey(\"raw\", enc.encode(secret), { name: \"HMAC\", hash: \"SHA-256\" }, false, [\"sign\"]);\n return base64url(await crypto.subtle.sign(\"HMAC\", key, enc.encode(value)));\n}\n\n/** Sign a value as `value.sig` for tamper-evident cookie storage (HMAC-SHA256, base64url). */\nexport async function signValue(value: string, secret: string): Promise<string> {\n return `${value}.${await hmac(value, secret)}`;\n}\n\n/** The original value of a signed one, or null when it was tampered with. */\nexport async function verifyValue(signed: string, secret: string): Promise<string | null> {\n const idx = signed.lastIndexOf(\".\");\n if (idx < 0) return null;\n const value = signed.slice(0, idx);\n const sig = signed.slice(idx + 1);\n const expected = await hmac(value, secret);\n if (sig.length !== expected.length) return null;\n let diff = 0;\n for (let i = 0; i < sig.length; i++) diff |= sig.charCodeAt(i) ^ expected.charCodeAt(i);\n return diff === 0 ? value : null;\n}\n\n/** Only same-origin relative paths: blocks \"//evil.com\", \"https://evil.com\" and \"/\\\\evil.com\". */\nexport function safeRedirectPath(to: string): string {\n return to.startsWith(\"/\") && !to.startsWith(\"//\") && !to.startsWith(\"/\\\\\") ? to : \"/\";\n}\n\n/** A preview token's `entrySlug`, read from its payload UNVERIFIED (the backend verifies it). */\nexport function decodeTokenSlug(token: string): string | null {\n try {\n const part = token.split(\".\")[1];\n if (!part) return null;\n // atob gives bytes as Latin-1: decoded as UTF-8, a slug like `über-uns` survives.\n const bytes = Uint8Array.from(atob(part.replace(/-/g, \"+\").replace(/_/g, \"/\")), (c) => c.charCodeAt(0));\n const json = JSON.parse(new TextDecoder().decode(bytes)) as { entrySlug?: string };\n return json.entrySlug ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Ask the backend whether a preview token is good: the backend is the signature authority, so a\n * draft route enables draft mode only on its yes.\n */\nexport async function previewTokenValid(apiUrl: string, token: string, fetchImpl: typeof fetch = fetch): Promise<boolean> {\n const slug = decodeTokenSlug(token);\n if (!slug) return false;\n const res = await fetchImpl(`${apiUrl.replace(/\\/+$/, \"\")}/api/v1/preview/${encodeURIComponent(slug)}?token=${encodeURIComponent(token)}`);\n await res.body?.cancel().catch(() => {});\n return res.ok;\n}\n\n/** The draft cookie's preview token from a `Cookie` header, or null when absent or tampered with. */\nexport async function draftTokenFrom(cookieHeader: string | null | undefined, secret: string | undefined): Promise<string | null> {\n if (!cookieHeader || !secret) return null;\n const m = new RegExp(`(?:^|;\\\\s*)${DRAFT_COOKIE}=([^;]+)`).exec(cookieHeader);\n if (!m) return null;\n try {\n return await verifyValue(decodeURIComponent(m[1]!), secret);\n } catch {\n return null;\n }\n}\n\n/**\n * The draft-enable route for any server that speaks Web `Request`/`Response` (SvelteKit `+server.ts`,\n * Nuxt via `toWebRequest`, Remix, Hono): `GET ?token=…&redirect=/x` checks the token with the\n * backend, sets the signed draft cookie and redirects. 401 on a refused token; 500 without a secret.\n */\nexport async function draftEnableResponse(request: Request, opts: { apiUrl: string; secret: string | undefined; fetchImpl?: typeof fetch }): Promise<Response> {\n const url = new URL(request.url);\n const token = url.searchParams.get(\"token\");\n if (!opts.secret) return new Response(\"BCMS_DRAFT_SECRET is not configured\", { status: 500 });\n if (!token) return new Response(\"Missing token\", { status: 400 });\n if (!(await previewTokenValid(opts.apiUrl, token, opts.fetchImpl))) return new Response(\"Invalid or expired token\", { status: 401 });\n const cookie = `${DRAFT_COOKIE}=${encodeURIComponent(await signValue(token, opts.secret))}; Path=/; HttpOnly; SameSite=Lax; Max-Age=86400${url.protocol === \"https:\" ? \"; Secure\" : \"\"}`;\n return new Response(null, { status: 302, headers: { Location: safeRedirectPath(url.searchParams.get(\"redirect\") ?? \"/\"), \"Set-Cookie\": cookie } });\n}\n\n/** Leave draft mode: clear the cookie and redirect (`?redirect=/x`, same-origin only). */\nexport function draftDisableResponse(request: Request): Response {\n const url = new URL(request.url);\n return new Response(null, {\n status: 302,\n headers: { Location: safeRedirectPath(url.searchParams.get(\"redirect\") ?? \"/\"), \"Set-Cookie\": `${DRAFT_COOKIE}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0` },\n });\n}\n","/// <reference lib=\"dom\" />\n/**\n * A published page's sections as server-rendered HTML, for a front end that renders its own\n * document (SvelteKit, Nuxt, any server): the hosted renderer's markup, its scoped stylesheet and\n * the runtime its forms, sliders and tabs need (`GET /api/v1/sites/fragment/:projectId/:slug`).\n * One renderer for every site, so the markup is the hosted site's and the editor's.\n */\nimport { BetterCMSError, ErrorCodes } from \"./errors.js\";\nimport { createClient, type BetterCMSReadClient } from \"./read-client.js\";\nimport { resolveBcmsConfig, type BcmsSiteConfig, type BcmsSiteOptions } from \"./config.js\";\nimport { draftDisableResponse, draftEnableResponse, draftTokenFrom } from \"./draft.js\";\n\nexport interface SectionsFragment {\n html: string;\n css: string;\n scripts: string;\n}\n\n/** Fetch a page's sections (`home` is `/`). Null when the page is not published. */\nexport async function fetchSections(\n opts: { apiUrl: string; projectId: string; slug: string; fetch?: typeof fetch },\n): Promise<SectionsFragment | null> {\n const doFetch = opts.fetch ?? fetch;\n const url = `${opts.apiUrl.replace(/\\/+$/, \"\")}/api/v1/sites/fragment/${encodeURIComponent(opts.projectId)}/${encodeURIComponent(opts.slug || \"home\")}`;\n const res = await doFetch(url, { headers: { accept: \"application/json\" } });\n if (res.status === 404) return null;\n if (!res.ok) throw new BetterCMSError(`Sections for \"${opts.slug}\" could not be read (${res.status})`, res.status, res.status === 403 ? ErrorCodes.FORBIDDEN : ErrorCodes.INTERNAL_ERROR);\n return ((await res.json()) as { data: SectionsFragment }).data;\n}\n\n/** The fragment as one string for `{@html}` / `v-html`: its stylesheet, its markup, its scripts. */\nexport function sectionsMarkup(fragment: SectionsFragment | null | undefined): string {\n if (!fragment) return \"\";\n return `<style>${fragment.css}</style>${fragment.html}${fragment.scripts}`;\n}\n\n/**\n * Run the scripts inside `root`. A server-rendered first load runs them itself; a client-side\n * navigation inserts the markup with innerHTML, where a <script> never runs, so the forms,\n * sliders and tabs of the new page would be dead. Each script runs once per element.\n */\nexport function runSectionScripts(root: Element): void {\n for (const old of Array.from(root.querySelectorAll(\"script\"))) {\n if (old.hasAttribute(\"data-bcms-ran\")) continue;\n const fresh = document.createElement(\"script\");\n for (const attr of Array.from(old.attributes)) fresh.setAttribute(attr.name, attr.value);\n fresh.setAttribute(\"data-bcms-ran\", \"\");\n fresh.textContent = old.textContent;\n old.replaceWith(fresh);\n }\n}\n\n\nexport interface BcmsSite {\n config: BcmsSiteConfig;\n /** The read client for a request: drafts while its draft cookie holds a valid token. */\n client(request?: Request): Promise<BetterCMSReadClient>;\n /** A published page's sections (`home` is `/`); pass the framework's `fetch` on the server. */\n sections(slug: string, fetchImpl?: typeof fetch): Promise<SectionsFragment | null>;\n /** GET handler for the draft path the editor opens (`/api/draft`). */\n draftEnable(request: Request): Promise<Response>;\n /** GET handler that leaves draft mode. */\n draftDisable(request: Request): Response;\n}\n\n/**\n * Everything a server-rendered site needs from BetterCMS, built once from its settings: the\n * precedence every adapter shares (options, `<PREFIX>BCMS_*`, `BCMS_*`), the read client per\n * request, the sections fragment and the draft routes. The SvelteKit and Nuxt adapters are this\n * with their env prefix.\n */\nexport function createBcmsSite(\n options: BcmsSiteOptions & { env?: Record<string, string | undefined>; draftSecret?: string; prefixes: string[]; adapter: string },\n): BcmsSite {\n const env = options.env ?? {};\n const { config, missing } = resolveBcmsConfig({ options, env, prefixes: [...options.prefixes, \"\"] });\n if (missing.length > 0) {\n const names = missing.map((m) => `${options.prefixes[0]}BCMS_${m === \"apiUrl\" ? \"API_URL\" : \"WORKSPACE\"}`).join(\" and \");\n throw new Error(`[${options.adapter}] Missing ${names}: set them, or pass them to createBcms().`);\n }\n const secret = options.draftSecret ?? env.BCMS_DRAFT_SECRET;\n return {\n config,\n async client(request) {\n const token = request ? await draftTokenFrom(request.headers.get(\"cookie\"), secret) : null;\n return createClient({\n apiUrl: config.apiUrl,\n workspace: config.workspace,\n projectId: config.projectId,\n ...(token ? { perspective: \"drafts\" as const, previewToken: token } : {}),\n });\n },\n sections(slug, fetchImpl) {\n if (!config.projectId) throw new Error(`[${options.adapter}] Sections need the project: set ${options.prefixes[0]}BCMS_PROJECT_ID.`);\n return fetchSections({ apiUrl: config.apiUrl, projectId: config.projectId, slug, fetch: fetchImpl });\n },\n draftEnable: (request) => draftEnableResponse(request, { apiUrl: config.apiUrl, secret }),\n draftDisable: draftDisableResponse,\n };\n}\n"],"mappings":";AAGO,IAAM,aAAa;AAAA,EACxB,mBAAmB;AAAA,EACnB,cAAc;AAAA,EACd,WAAW;AAAA,EACX,cAAc;AAAA,EACd,gBAAgB;AAAA,EAChB,kBAAkB;AAAA,EAClB,eAAe;AAAA,EACf,uBAAuB;AACzB;AAKA,SAAS,aAAa,QAAoC;AACxD,MAAI,WAAW,IAAK,QAAO,WAAW;AACtC,MAAI,WAAW,IAAK,QAAO,WAAW;AACtC,MAAI,WAAW,IAAK,QAAO,WAAW;AACtC,MAAI,WAAW,IAAK,QAAO,WAAW;AACtC,MAAI,UAAU,IAAK,QAAO,WAAW;AACrC,MAAI,WAAW,IAAK,QAAO,WAAW;AACtC,SAAO,WAAW;AACpB;AAKO,IAAM,iBAAN,MAAM,wBAAuB,MAAM;AAAA,EAC/B;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA;AAAA,EAET,YACE,SACA,QACA,MACA,UACA,aACA,MACA;AACA,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,OAAO;AACZ,SAAK,WAAW;AAChB,SAAK,cAAc;AACnB,SAAK,OAAO;AACZ,QAAI,MAAM,mBAAmB;AAC3B,YAAM,kBAAkB,MAAM,eAAc;AAAA,IAC9C;AAAA,EACF;AAAA,EAEA,SAAS;AACP,WAAO;AAAA,MACL,MAAM,KAAK;AAAA,MACX,SAAS,KAAK;AAAA,MACd,QAAQ,KAAK;AAAA,MACb,MAAM,KAAK;AAAA,MACX,UAAU,KAAK;AAAA,MACf,aAAa,KAAK;AAAA,IACpB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,aAAa,KAAK,KAAwC;AACxD,QAAI,UAAU,IAAI,cAAc;AAChC,QAAI;AACJ,QAAI;AACJ,QAAI,OAAO,aAAa,IAAI,MAAM;AAClC,QAAI;AACJ,QAAI;AACF,YAAM,OAAQ,MAAM,IAAI,KAAK;AAM7B,UAAI,MAAM,UAAU,OAAO,KAAK,WAAW,UAAU;AACnD,sBAAc,KAAK;AACnB,eAAO,WAAW;AAAA,MACpB;AACA,UAAI,MAAM,QAAS,WAAU,KAAK;AAAA,eACzB,MAAM,MAAO,WAAU,KAAK;AAAA,eAC5B,aAAa;AACpB,cAAM,CAAC,OAAO,MAAM,IAAI,OAAO,QAAQ,WAAW,EAAE;AAAA,UAClD,CAAC,CAAC,EAAE,CAAC,MAAM,OAAO,MAAM,YAAY,EAAE,SAAS;AAAA,QACjD,KAAK,CAAC;AACN,YAAI,MAAO,WAAU,GAAG,KAAK,KAAK,MAAM;AAAA,MAC1C;AACA,UAAI,MAAM,KAAM,YAAW,KAAK;AAChC,YAAM;AAAA,IACR,QAAQ;AAAA,IAER;AACA,WAAO,IAAI,gBAAe,SAAS,IAAI,QAAQ,MAAM,UAAU,aAAa,GAAG;AAAA,EACjF;AACF;;;ACtGO,IAAM,iBAAiB,CAAC,KAAK,GAAG;AAGhC,IAAM,cAAc;AAOpB,IAAM,eAAe;AAG5B,IAAM,gBAAgB;AAUtB,IAAM,YAAY;AAEX,SAAS,kBAAkB,QAAyB;AACzD,SAAQ,eAAqC,SAAS,MAAM;AAC9D;AAOO,SAAS,aAAa,KAA+C;AAC1E,QAAM,MAAM,IAAI,SAAS,MAAM,aAAa;AAC5C,MAAI,CAAC,IAAK,QAAO;AAEjB,QAAM,UAAU,OAAO,GAAG;AAC1B,MAAI,OAAO,SAAS,OAAO,GAAG;AAC5B,WAAO,KAAK,IAAI,KAAK,IAAI,SAAS,CAAC,IAAI,KAAM,YAAY;AAAA,EAC3D;AAEA,QAAM,KAAK,KAAK,MAAM,GAAG;AACzB,MAAI,CAAC,OAAO,MAAM,EAAE,GAAG;AACrB,WAAO,KAAK,IAAI,KAAK,IAAI,GAAG,KAAK,KAAK,IAAI,CAAC,GAAG,YAAY;AAAA,EAC5D;AACA,SAAO;AACT;AASO,SAAS,UAAU,SAAiB,KAAyC;AAClF,QAAM,aAAa,MAAM,aAAa,GAAG,IAAI;AAI7C,QAAM,OAAO,KAAK,IAAI,cAAc,gBAAgB,KAAK,SAAS,eAAe,SAAS;AAG1F,SAAO,OAAO,KAAK,OAAO,IAAI;AAChC;AAEO,IAAM,QAAQ,CAAC,OACpB,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;;;ACxElD,IAAM,kBAAkB;AAuGxB,SAAS,GAAG,QAA6D;AACvE,QAAM,KAAK,IAAI,gBAAgB;AAC/B,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,MAAM,GAAG;AAC3C,QAAI,MAAM,UAAa,MAAM,GAAI,IAAG,IAAI,GAAG,OAAO,CAAC,CAAC;AAAA,EACtD;AACA,QAAM,IAAI,GAAG,SAAS;AACtB,SAAO,IAAI,IAAI,CAAC,KAAK;AACvB;AAOA,SAAS,OAAU,MAAkB;AACnC,MAAI,QAAQ,OAAO,SAAS,UAAU;AACpC,UAAM,IAAI;AACV,UAAM,YAAY,QAAQ,KAAK,UAAU,KAAK,WAAW;AACzD,QAAI,CAAC,aAAa,UAAU,EAAG,QAAO,EAAE;AAAA,EAC1C;AACA,SAAO;AACT;AAUA,SAAS,UAAU,OAA8B;AAC/C,MAAI;AACF,UAAM,OAAO,MAAM,MAAM,GAAG,EAAE,CAAC;AAC/B,QAAI,CAAC,KAAM,QAAO;AAClB,UAAM,OAAO,KAAK,KAAK,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG,CAAC;AAC5D,WAAQ,KAAK,MAAM,IAAI,EAA6B,aAAa;AAAA,EACnE,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAgBA,SAAS,qBAAqB;AAC5B,QAAM,WAAW,oBAAI,IAA8B;AACnD,QAAM,UAAU,oBAAI,IAAqB;AAEzC,QAAM,OAAO,CAAI,UACf,OAAO,oBAAoB,aAAa,gBAAgB,KAAK,IAAK,KAAK,MAAM,KAAK,UAAU,KAAK,CAAC;AAEpG,SAAO,eAAe,SAAY,KAAa,SAAuC;AACpF,QAAI,QAAQ,IAAI,GAAG,EAAG,QAAO,KAAK,QAAQ,IAAI,GAAG,CAAM;AAEvD,UAAM,UAAU,SAAS,IAAI,GAAG;AAChC,QAAI,QAAS,QAAO,KAAK,MAAM,OAAO;AAEtC,UAAM,OAAO,YAAY;AACvB,UAAI;AACF,cAAM,QAAQ,MAAM,QAAQ;AAC5B,gBAAQ,IAAI,KAAK,KAAK;AACtB,eAAO;AAAA,MACT,UAAE;AACA,iBAAS,OAAO,GAAG;AAAA,MACrB;AAAA,IACF,GAAG;AACH,aAAS,IAAI,KAAK,GAAG;AACrB,WAAO,KAAK,MAAM,GAAG;AAAA,EACvB;AACF;AAEO,SAAS,aAAa,SAAmD;AAC9E,QAAM,SAAS,QAAQ,OAAO,QAAQ,QAAQ,EAAE;AAChD,QAAM,EAAE,WAAW,QAAQ,cAAc,aAAa,aAAa,IAAI;AACvE,QAAM,mBAAmB,QAAQ;AACjC,QAAM,UAAU,QAAQ,WAAW;AACnC,QAAM,OAAO,GAAG,MAAM,oBAAoB,SAAS;AAEnD,QAAM,UAAU,MAA8B;AAC5C,UAAM,IAA4B,EAAE,gBAAgB,mBAAmB;AACvE,QAAI,OAAQ,GAAE,WAAW,IAAI;AAC7B,WAAO;AAAA,EACT;AAIA,QAAM,WACJ,QAAQ,SAAS,gBAAgB,cAAc,mBAAmB,IAAI;AAExE,QAAM,YAAY,CAAI,QACpB,WAAW,SAAS,KAAK,MAAM,QAAW,GAAG,CAAC,IAAI,QAAW,GAAG;AAElE,iBAAe,QAAW,KAAyB;AACjD,QAAI;AACJ,aAAS,UAAU,GAAG,WAAW,aAAa,WAAW;AACvD,YAAM,aAAa,IAAI,gBAAgB;AACvC,YAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,OAAO;AAC1D,UAAI;AACF,cAAM,MAAM,MAAM,WAAW,MAAM,KAAK;AAAA,UACtC,SAAS,QAAQ;AAAA,UACjB,QAAQ,WAAW;AAAA,QACrB,CAAC;AACD,qBAAa,KAAK;AAClB,YAAI,IAAI,GAAI,QAAQ,MAAM,IAAI,KAAK;AAEnC,cAAM,YAAY,UAAU,eAAe,kBAAkB,IAAI,MAAM;AACvE,YAAI,CAAC,UAAW,OAAM,MAAM,eAAe,KAAK,GAAG;AAGnD,cAAM,MAAM,UAAU,SAAS,GAAG,CAAC;AAAA,MACrC,SAAS,KAAK;AACZ,qBAAa,KAAK;AAClB,YAAI,eAAe,SAAS,IAAI,SAAS,cAAc;AACrD,gBAAM,IAAI,eAAe,mBAAmB,KAAK,eAAe;AAAA,QAClE;AACA,YAAI,eAAe,eAAgB,OAAM;AACzC,oBAAY;AACZ,YAAI,UAAU,aAAa;AACzB,gBAAM,MAAM,UAAU,OAAO,CAAC;AAC9B;AAAA,QACF;AACA,cAAM,IAAI;AAAA,UACR,kBAAkB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,UAClE;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF;AACA,UAAM,aAAa,IAAI,eAAe,eAAe,GAAG,eAAe;AAAA,EACzE;AAEA,QAAM,QAAQ,gBAAgB;AAO9B,QAAM,QAAQ,QAAQ,SAAS;AAE/B,SAAO;AAAA,IACL;AAAA,IAEA,MAAM,SAAsC,MAAc,MAAwB;AAShF,YAAM,MACJ,SAAS,gBAAgB,UAAU,YAAY,MAAM,OACjD,GAAG,MAAM,mBAAmB,mBAAmB,IAAI,CAAC,GAAG,GAAG;AAAA,QACxD,OAAO;AAAA,QACP,OAAO,MAAM;AAAA,QACb,QAAQ,MAAM,QAAQ,KAAK,GAAG;AAAA,QAC9B,OAAO,QAAQ,MAAM;AAAA,MACvB,CAAC,CAAC,KACF,GAAG,IAAI,oBAAoB,mBAAmB,IAAI,CAAC,GAAG,GAAG;AAAA,QACvD,OAAO,MAAM;AAAA,QACb,QAAQ,MAAM,QAAQ,KAAK,GAAG;AAAA,QAC9B,aAAa,QAAQ,UAAU;AAAA,QAC/B,OAAO,SAAS,QAAQ,MAAM;AAAA,QAC9B,WAAW;AAAA,MACb,CAAC,CAAC;AACR,aAAO,OAAyB,MAAM,UAAU,GAAG,CAAC;AAAA,IACtD;AAAA,IAEA,MAAM,YAAyC,MAA2B;AACxE,YAAM,MAAM,GAAG,IAAI,mBAAmB,GAAG;AAAA,QACvC,OAAO,MAAM;AAAA,QACb,MAAM,MAAM;AAAA,QACZ,SAAS,MAAM;AAAA,QACf,OAAO,MAAM;AAAA,QACb,QAAQ,MAAM,QAAQ,KAAK,GAAG;AAAA,QAC9B,aAAa,QAAQ,UAAU;AAAA,QAC/B,OAAO,SAAS,QAAQ,MAAM;AAAA,QAC9B,WAAW;AAAA,MACb,CAAC,CAAC;AACF,aAAO,OAAuC,MAAM,UAAU,GAAG,CAAC;AAAA,IACpE;AAAA,IAEA,MAAM,QAAQ,MAAc;AAM1B,UAAI,SAAS,gBAAgB,UAAU,YAAY,MAAM,MAAM;AAC7D,cAAM,MAAM,GAAG,MAAM,mBAAmB,mBAAmB,IAAI,CAAC,GAAG,GAAG;AAAA,UACpE,OAAO;AAAA,UACP,OAAO,QAAQ,MAAM;AAAA,QACvB,CAAC,CAAC;AACF,cAAM,IAAI,OAAgC,MAAM,UAAU,GAAG,CAAC;AAC9D,eAAO;AAAA,UACL,MAAM,EAAE;AAAA,UACR,OAAO,EAAE;AAAA,UACT,WAAY,EAAE,aAA+B;AAAA,UAC7C,iBAAkB,EAAE,mBAAqC;AAAA,UACzD,UAAW,EAAE,YAAyC;AAAA,UACtD,QAAS,EAAE,aAAwC,CAAC;AAAA;AAAA,UAEpD,GAAI,EAAE,cAAc,EAAE,aAAa,EAAE,YAAwD,IAAI,CAAC;AAAA,UAClG,WAAY,EAAE,aAA+B;AAAA,UAC7C,aAAc,EAAE,eAAiC;AAAA,QACnD;AAAA,MACF;AAEA,UAAI,OAAO;AACX,iBAAS;AACP,cAAM,OAAO,MAAM,KAAK,UAAU,EAAE,MAAM,SAAS,IAAI,CAAC;AACxD,cAAM,QAAQ,KAAK,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI;AACpD,YAAI,MAAO,QAAO;AAClB,YAAI,CAAC,KAAK,YAAa,QAAO;AAC9B,gBAAQ;AAAA,MACV;AAAA,IACF;AAAA,IAEA,MAAM,UAAU,MAAyB;AACvC,YAAM,MAAM,GAAG,IAAI,SAAS,GAAG;AAAA,QAC7B,MAAM,MAAM;AAAA,QACZ,SAAS,MAAM;AAAA,QACf,WAAW;AAAA,MACb,CAAC,CAAC;AACF,aAAO,OAAmC,MAAM,UAAU,GAAG,CAAC;AAAA,IAChE;AAAA,IAEA,MAAM,iBAAiB;AAGrB,YAAM,MAAM,GAAG,IAAI,cAAc,GAAG;AAAA,QAClC,aAAa,QAAQ,UAAU;AAAA,QAC/B,OAAO,QAAQ,eAAe;AAAA,QAC9B,WAAW;AAAA,MACb,CAAC,CAAC;AACF,aAAO,OAA4B,MAAM,UAAU,GAAG,CAAC;AAAA,IACzD;AAAA,IAEA,MAAM,YAAY;AAChB,YAAM,MAAM,GAAG,IAAI,UAAU,GAAG,EAAE,aAAa,QAAQ,UAAU,QAAW,WAAW,iBAAiB,CAAC,CAAC;AAC1G,UAAI;AACF,eAAO,OAAwB,MAAM,UAAU,GAAG,CAAC;AAAA,MACrD,SAAS,OAAO;AACd,YAAI,iBAAiB,kBAAkB,MAAM,WAAW,IAAK,QAAO;AACpE,cAAM;AAAA,MACR;AAAA,IACF;AAAA,IAEA,MAAM,UAAU,MAA+B;AAC7C,YAAM,MAAM,GAAG,IAAI,SAAS,GAAG;AAAA,QAC7B,WAAW,MAAM,aAAa;AAAA,MAChC,CAAC,CAAC;AACF,aAAO;AAAA,QACL,MAAM,UAAU,GAAG;AAAA,MACrB;AAAA,IACF;AAAA,IAEA,IAAI,UAAU,IAAI;AAChB,aAAO,KAAK,QAAQ,SAAS,IAAI,QAAQ,IAAI,EAAE,KAAK,QAAQ,SAAS,IAAI,QAAQ;AAAA,IACnF;AAAA,EACF;AACF;;;ACxYO,IAAM,oBAAoB;AA4B1B,SAAS,kBAAkB,OAAgG;AAChI,QAAM,MAAM,MAAM,OAAO,CAAC;AAC1B,QAAM,UAAU,CAAC,SAAiB;AAChC,eAAW,KAAK,MAAM,UAAU;AAC9B,YAAM,IAAI,IAAI,GAAG,CAAC,QAAQ,IAAI,EAAE;AAChC,UAAI,EAAG,QAAO;AAAA,IAChB;AACA,WAAO;AAAA,EACT;AACA,MAAI;AACJ,QAAM,WAAW,MAAO,SAAS,MAAM,WAAW,KAAK,CAAC;AACxD,QAAM,IAAI,MAAM,WAAW,CAAC;AAC5B,QAAM,SAAyB;AAAA,IAC7B,SAAS,EAAE,UAAU,QAAQ,SAAS,KAAK,SAAS,EAAE,UAAU,IAAI,QAAQ,QAAQ,EAAE;AAAA,IACtF,WAAW,EAAE,aAAa,QAAQ,WAAW,KAAK,SAAS,EAAE,aAAa;AAAA,IAC1E,WAAW,EAAE,aAAa,QAAQ,YAAY,KAAK,SAAS,EAAE;AAAA,IAC9D,WAAW,EAAE,YAAY,QAAQ,WAAW,KAAK,mBAAmB,QAAQ,QAAQ,EAAE;AAAA,IACtF,WAAW,EAAE,aAAa,QAAQ,YAAY;AAAA,EAChD;AACA,QAAM,UAAW,CAAC,UAAU,WAAW,EAAY,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AAC3E,SAAO,EAAE,QAAQ,QAAQ;AAC3B;;;ACjDO,IAAM,eAAe;AAE5B,IAAM,MAAM,IAAI,YAAY;AAE5B,SAAS,UAAU,OAA4B;AAC7C,QAAM,IAAI,KAAK,OAAO,aAAa,GAAG,IAAI,WAAW,KAAK,CAAC,CAAC;AAC5D,SAAO,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,EAAE;AACpE;AAEA,eAAe,KAAK,OAAe,QAAiC;AAClE,QAAM,MAAM,MAAM,OAAO,OAAO,UAAU,OAAO,IAAI,OAAO,MAAM,GAAG,EAAE,MAAM,QAAQ,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC;AACvH,SAAO,UAAU,MAAM,OAAO,OAAO,KAAK,QAAQ,KAAK,IAAI,OAAO,KAAK,CAAC,CAAC;AAC3E;AAGA,eAAsB,UAAU,OAAe,QAAiC;AAC9E,SAAO,GAAG,KAAK,IAAI,MAAM,KAAK,OAAO,MAAM,CAAC;AAC9C;AAGA,eAAsB,YAAY,QAAgB,QAAwC;AACxF,QAAM,MAAM,OAAO,YAAY,GAAG;AAClC,MAAI,MAAM,EAAG,QAAO;AACpB,QAAM,QAAQ,OAAO,MAAM,GAAG,GAAG;AACjC,QAAM,MAAM,OAAO,MAAM,MAAM,CAAC;AAChC,QAAM,WAAW,MAAM,KAAK,OAAO,MAAM;AACzC,MAAI,IAAI,WAAW,SAAS,OAAQ,QAAO;AAC3C,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,IAAK,SAAQ,IAAI,WAAW,CAAC,IAAI,SAAS,WAAW,CAAC;AACtF,SAAO,SAAS,IAAI,QAAQ;AAC9B;AAGO,SAAS,iBAAiB,IAAoB;AACnD,SAAO,GAAG,WAAW,GAAG,KAAK,CAAC,GAAG,WAAW,IAAI,KAAK,CAAC,GAAG,WAAW,KAAK,IAAI,KAAK;AACpF;AAGO,SAAS,gBAAgB,OAA8B;AAC5D,MAAI;AACF,UAAM,OAAO,MAAM,MAAM,GAAG,EAAE,CAAC;AAC/B,QAAI,CAAC,KAAM,QAAO;AAElB,UAAM,QAAQ,WAAW,KAAK,KAAK,KAAK,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AACtG,UAAM,OAAO,KAAK,MAAM,IAAI,YAAY,EAAE,OAAO,KAAK,CAAC;AACvD,WAAO,KAAK,aAAa;AAAA,EAC3B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMA,eAAsB,kBAAkB,QAAgB,OAAe,YAA0B,OAAyB;AACxH,QAAM,OAAO,gBAAgB,KAAK;AAClC,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,MAAM,MAAM,UAAU,GAAG,OAAO,QAAQ,QAAQ,EAAE,CAAC,mBAAmB,mBAAmB,IAAI,CAAC,UAAU,mBAAmB,KAAK,CAAC,EAAE;AACzI,QAAM,IAAI,MAAM,OAAO,EAAE,MAAM,MAAM;AAAA,EAAC,CAAC;AACvC,SAAO,IAAI;AACb;AAGA,eAAsB,eAAe,cAAyC,QAAoD;AAChI,MAAI,CAAC,gBAAgB,CAAC,OAAQ,QAAO;AACrC,QAAM,IAAI,IAAI,OAAO,cAAc,YAAY,UAAU,EAAE,KAAK,YAAY;AAC5E,MAAI,CAAC,EAAG,QAAO;AACf,MAAI;AACF,WAAO,MAAM,YAAY,mBAAmB,EAAE,CAAC,CAAE,GAAG,MAAM;AAAA,EAC5D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAOA,eAAsB,oBAAoB,SAAkB,MAAmG;AAC7J,QAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAC/B,QAAM,QAAQ,IAAI,aAAa,IAAI,OAAO;AAC1C,MAAI,CAAC,KAAK,OAAQ,QAAO,IAAI,SAAS,uCAAuC,EAAE,QAAQ,IAAI,CAAC;AAC5F,MAAI,CAAC,MAAO,QAAO,IAAI,SAAS,iBAAiB,EAAE,QAAQ,IAAI,CAAC;AAChE,MAAI,CAAE,MAAM,kBAAkB,KAAK,QAAQ,OAAO,KAAK,SAAS,EAAI,QAAO,IAAI,SAAS,4BAA4B,EAAE,QAAQ,IAAI,CAAC;AACnI,QAAM,SAAS,GAAG,YAAY,IAAI,mBAAmB,MAAM,UAAU,OAAO,KAAK,MAAM,CAAC,CAAC,kDAAkD,IAAI,aAAa,WAAW,aAAa,EAAE;AACtL,SAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,KAAK,SAAS,EAAE,UAAU,iBAAiB,IAAI,aAAa,IAAI,UAAU,KAAK,GAAG,GAAG,cAAc,OAAO,EAAE,CAAC;AACnJ;AAGO,SAAS,qBAAqB,SAA4B;AAC/D,QAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAC/B,SAAO,IAAI,SAAS,MAAM;AAAA,IACxB,QAAQ;AAAA,IACR,SAAS,EAAE,UAAU,iBAAiB,IAAI,aAAa,IAAI,UAAU,KAAK,GAAG,GAAG,cAAc,GAAG,YAAY,+CAA+C;AAAA,EAC9J,CAAC;AACH;;;ACvFA,eAAsB,cACpB,MACkC;AAClC,QAAM,UAAU,KAAK,SAAS;AAC9B,QAAM,MAAM,GAAG,KAAK,OAAO,QAAQ,QAAQ,EAAE,CAAC,0BAA0B,mBAAmB,KAAK,SAAS,CAAC,IAAI,mBAAmB,KAAK,QAAQ,MAAM,CAAC;AACrJ,QAAM,MAAM,MAAM,QAAQ,KAAK,EAAE,SAAS,EAAE,QAAQ,mBAAmB,EAAE,CAAC;AAC1E,MAAI,IAAI,WAAW,IAAK,QAAO;AAC/B,MAAI,CAAC,IAAI,GAAI,OAAM,IAAI,eAAe,iBAAiB,KAAK,IAAI,wBAAwB,IAAI,MAAM,KAAK,IAAI,QAAQ,IAAI,WAAW,MAAM,WAAW,YAAY,WAAW,cAAc;AACxL,UAAS,MAAM,IAAI,KAAK,GAAkC;AAC5D;AAGO,SAAS,eAAe,UAAuD;AACpF,MAAI,CAAC,SAAU,QAAO;AACtB,SAAO,UAAU,SAAS,GAAG,WAAW,SAAS,IAAI,GAAG,SAAS,OAAO;AAC1E;AAOO,SAAS,kBAAkB,MAAqB;AACrD,aAAW,OAAO,MAAM,KAAK,KAAK,iBAAiB,QAAQ,CAAC,GAAG;AAC7D,QAAI,IAAI,aAAa,eAAe,EAAG;AACvC,UAAM,QAAQ,SAAS,cAAc,QAAQ;AAC7C,eAAW,QAAQ,MAAM,KAAK,IAAI,UAAU,EAAG,OAAM,aAAa,KAAK,MAAM,KAAK,KAAK;AACvF,UAAM,aAAa,iBAAiB,EAAE;AACtC,UAAM,cAAc,IAAI;AACxB,QAAI,YAAY,KAAK;AAAA,EACvB;AACF;AAqBO,SAAS,eACd,SACU;AACV,QAAM,MAAM,QAAQ,OAAO,CAAC;AAC5B,QAAM,EAAE,QAAQ,QAAQ,IAAI,kBAAkB,EAAE,SAAS,KAAK,UAAU,CAAC,GAAG,QAAQ,UAAU,EAAE,EAAE,CAAC;AACnG,MAAI,QAAQ,SAAS,GAAG;AACtB,UAAM,QAAQ,QAAQ,IAAI,CAAC,MAAM,GAAG,QAAQ,SAAS,CAAC,CAAC,QAAQ,MAAM,WAAW,YAAY,WAAW,EAAE,EAAE,KAAK,OAAO;AACvH,UAAM,IAAI,MAAM,IAAI,QAAQ,OAAO,aAAa,KAAK,2CAA2C;AAAA,EAClG;AACA,QAAM,SAAS,QAAQ,eAAe,IAAI;AAC1C,SAAO;AAAA,IACL;AAAA,IACA,MAAM,OAAO,SAAS;AACpB,YAAM,QAAQ,UAAU,MAAM,eAAe,QAAQ,QAAQ,IAAI,QAAQ,GAAG,MAAM,IAAI;AACtF,aAAO,aAAa;AAAA,QAClB,QAAQ,OAAO;AAAA,QACf,WAAW,OAAO;AAAA,QAClB,WAAW,OAAO;AAAA,QAClB,GAAI,QAAQ,EAAE,aAAa,UAAmB,cAAc,MAAM,IAAI,CAAC;AAAA,MACzE,CAAC;AAAA,IACH;AAAA,IACA,SAAS,MAAM,WAAW;AACxB,UAAI,CAAC,OAAO,UAAW,OAAM,IAAI,MAAM,IAAI,QAAQ,OAAO,oCAAoC,QAAQ,SAAS,CAAC,CAAC,kBAAkB;AACnI,aAAO,cAAc,EAAE,QAAQ,OAAO,QAAQ,WAAW,OAAO,WAAW,MAAM,OAAO,UAAU,CAAC;AAAA,IACrG;AAAA,IACA,aAAa,CAAC,YAAY,oBAAoB,SAAS,EAAE,QAAQ,OAAO,QAAQ,OAAO,CAAC;AAAA,IACxF,cAAc;AAAA,EAChB;AACF;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bettercms-ai/sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.37.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@bettercms-ai/image-url": "^0.4.0",
|
|
47
|
-
"@bettercms-ai/types": "^1.
|
|
47
|
+
"@bettercms-ai/types": "^1.57.0"
|
|
48
48
|
},
|
|
49
49
|
"scripts": {
|
|
50
50
|
"typecheck": "tsc --noEmit",
|