@reblu/site-client 5.20.0 → 5.21.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/CHANGELOG.md +6 -0
- package/README.md +13 -1
- package/dist/index.cjs +8 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @reblu/site-client
|
|
2
2
|
|
|
3
|
+
## 5.21.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- c8bf6c1: Featured reviews (REB-1272). `@reblu/site-contracts` exports `FeaturedReviews` (`{ layout: 'grid' | 'carousel', reviews: Review[] }`) and `FeaturedReviewsLayout` for `GET /api/site/reviews/featured`. `@reblu/site-client` adds `client.reviews.featured({ limit? }, cache?)`: the reviews the tenant featured for its home in nexus, published only and in the featured order, plus the layout it chose (404 without the tenant's `reviews` module). `reviews.list()` and every other namespace are unchanged.
|
|
8
|
+
|
|
3
9
|
## 5.20.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -199,7 +199,7 @@ resource; list methods return `{ data, meta? }` (pagination metadata preserved).
|
|
|
199
199
|
| `analytics` | `get()` |
|
|
200
200
|
| `legal` | `get()` — privacy, terms, cookies, `legalNotice`, `withdrawalPolicy` |
|
|
201
201
|
| `trustBadges` | `list(params?)` — `{ type? }`; only active badges inside their validity window |
|
|
202
|
-
| `reviews` | `list(params?)` — `{ pageId?, limit? }`; published reviews only (REB-1272). 404 without the tenant's `reviews` module |
|
|
202
|
+
| `reviews` | `list(params?)` — `{ pageId?, limit? }`; published reviews only (REB-1272). `featured(params?)` — `{ limit? }` → `{ layout, reviews }`, the home's featured reviews. 404 without the tenant's `reviews` module |
|
|
203
203
|
| `ctas` | `list(slot)` |
|
|
204
204
|
| `blog` | `list(params?)`, `bySlug(slug)`, `related(slug)`, `categories()`, `slugs()`, `preview(id)` |
|
|
205
205
|
| `pages` | `list(params?)`, `bySlug(slug)`, `featured(category, opts?)`, `slugs()` |
|
|
@@ -247,6 +247,18 @@ The endpoint answers 404 when the tenant does not have the `reviews` module, so
|
|
|
247
247
|
catch it and render nothing: `.catch(() => ({ data: [] }))`. Render the list
|
|
248
248
|
with `Reviews` from `@reblu/site-blocks`.
|
|
249
249
|
|
|
250
|
+
`reviews.featured()` (owner, 8-Nov) returns the reviews the tenant featured
|
|
251
|
+
for its home in nexus, published only and in the featured order, plus the
|
|
252
|
+
`layout` it chose (`'grid'` or `'carousel'`). Render both with
|
|
253
|
+
`FeaturedReviews` from `@reblu/site-blocks/client`:
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
const featured = await client.reviews
|
|
257
|
+
.featured({ limit: 9 })
|
|
258
|
+
.catch(() => ({ layout: 'grid' as const, reviews: [] }))
|
|
259
|
+
// <FeaturedReviews reviews={featured.reviews} layout={featured.layout} />
|
|
260
|
+
```
|
|
261
|
+
|
|
250
262
|
`pages.bySlug(slug)` returns a `PageDetail` whose `blocks[]` mirror their
|
|
251
263
|
parent section's `heading`/`backgroundColor`/`cta`, with embed blocks
|
|
252
264
|
(`doctor_embed`/`specialty_embed`) additionally carrying a server-resolved
|
package/dist/index.cjs
CHANGED
|
@@ -681,7 +681,14 @@ function makeReviewsNamespace(ctx) {
|
|
|
681
681
|
{ cachePolicy: cache }
|
|
682
682
|
);
|
|
683
683
|
}
|
|
684
|
-
|
|
684
|
+
function featured(params = {}, cache) {
|
|
685
|
+
return requestOne(
|
|
686
|
+
ctx,
|
|
687
|
+
buildUrl("/api/site/reviews/featured", { limit: params.limit }),
|
|
688
|
+
{ cachePolicy: cache }
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
return { list, featured };
|
|
685
692
|
}
|
|
686
693
|
|
|
687
694
|
// src/domains/trust-badges.ts
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cache.ts","../src/checkout-redirect.ts","../src/build-url.ts","../src/errors.ts","../src/client.ts","../src/domains/academy.ts","../src/domains/analytics.ts","../src/domains/blog.ts","../src/domains/config.ts","../src/domains/contact-form.ts","../src/domains/ctas.ts","../src/domains/data-requests.ts","../src/domains/leads.ts","../src/domains/legal.ts","../src/domains/pages.ts","../src/domains/shop.ts","../src/domains/reviews.ts","../src/domains/trust-badges.ts","../src/domains/withdrawal-requests.ts","../src/contact-form-metadata.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Cache adapter — the seam that severs the SDK from any framework's caching\n * model (design doc D4 / §3.2). The client owns *when* to cache (a per-call\n * `RebluCachePolicy`); the adapter owns *how* (the framework-specific\n * `RequestInit` fields). `@reblu/site-client` ships the framework-agnostic\n * `PlainCacheAdapter`; `@reblu/site-next` (REB-418) supplies a `NextCacheAdapter`\n * that returns `{ next: { revalidate } }`.\n */\n\nexport interface RebluCachePolicy {\n /**\n * Seconds to revalidate. `undefined` → use the adapter's default; `0`/`false`\n * → never cache (always fetch fresh).\n */\n revalidate?: number | false\n}\n\nexport interface RebluCacheAdapter {\n /** Map a per-call cache policy to framework-specific `fetch` `RequestInit` fields. */\n resolveRequestInit(policy: RebluCachePolicy): RequestInit\n /**\n * Optional (REB-1245): perform the HTTP call yourself. When present, the\n * client calls it for every attempt instead of `transport(url, init)`, with\n * `init` already carrying `resolveRequestInit`'s fields. An adapter uses it to\n * cache the read's OUTCOME (a 404 included), which a framework fetch cache\n * may refuse to store. Return a `Response` exactly as `fetch` would: the\n * client still maps non-2xx to `ApiError` and retries 429/503 reads.\n */\n fetch?(url: string, init: RequestInit, transport: typeof fetch): Promise<Response>\n}\n\n/**\n * Default adapter — plain `fetch` cache semantics that work in Node, edge,\n * browser and tests. No `next`, no `server-only`.\n */\nexport class PlainCacheAdapter implements RebluCacheAdapter {\n constructor(private readonly defaultRevalidate: number | false = false) {}\n\n resolveRequestInit(policy: RebluCachePolicy): RequestInit {\n const revalidate = policy.revalidate ?? this.defaultRevalidate\n return revalidate === false || revalidate === 0\n ? { cache: 'no-store' }\n : { cache: 'force-cache' }\n }\n}\n","/**\n * Stripe Checkout redirect-origin guard (REB-836, defence in depth).\n *\n * `shop.checkout()` returns a URL every SITE immediately hands to\n * `window.location.assign(url)`. Nothing upstream of this SDK validated it\n * before REB-836 — a compromised/misconfigured base-api, a tampered\n * response, or a MITM proxy could have sent a buyer's browser anywhere.\n * `isSafeCheckoutRedirectUrl` is the shared predicate `domains/shop.ts` uses\n * to refuse an unsafe URL before ever returning it to the caller; a SITE's\n * `@reblu/site-next` Route Handler re-checks the SAME property server-side\n * (`packages/site-next/src/shop/checkout-redirect.ts`, kept as an\n * independent copy — see that file's docblock for why it isn't a shared\n * import). Update both together if Stripe ever changes its Checkout host.\n */\n\n/** The only Checkout redirect host trusted by default. */\nexport const DEFAULT_CHECKOUT_REDIRECT_HOSTS: readonly string[] = ['checkout.stripe.com']\n\n/**\n * True only when `url` parses as an absolute URL, uses `https:`, and its\n * hostname is in `allowedHosts` (default: {@link DEFAULT_CHECKOUT_REDIRECT_HOSTS}).\n * Never throws — an unparsable `url` is simply unsafe.\n */\nexport function isSafeCheckoutRedirectUrl(\n url: string,\n allowedHosts: readonly string[] = DEFAULT_CHECKOUT_REDIRECT_HOSTS,\n): boolean {\n let parsed: URL\n try {\n parsed = new URL(url)\n } catch {\n return false\n }\n return parsed.protocol === 'https:' && allowedHosts.includes(parsed.hostname)\n}\n\n/**\n * Thrown by `shop.checkout()` when base-api's Checkout URL fails\n * {@link isSafeCheckoutRedirectUrl} — the SDK never hands an unsafe URL back\n * to a caller that would otherwise `window.location.assign` it.\n */\nexport class UnsafeCheckoutRedirectError extends Error {\n constructor(\n public readonly url: string,\n public readonly allowedHosts: readonly string[],\n ) {\n super(\n `Refusing to return an unsafe Stripe Checkout redirect URL: ${JSON.stringify(url)} ` +\n `(allowed hosts: ${allowedHosts.join(', ')})`,\n )\n this.name = 'UnsafeCheckoutRedirectError'\n }\n}\n","/** A value that can be serialized into a query string. */\nexport type QueryValue = string | number | boolean | undefined | null\n\n/**\n * Append a query string to `base`, dropping `undefined`/`null` values.\n *\n * Single definition for the whole client (design doc §3.1 dedupes the two copies\n * that lived in demo-site's `client.ts` and `index.ts`). Falsy-but-meaningful\n * values (`0`, `false`, `''`) are preserved; only `undefined`/`null` are omitted.\n * Array values (REB-475 faceted `attr[key]`) emit one repeated param per element.\n */\nexport function buildUrl(\n base: string,\n params?: Record<string, QueryValue | QueryValue[]>,\n): string {\n if (!params) return base\n const pairs: [string, string][] = []\n for (const [key, value] of Object.entries(params)) {\n const values = Array.isArray(value) ? value : [value]\n for (const v of values) {\n if (v === undefined || v === null) continue\n pairs.push([key, String(v)])\n }\n }\n if (pairs.length === 0) return base\n const qs = new URLSearchParams(pairs).toString()\n return `${base}?${qs}`\n}\n","import type { AcademyCheckoutErrorCode } from '@reblu/site-contracts'\n\n/**\n * The `details` shape `/api/site/contact-form` and `/api/site/leads` attach on a\n * 400/422, straight from zod's `.flatten()` (see\n * `details: parsed.error.flatten()` in\n * `apps/base-api/src/lib/server/services/contact-form.service.ts:114` and\n * `apiError('Validation error', 422, parsed.error.flatten())` in\n * `apps/base-api/src/app/api/site/leads/route.ts:40`). `fieldErrors` is what a\n * SITE actually needs — REB-659's `details.fieldErrors.phone` — `formErrors` is\n * kept alongside it because `.flatten()` always emits both and dropping it would\n * silently discard form-level (non-field) messages.\n */\nexport interface ApiErrorDetails {\n fieldErrors?: Record<string, string[]>\n formErrors?: string[]\n /**\n * REB-836: the affected product ids on a shop checkout `STOCK_CHANGED` /\n * `PRICE_CHANGED` error — see `ApiErrorBody.code`.\n */\n productIds?: string[]\n /** REB-842: optional request-field identifier for checkout validation errors. */\n field?: string\n}\n\n/**\n * The parsed shape of a non-2xx SITE API response body: `{ error, details? }`\n * (see `apiError()` in `apps/base-api/src/lib/api/response.ts:33` — the general\n * shape both routes above build on).\n */\nexport interface ApiErrorBody {\n error: string\n details?: ApiErrorDetails\n /**\n * REB-836: stable, machine-readable error code (e.g. `ShopErrorCode` from\n * `@reblu/site-contracts`) the checkout/validate-cart/apply-discount write\n * endpoints may set — additive and OPTIONAL, so a consumer that only reads\n * `error`/`message` keeps working unchanged. `string` rather than\n * `ShopErrorCode` here because `ApiError` is a generic transport error used\n * by every `/api/site/*` domain, not just shop.\n */\n code?: string\n}\n\nfunction isStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((item) => typeof item === 'string')\n}\n\n/**\n * Validates `ApiErrorDetails` deeply — `fieldErrors` must be a record whose\n * values are `string[] | undefined`, `formErrors` (if present) must be\n * `string[]`, and `field` (if present) must be a string. Without this, a body like\n * `{ fieldErrors: { phone: \"abc\" } }` passed the old shallow check, so\n * `body.details?.fieldErrors?.phone?.[0]` silently returned the character\n * `\"a\"` instead of a validation message (REB-764 review finding).\n */\nfunction isApiErrorDetails(value: unknown): value is ApiErrorDetails {\n if (typeof value !== 'object' || value === null) return false\n const details = value as {\n fieldErrors?: unknown\n formErrors?: unknown\n productIds?: unknown\n field?: unknown\n }\n\n if (details.fieldErrors !== undefined) {\n if (\n typeof details.fieldErrors !== 'object' ||\n details.fieldErrors === null ||\n Array.isArray(details.fieldErrors)\n ) {\n return false\n }\n for (const fieldValue of Object.values(details.fieldErrors as Record<string, unknown>)) {\n if (fieldValue !== undefined && !isStringArray(fieldValue)) return false\n }\n }\n\n if (details.formErrors !== undefined && !isStringArray(details.formErrors)) return false\n\n // REB-836: STOCK_CHANGED/PRICE_CHANGED carry the affected product ids here.\n if (details.productIds !== undefined && !isStringArray(details.productIds)) return false\n if ('field' in details && typeof details.field !== 'string') return false\n\n return true\n}\n\n/**\n * `details`, when present, must match `ApiErrorDetails` deeply — an\n * unrecognised `details` shape makes the whole body unrecognised (`body`\n * stays `undefined`), the stricter reading consistent with\n * `parseApiErrorBody`'s existing all-or-nothing semantics: a body is either\n * shaped like `ApiErrorBody` or it isn't. Silently dropping a malformed\n * `details` instead would let a consumer treat a schema-drifted or corrupted\n * response as merely detail-less, hiding the mismatch rather than surfacing\n * it as \"unparseable\" (REB-764 review finding).\n */\nfunction isApiErrorBody(value: unknown): value is ApiErrorBody {\n if (typeof value !== 'object' || value === null) return false\n const body = value as { error?: unknown; details?: unknown; code?: unknown }\n if (typeof body.error !== 'string') return false\n if (body.details !== undefined && !isApiErrorDetails(body.details)) return false\n // REB-836: `code` must be a string when present — same all-or-nothing\n // reading as `details` above (an unrecognised shape makes the whole body\n // unrecognised, never silently dropped).\n if (body.code !== undefined && typeof body.code !== 'string') return false\n return true\n}\n\n/**\n * Parses `rawBody` (the exact text `message` is built from) into a typed\n * `ApiErrorBody`, or `undefined` when it is not JSON, or JSON that does not\n * match the expected shape — an edge proxy 502 returns HTML, and this must\n * never throw while constructing the error it is reporting.\n */\nfunction parseApiErrorBody(rawBody: string): ApiErrorBody | undefined {\n if (!rawBody) return undefined\n try {\n const parsed: unknown = JSON.parse(rawBody)\n return isApiErrorBody(parsed) ? parsed : undefined\n } catch {\n return undefined\n }\n}\n\n/**\n * Typed transport error for the Reblu SITE API.\n *\n * Every non-2xx response throws an `ApiError` (never a bare `fetch` rejection),\n * so consumers can `catch (e) { if (e instanceof ApiError) … }` and fall back\n * gracefully (e.g. render an empty section) — the one behavioural contract the\n * SITE relies on. Moved verbatim from demo-site's `client.ts` (design doc §3.4).\n */\nexport class ApiError extends Error {\n /**\n * The parsed response body, when it was JSON shaped like `{ error, details? }`\n * (REB-764). `undefined` when the body was not JSON or didn't match — parsing\n * is defensive and never throws. Read `body.details?.fieldErrors?.phone`\n * instead of regex-stripping this error's `message`.\n */\n public readonly body: ApiErrorBody | undefined\n\n constructor(\n public readonly status: number,\n public readonly path: string,\n message: string,\n ) {\n super(`API ${status} @ ${path}: ${message}`)\n this.name = 'ApiError'\n this.body = parseApiErrorBody(message)\n }\n}\n\n/** One page a slug could mean (REB-867) — `null` = the uncategorised page. */\nexport interface PageSlugCandidate {\n categorySlug: string | null\n}\n\nfunction parseCandidates(rawBody: string): PageSlugCandidate[] {\n try {\n const parsed = JSON.parse(rawBody) as { candidates?: unknown }\n if (!Array.isArray(parsed.candidates)) return []\n return parsed.candidates.flatMap((c: unknown) => {\n const slug = (c as { categorySlug?: unknown } | null)?.categorySlug\n return typeof slug === 'string' || slug === null ? [{ categorySlug: slug }] : []\n })\n } catch {\n return []\n }\n}\n\n/**\n * REB-867 — `GET /api/site/pages/[slug]` answered 409 `ambiguous_slug`: page\n * slugs are unique PER CATEGORY and this one exists in more than one, so the\n * server refused to pick. Retry with `pages.bySlug(slug, { category })` using\n * one of `candidates`. Extends `ApiError`, so an existing\n * `catch (e) { if (e instanceof ApiError) … }` keeps handling it.\n */\nexport class AmbiguousPageSlugError extends ApiError {\n public readonly candidates: PageSlugCandidate[]\n\n constructor(source: ApiError, rawBody: string) {\n super(source.status, source.path, rawBody)\n this.name = 'AmbiguousPageSlugError'\n this.candidates = parseCandidates(rawBody)\n }\n}\n\n/** The raw body an `ApiError` was built from (its message minus the prefix). */\nexport function apiErrorRawBody(error: ApiError): string {\n const prefix = `API ${error.status} @ ${error.path}: `\n return error.message.startsWith(prefix) ? error.message.slice(prefix.length) : error.message\n}\n\n/**\n * `details` of a guest course checkout refusal (REB-1264): `field` on\n * `INVALID_INPUT`, `reason` on `ENROLLMENT_REQUEST_REQUIRED`\n * (`VALIDATION_REQUIRED` | `FREE_COURSE`), and `fieldErrors` — one message\n * per field, a plain string — on the terms, consent and billing refusals.\n */\nexport interface AcademyCheckoutErrorDetails {\n field?: string\n reason?: string\n fieldErrors?: Record<string, string>\n /** `INVOICING_NOT_READY`: what the academy still lacks to invoice (e.g. `taxId`, `series`). */\n missing?: string[]\n}\n\n/** Keeps only the well-typed parts of `details` (strings, and string-valued `fieldErrors`). */\nfunction toAcademyCheckoutDetails(value: unknown): AcademyCheckoutErrorDetails | undefined {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return undefined\n const raw = value as {\n field?: unknown\n reason?: unknown\n fieldErrors?: unknown\n missing?: unknown\n }\n const details: AcademyCheckoutErrorDetails = {}\n if (typeof raw.field === 'string') details.field = raw.field\n if (typeof raw.reason === 'string') details.reason = raw.reason\n if (Array.isArray(raw.missing) && raw.missing.every((item) => typeof item === 'string')) {\n details.missing = raw.missing as string[]\n }\n if (\n typeof raw.fieldErrors === 'object' &&\n raw.fieldErrors !== null &&\n !Array.isArray(raw.fieldErrors)\n ) {\n const fieldErrors = Object.fromEntries(\n Object.entries(raw.fieldErrors).filter(\n (entry): entry is [string, string] => typeof entry[1] === 'string',\n ),\n )\n if (Object.keys(fieldErrors).length > 0) details.fieldErrors = fieldErrors\n }\n return Object.keys(details).length > 0 ? details : undefined\n}\n\nfunction parseAcademyCheckoutBody(rawBody: string): {\n error?: string\n code?: AcademyCheckoutErrorCode\n details?: AcademyCheckoutErrorDetails\n} {\n try {\n const parsed = JSON.parse(rawBody) as { error?: unknown; code?: unknown; details?: unknown }\n if (typeof parsed !== 'object' || parsed === null) return {}\n const details = toAcademyCheckoutDetails(parsed.details)\n return {\n ...(typeof parsed.error === 'string' ? { error: parsed.error } : {}),\n ...(isAcademyCheckoutErrorCode(parsed.code) ? { code: parsed.code } : {}),\n ...(details ? { details } : {}),\n }\n } catch {\n return {}\n }\n}\n\n// A `Record` so the compiler rejects a code base-api adds and this list lacks.\nconst ACADEMY_CHECKOUT_CODE_SET: Record<AcademyCheckoutErrorCode, true> = {\n INVALID_INPUT: true,\n RATE_LIMITED: true,\n COURSE_NOT_FOUND: true,\n SALES_CLOSED: true,\n ENROLLMENT_REQUEST_REQUIRED: true,\n INVOICING_NOT_READY: true,\n TERMS_NOT_ACCEPTED: true,\n TERMS_VERSION_CHANGED: true,\n CONSENT_REQUIRED: true,\n CONSENT_VERSION_CHANGED: true,\n INVALID_BILLING: true,\n}\n\nfunction isAcademyCheckoutErrorCode(value: unknown): value is AcademyCheckoutErrorCode {\n return typeof value === 'string' && Object.hasOwn(ACADEMY_CHECKOUT_CODE_SET, value)\n}\n\n/**\n * REB-1264 — `academy.checkout()` was refused. `code` is the stable\n * `AcademyCheckoutErrorCode` (`undefined` when the answer carried none, e.g.\n * an edge 502), `details` its extra data, `errorMessage` the server's text.\n * On `ENROLLMENT_REQUEST_REQUIRED` the course is not bought in one step:\n * show the enrollment request form instead. Extends `ApiError`, so an\n * existing `catch (e) { if (e instanceof ApiError) … }` keeps handling it.\n *\n * Its own parser because these refusals send `details.fieldErrors` as one\n * string per field, a shape `ApiError.body` (string arrays) does not accept.\n */\nexport class AcademyCheckoutError extends ApiError {\n public readonly code: AcademyCheckoutErrorCode | undefined\n public readonly details: AcademyCheckoutErrorDetails | undefined\n public readonly errorMessage: string | undefined\n\n constructor(source: ApiError) {\n const rawBody = apiErrorRawBody(source)\n super(source.status, source.path, rawBody)\n this.name = 'AcademyCheckoutError'\n const parsed = parseAcademyCheckoutBody(rawBody)\n this.code = parsed.code\n this.details = parsed.details\n this.errorMessage = parsed.error\n }\n}\n","import type { PaginationMeta } from '@reblu/site-contracts'\nimport type { RebluCacheAdapter, RebluCachePolicy } from './cache'\nimport { ApiError } from './errors'\nimport type { ListResult } from './types'\n\n/**\n * Resolved, immutable client context threaded into every domain namespace. Built\n * once by `createRebluClient` from the consumer's config — the SDK never reads\n * `process.env`, so the consumer owns `baseUrl`/`apiKey` (design doc §3.3).\n */\nexport interface RebluClientContext {\n baseUrl: string\n apiKey: string\n apiVersion: string\n cache: RebluCacheAdapter\n timeoutMs: number\n fetch: typeof fetch\n /**\n * Allowed hosts for the Stripe Checkout URL `shop.checkout()` returns\n * (REB-836, defence in depth). Defaults to\n * `DEFAULT_CHECKOUT_REDIRECT_HOSTS` (`['checkout.stripe.com']`) — tests\n * inject a different value via `RebluClientConfig.checkoutRedirectHosts`.\n */\n checkoutRedirectHosts: readonly string[]\n /**\n * REB-1214 — returns the memo store of the CURRENT server request, so\n * identical GET reads within it are fetched once (e.g. `generateMetadata`\n * and the page body). `@reblu/site-next` supplies one backed by React\n * `cache()` (`rebluRequestScope`). Absent, or returning `undefined`,\n * identical GETs still share one fetch while they are in flight.\n */\n requestScope?: RequestScope\n /** In-flight GETs when no request scope is active. Created by `createRebluClient`. */\n inFlight?: Map<string, Promise<unknown>>\n}\n\n/** Memo store of one server request, keyed by read identity (REB-1214). */\nexport type RequestScope = () => Map<string, Promise<unknown>> | undefined\n\n/** Upper bound for a `Retry-After` wait before the single retry (REB-1214). */\nexport const RETRY_AFTER_CAP_MS = 2000\n/** Wait before the single retry when `Retry-After` is absent or unparseable. */\nexport const DEFAULT_RETRY_DELAY_MS = 250\n/** Statuses that mean \"try again shortly\" for a read. */\nconst RETRYABLE_READ_STATUSES = new Set([429, 503])\n\n/** `Retry-After` (seconds or HTTP date) → ms, capped; default when unusable. */\nfunction retryDelayMs(header: string | null): number {\n if (header !== null && header.trim() !== '') {\n const value = header.trim()\n const seconds = /^\\d+$/.test(value) ? Number(value) * 1000 : NaN\n const ms = Number.isFinite(seconds) ? seconds : Date.parse(value) - Date.now()\n if (Number.isFinite(ms)) return Math.min(Math.max(0, ms), RETRY_AFTER_CAP_MS)\n }\n return DEFAULT_RETRY_DELAY_MS\n}\n\nconst sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms))\n\n/** Per-call request options. `cachePolicy` is interpreted by the cache adapter. */\nexport interface RequestOptions {\n method?: string\n body?: string\n headers?: Record<string, string>\n cachePolicy?: RebluCachePolicy\n}\n\n/**\n * Core transport (design doc §3.4). Injects the auth + contract-version headers,\n * merges the adapter's cache `RequestInit`, enforces a hard timeout per attempt,\n * throws a typed `ApiError` on non-2xx, and returns the parsed JSON body verbatim\n * (envelope unwrapping is the caller's concern).\n *\n * REB-1214 — GET reads only: identical reads are deduped (see\n * `RebluClientContext.requestScope`) and a 429/503 is retried once after\n * `Retry-After`, capped at {@link RETRY_AFTER_CAP_MS}. If the retry fails too,\n * the second response's `ApiError` is thrown — the same type as before.\n * Writes are never deduped nor retried.\n */\nexport async function request<T>(\n ctx: RebluClientContext,\n path: string,\n opts: RequestOptions,\n): Promise<T> {\n const method = opts.method ?? 'GET'\n const init: RequestInit = {\n method,\n body: opts.body,\n headers: {\n 'X-Reblu-Api-Key': ctx.apiKey,\n 'X-Reblu-Api-Version': ctx.apiVersion,\n 'Content-Type': 'application/json',\n ...opts.headers,\n },\n ...ctx.cache.resolveRequestInit(opts.cachePolicy ?? {}),\n }\n if (method !== 'GET' || opts.body !== undefined) {\n return attempt<T>(ctx, path, init)\n }\n\n // REB-1214 — a read is identified by everything that shapes its response:\n // URL, headers and the resolved cache init. Kept for the whole request when\n // a scope is active; otherwise only while in flight.\n const key = JSON.stringify([ctx.baseUrl + path, init])\n const scope = ctx.requestScope?.()\n const store = scope ?? ctx.inFlight\n const existing = store?.get(key)\n if (existing) return existing as Promise<T>\n\n const pending = readWithRetry<T>(ctx, path, init)\n if (store) {\n store.set(key, pending)\n // A failure is never memoised; without a scope nothing outlives the fetch.\n pending.then(\n () => {\n if (!scope) store.delete(key)\n },\n () => {\n if (store.get(key) === pending) store.delete(key)\n },\n )\n }\n return pending\n}\n\n/** A GET answered 429/503 is retried once after `Retry-After` (capped). */\nasync function readWithRetry<T>(\n ctx: RebluClientContext,\n path: string,\n init: RequestInit,\n): Promise<T> {\n try {\n return await attempt<T>(ctx, path, init)\n } catch (error) {\n if (!(error instanceof RetryableReadError)) throw error\n await sleep(retryDelayMs(error.retryAfter))\n try {\n return await attempt<T>(ctx, path, init)\n } catch (retryError) {\n throw retryError instanceof RetryableReadError ? retryError.apiError : retryError\n }\n }\n}\n\n/** Internal carrier: a retryable read status plus the `ApiError` to throw if it persists. */\nclass RetryableReadError extends Error {\n constructor(\n readonly apiError: ApiError,\n readonly retryAfter: string | null,\n ) {\n super(apiError.message)\n }\n}\n\n/**\n * One HTTP attempt: enforces the hard timeout via `AbortController`, throws a\n * typed `ApiError` on non-2xx, and returns the parsed JSON body verbatim\n * (envelope unwrapping is the caller's concern).\n */\nasync function attempt<T>(ctx: RebluClientContext, path: string, init: RequestInit): Promise<T> {\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), ctx.timeoutMs)\n\n try {\n const url = ctx.baseUrl + path\n const attemptInit = { ...init, signal: controller.signal }\n // REB-1245 — an adapter may own the call (e.g. to cache a 404 outcome).\n const res = ctx.cache.fetch\n ? await ctx.cache.fetch(url, attemptInit, ctx.fetch)\n : await ctx.fetch(url, attemptInit)\n\n if (!res.ok) {\n const error = new ApiError(res.status, path, await res.text())\n if (init.method === 'GET' && RETRYABLE_READ_STATUSES.has(res.status)) {\n throw new RetryableReadError(error, res.headers.get('retry-after'))\n }\n throw error\n }\n return (await res.json()) as T\n } finally {\n clearTimeout(timer)\n }\n}\n\n/** Fetch a single resource and unwrap the `{ data: T }` envelope. */\nexport async function requestOne<T>(\n ctx: RebluClientContext,\n path: string,\n opts: RequestOptions,\n): Promise<T> {\n const body = await request<{ data: T }>(ctx, path, opts)\n return body.data\n}\n\n/** Fetch a list resource, returning the `{ data, meta }` envelope (meta preserved). */\nexport async function requestList<T>(\n ctx: RebluClientContext,\n path: string,\n opts: RequestOptions,\n): Promise<ListResult<T>> {\n const body = await request<{ data: T[]; meta?: PaginationMeta }>(ctx, path, opts)\n return { data: body.data, meta: body.meta }\n}\n\n/**\n * Header carrying the visitor IP from a SITE server to base-api (REB-1005).\n * base-api keys its per-visitor limit on public writes on it, and trusts it\n * only behind a valid API key.\n */\nexport const VISITOR_IP_HEADER = 'x-reblu-visitor-ip'\n\n/**\n * Per-call options for a write (REB-1005).\n *\n * - `visitorIp` — the visitor's IP when a SITE calls the SDK server-side, sent\n * as `x-reblu-visitor-ip`. Do NOT rely on forwarding `x-forwarded-for`: on\n * Vercel the SITE→base-api hop gets it overwritten with the SITE function's\n * egress IP. Read it from the INCOMING request with\n * {@link visitorIpFromHeaders}.\n * - `headers` — other extra headers (e.g. `user-agent`). They can never set\n * `x-reblu-visitor-ip` nor replace `X-Reblu-Api-Key`, `X-Reblu-Api-Version`\n * or `Content-Type`.\n */\nexport interface RebluMutationOptions {\n visitorIp?: string\n headers?: Record<string, string>\n}\n\n/** The visitor IP of an incoming request: first `x-forwarded-for` entry, else `x-real-ip`. */\nexport function visitorIpFromHeaders(headers: {\n get(name: string): string | null\n}): string | undefined {\n return (\n headers.get('x-forwarded-for')?.split(',')[0]?.trim() ||\n headers.get('x-real-ip')?.trim() ||\n undefined\n )\n}\n\n/** Headers the SDK owns on every request — an extra header never sets or overrides these. */\nconst RESERVED_HEADERS = new Set([\n 'x-reblu-api-key',\n 'x-reblu-api-version',\n 'content-type',\n VISITOR_IP_HEADER,\n])\n\n/**\n * The extra headers a write sends for `options`: caller headers minus the\n * reserved ones, plus `x-reblu-visitor-ip` from `visitorIp`. Shared by\n * {@link requestMutation} and the writes that cannot use it (academy).\n */\nexport function extraHeaders(\n options: RebluMutationOptions | undefined,\n): Record<string, string> | undefined {\n if (!options?.headers && !options?.visitorIp) return undefined\n const safe: Record<string, string> = {}\n for (const [name, value] of Object.entries(options.headers ?? {})) {\n if (!RESERVED_HEADERS.has(name.toLowerCase())) safe[name] = value\n }\n const visitorIp = options.visitorIp?.trim()\n if (visitorIp) safe[VISITOR_IP_HEADER] = visitorIp\n return safe\n}\n\n/**\n * POST a mutation: serialize `body` to JSON, force a cache bypass (`revalidate:\n * false` → `no-store` regardless of the injected adapter, since a mutation must\n * never be served from cache), then unwrap the `{ data: T }` envelope. Mirrors\n * every `/api/site/*` write route, which answers with `apiOk`/`{ data }`.\n */\nexport async function requestMutation<Req, Res>(\n ctx: RebluClientContext,\n path: string,\n body: Req,\n options?: RebluMutationOptions,\n): Promise<Res> {\n const headers = extraHeaders(options)\n const envelope = await request<{ data: Res }>(ctx, path, {\n method: 'POST',\n body: JSON.stringify(body),\n cachePolicy: { revalidate: false },\n ...(headers ? { headers } : {}),\n })\n return envelope.data\n}\n","import type {\n AcademyCategoryPublic,\n AcademyCheckoutRequest,\n AcademyCheckoutSession,\n AcademyConfigPublic,\n AcademyCourseDetail,\n AcademyCourseSummary,\n AcademyInstructorPublic,\n AcademyLessonPreview,\n} from '@reblu/site-contracts'\nimport { buildUrl, type QueryValue } from '../build-url'\nimport type { RebluCachePolicy } from '../cache'\nimport { isSafeCheckoutRedirectUrl, UnsafeCheckoutRedirectError } from '../checkout-redirect'\nimport { AcademyCheckoutError, ApiError } from '../errors'\nimport {\n extraHeaders,\n request,\n requestList,\n requestMutation,\n requestOne,\n type RebluClientContext,\n type RebluMutationOptions,\n} from '../client'\nimport type {\n AcademyCourseListParams,\n AcademyEnrollmentRequestInput,\n AcademyEnrollmentRequestResult,\n ListResult,\n} from '../types'\n\n/**\n * Academy domain — the public course catalogue (`GET /api/site/academy/*`,\n * REB-528 / REB-534) plus the anonymous enrollment request (REB-496). The reads\n * are published-filtered server-side, so nothing here needs an entitlement\n * notion; the one write is unauthenticated by design.\n *\n * `enrollmentRequest` drops to the raw `request()` transport rather than\n * `requestMutation` (REB-584). The route answers with a BARE `{ message }`, not\n * the `{ data }` envelope every other write uses, so `requestMutation` would\n * unwrap a missing `.data` and hand the caller `undefined`. Normalizing the\n * route's envelope instead was rejected: its response shape is load-bearing\n * anti-enumeration behaviour, and changing it would ripple into base-api tests\n * for no gain on the SITE side.\n */\nexport function makeAcademyNamespace(ctx: RebluClientContext) {\n return {\n /** Published courses, filtered + paginated. `meta` carries the pagination. */\n courses: (\n params?: AcademyCourseListParams,\n cache?: RebluCachePolicy,\n ): Promise<ListResult<AcademyCourseSummary>> =>\n requestList<AcademyCourseSummary>(\n ctx,\n buildUrl('/api/site/academy/courses', params as Record<string, QueryValue> | undefined),\n { cachePolicy: cache },\n ),\n\n /** A single published course by slug, with its modules, lessons and CTA gate. */\n bySlug: (slug: string, cache?: RebluCachePolicy): Promise<AcademyCourseDetail> =>\n requestOne<AcademyCourseDetail>(ctx, `/api/site/academy/courses/${slug}`, {\n cachePolicy: cache,\n }),\n\n /**\n * A free-preview lesson, viewable by the public (REB-1135): title, duration,\n * Markdown body and a ready-to-embed video. Any lesson the public may not see\n * is a uniform 404 (`ApiError`). Never cached: the payload carries a\n * short-lived signed playback token. Server-side, prefer `getLessonPreview`\n * from `@reblu/site-next/academy-server`, which maps that 404 to `null`.\n */\n lessonPreview: (courseSlug: string, lessonSlug: string): Promise<AcademyLessonPreview> =>\n requestOne<AcademyLessonPreview>(\n ctx,\n `/api/site/academy/courses/${encodeURIComponent(courseSlug)}/lessons/${encodeURIComponent(lessonSlug)}`,\n { cachePolicy: { revalidate: false } },\n ),\n\n /**\n * Instructors of published courses, sorted by name, backing the catalog\n * instructor filter (REB-547). Pass an `id` as `courses({ instructor })`.\n * Not paginated.\n */\n instructors: (cache?: RebluCachePolicy): Promise<ListResult<AcademyInstructorPublic>> =>\n requestList<AcademyInstructorPublic>(ctx, '/api/site/academy/instructors', {\n cachePolicy: cache,\n }),\n\n /**\n * Tenant academy settings for the SITE (REB-1234). `catalogFilters` says\n * which catalogue filters the tenant switched on in nexus — pass it as\n * `enabled` to `CourseCatalogFilters` (`@reblu/site-blocks`). A filter the\n * tenant never set comes back `true`.\n */\n config: (cache?: RebluCachePolicy): Promise<AcademyConfigPublic> =>\n requestOne<AcademyConfigPublic>(ctx, '/api/site/academy/config', { cachePolicy: cache }),\n\n /** Active course categories backing the catalog filter. Not paginated. */\n categories: (cache?: RebluCachePolicy): Promise<ListResult<AcademyCategoryPublic>> =>\n requestList<AcademyCategoryPublic>(ctx, '/api/site/academy/categories', {\n cachePolicy: cache,\n }),\n\n /**\n * REB-1264 — guest checkout of a course that needs no validation: name,\n * email, the accepted terms version, the course's consent box version\n * and, optionally, full-invoice data. Returns the Stripe Checkout `url`\n * to send the buyer to; the account is created after payment. The\n * answer never depends on whether the email has an account.\n *\n * The `url` is re-checked like `shop.checkout()` (`https:` on\n * `ctx.checkoutRedirectHosts`, else {@link UnsafeCheckoutRedirectError}).\n * A refusal throws {@link AcademyCheckoutError} with its stable `code`;\n * on `ENROLLMENT_REQUEST_REQUIRED` use `enrollmentRequest` instead.\n *\n * Server-side, pass `options.visitorIp` (see `visitorIpFromHeaders`):\n * base-api keys this route's per-visitor limit on it.\n */\n checkout: async (\n body: AcademyCheckoutRequest,\n options?: RebluMutationOptions,\n ): Promise<AcademyCheckoutSession> => {\n let session: AcademyCheckoutSession\n try {\n session = await requestMutation<AcademyCheckoutRequest, AcademyCheckoutSession>(\n ctx,\n '/api/site/academy/checkout',\n body,\n options,\n )\n } catch (error) {\n throw error instanceof ApiError ? new AcademyCheckoutError(error) : error\n }\n if (!isSafeCheckoutRedirectUrl(session.url, ctx.checkoutRedirectHosts)) {\n throw new UnsafeCheckoutRedirectError(session.url, ctx.checkoutRedirectHosts)\n }\n return session\n },\n\n /**\n * Submit an anonymous enrollment request for a course.\n *\n * The reply is UNIFORM by design: the caller cannot tell whether the email\n * was new (a request row was created) or already known (a sign-in link was\n * sent and NO request row exists). Never present the returned message as\n * confirmation that a request was recorded — it is not one.\n *\n * Server-side, pass `options.visitorIp` (from the incoming request, see\n * `visitorIpFromHeaders`): base-api keys this route's per-IP limit on it\n * and, without it, throttles every visitor of the SITE in one shared\n * tenant-level bucket (REB-1006).\n */\n enrollmentRequest: (\n body: AcademyEnrollmentRequestInput,\n options?: RebluMutationOptions,\n ): Promise<AcademyEnrollmentRequestResult> => {\n const headers = extraHeaders(options)\n return request<AcademyEnrollmentRequestResult>(ctx, '/api/site/academy/enrollment-requests', {\n method: 'POST',\n body: JSON.stringify(body),\n cachePolicy: { revalidate: false },\n ...(headers ? { headers } : {}),\n })\n },\n }\n}\n","import type { Analytics } from '@reblu/site-contracts'\nimport type { RebluCachePolicy } from '../cache'\nimport { requestOne, type RebluClientContext } from '../client'\n\n/** `GET /api/site/analytics` — the tenant's analytics provider configuration. */\nexport function makeAnalyticsNamespace(ctx: RebluClientContext) {\n return {\n get: (cache?: RebluCachePolicy): Promise<Analytics> =>\n requestOne<Analytics>(ctx, '/api/site/analytics', { cachePolicy: cache }),\n }\n}\n","import type {\n BlogCategoryWithCount,\n BlogPostDetail,\n BlogPostListItem,\n BlogPostPreview,\n BlogSlug,\n} from '@reblu/site-contracts'\nimport { buildUrl, type QueryValue } from '../build-url'\nimport type { RebluCachePolicy } from '../cache'\nimport { requestList, requestOne, type RebluClientContext } from '../client'\nimport type { BlogListParams, ListResult } from '../types'\n\n/** Blog domain — `GET /api/site/blog/*`. */\nexport function makeBlogNamespace(ctx: RebluClientContext) {\n return {\n list: (\n params?: BlogListParams,\n cache?: RebluCachePolicy,\n ): Promise<ListResult<BlogPostListItem>> =>\n requestList<BlogPostListItem>(\n ctx,\n buildUrl('/api/site/blog', params as Record<string, QueryValue> | undefined),\n { cachePolicy: cache },\n ),\n\n bySlug: (slug: string, cache?: RebluCachePolicy): Promise<BlogPostDetail> =>\n requestOne<BlogPostDetail>(ctx, `/api/site/blog/${slug}`, { cachePolicy: cache }),\n\n related: (slug: string, cache?: RebluCachePolicy): Promise<ListResult<BlogPostListItem>> =>\n requestList<BlogPostListItem>(ctx, `/api/site/blog/${slug}/related`, { cachePolicy: cache }),\n\n categories: (cache?: RebluCachePolicy): Promise<ListResult<BlogCategoryWithCount>> =>\n requestList<BlogCategoryWithCount>(ctx, '/api/site/blog/categories', { cachePolicy: cache }),\n\n slugs: (cache?: RebluCachePolicy): Promise<ListResult<BlogSlug>> =>\n requestList<BlogSlug>(ctx, '/api/site/blog/slugs', { cachePolicy: cache }),\n\n /** Draft preview — never cached (unpublished content). */\n preview: (id: string): Promise<BlogPostPreview> =>\n requestOne<BlogPostPreview>(ctx, `/api/site/blog/preview/${id}`, {\n cachePolicy: { revalidate: false },\n }),\n }\n}\n","import type { Config } from '@reblu/site-contracts'\nimport type { RebluCachePolicy } from '../cache'\nimport { requestOne, type RebluClientContext } from '../client'\n\n/** `GET /api/site/config` — the tenant's public site configuration. */\nexport function makeConfigNamespace(ctx: RebluClientContext) {\n return {\n get: (cache?: RebluCachePolicy): Promise<Config> =>\n requestOne<Config>(ctx, '/api/site/config', { cachePolicy: cache }),\n }\n}\n","import type { ContactFormInput, ContactFormSubmission } from '@reblu/site-contracts'\nimport { requestMutation, type RebluClientContext, type RebluMutationOptions } from '../client'\n\n/**\n * Contact form domain — WRITE only (`POST /api/site/contact-form`, REB-438).\n * Posts a body typed by `@reblu/site-contracts` and bypasses the cache; returns\n * the created lead's id + creation timestamp.\n */\nexport function makeContactFormNamespace(ctx: RebluClientContext) {\n return {\n /**\n * Submit a contact form. Returns the created lead acknowledgement.\n * Server-side (a Server Action / Route Handler), pass `options.visitorIp`\n * (from the incoming request, see `visitorIpFromHeaders`) so base-api's\n * per-IP limit (REB-1005) applies to the real visitor.\n */\n submit: (\n body: ContactFormInput,\n options?: RebluMutationOptions,\n ): Promise<ContactFormSubmission> =>\n requestMutation<ContactFormInput, ContactFormSubmission>(\n ctx,\n '/api/site/contact-form',\n body,\n options,\n ),\n }\n}\n","import type { Cta } from '@reblu/site-contracts'\nimport { buildUrl } from '../build-url'\nimport type { RebluCachePolicy } from '../cache'\nimport { requestList, type RebluClientContext } from '../client'\nimport type { ListResult } from '../types'\n\n/**\n * `GET /api/site/ctas?slot=…` — editable CTAs for a given slot.\n *\n * `slot` is typed as `string` on purpose: the slot enum lives in base-api's\n * `validations/` (not the response contracts), so `@reblu/site-contracts` does\n * not export it. Keeping it a `string` avoids duplicating (and drifting from)\n * the server-owned enum. The server validates the slot.\n */\nexport function makeCtasNamespace(ctx: RebluClientContext) {\n return {\n list: (slot: string, cache?: RebluCachePolicy): Promise<ListResult<Cta>> =>\n requestList<Cta>(ctx, buildUrl('/api/site/ctas', { slot }), { cachePolicy: cache }),\n }\n}\n","import type { DataRequestInput, DataRequestSubmission } from '@reblu/site-contracts'\nimport { requestMutation, type RebluClientContext, type RebluMutationOptions } from '../client'\n\n/**\n * GDPR data-subject requests domain — WRITE only (`POST /api/site/data-requests`,\n * REB-77 endpoint / REB-980 SDK). Posts the public rights-form body typed by\n * `@reblu/site-contracts`, bypasses the cache, and returns the request id, when\n * it was received and the 72h deadline (`slaDueAt`) it is now held to.\n *\n * base-api requires a reCAPTCHA v3 token (`captcha`) and rate-limits per tenant\n * and per visitor IP (REB-1005); a rejection surfaces as an `ApiError` carrying\n * the upstream `status` (400 validation, 422 captcha, 429 rate limit).\n */\nexport function makeDataRequestsNamespace(ctx: RebluClientContext) {\n return {\n /**\n * Submit a data-subject request. Returns its acknowledgement with the 72h\n * deadline. Server-side, pass `options.visitorIp` (from the incoming\n * request, see `visitorIpFromHeaders`) so base-api's per-IP limit\n * (REB-1005) sees the real visitor.\n */\n submit: (\n body: DataRequestInput,\n options?: RebluMutationOptions,\n ): Promise<DataRequestSubmission> =>\n requestMutation<DataRequestInput, DataRequestSubmission>(\n ctx,\n '/api/site/data-requests',\n body,\n options,\n ),\n }\n}\n","import type { LeadInput, LeadSubmission } from '@reblu/site-contracts'\nimport { requestMutation, type RebluClientContext } from '../client'\n\n/**\n * Leads domain — WRITE only (`POST /api/site/leads`, REB-438). The generic\n * lead-ingestion endpoint: posts a body typed by `@reblu/site-contracts`,\n * bypasses the cache, and returns the created lead's id + creation timestamp.\n */\nexport function makeLeadsNamespace(ctx: RebluClientContext) {\n return {\n /** Create a lead. Returns the created lead acknowledgement. */\n create: (body: LeadInput): Promise<LeadSubmission> =>\n requestMutation<LeadInput, LeadSubmission>(ctx, '/api/site/leads', body),\n }\n}\n","import type { Legal } from '@reblu/site-contracts'\nimport type { RebluCachePolicy } from '../cache'\nimport { requestOne, type RebluClientContext } from '../client'\n\n/**\n * `GET /api/site/legal` — the tenant's legal content (privacy, terms, cookies,\n * and since REB-1201 `legalNotice` and `withdrawalPolicy`). Render each with\n * `LegalDocument` from `@reblu/site-blocks`.\n */\nexport function makeLegalNamespace(ctx: RebluClientContext) {\n return {\n get: (cache?: RebluCachePolicy): Promise<Legal> =>\n requestOne<Legal>(ctx, '/api/site/legal', { cachePolicy: cache }),\n }\n}\n","import type { PageDetail, PageListItem, PageSlug } from '@reblu/site-contracts'\nimport { buildUrl, type QueryValue } from '../build-url'\nimport type { RebluCachePolicy } from '../cache'\nimport { requestList, requestOne, type RebluClientContext } from '../client'\nimport { AmbiguousPageSlugError, ApiError, apiErrorRawBody } from '../errors'\nimport type { ListResult, PagesListParams } from '../types'\n\n/**\n * REB-867 — options for `pages.bySlug`. Page slugs are unique PER CATEGORY:\n * - `category: '<categorySlug>'` → the page with that slug in that category;\n * - `category: null` → the UNCATEGORISED page with that slug;\n * - omitted → the unique page with that slug; throws\n * `AmbiguousPageSlugError` when it exists in more than one category.\n */\nexport interface PageBySlugOptions {\n category?: string | null\n cache?: RebluCachePolicy\n}\n\n/**\n * The two second-argument shapes share no key: a `RebluCachePolicy` only ever\n * has `revalidate`, `PageBySlugOptions` only `category`/`cache`. So the\n * presence of `category` or `cache` (even `category: undefined`) selects the\n * options form; anything else — including `{}` — is a cache policy, exactly as\n * before REB-867.\n */\n/** REB-869 — options for `pages.featured`. */\nexport interface FeaturedPagesOptions {\n /**\n * How many pages at most: an integer >= 1. Default 3. Values above the\n * server's cap (3) are clamped to 3; anything else invalid (0, negative,\n * fractional, NaN) throws a `RangeError` before any request is made.\n */\n limit?: number\n /**\n * When the category has nothing featured, return its first `limit` pages in\n * normal order instead of `[]`. Default `true`, so a teaser is never empty\n * just because nobody picked pages yet.\n */\n fallback?: boolean\n cache?: RebluCachePolicy\n}\n\n/** The server caps a featured list at 3 (REB-869). */\nconst FEATURED_PAGES_MAX = 3\n\nfunction isBySlugOptions(value: RebluCachePolicy | PageBySlugOptions): value is PageBySlugOptions {\n return 'category' in value || 'cache' in value\n}\n\n/** Dynamic pages domain — `GET /api/site/pages/*`. */\nexport function makePagesNamespace(ctx: RebluClientContext) {\n const namespace = {\n list: (\n params?: PagesListParams,\n cache?: RebluCachePolicy,\n ): Promise<ListResult<PageListItem>> =>\n requestList<PageListItem>(\n ctx,\n // REB-63: `type` can now be a single PageType or an array (repeated\n // `?type=` params) — widen the cast alongside `PagesListParams`.\n buildUrl('/api/site/pages', params as Record<string, QueryValue | QueryValue[]> | undefined),\n { cachePolicy: cache },\n ),\n\n /**\n * A published page by slug. The second argument is either a cache policy\n * (backward-compatible) or {@link PageBySlugOptions} (REB-867).\n */\n bySlug: async (\n slug: string,\n cacheOrOptions?: RebluCachePolicy | PageBySlugOptions,\n ): Promise<PageDetail> => {\n const options: PageBySlugOptions =\n cacheOrOptions && isBySlugOptions(cacheOrOptions)\n ? cacheOrOptions\n : { cache: cacheOrOptions }\n const path =\n options.category === undefined\n ? `/api/site/pages/${slug}`\n : buildUrl(`/api/site/pages/${slug}`, { category: options.category ?? '' })\n try {\n return await requestOne<PageDetail>(ctx, path, { cachePolicy: options.cache })\n } catch (error) {\n if (error instanceof ApiError && error.status === 409 && error.body?.code === 'ambiguous_slug') {\n throw new AmbiguousPageSlugError(error, apiErrorRawBody(error))\n }\n throw error\n }\n },\n\n /**\n * REB-869 — the pages to show in a category teaser: the category's\n * featured pages in the tenant's order; when none are featured (and\n * `fallback` is not `false`), the first `limit` pages of the category in\n * normal order. Returns the items only (no pagination applies).\n */\n featured: async (\n category: string,\n { limit: requested = FEATURED_PAGES_MAX, fallback = true, cache }: FeaturedPagesOptions = {},\n ): Promise<PageListItem[]> => {\n if (!Number.isInteger(requested) || requested < 1) {\n throw new RangeError(`pages.featured: limit must be an integer >= 1 (got ${requested})`)\n }\n const limit = Math.min(requested, FEATURED_PAGES_MAX)\n const featured = await namespace.list({ category, featured: true }, cache)\n if (featured.data.length > 0 || !fallback) return featured.data.slice(0, limit)\n const first = await namespace.list({ category, pageSize: limit }, cache)\n return first.data.slice(0, limit)\n },\n\n slugs: (cache?: RebluCachePolicy): Promise<ListResult<PageSlug>> =>\n requestList<PageSlug>(ctx, '/api/site/pages/slugs', { cachePolicy: cache }),\n }\n return namespace\n}\n","import type {\n ShopApplyDiscountInput,\n ShopCartValidation,\n ShopCategory,\n ShopCheckEmailInput,\n ShopCheckoutInput,\n ShopCheckoutSession,\n ShopConfig,\n ShopDiscountApplication,\n ShopEmailCheck,\n ShopFacets,\n ShopOrder,\n ShopProductDetail,\n ShopProductListItem,\n ShopQuote,\n ShopQuoteInput,\n ShopValidateCartInput,\n} from '@reblu/site-contracts'\nimport { buildUrl, type QueryValue } from '../build-url'\nimport type { RebluCachePolicy } from '../cache'\nimport {\n requestList,\n requestMutation,\n requestOne,\n type RebluClientContext,\n type RebluMutationOptions,\n} from '../client'\nimport { isSafeCheckoutRedirectUrl, UnsafeCheckoutRedirectError } from '../checkout-redirect'\nimport type { ListResult, ShopProductsParams } from '../types'\n\n/**\n * Flatten `ShopProductsParams` into a query record: scalars pass through; the\n * `taxonomy`/`attr` maps become repeatable `taxonomy[key]`/`attr[key]` params.\n * Array attr values repeat the same bracket key.\n */\nfunction flattenShopParams(params?: ShopProductsParams): Record<string, QueryValue | QueryValue[]> {\n if (!params) return {}\n const { taxonomy, attr, ...scalars } = params\n const out: Record<string, QueryValue | QueryValue[]> = { ...scalars }\n if (taxonomy) {\n for (const [key, value] of Object.entries(taxonomy)) out[`taxonomy[${key}]`] = value\n }\n if (attr) {\n for (const [key, value] of Object.entries(attr)) out[`attr[${key}]`] = value\n }\n return out\n}\n\n/**\n * Shop domain — READ surface (`GET /api/site/shop/*`) plus the mutation flow\n * (`POST` checkout, validate-cart, apply-discount, and the deprecated\n * check-email) added in REB-438. Every write posts a body typed by `@reblu/site-contracts` and bypasses\n * the cache. Request/response DTOs live in the contracts package (zero Zod).\n */\nexport function makeShopNamespace(ctx: RebluClientContext) {\n return {\n products: (\n params?: ShopProductsParams,\n cache?: RebluCachePolicy,\n ): Promise<ListResult<ShopProductListItem>> =>\n requestList<ShopProductListItem>(\n ctx,\n buildUrl('/api/site/shop/products', flattenShopParams(params)),\n { cachePolicy: cache },\n ),\n\n /** Available facets (brand/attributes/price/on-sale) for the given filters. */\n facets: (params?: ShopProductsParams, cache?: RebluCachePolicy): Promise<ShopFacets> =>\n requestOne<ShopFacets>(ctx, buildUrl('/api/site/shop/facets', flattenShopParams(params)), {\n cachePolicy: cache,\n }),\n\n bySlug: (slug: string, cache?: RebluCachePolicy): Promise<ShopProductDetail> =>\n requestOne<ShopProductDetail>(ctx, `/api/site/shop/products/${slug}`, { cachePolicy: cache }),\n\n categories: (cache?: RebluCachePolicy): Promise<ListResult<ShopCategory>> =>\n requestList<ShopCategory>(ctx, '/api/site/shop/categories', { cachePolicy: cache }),\n\n config: (cache?: RebluCachePolicy): Promise<ShopConfig> =>\n requestOne<ShopConfig>(ctx, '/api/site/shop/config', { cachePolicy: cache }),\n\n // ── Mutations (REB-438) ──────────────────────────────────────────────────\n\n /**\n * Start a hosted checkout session. Returns the Stripe URL + session id.\n *\n * REB-836 (defence in depth): every SITE hands `url` straight to\n * `window.location.assign`, so this validates it is `https:` on an\n * allowed host (`ctx.checkoutRedirectHosts`, default\n * `checkout.stripe.com`) BEFORE returning — throwing\n * {@link UnsafeCheckoutRedirectError} instead of ever handing back a URL\n * that could redirect a buyer's browser off Stripe's domain.\n */\n checkout: async (body: ShopCheckoutInput): Promise<ShopCheckoutSession> => {\n const session = await requestMutation<ShopCheckoutInput, ShopCheckoutSession>(\n ctx,\n '/api/site/shop/checkout',\n body,\n )\n if (!isSafeCheckoutRedirectUrl(session.url, ctx.checkoutRedirectHosts)) {\n throw new UnsafeCheckoutRedirectError(session.url, ctx.checkoutRedirectHosts)\n }\n return session\n },\n\n /** Validate a cart against live product/stock/price state before checkout. */\n validateCart: (body: ShopValidateCartInput): Promise<ShopCartValidation> =>\n requestMutation<ShopValidateCartInput, ShopCartValidation>(\n ctx,\n '/api/site/shop/validate-cart',\n body,\n ),\n\n /**\n * REB-1197 — the pre-payment summary of the single-step checkout: lines,\n * discount, delivery, shipping, tax, weight, delivery term and the final\n * total, priced by the same function as `checkout` (the total shown is the\n * total Stripe charges). Holds nothing: no discount slot, no session.\n *\n * Server-side, pass `options.visitorIp` (see `visitorIpFromHeaders`): a\n * quote with a `discountCode` counts against the same per-visitor limit\n * as `applyDiscount`.\n */\n quote: (body: ShopQuoteInput, options?: RebluMutationOptions): Promise<ShopQuote> =>\n requestMutation<ShopQuoteInput, ShopQuote>(ctx, '/api/site/shop/quote', body, options),\n\n /** Validate a discount code against the current cart subtotal. */\n applyDiscount: (body: ShopApplyDiscountInput): Promise<ShopDiscountApplication> =>\n requestMutation<ShopApplyDiscountInput, ShopDiscountApplication>(\n ctx,\n '/api/site/shop/apply-discount',\n body,\n ),\n\n /**\n * @deprecated REB-834 — the endpoint no longer reveals whether an account\n * exists: it always resolves `{ exists: false }`. Do not call it and never\n * branch UI on `exists`; the checkout always offers «Iniciar sesión o\n * continuar como invitado» and the server links the order to an existing\n * account after payment. Removed in the next major.\n *\n * Server-side, pass `options.visitorIp` (from the incoming request, see\n * `visitorIpFromHeaders`): base-api keys this route's per-IP limit on it\n * and, without it, throttles every visitor of the SITE in one shared\n * tenant-level bucket (REB-1006).\n */\n checkEmail: (\n body: ShopCheckEmailInput,\n options?: RebluMutationOptions,\n ): Promise<ShopEmailCheck> =>\n requestMutation<ShopCheckEmailInput, ShopEmailCheck>(\n ctx,\n '/api/site/shop/check-email',\n body,\n options,\n ),\n\n /**\n * Look up a confirmed (or still-pending) order by its Stripe Checkout\n * session id — the order-confirmation flow (REB-829). `GET`, so it goes\n * through `requestOne` like every other read, not `requestMutation`.\n *\n * Never cached by default (REB-1245): an order belongs to one buyer, and\n * \"not found yet\" right after the Stripe redirect must not be remembered.\n * Pass an explicit `cache` policy only if you know you want one.\n */\n order: (sessionId: string, cache?: RebluCachePolicy): Promise<ShopOrder> =>\n requestOne<ShopOrder>(ctx, `/api/site/shop/order/${encodeURIComponent(sessionId)}`, {\n cachePolicy: cache ?? { revalidate: false },\n }),\n }\n}\n","import type { Review } from '@reblu/site-contracts'\nimport { buildUrl } from '../build-url'\nimport type { RebluCachePolicy } from '../cache'\nimport { requestList, type RebluClientContext } from '../client'\nimport type { ListResult } from '../types'\n\n/** Filters for {@link makeReviewsNamespace}'s `list` (REB-1272). */\nexport interface ReviewListParams {\n /** Only the reviews linked to this page (its `id`). Omit for every published review. */\n pageId?: string\n /** At most this many reviews (1..100). Omit for all of them. */\n limit?: number\n}\n\n/**\n * `GET /api/site/reviews` — the tenant's PUBLISHED reviews in the order set in\n * nexus, each with its source and the https link to the original. Publishing,\n * hiding or reordering in nexus needs no SITE change. Requires the tenant's\n * `reviews` module: without it the API answers 404 (an `ApiError`).\n */\nexport function makeReviewsNamespace(ctx: RebluClientContext) {\n function list(\n params: ReviewListParams = {},\n cache?: RebluCachePolicy,\n ): Promise<ListResult<Review>> {\n return requestList<Review>(\n ctx,\n buildUrl('/api/site/reviews', { pageId: params.pageId, limit: params.limit }),\n { cachePolicy: cache },\n )\n }\n return { list }\n}\n","import type { TrustBadge, TrustBadgeType } from '@reblu/site-contracts'\nimport { buildUrl } from '../build-url'\nimport type { RebluCachePolicy } from '../cache'\nimport { requestList, type RebluClientContext } from '../client'\nimport type { ListResult } from '../types'\n\n/** Filters for {@link makeTrustBadgesNamespace}'s `list` (REB-996). */\nexport interface TrustBadgeListParams {\n /** Only badges of this type (e.g. `'SUBVENTION'`). Omit for every type. */\n type?: TrustBadgeType\n}\n\nfunction isCachePolicy(value: TrustBadgeListParams | RebluCachePolicy): value is RebluCachePolicy {\n return Object.prototype.hasOwnProperty.call(value, 'revalidate')\n}\n\n/**\n * `GET /api/site/trust-badges` — the tenant's PUBLIC trust badges (no\n * pagination): active in nexus and inside their optional validity window, so\n * expiring or switching one off needs no SITE change.\n */\nexport function makeTrustBadgesNamespace(ctx: RebluClientContext) {\n function list(cache?: RebluCachePolicy): Promise<ListResult<TrustBadge>>\n function list(\n params: TrustBadgeListParams,\n cache?: RebluCachePolicy,\n ): Promise<ListResult<TrustBadge>>\n function list(\n paramsOrCache?: TrustBadgeListParams | RebluCachePolicy,\n cache?: RebluCachePolicy,\n ): Promise<ListResult<TrustBadge>> {\n // `list(cache)` is the pre-REB-996 call shape — kept so this stays a minor.\n const legacyCache = paramsOrCache && isCachePolicy(paramsOrCache) ? paramsOrCache : undefined\n const params = legacyCache ? undefined : (paramsOrCache as TrustBadgeListParams | undefined)\n return requestList<TrustBadge>(\n ctx,\n buildUrl('/api/site/trust-badges', { type: params?.type }),\n { cachePolicy: legacyCache ?? cache },\n )\n }\n return { list }\n}\n","import type { WithdrawalRequestInput, WithdrawalRequestSubmission } from '@reblu/site-contracts'\nimport { requestMutation, type RebluClientContext, type RebluMutationOptions } from '../client'\n\n/**\n * Withdrawal requests domain (REB-1201) — WRITE only\n * (`POST /api/site/withdrawal-requests`). A buyer communicates that they\n * withdraw from a purchase: base-api stores it, emails the tenant team and\n * acknowledges it to the buyer. Returns the request id, when it was received\n * and whether the buyer's acknowledgement email went out.\n *\n * base-api requires a reCAPTCHA v3 token (`captcha`) and rate-limits per tenant\n * and per visitor IP; a rejection surfaces as an `ApiError` carrying the\n * upstream `status` (400 validation, 422 captcha, 429 rate limit).\n */\nexport function makeWithdrawalRequestsNamespace(ctx: RebluClientContext) {\n return {\n /**\n * Submit a withdrawal communication. Server-side, pass `options.visitorIp`\n * (see `visitorIpFromHeaders`) so base-api's per-IP limit sees the visitor.\n */\n submit: (\n body: WithdrawalRequestInput,\n options?: RebluMutationOptions,\n ): Promise<WithdrawalRequestSubmission> =>\n requestMutation<WithdrawalRequestInput, WithdrawalRequestSubmission>(\n ctx,\n '/api/site/withdrawal-requests',\n body,\n options,\n ),\n }\n}\n","import type { ContactFormInput } from '@reblu/site-contracts'\n\n/**\n * REB-1077 — the `metadata` block of a contact-form submission, built from the\n * page the visitor is on so a SITE never hand-codes `fromPage`.\n *\n * Call it in the browser, at submit time (inside the form's submit handler):\n * it reads `globalThis.location` unless a `location` is passed.\n */\n\n/** The part of `window.location` these helpers read. */\nexport interface PageLocation {\n pathname: string\n href?: string\n}\n\nexport type ContactFormMetadata = ContactFormInput['metadata']\n\nexport interface ContactFormMetadataOptions {\n /**\n * Explicit page path, overriding the one read from the location (e.g. a\n * modal form that wants to report the landing it belongs to).\n */\n fromPage?: string\n /** @default { platform: 'organic' } */\n leadSource?: ContactFormMetadata['leadSource']\n /** @default globalThis.location */\n location?: PageLocation | null\n /** Submission time. @default new Date() */\n now?: Date\n}\n\nfunction browserLocation(): PageLocation | null {\n const location = (globalThis as { location?: PageLocation }).location\n return location && typeof location.pathname === 'string' ? location : null\n}\n\n/**\n * Path of the page the visitor is on — `location.pathname`, never with a query\n * string or hash. `'/'` when there is no location (server render).\n */\nexport function currentPagePath(location: PageLocation | null = browserLocation()): string {\n const path = location?.pathname.split(/[?#]/, 1)[0] ?? ''\n return path.startsWith('/') ? path : '/'\n}\n\n/**\n * The `metadata` for `contactForm.submit()` / `POST /api/site/contact-form`:\n * `fromPage` is the current page path, `currentUrl` the full href (base-api\n * reads the UTMs from it), `timestamp` the submission time.\n *\n * ```ts\n * body: JSON.stringify({ ...fields, captcha, metadata: buildContactFormMetadata() })\n * ```\n */\nexport function buildContactFormMetadata(\n options: ContactFormMetadataOptions = {},\n): ContactFormMetadata {\n const location = options.location === undefined ? browserLocation() : options.location\n return {\n fromPage: options.fromPage ?? currentPagePath(location),\n timestamp: (options.now ?? new Date()).toISOString(),\n currentUrl: location?.href ?? '',\n leadSource: options.leadSource ?? { platform: 'organic' },\n }\n}\n","/**\n * `@reblu/site-client` — framework-agnostic HTTP client for the Reblu SITE API\n * (REB-415 / design doc §3).\n *\n * The consumer owns the environment: `baseUrl`/`apiKey` are passed in, never read\n * from `process.env`. Caching is delegated to an injectable `RebluCacheAdapter`\n * (default `PlainCacheAdapter`), which severs any coupling to a specific\n * framework's cache model — `@reblu/site-next` supplies the ISR-aware adapter.\n * Response DTOs are typed by the peer, type-only `@reblu/site-contracts`.\n */\nimport { PlainCacheAdapter, type RebluCacheAdapter } from './cache'\nimport type { RebluClientContext, RequestScope } from './client'\nimport { DEFAULT_CHECKOUT_REDIRECT_HOSTS } from './checkout-redirect'\nimport { makeAcademyNamespace } from './domains/academy'\nimport { makeAnalyticsNamespace } from './domains/analytics'\nimport { makeBlogNamespace } from './domains/blog'\nimport { makeConfigNamespace } from './domains/config'\nimport { makeContactFormNamespace } from './domains/contact-form'\nimport { makeCtasNamespace } from './domains/ctas'\nimport { makeDataRequestsNamespace } from './domains/data-requests'\nimport { makeLeadsNamespace } from './domains/leads'\nimport { makeLegalNamespace } from './domains/legal'\nimport { makePagesNamespace } from './domains/pages'\nimport { makeShopNamespace } from './domains/shop'\nimport { makeReviewsNamespace } from './domains/reviews'\nimport { makeTrustBadgesNamespace } from './domains/trust-badges'\nimport { makeWithdrawalRequestsNamespace } from './domains/withdrawal-requests'\n\nconst DEFAULT_API_VERSION = '1'\n/** Parity with demo-site's upstream timeout (client.ts:36). */\nconst DEFAULT_TIMEOUT_MS = 8000\n\nexport interface RebluClientConfig {\n /** Absolute origin of the Reblu SITE API, e.g. `https://api.reblu.app`. */\n baseUrl: string\n /** Tenant SITE API key (`rbl_…`), sent as `X-Reblu-Api-Key`. */\n apiKey: string\n /** Contract version sent as `X-Reblu-Api-Version`. Defaults to `'1'`. */\n apiVersion?: string\n /** Cache strategy. Defaults to `new PlainCacheAdapter()` (no-store). */\n cache?: RebluCacheAdapter\n /** Per-request hard timeout in ms. Defaults to `8000`. */\n timeoutMs?: number\n /** Injectable `fetch` — for tests, custom runtimes, or instrumentation. */\n fetch?: typeof fetch\n /**\n * Allowed hosts for the Stripe Checkout URL `shop.checkout()` returns\n * (REB-836, defence in depth — see `./checkout-redirect`). Defaults to\n * `['checkout.stripe.com']`. Override only to point at a test double (e.g.\n * a Stripe CLI mock host) — never to widen production trust.\n */\n checkoutRedirectHosts?: string[]\n /**\n * REB-1214 — memo store of the current server request, so identical GET\n * reads within it are fetched once. In Next.js pass `rebluRequestScope`\n * from `@reblu/site-next` (React `cache()`); see the README. Without it,\n * identical GETs share one fetch only while in flight.\n */\n requestScope?: RequestScope\n}\n\n/**\n * Build a Reblu SITE API client. Each domain is a small namespace of bound\n * methods assembled from a separate module, so a consumer importing only a\n * couple of domains can tree-shake the rest.\n */\nexport function createRebluClient(config: RebluClientConfig) {\n // Wrap global fetch so calling `ctx.fetch()` never loses its `this` binding\n // (browsers throw \"Illegal invocation\" for an unbound `window.fetch`).\n const boundFetch: typeof fetch = config.fetch ?? ((input, init) => fetch(input, init))\n\n const ctx: RebluClientContext = {\n baseUrl: config.baseUrl,\n apiKey: config.apiKey,\n apiVersion: config.apiVersion ?? DEFAULT_API_VERSION,\n cache: config.cache ?? new PlainCacheAdapter(),\n timeoutMs: config.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n fetch: boundFetch,\n checkoutRedirectHosts: config.checkoutRedirectHosts ?? DEFAULT_CHECKOUT_REDIRECT_HOSTS,\n requestScope: config.requestScope,\n inFlight: new Map(),\n }\n\n return {\n config: makeConfigNamespace(ctx),\n analytics: makeAnalyticsNamespace(ctx),\n legal: makeLegalNamespace(ctx),\n trustBadges: makeTrustBadgesNamespace(ctx),\n ctas: makeCtasNamespace(ctx),\n blog: makeBlogNamespace(ctx),\n pages: makePagesNamespace(ctx),\n shop: makeShopNamespace(ctx),\n academy: makeAcademyNamespace(ctx),\n contactForm: makeContactFormNamespace(ctx),\n leads: makeLeadsNamespace(ctx),\n dataRequests: makeDataRequestsNamespace(ctx),\n withdrawalRequests: makeWithdrawalRequestsNamespace(ctx),\n reviews: makeReviewsNamespace(ctx),\n }\n}\n\n/** The assembled client returned by {@link createRebluClient}. */\nexport type RebluClient = ReturnType<typeof createRebluClient>\n\nexport { ApiError, AmbiguousPageSlugError, AcademyCheckoutError } from './errors'\nexport type {\n ApiErrorBody,\n ApiErrorDetails,\n PageSlugCandidate,\n AcademyCheckoutErrorDetails,\n} from './errors'\nexport type { PageBySlugOptions, FeaturedPagesOptions } from './domains/pages'\nexport type { TrustBadgeListParams } from './domains/trust-badges'\nexport type { ReviewListParams } from './domains/reviews'\nexport {\n DEFAULT_CHECKOUT_REDIRECT_HOSTS,\n isSafeCheckoutRedirectUrl,\n UnsafeCheckoutRedirectError,\n} from './checkout-redirect'\nexport { PlainCacheAdapter } from './cache'\nexport type { RebluCacheAdapter, RebluCachePolicy } from './cache'\nexport type {\n ListResult,\n BlogListParams,\n PagesListParams,\n ShopProductsParams,\n AcademyCourseListParams,\n AcademyEnrollmentRequestInput,\n AcademyEnrollmentRequestResult,\n} from './types'\nexport {\n VISITOR_IP_HEADER,\n visitorIpFromHeaders,\n RETRY_AFTER_CAP_MS,\n type RebluMutationOptions,\n type RequestScope,\n} from './client'\nexport {\n buildContactFormMetadata,\n currentPagePath,\n type ContactFormMetadata,\n type ContactFormMetadataOptions,\n type PageLocation,\n} from './contact-form-metadata'\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/cache.ts","../src/checkout-redirect.ts","../src/build-url.ts","../src/errors.ts","../src/client.ts","../src/domains/academy.ts","../src/domains/analytics.ts","../src/domains/blog.ts","../src/domains/config.ts","../src/domains/contact-form.ts","../src/domains/ctas.ts","../src/domains/data-requests.ts","../src/domains/leads.ts","../src/domains/legal.ts","../src/domains/pages.ts","../src/domains/shop.ts","../src/domains/reviews.ts","../src/domains/trust-badges.ts","../src/domains/withdrawal-requests.ts","../src/contact-form-metadata.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Cache adapter — the seam that severs the SDK from any framework's caching\n * model (design doc D4 / §3.2). The client owns *when* to cache (a per-call\n * `RebluCachePolicy`); the adapter owns *how* (the framework-specific\n * `RequestInit` fields). `@reblu/site-client` ships the framework-agnostic\n * `PlainCacheAdapter`; `@reblu/site-next` (REB-418) supplies a `NextCacheAdapter`\n * that returns `{ next: { revalidate } }`.\n */\n\nexport interface RebluCachePolicy {\n /**\n * Seconds to revalidate. `undefined` → use the adapter's default; `0`/`false`\n * → never cache (always fetch fresh).\n */\n revalidate?: number | false\n}\n\nexport interface RebluCacheAdapter {\n /** Map a per-call cache policy to framework-specific `fetch` `RequestInit` fields. */\n resolveRequestInit(policy: RebluCachePolicy): RequestInit\n /**\n * Optional (REB-1245): perform the HTTP call yourself. When present, the\n * client calls it for every attempt instead of `transport(url, init)`, with\n * `init` already carrying `resolveRequestInit`'s fields. An adapter uses it to\n * cache the read's OUTCOME (a 404 included), which a framework fetch cache\n * may refuse to store. Return a `Response` exactly as `fetch` would: the\n * client still maps non-2xx to `ApiError` and retries 429/503 reads.\n */\n fetch?(url: string, init: RequestInit, transport: typeof fetch): Promise<Response>\n}\n\n/**\n * Default adapter — plain `fetch` cache semantics that work in Node, edge,\n * browser and tests. No `next`, no `server-only`.\n */\nexport class PlainCacheAdapter implements RebluCacheAdapter {\n constructor(private readonly defaultRevalidate: number | false = false) {}\n\n resolveRequestInit(policy: RebluCachePolicy): RequestInit {\n const revalidate = policy.revalidate ?? this.defaultRevalidate\n return revalidate === false || revalidate === 0\n ? { cache: 'no-store' }\n : { cache: 'force-cache' }\n }\n}\n","/**\n * Stripe Checkout redirect-origin guard (REB-836, defence in depth).\n *\n * `shop.checkout()` returns a URL every SITE immediately hands to\n * `window.location.assign(url)`. Nothing upstream of this SDK validated it\n * before REB-836 — a compromised/misconfigured base-api, a tampered\n * response, or a MITM proxy could have sent a buyer's browser anywhere.\n * `isSafeCheckoutRedirectUrl` is the shared predicate `domains/shop.ts` uses\n * to refuse an unsafe URL before ever returning it to the caller; a SITE's\n * `@reblu/site-next` Route Handler re-checks the SAME property server-side\n * (`packages/site-next/src/shop/checkout-redirect.ts`, kept as an\n * independent copy — see that file's docblock for why it isn't a shared\n * import). Update both together if Stripe ever changes its Checkout host.\n */\n\n/** The only Checkout redirect host trusted by default. */\nexport const DEFAULT_CHECKOUT_REDIRECT_HOSTS: readonly string[] = ['checkout.stripe.com']\n\n/**\n * True only when `url` parses as an absolute URL, uses `https:`, and its\n * hostname is in `allowedHosts` (default: {@link DEFAULT_CHECKOUT_REDIRECT_HOSTS}).\n * Never throws — an unparsable `url` is simply unsafe.\n */\nexport function isSafeCheckoutRedirectUrl(\n url: string,\n allowedHosts: readonly string[] = DEFAULT_CHECKOUT_REDIRECT_HOSTS,\n): boolean {\n let parsed: URL\n try {\n parsed = new URL(url)\n } catch {\n return false\n }\n return parsed.protocol === 'https:' && allowedHosts.includes(parsed.hostname)\n}\n\n/**\n * Thrown by `shop.checkout()` when base-api's Checkout URL fails\n * {@link isSafeCheckoutRedirectUrl} — the SDK never hands an unsafe URL back\n * to a caller that would otherwise `window.location.assign` it.\n */\nexport class UnsafeCheckoutRedirectError extends Error {\n constructor(\n public readonly url: string,\n public readonly allowedHosts: readonly string[],\n ) {\n super(\n `Refusing to return an unsafe Stripe Checkout redirect URL: ${JSON.stringify(url)} ` +\n `(allowed hosts: ${allowedHosts.join(', ')})`,\n )\n this.name = 'UnsafeCheckoutRedirectError'\n }\n}\n","/** A value that can be serialized into a query string. */\nexport type QueryValue = string | number | boolean | undefined | null\n\n/**\n * Append a query string to `base`, dropping `undefined`/`null` values.\n *\n * Single definition for the whole client (design doc §3.1 dedupes the two copies\n * that lived in demo-site's `client.ts` and `index.ts`). Falsy-but-meaningful\n * values (`0`, `false`, `''`) are preserved; only `undefined`/`null` are omitted.\n * Array values (REB-475 faceted `attr[key]`) emit one repeated param per element.\n */\nexport function buildUrl(\n base: string,\n params?: Record<string, QueryValue | QueryValue[]>,\n): string {\n if (!params) return base\n const pairs: [string, string][] = []\n for (const [key, value] of Object.entries(params)) {\n const values = Array.isArray(value) ? value : [value]\n for (const v of values) {\n if (v === undefined || v === null) continue\n pairs.push([key, String(v)])\n }\n }\n if (pairs.length === 0) return base\n const qs = new URLSearchParams(pairs).toString()\n return `${base}?${qs}`\n}\n","import type { AcademyCheckoutErrorCode } from '@reblu/site-contracts'\n\n/**\n * The `details` shape `/api/site/contact-form` and `/api/site/leads` attach on a\n * 400/422, straight from zod's `.flatten()` (see\n * `details: parsed.error.flatten()` in\n * `apps/base-api/src/lib/server/services/contact-form.service.ts:114` and\n * `apiError('Validation error', 422, parsed.error.flatten())` in\n * `apps/base-api/src/app/api/site/leads/route.ts:40`). `fieldErrors` is what a\n * SITE actually needs — REB-659's `details.fieldErrors.phone` — `formErrors` is\n * kept alongside it because `.flatten()` always emits both and dropping it would\n * silently discard form-level (non-field) messages.\n */\nexport interface ApiErrorDetails {\n fieldErrors?: Record<string, string[]>\n formErrors?: string[]\n /**\n * REB-836: the affected product ids on a shop checkout `STOCK_CHANGED` /\n * `PRICE_CHANGED` error — see `ApiErrorBody.code`.\n */\n productIds?: string[]\n /** REB-842: optional request-field identifier for checkout validation errors. */\n field?: string\n}\n\n/**\n * The parsed shape of a non-2xx SITE API response body: `{ error, details? }`\n * (see `apiError()` in `apps/base-api/src/lib/api/response.ts:33` — the general\n * shape both routes above build on).\n */\nexport interface ApiErrorBody {\n error: string\n details?: ApiErrorDetails\n /**\n * REB-836: stable, machine-readable error code (e.g. `ShopErrorCode` from\n * `@reblu/site-contracts`) the checkout/validate-cart/apply-discount write\n * endpoints may set — additive and OPTIONAL, so a consumer that only reads\n * `error`/`message` keeps working unchanged. `string` rather than\n * `ShopErrorCode` here because `ApiError` is a generic transport error used\n * by every `/api/site/*` domain, not just shop.\n */\n code?: string\n}\n\nfunction isStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((item) => typeof item === 'string')\n}\n\n/**\n * Validates `ApiErrorDetails` deeply — `fieldErrors` must be a record whose\n * values are `string[] | undefined`, `formErrors` (if present) must be\n * `string[]`, and `field` (if present) must be a string. Without this, a body like\n * `{ fieldErrors: { phone: \"abc\" } }` passed the old shallow check, so\n * `body.details?.fieldErrors?.phone?.[0]` silently returned the character\n * `\"a\"` instead of a validation message (REB-764 review finding).\n */\nfunction isApiErrorDetails(value: unknown): value is ApiErrorDetails {\n if (typeof value !== 'object' || value === null) return false\n const details = value as {\n fieldErrors?: unknown\n formErrors?: unknown\n productIds?: unknown\n field?: unknown\n }\n\n if (details.fieldErrors !== undefined) {\n if (\n typeof details.fieldErrors !== 'object' ||\n details.fieldErrors === null ||\n Array.isArray(details.fieldErrors)\n ) {\n return false\n }\n for (const fieldValue of Object.values(details.fieldErrors as Record<string, unknown>)) {\n if (fieldValue !== undefined && !isStringArray(fieldValue)) return false\n }\n }\n\n if (details.formErrors !== undefined && !isStringArray(details.formErrors)) return false\n\n // REB-836: STOCK_CHANGED/PRICE_CHANGED carry the affected product ids here.\n if (details.productIds !== undefined && !isStringArray(details.productIds)) return false\n if ('field' in details && typeof details.field !== 'string') return false\n\n return true\n}\n\n/**\n * `details`, when present, must match `ApiErrorDetails` deeply — an\n * unrecognised `details` shape makes the whole body unrecognised (`body`\n * stays `undefined`), the stricter reading consistent with\n * `parseApiErrorBody`'s existing all-or-nothing semantics: a body is either\n * shaped like `ApiErrorBody` or it isn't. Silently dropping a malformed\n * `details` instead would let a consumer treat a schema-drifted or corrupted\n * response as merely detail-less, hiding the mismatch rather than surfacing\n * it as \"unparseable\" (REB-764 review finding).\n */\nfunction isApiErrorBody(value: unknown): value is ApiErrorBody {\n if (typeof value !== 'object' || value === null) return false\n const body = value as { error?: unknown; details?: unknown; code?: unknown }\n if (typeof body.error !== 'string') return false\n if (body.details !== undefined && !isApiErrorDetails(body.details)) return false\n // REB-836: `code` must be a string when present — same all-or-nothing\n // reading as `details` above (an unrecognised shape makes the whole body\n // unrecognised, never silently dropped).\n if (body.code !== undefined && typeof body.code !== 'string') return false\n return true\n}\n\n/**\n * Parses `rawBody` (the exact text `message` is built from) into a typed\n * `ApiErrorBody`, or `undefined` when it is not JSON, or JSON that does not\n * match the expected shape — an edge proxy 502 returns HTML, and this must\n * never throw while constructing the error it is reporting.\n */\nfunction parseApiErrorBody(rawBody: string): ApiErrorBody | undefined {\n if (!rawBody) return undefined\n try {\n const parsed: unknown = JSON.parse(rawBody)\n return isApiErrorBody(parsed) ? parsed : undefined\n } catch {\n return undefined\n }\n}\n\n/**\n * Typed transport error for the Reblu SITE API.\n *\n * Every non-2xx response throws an `ApiError` (never a bare `fetch` rejection),\n * so consumers can `catch (e) { if (e instanceof ApiError) … }` and fall back\n * gracefully (e.g. render an empty section) — the one behavioural contract the\n * SITE relies on. Moved verbatim from demo-site's `client.ts` (design doc §3.4).\n */\nexport class ApiError extends Error {\n /**\n * The parsed response body, when it was JSON shaped like `{ error, details? }`\n * (REB-764). `undefined` when the body was not JSON or didn't match — parsing\n * is defensive and never throws. Read `body.details?.fieldErrors?.phone`\n * instead of regex-stripping this error's `message`.\n */\n public readonly body: ApiErrorBody | undefined\n\n constructor(\n public readonly status: number,\n public readonly path: string,\n message: string,\n ) {\n super(`API ${status} @ ${path}: ${message}`)\n this.name = 'ApiError'\n this.body = parseApiErrorBody(message)\n }\n}\n\n/** One page a slug could mean (REB-867) — `null` = the uncategorised page. */\nexport interface PageSlugCandidate {\n categorySlug: string | null\n}\n\nfunction parseCandidates(rawBody: string): PageSlugCandidate[] {\n try {\n const parsed = JSON.parse(rawBody) as { candidates?: unknown }\n if (!Array.isArray(parsed.candidates)) return []\n return parsed.candidates.flatMap((c: unknown) => {\n const slug = (c as { categorySlug?: unknown } | null)?.categorySlug\n return typeof slug === 'string' || slug === null ? [{ categorySlug: slug }] : []\n })\n } catch {\n return []\n }\n}\n\n/**\n * REB-867 — `GET /api/site/pages/[slug]` answered 409 `ambiguous_slug`: page\n * slugs are unique PER CATEGORY and this one exists in more than one, so the\n * server refused to pick. Retry with `pages.bySlug(slug, { category })` using\n * one of `candidates`. Extends `ApiError`, so an existing\n * `catch (e) { if (e instanceof ApiError) … }` keeps handling it.\n */\nexport class AmbiguousPageSlugError extends ApiError {\n public readonly candidates: PageSlugCandidate[]\n\n constructor(source: ApiError, rawBody: string) {\n super(source.status, source.path, rawBody)\n this.name = 'AmbiguousPageSlugError'\n this.candidates = parseCandidates(rawBody)\n }\n}\n\n/** The raw body an `ApiError` was built from (its message minus the prefix). */\nexport function apiErrorRawBody(error: ApiError): string {\n const prefix = `API ${error.status} @ ${error.path}: `\n return error.message.startsWith(prefix) ? error.message.slice(prefix.length) : error.message\n}\n\n/**\n * `details` of a guest course checkout refusal (REB-1264): `field` on\n * `INVALID_INPUT`, `reason` on `ENROLLMENT_REQUEST_REQUIRED`\n * (`VALIDATION_REQUIRED` | `FREE_COURSE`), and `fieldErrors` — one message\n * per field, a plain string — on the terms, consent and billing refusals.\n */\nexport interface AcademyCheckoutErrorDetails {\n field?: string\n reason?: string\n fieldErrors?: Record<string, string>\n /** `INVOICING_NOT_READY`: what the academy still lacks to invoice (e.g. `taxId`, `series`). */\n missing?: string[]\n}\n\n/** Keeps only the well-typed parts of `details` (strings, and string-valued `fieldErrors`). */\nfunction toAcademyCheckoutDetails(value: unknown): AcademyCheckoutErrorDetails | undefined {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return undefined\n const raw = value as {\n field?: unknown\n reason?: unknown\n fieldErrors?: unknown\n missing?: unknown\n }\n const details: AcademyCheckoutErrorDetails = {}\n if (typeof raw.field === 'string') details.field = raw.field\n if (typeof raw.reason === 'string') details.reason = raw.reason\n if (Array.isArray(raw.missing) && raw.missing.every((item) => typeof item === 'string')) {\n details.missing = raw.missing as string[]\n }\n if (\n typeof raw.fieldErrors === 'object' &&\n raw.fieldErrors !== null &&\n !Array.isArray(raw.fieldErrors)\n ) {\n const fieldErrors = Object.fromEntries(\n Object.entries(raw.fieldErrors).filter(\n (entry): entry is [string, string] => typeof entry[1] === 'string',\n ),\n )\n if (Object.keys(fieldErrors).length > 0) details.fieldErrors = fieldErrors\n }\n return Object.keys(details).length > 0 ? details : undefined\n}\n\nfunction parseAcademyCheckoutBody(rawBody: string): {\n error?: string\n code?: AcademyCheckoutErrorCode\n details?: AcademyCheckoutErrorDetails\n} {\n try {\n const parsed = JSON.parse(rawBody) as { error?: unknown; code?: unknown; details?: unknown }\n if (typeof parsed !== 'object' || parsed === null) return {}\n const details = toAcademyCheckoutDetails(parsed.details)\n return {\n ...(typeof parsed.error === 'string' ? { error: parsed.error } : {}),\n ...(isAcademyCheckoutErrorCode(parsed.code) ? { code: parsed.code } : {}),\n ...(details ? { details } : {}),\n }\n } catch {\n return {}\n }\n}\n\n// A `Record` so the compiler rejects a code base-api adds and this list lacks.\nconst ACADEMY_CHECKOUT_CODE_SET: Record<AcademyCheckoutErrorCode, true> = {\n INVALID_INPUT: true,\n RATE_LIMITED: true,\n COURSE_NOT_FOUND: true,\n SALES_CLOSED: true,\n ENROLLMENT_REQUEST_REQUIRED: true,\n INVOICING_NOT_READY: true,\n TERMS_NOT_ACCEPTED: true,\n TERMS_VERSION_CHANGED: true,\n CONSENT_REQUIRED: true,\n CONSENT_VERSION_CHANGED: true,\n INVALID_BILLING: true,\n}\n\nfunction isAcademyCheckoutErrorCode(value: unknown): value is AcademyCheckoutErrorCode {\n return typeof value === 'string' && Object.hasOwn(ACADEMY_CHECKOUT_CODE_SET, value)\n}\n\n/**\n * REB-1264 — `academy.checkout()` was refused. `code` is the stable\n * `AcademyCheckoutErrorCode` (`undefined` when the answer carried none, e.g.\n * an edge 502), `details` its extra data, `errorMessage` the server's text.\n * On `ENROLLMENT_REQUEST_REQUIRED` the course is not bought in one step:\n * show the enrollment request form instead. Extends `ApiError`, so an\n * existing `catch (e) { if (e instanceof ApiError) … }` keeps handling it.\n *\n * Its own parser because these refusals send `details.fieldErrors` as one\n * string per field, a shape `ApiError.body` (string arrays) does not accept.\n */\nexport class AcademyCheckoutError extends ApiError {\n public readonly code: AcademyCheckoutErrorCode | undefined\n public readonly details: AcademyCheckoutErrorDetails | undefined\n public readonly errorMessage: string | undefined\n\n constructor(source: ApiError) {\n const rawBody = apiErrorRawBody(source)\n super(source.status, source.path, rawBody)\n this.name = 'AcademyCheckoutError'\n const parsed = parseAcademyCheckoutBody(rawBody)\n this.code = parsed.code\n this.details = parsed.details\n this.errorMessage = parsed.error\n }\n}\n","import type { PaginationMeta } from '@reblu/site-contracts'\nimport type { RebluCacheAdapter, RebluCachePolicy } from './cache'\nimport { ApiError } from './errors'\nimport type { ListResult } from './types'\n\n/**\n * Resolved, immutable client context threaded into every domain namespace. Built\n * once by `createRebluClient` from the consumer's config — the SDK never reads\n * `process.env`, so the consumer owns `baseUrl`/`apiKey` (design doc §3.3).\n */\nexport interface RebluClientContext {\n baseUrl: string\n apiKey: string\n apiVersion: string\n cache: RebluCacheAdapter\n timeoutMs: number\n fetch: typeof fetch\n /**\n * Allowed hosts for the Stripe Checkout URL `shop.checkout()` returns\n * (REB-836, defence in depth). Defaults to\n * `DEFAULT_CHECKOUT_REDIRECT_HOSTS` (`['checkout.stripe.com']`) — tests\n * inject a different value via `RebluClientConfig.checkoutRedirectHosts`.\n */\n checkoutRedirectHosts: readonly string[]\n /**\n * REB-1214 — returns the memo store of the CURRENT server request, so\n * identical GET reads within it are fetched once (e.g. `generateMetadata`\n * and the page body). `@reblu/site-next` supplies one backed by React\n * `cache()` (`rebluRequestScope`). Absent, or returning `undefined`,\n * identical GETs still share one fetch while they are in flight.\n */\n requestScope?: RequestScope\n /** In-flight GETs when no request scope is active. Created by `createRebluClient`. */\n inFlight?: Map<string, Promise<unknown>>\n}\n\n/** Memo store of one server request, keyed by read identity (REB-1214). */\nexport type RequestScope = () => Map<string, Promise<unknown>> | undefined\n\n/** Upper bound for a `Retry-After` wait before the single retry (REB-1214). */\nexport const RETRY_AFTER_CAP_MS = 2000\n/** Wait before the single retry when `Retry-After` is absent or unparseable. */\nexport const DEFAULT_RETRY_DELAY_MS = 250\n/** Statuses that mean \"try again shortly\" for a read. */\nconst RETRYABLE_READ_STATUSES = new Set([429, 503])\n\n/** `Retry-After` (seconds or HTTP date) → ms, capped; default when unusable. */\nfunction retryDelayMs(header: string | null): number {\n if (header !== null && header.trim() !== '') {\n const value = header.trim()\n const seconds = /^\\d+$/.test(value) ? Number(value) * 1000 : NaN\n const ms = Number.isFinite(seconds) ? seconds : Date.parse(value) - Date.now()\n if (Number.isFinite(ms)) return Math.min(Math.max(0, ms), RETRY_AFTER_CAP_MS)\n }\n return DEFAULT_RETRY_DELAY_MS\n}\n\nconst sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms))\n\n/** Per-call request options. `cachePolicy` is interpreted by the cache adapter. */\nexport interface RequestOptions {\n method?: string\n body?: string\n headers?: Record<string, string>\n cachePolicy?: RebluCachePolicy\n}\n\n/**\n * Core transport (design doc §3.4). Injects the auth + contract-version headers,\n * merges the adapter's cache `RequestInit`, enforces a hard timeout per attempt,\n * throws a typed `ApiError` on non-2xx, and returns the parsed JSON body verbatim\n * (envelope unwrapping is the caller's concern).\n *\n * REB-1214 — GET reads only: identical reads are deduped (see\n * `RebluClientContext.requestScope`) and a 429/503 is retried once after\n * `Retry-After`, capped at {@link RETRY_AFTER_CAP_MS}. If the retry fails too,\n * the second response's `ApiError` is thrown — the same type as before.\n * Writes are never deduped nor retried.\n */\nexport async function request<T>(\n ctx: RebluClientContext,\n path: string,\n opts: RequestOptions,\n): Promise<T> {\n const method = opts.method ?? 'GET'\n const init: RequestInit = {\n method,\n body: opts.body,\n headers: {\n 'X-Reblu-Api-Key': ctx.apiKey,\n 'X-Reblu-Api-Version': ctx.apiVersion,\n 'Content-Type': 'application/json',\n ...opts.headers,\n },\n ...ctx.cache.resolveRequestInit(opts.cachePolicy ?? {}),\n }\n if (method !== 'GET' || opts.body !== undefined) {\n return attempt<T>(ctx, path, init)\n }\n\n // REB-1214 — a read is identified by everything that shapes its response:\n // URL, headers and the resolved cache init. Kept for the whole request when\n // a scope is active; otherwise only while in flight.\n const key = JSON.stringify([ctx.baseUrl + path, init])\n const scope = ctx.requestScope?.()\n const store = scope ?? ctx.inFlight\n const existing = store?.get(key)\n if (existing) return existing as Promise<T>\n\n const pending = readWithRetry<T>(ctx, path, init)\n if (store) {\n store.set(key, pending)\n // A failure is never memoised; without a scope nothing outlives the fetch.\n pending.then(\n () => {\n if (!scope) store.delete(key)\n },\n () => {\n if (store.get(key) === pending) store.delete(key)\n },\n )\n }\n return pending\n}\n\n/** A GET answered 429/503 is retried once after `Retry-After` (capped). */\nasync function readWithRetry<T>(\n ctx: RebluClientContext,\n path: string,\n init: RequestInit,\n): Promise<T> {\n try {\n return await attempt<T>(ctx, path, init)\n } catch (error) {\n if (!(error instanceof RetryableReadError)) throw error\n await sleep(retryDelayMs(error.retryAfter))\n try {\n return await attempt<T>(ctx, path, init)\n } catch (retryError) {\n throw retryError instanceof RetryableReadError ? retryError.apiError : retryError\n }\n }\n}\n\n/** Internal carrier: a retryable read status plus the `ApiError` to throw if it persists. */\nclass RetryableReadError extends Error {\n constructor(\n readonly apiError: ApiError,\n readonly retryAfter: string | null,\n ) {\n super(apiError.message)\n }\n}\n\n/**\n * One HTTP attempt: enforces the hard timeout via `AbortController`, throws a\n * typed `ApiError` on non-2xx, and returns the parsed JSON body verbatim\n * (envelope unwrapping is the caller's concern).\n */\nasync function attempt<T>(ctx: RebluClientContext, path: string, init: RequestInit): Promise<T> {\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), ctx.timeoutMs)\n\n try {\n const url = ctx.baseUrl + path\n const attemptInit = { ...init, signal: controller.signal }\n // REB-1245 — an adapter may own the call (e.g. to cache a 404 outcome).\n const res = ctx.cache.fetch\n ? await ctx.cache.fetch(url, attemptInit, ctx.fetch)\n : await ctx.fetch(url, attemptInit)\n\n if (!res.ok) {\n const error = new ApiError(res.status, path, await res.text())\n if (init.method === 'GET' && RETRYABLE_READ_STATUSES.has(res.status)) {\n throw new RetryableReadError(error, res.headers.get('retry-after'))\n }\n throw error\n }\n return (await res.json()) as T\n } finally {\n clearTimeout(timer)\n }\n}\n\n/** Fetch a single resource and unwrap the `{ data: T }` envelope. */\nexport async function requestOne<T>(\n ctx: RebluClientContext,\n path: string,\n opts: RequestOptions,\n): Promise<T> {\n const body = await request<{ data: T }>(ctx, path, opts)\n return body.data\n}\n\n/** Fetch a list resource, returning the `{ data, meta }` envelope (meta preserved). */\nexport async function requestList<T>(\n ctx: RebluClientContext,\n path: string,\n opts: RequestOptions,\n): Promise<ListResult<T>> {\n const body = await request<{ data: T[]; meta?: PaginationMeta }>(ctx, path, opts)\n return { data: body.data, meta: body.meta }\n}\n\n/**\n * Header carrying the visitor IP from a SITE server to base-api (REB-1005).\n * base-api keys its per-visitor limit on public writes on it, and trusts it\n * only behind a valid API key.\n */\nexport const VISITOR_IP_HEADER = 'x-reblu-visitor-ip'\n\n/**\n * Per-call options for a write (REB-1005).\n *\n * - `visitorIp` — the visitor's IP when a SITE calls the SDK server-side, sent\n * as `x-reblu-visitor-ip`. Do NOT rely on forwarding `x-forwarded-for`: on\n * Vercel the SITE→base-api hop gets it overwritten with the SITE function's\n * egress IP. Read it from the INCOMING request with\n * {@link visitorIpFromHeaders}.\n * - `headers` — other extra headers (e.g. `user-agent`). They can never set\n * `x-reblu-visitor-ip` nor replace `X-Reblu-Api-Key`, `X-Reblu-Api-Version`\n * or `Content-Type`.\n */\nexport interface RebluMutationOptions {\n visitorIp?: string\n headers?: Record<string, string>\n}\n\n/** The visitor IP of an incoming request: first `x-forwarded-for` entry, else `x-real-ip`. */\nexport function visitorIpFromHeaders(headers: {\n get(name: string): string | null\n}): string | undefined {\n return (\n headers.get('x-forwarded-for')?.split(',')[0]?.trim() ||\n headers.get('x-real-ip')?.trim() ||\n undefined\n )\n}\n\n/** Headers the SDK owns on every request — an extra header never sets or overrides these. */\nconst RESERVED_HEADERS = new Set([\n 'x-reblu-api-key',\n 'x-reblu-api-version',\n 'content-type',\n VISITOR_IP_HEADER,\n])\n\n/**\n * The extra headers a write sends for `options`: caller headers minus the\n * reserved ones, plus `x-reblu-visitor-ip` from `visitorIp`. Shared by\n * {@link requestMutation} and the writes that cannot use it (academy).\n */\nexport function extraHeaders(\n options: RebluMutationOptions | undefined,\n): Record<string, string> | undefined {\n if (!options?.headers && !options?.visitorIp) return undefined\n const safe: Record<string, string> = {}\n for (const [name, value] of Object.entries(options.headers ?? {})) {\n if (!RESERVED_HEADERS.has(name.toLowerCase())) safe[name] = value\n }\n const visitorIp = options.visitorIp?.trim()\n if (visitorIp) safe[VISITOR_IP_HEADER] = visitorIp\n return safe\n}\n\n/**\n * POST a mutation: serialize `body` to JSON, force a cache bypass (`revalidate:\n * false` → `no-store` regardless of the injected adapter, since a mutation must\n * never be served from cache), then unwrap the `{ data: T }` envelope. Mirrors\n * every `/api/site/*` write route, which answers with `apiOk`/`{ data }`.\n */\nexport async function requestMutation<Req, Res>(\n ctx: RebluClientContext,\n path: string,\n body: Req,\n options?: RebluMutationOptions,\n): Promise<Res> {\n const headers = extraHeaders(options)\n const envelope = await request<{ data: Res }>(ctx, path, {\n method: 'POST',\n body: JSON.stringify(body),\n cachePolicy: { revalidate: false },\n ...(headers ? { headers } : {}),\n })\n return envelope.data\n}\n","import type {\n AcademyCategoryPublic,\n AcademyCheckoutRequest,\n AcademyCheckoutSession,\n AcademyConfigPublic,\n AcademyCourseDetail,\n AcademyCourseSummary,\n AcademyInstructorPublic,\n AcademyLessonPreview,\n} from '@reblu/site-contracts'\nimport { buildUrl, type QueryValue } from '../build-url'\nimport type { RebluCachePolicy } from '../cache'\nimport { isSafeCheckoutRedirectUrl, UnsafeCheckoutRedirectError } from '../checkout-redirect'\nimport { AcademyCheckoutError, ApiError } from '../errors'\nimport {\n extraHeaders,\n request,\n requestList,\n requestMutation,\n requestOne,\n type RebluClientContext,\n type RebluMutationOptions,\n} from '../client'\nimport type {\n AcademyCourseListParams,\n AcademyEnrollmentRequestInput,\n AcademyEnrollmentRequestResult,\n ListResult,\n} from '../types'\n\n/**\n * Academy domain — the public course catalogue (`GET /api/site/academy/*`,\n * REB-528 / REB-534) plus the anonymous enrollment request (REB-496). The reads\n * are published-filtered server-side, so nothing here needs an entitlement\n * notion; the one write is unauthenticated by design.\n *\n * `enrollmentRequest` drops to the raw `request()` transport rather than\n * `requestMutation` (REB-584). The route answers with a BARE `{ message }`, not\n * the `{ data }` envelope every other write uses, so `requestMutation` would\n * unwrap a missing `.data` and hand the caller `undefined`. Normalizing the\n * route's envelope instead was rejected: its response shape is load-bearing\n * anti-enumeration behaviour, and changing it would ripple into base-api tests\n * for no gain on the SITE side.\n */\nexport function makeAcademyNamespace(ctx: RebluClientContext) {\n return {\n /** Published courses, filtered + paginated. `meta` carries the pagination. */\n courses: (\n params?: AcademyCourseListParams,\n cache?: RebluCachePolicy,\n ): Promise<ListResult<AcademyCourseSummary>> =>\n requestList<AcademyCourseSummary>(\n ctx,\n buildUrl('/api/site/academy/courses', params as Record<string, QueryValue> | undefined),\n { cachePolicy: cache },\n ),\n\n /** A single published course by slug, with its modules, lessons and CTA gate. */\n bySlug: (slug: string, cache?: RebluCachePolicy): Promise<AcademyCourseDetail> =>\n requestOne<AcademyCourseDetail>(ctx, `/api/site/academy/courses/${slug}`, {\n cachePolicy: cache,\n }),\n\n /**\n * A free-preview lesson, viewable by the public (REB-1135): title, duration,\n * Markdown body and a ready-to-embed video. Any lesson the public may not see\n * is a uniform 404 (`ApiError`). Never cached: the payload carries a\n * short-lived signed playback token. Server-side, prefer `getLessonPreview`\n * from `@reblu/site-next/academy-server`, which maps that 404 to `null`.\n */\n lessonPreview: (courseSlug: string, lessonSlug: string): Promise<AcademyLessonPreview> =>\n requestOne<AcademyLessonPreview>(\n ctx,\n `/api/site/academy/courses/${encodeURIComponent(courseSlug)}/lessons/${encodeURIComponent(lessonSlug)}`,\n { cachePolicy: { revalidate: false } },\n ),\n\n /**\n * Instructors of published courses, sorted by name, backing the catalog\n * instructor filter (REB-547). Pass an `id` as `courses({ instructor })`.\n * Not paginated.\n */\n instructors: (cache?: RebluCachePolicy): Promise<ListResult<AcademyInstructorPublic>> =>\n requestList<AcademyInstructorPublic>(ctx, '/api/site/academy/instructors', {\n cachePolicy: cache,\n }),\n\n /**\n * Tenant academy settings for the SITE (REB-1234). `catalogFilters` says\n * which catalogue filters the tenant switched on in nexus — pass it as\n * `enabled` to `CourseCatalogFilters` (`@reblu/site-blocks`). A filter the\n * tenant never set comes back `true`.\n */\n config: (cache?: RebluCachePolicy): Promise<AcademyConfigPublic> =>\n requestOne<AcademyConfigPublic>(ctx, '/api/site/academy/config', { cachePolicy: cache }),\n\n /** Active course categories backing the catalog filter. Not paginated. */\n categories: (cache?: RebluCachePolicy): Promise<ListResult<AcademyCategoryPublic>> =>\n requestList<AcademyCategoryPublic>(ctx, '/api/site/academy/categories', {\n cachePolicy: cache,\n }),\n\n /**\n * REB-1264 — guest checkout of a course that needs no validation: name,\n * email, the accepted terms version, the course's consent box version\n * and, optionally, full-invoice data. Returns the Stripe Checkout `url`\n * to send the buyer to; the account is created after payment. The\n * answer never depends on whether the email has an account.\n *\n * The `url` is re-checked like `shop.checkout()` (`https:` on\n * `ctx.checkoutRedirectHosts`, else {@link UnsafeCheckoutRedirectError}).\n * A refusal throws {@link AcademyCheckoutError} with its stable `code`;\n * on `ENROLLMENT_REQUEST_REQUIRED` use `enrollmentRequest` instead.\n *\n * Server-side, pass `options.visitorIp` (see `visitorIpFromHeaders`):\n * base-api keys this route's per-visitor limit on it.\n */\n checkout: async (\n body: AcademyCheckoutRequest,\n options?: RebluMutationOptions,\n ): Promise<AcademyCheckoutSession> => {\n let session: AcademyCheckoutSession\n try {\n session = await requestMutation<AcademyCheckoutRequest, AcademyCheckoutSession>(\n ctx,\n '/api/site/academy/checkout',\n body,\n options,\n )\n } catch (error) {\n throw error instanceof ApiError ? new AcademyCheckoutError(error) : error\n }\n if (!isSafeCheckoutRedirectUrl(session.url, ctx.checkoutRedirectHosts)) {\n throw new UnsafeCheckoutRedirectError(session.url, ctx.checkoutRedirectHosts)\n }\n return session\n },\n\n /**\n * Submit an anonymous enrollment request for a course.\n *\n * The reply is UNIFORM by design: the caller cannot tell whether the email\n * was new (a request row was created) or already known (a sign-in link was\n * sent and NO request row exists). Never present the returned message as\n * confirmation that a request was recorded — it is not one.\n *\n * Server-side, pass `options.visitorIp` (from the incoming request, see\n * `visitorIpFromHeaders`): base-api keys this route's per-IP limit on it\n * and, without it, throttles every visitor of the SITE in one shared\n * tenant-level bucket (REB-1006).\n */\n enrollmentRequest: (\n body: AcademyEnrollmentRequestInput,\n options?: RebluMutationOptions,\n ): Promise<AcademyEnrollmentRequestResult> => {\n const headers = extraHeaders(options)\n return request<AcademyEnrollmentRequestResult>(ctx, '/api/site/academy/enrollment-requests', {\n method: 'POST',\n body: JSON.stringify(body),\n cachePolicy: { revalidate: false },\n ...(headers ? { headers } : {}),\n })\n },\n }\n}\n","import type { Analytics } from '@reblu/site-contracts'\nimport type { RebluCachePolicy } from '../cache'\nimport { requestOne, type RebluClientContext } from '../client'\n\n/** `GET /api/site/analytics` — the tenant's analytics provider configuration. */\nexport function makeAnalyticsNamespace(ctx: RebluClientContext) {\n return {\n get: (cache?: RebluCachePolicy): Promise<Analytics> =>\n requestOne<Analytics>(ctx, '/api/site/analytics', { cachePolicy: cache }),\n }\n}\n","import type {\n BlogCategoryWithCount,\n BlogPostDetail,\n BlogPostListItem,\n BlogPostPreview,\n BlogSlug,\n} from '@reblu/site-contracts'\nimport { buildUrl, type QueryValue } from '../build-url'\nimport type { RebluCachePolicy } from '../cache'\nimport { requestList, requestOne, type RebluClientContext } from '../client'\nimport type { BlogListParams, ListResult } from '../types'\n\n/** Blog domain — `GET /api/site/blog/*`. */\nexport function makeBlogNamespace(ctx: RebluClientContext) {\n return {\n list: (\n params?: BlogListParams,\n cache?: RebluCachePolicy,\n ): Promise<ListResult<BlogPostListItem>> =>\n requestList<BlogPostListItem>(\n ctx,\n buildUrl('/api/site/blog', params as Record<string, QueryValue> | undefined),\n { cachePolicy: cache },\n ),\n\n bySlug: (slug: string, cache?: RebluCachePolicy): Promise<BlogPostDetail> =>\n requestOne<BlogPostDetail>(ctx, `/api/site/blog/${slug}`, { cachePolicy: cache }),\n\n related: (slug: string, cache?: RebluCachePolicy): Promise<ListResult<BlogPostListItem>> =>\n requestList<BlogPostListItem>(ctx, `/api/site/blog/${slug}/related`, { cachePolicy: cache }),\n\n categories: (cache?: RebluCachePolicy): Promise<ListResult<BlogCategoryWithCount>> =>\n requestList<BlogCategoryWithCount>(ctx, '/api/site/blog/categories', { cachePolicy: cache }),\n\n slugs: (cache?: RebluCachePolicy): Promise<ListResult<BlogSlug>> =>\n requestList<BlogSlug>(ctx, '/api/site/blog/slugs', { cachePolicy: cache }),\n\n /** Draft preview — never cached (unpublished content). */\n preview: (id: string): Promise<BlogPostPreview> =>\n requestOne<BlogPostPreview>(ctx, `/api/site/blog/preview/${id}`, {\n cachePolicy: { revalidate: false },\n }),\n }\n}\n","import type { Config } from '@reblu/site-contracts'\nimport type { RebluCachePolicy } from '../cache'\nimport { requestOne, type RebluClientContext } from '../client'\n\n/** `GET /api/site/config` — the tenant's public site configuration. */\nexport function makeConfigNamespace(ctx: RebluClientContext) {\n return {\n get: (cache?: RebluCachePolicy): Promise<Config> =>\n requestOne<Config>(ctx, '/api/site/config', { cachePolicy: cache }),\n }\n}\n","import type { ContactFormInput, ContactFormSubmission } from '@reblu/site-contracts'\nimport { requestMutation, type RebluClientContext, type RebluMutationOptions } from '../client'\n\n/**\n * Contact form domain — WRITE only (`POST /api/site/contact-form`, REB-438).\n * Posts a body typed by `@reblu/site-contracts` and bypasses the cache; returns\n * the created lead's id + creation timestamp.\n */\nexport function makeContactFormNamespace(ctx: RebluClientContext) {\n return {\n /**\n * Submit a contact form. Returns the created lead acknowledgement.\n * Server-side (a Server Action / Route Handler), pass `options.visitorIp`\n * (from the incoming request, see `visitorIpFromHeaders`) so base-api's\n * per-IP limit (REB-1005) applies to the real visitor.\n */\n submit: (\n body: ContactFormInput,\n options?: RebluMutationOptions,\n ): Promise<ContactFormSubmission> =>\n requestMutation<ContactFormInput, ContactFormSubmission>(\n ctx,\n '/api/site/contact-form',\n body,\n options,\n ),\n }\n}\n","import type { Cta } from '@reblu/site-contracts'\nimport { buildUrl } from '../build-url'\nimport type { RebluCachePolicy } from '../cache'\nimport { requestList, type RebluClientContext } from '../client'\nimport type { ListResult } from '../types'\n\n/**\n * `GET /api/site/ctas?slot=…` — editable CTAs for a given slot.\n *\n * `slot` is typed as `string` on purpose: the slot enum lives in base-api's\n * `validations/` (not the response contracts), so `@reblu/site-contracts` does\n * not export it. Keeping it a `string` avoids duplicating (and drifting from)\n * the server-owned enum. The server validates the slot.\n */\nexport function makeCtasNamespace(ctx: RebluClientContext) {\n return {\n list: (slot: string, cache?: RebluCachePolicy): Promise<ListResult<Cta>> =>\n requestList<Cta>(ctx, buildUrl('/api/site/ctas', { slot }), { cachePolicy: cache }),\n }\n}\n","import type { DataRequestInput, DataRequestSubmission } from '@reblu/site-contracts'\nimport { requestMutation, type RebluClientContext, type RebluMutationOptions } from '../client'\n\n/**\n * GDPR data-subject requests domain — WRITE only (`POST /api/site/data-requests`,\n * REB-77 endpoint / REB-980 SDK). Posts the public rights-form body typed by\n * `@reblu/site-contracts`, bypasses the cache, and returns the request id, when\n * it was received and the 72h deadline (`slaDueAt`) it is now held to.\n *\n * base-api requires a reCAPTCHA v3 token (`captcha`) and rate-limits per tenant\n * and per visitor IP (REB-1005); a rejection surfaces as an `ApiError` carrying\n * the upstream `status` (400 validation, 422 captcha, 429 rate limit).\n */\nexport function makeDataRequestsNamespace(ctx: RebluClientContext) {\n return {\n /**\n * Submit a data-subject request. Returns its acknowledgement with the 72h\n * deadline. Server-side, pass `options.visitorIp` (from the incoming\n * request, see `visitorIpFromHeaders`) so base-api's per-IP limit\n * (REB-1005) sees the real visitor.\n */\n submit: (\n body: DataRequestInput,\n options?: RebluMutationOptions,\n ): Promise<DataRequestSubmission> =>\n requestMutation<DataRequestInput, DataRequestSubmission>(\n ctx,\n '/api/site/data-requests',\n body,\n options,\n ),\n }\n}\n","import type { LeadInput, LeadSubmission } from '@reblu/site-contracts'\nimport { requestMutation, type RebluClientContext } from '../client'\n\n/**\n * Leads domain — WRITE only (`POST /api/site/leads`, REB-438). The generic\n * lead-ingestion endpoint: posts a body typed by `@reblu/site-contracts`,\n * bypasses the cache, and returns the created lead's id + creation timestamp.\n */\nexport function makeLeadsNamespace(ctx: RebluClientContext) {\n return {\n /** Create a lead. Returns the created lead acknowledgement. */\n create: (body: LeadInput): Promise<LeadSubmission> =>\n requestMutation<LeadInput, LeadSubmission>(ctx, '/api/site/leads', body),\n }\n}\n","import type { Legal } from '@reblu/site-contracts'\nimport type { RebluCachePolicy } from '../cache'\nimport { requestOne, type RebluClientContext } from '../client'\n\n/**\n * `GET /api/site/legal` — the tenant's legal content (privacy, terms, cookies,\n * and since REB-1201 `legalNotice` and `withdrawalPolicy`). Render each with\n * `LegalDocument` from `@reblu/site-blocks`.\n */\nexport function makeLegalNamespace(ctx: RebluClientContext) {\n return {\n get: (cache?: RebluCachePolicy): Promise<Legal> =>\n requestOne<Legal>(ctx, '/api/site/legal', { cachePolicy: cache }),\n }\n}\n","import type { PageDetail, PageListItem, PageSlug } from '@reblu/site-contracts'\nimport { buildUrl, type QueryValue } from '../build-url'\nimport type { RebluCachePolicy } from '../cache'\nimport { requestList, requestOne, type RebluClientContext } from '../client'\nimport { AmbiguousPageSlugError, ApiError, apiErrorRawBody } from '../errors'\nimport type { ListResult, PagesListParams } from '../types'\n\n/**\n * REB-867 — options for `pages.bySlug`. Page slugs are unique PER CATEGORY:\n * - `category: '<categorySlug>'` → the page with that slug in that category;\n * - `category: null` → the UNCATEGORISED page with that slug;\n * - omitted → the unique page with that slug; throws\n * `AmbiguousPageSlugError` when it exists in more than one category.\n */\nexport interface PageBySlugOptions {\n category?: string | null\n cache?: RebluCachePolicy\n}\n\n/**\n * The two second-argument shapes share no key: a `RebluCachePolicy` only ever\n * has `revalidate`, `PageBySlugOptions` only `category`/`cache`. So the\n * presence of `category` or `cache` (even `category: undefined`) selects the\n * options form; anything else — including `{}` — is a cache policy, exactly as\n * before REB-867.\n */\n/** REB-869 — options for `pages.featured`. */\nexport interface FeaturedPagesOptions {\n /**\n * How many pages at most: an integer >= 1. Default 3. Values above the\n * server's cap (3) are clamped to 3; anything else invalid (0, negative,\n * fractional, NaN) throws a `RangeError` before any request is made.\n */\n limit?: number\n /**\n * When the category has nothing featured, return its first `limit` pages in\n * normal order instead of `[]`. Default `true`, so a teaser is never empty\n * just because nobody picked pages yet.\n */\n fallback?: boolean\n cache?: RebluCachePolicy\n}\n\n/** The server caps a featured list at 3 (REB-869). */\nconst FEATURED_PAGES_MAX = 3\n\nfunction isBySlugOptions(value: RebluCachePolicy | PageBySlugOptions): value is PageBySlugOptions {\n return 'category' in value || 'cache' in value\n}\n\n/** Dynamic pages domain — `GET /api/site/pages/*`. */\nexport function makePagesNamespace(ctx: RebluClientContext) {\n const namespace = {\n list: (\n params?: PagesListParams,\n cache?: RebluCachePolicy,\n ): Promise<ListResult<PageListItem>> =>\n requestList<PageListItem>(\n ctx,\n // REB-63: `type` can now be a single PageType or an array (repeated\n // `?type=` params) — widen the cast alongside `PagesListParams`.\n buildUrl('/api/site/pages', params as Record<string, QueryValue | QueryValue[]> | undefined),\n { cachePolicy: cache },\n ),\n\n /**\n * A published page by slug. The second argument is either a cache policy\n * (backward-compatible) or {@link PageBySlugOptions} (REB-867).\n */\n bySlug: async (\n slug: string,\n cacheOrOptions?: RebluCachePolicy | PageBySlugOptions,\n ): Promise<PageDetail> => {\n const options: PageBySlugOptions =\n cacheOrOptions && isBySlugOptions(cacheOrOptions)\n ? cacheOrOptions\n : { cache: cacheOrOptions }\n const path =\n options.category === undefined\n ? `/api/site/pages/${slug}`\n : buildUrl(`/api/site/pages/${slug}`, { category: options.category ?? '' })\n try {\n return await requestOne<PageDetail>(ctx, path, { cachePolicy: options.cache })\n } catch (error) {\n if (error instanceof ApiError && error.status === 409 && error.body?.code === 'ambiguous_slug') {\n throw new AmbiguousPageSlugError(error, apiErrorRawBody(error))\n }\n throw error\n }\n },\n\n /**\n * REB-869 — the pages to show in a category teaser: the category's\n * featured pages in the tenant's order; when none are featured (and\n * `fallback` is not `false`), the first `limit` pages of the category in\n * normal order. Returns the items only (no pagination applies).\n */\n featured: async (\n category: string,\n { limit: requested = FEATURED_PAGES_MAX, fallback = true, cache }: FeaturedPagesOptions = {},\n ): Promise<PageListItem[]> => {\n if (!Number.isInteger(requested) || requested < 1) {\n throw new RangeError(`pages.featured: limit must be an integer >= 1 (got ${requested})`)\n }\n const limit = Math.min(requested, FEATURED_PAGES_MAX)\n const featured = await namespace.list({ category, featured: true }, cache)\n if (featured.data.length > 0 || !fallback) return featured.data.slice(0, limit)\n const first = await namespace.list({ category, pageSize: limit }, cache)\n return first.data.slice(0, limit)\n },\n\n slugs: (cache?: RebluCachePolicy): Promise<ListResult<PageSlug>> =>\n requestList<PageSlug>(ctx, '/api/site/pages/slugs', { cachePolicy: cache }),\n }\n return namespace\n}\n","import type {\n ShopApplyDiscountInput,\n ShopCartValidation,\n ShopCategory,\n ShopCheckEmailInput,\n ShopCheckoutInput,\n ShopCheckoutSession,\n ShopConfig,\n ShopDiscountApplication,\n ShopEmailCheck,\n ShopFacets,\n ShopOrder,\n ShopProductDetail,\n ShopProductListItem,\n ShopQuote,\n ShopQuoteInput,\n ShopValidateCartInput,\n} from '@reblu/site-contracts'\nimport { buildUrl, type QueryValue } from '../build-url'\nimport type { RebluCachePolicy } from '../cache'\nimport {\n requestList,\n requestMutation,\n requestOne,\n type RebluClientContext,\n type RebluMutationOptions,\n} from '../client'\nimport { isSafeCheckoutRedirectUrl, UnsafeCheckoutRedirectError } from '../checkout-redirect'\nimport type { ListResult, ShopProductsParams } from '../types'\n\n/**\n * Flatten `ShopProductsParams` into a query record: scalars pass through; the\n * `taxonomy`/`attr` maps become repeatable `taxonomy[key]`/`attr[key]` params.\n * Array attr values repeat the same bracket key.\n */\nfunction flattenShopParams(params?: ShopProductsParams): Record<string, QueryValue | QueryValue[]> {\n if (!params) return {}\n const { taxonomy, attr, ...scalars } = params\n const out: Record<string, QueryValue | QueryValue[]> = { ...scalars }\n if (taxonomy) {\n for (const [key, value] of Object.entries(taxonomy)) out[`taxonomy[${key}]`] = value\n }\n if (attr) {\n for (const [key, value] of Object.entries(attr)) out[`attr[${key}]`] = value\n }\n return out\n}\n\n/**\n * Shop domain — READ surface (`GET /api/site/shop/*`) plus the mutation flow\n * (`POST` checkout, validate-cart, apply-discount, and the deprecated\n * check-email) added in REB-438. Every write posts a body typed by `@reblu/site-contracts` and bypasses\n * the cache. Request/response DTOs live in the contracts package (zero Zod).\n */\nexport function makeShopNamespace(ctx: RebluClientContext) {\n return {\n products: (\n params?: ShopProductsParams,\n cache?: RebluCachePolicy,\n ): Promise<ListResult<ShopProductListItem>> =>\n requestList<ShopProductListItem>(\n ctx,\n buildUrl('/api/site/shop/products', flattenShopParams(params)),\n { cachePolicy: cache },\n ),\n\n /** Available facets (brand/attributes/price/on-sale) for the given filters. */\n facets: (params?: ShopProductsParams, cache?: RebluCachePolicy): Promise<ShopFacets> =>\n requestOne<ShopFacets>(ctx, buildUrl('/api/site/shop/facets', flattenShopParams(params)), {\n cachePolicy: cache,\n }),\n\n bySlug: (slug: string, cache?: RebluCachePolicy): Promise<ShopProductDetail> =>\n requestOne<ShopProductDetail>(ctx, `/api/site/shop/products/${slug}`, { cachePolicy: cache }),\n\n categories: (cache?: RebluCachePolicy): Promise<ListResult<ShopCategory>> =>\n requestList<ShopCategory>(ctx, '/api/site/shop/categories', { cachePolicy: cache }),\n\n config: (cache?: RebluCachePolicy): Promise<ShopConfig> =>\n requestOne<ShopConfig>(ctx, '/api/site/shop/config', { cachePolicy: cache }),\n\n // ── Mutations (REB-438) ──────────────────────────────────────────────────\n\n /**\n * Start a hosted checkout session. Returns the Stripe URL + session id.\n *\n * REB-836 (defence in depth): every SITE hands `url` straight to\n * `window.location.assign`, so this validates it is `https:` on an\n * allowed host (`ctx.checkoutRedirectHosts`, default\n * `checkout.stripe.com`) BEFORE returning — throwing\n * {@link UnsafeCheckoutRedirectError} instead of ever handing back a URL\n * that could redirect a buyer's browser off Stripe's domain.\n */\n checkout: async (body: ShopCheckoutInput): Promise<ShopCheckoutSession> => {\n const session = await requestMutation<ShopCheckoutInput, ShopCheckoutSession>(\n ctx,\n '/api/site/shop/checkout',\n body,\n )\n if (!isSafeCheckoutRedirectUrl(session.url, ctx.checkoutRedirectHosts)) {\n throw new UnsafeCheckoutRedirectError(session.url, ctx.checkoutRedirectHosts)\n }\n return session\n },\n\n /** Validate a cart against live product/stock/price state before checkout. */\n validateCart: (body: ShopValidateCartInput): Promise<ShopCartValidation> =>\n requestMutation<ShopValidateCartInput, ShopCartValidation>(\n ctx,\n '/api/site/shop/validate-cart',\n body,\n ),\n\n /**\n * REB-1197 — the pre-payment summary of the single-step checkout: lines,\n * discount, delivery, shipping, tax, weight, delivery term and the final\n * total, priced by the same function as `checkout` (the total shown is the\n * total Stripe charges). Holds nothing: no discount slot, no session.\n *\n * Server-side, pass `options.visitorIp` (see `visitorIpFromHeaders`): a\n * quote with a `discountCode` counts against the same per-visitor limit\n * as `applyDiscount`.\n */\n quote: (body: ShopQuoteInput, options?: RebluMutationOptions): Promise<ShopQuote> =>\n requestMutation<ShopQuoteInput, ShopQuote>(ctx, '/api/site/shop/quote', body, options),\n\n /** Validate a discount code against the current cart subtotal. */\n applyDiscount: (body: ShopApplyDiscountInput): Promise<ShopDiscountApplication> =>\n requestMutation<ShopApplyDiscountInput, ShopDiscountApplication>(\n ctx,\n '/api/site/shop/apply-discount',\n body,\n ),\n\n /**\n * @deprecated REB-834 — the endpoint no longer reveals whether an account\n * exists: it always resolves `{ exists: false }`. Do not call it and never\n * branch UI on `exists`; the checkout always offers «Iniciar sesión o\n * continuar como invitado» and the server links the order to an existing\n * account after payment. Removed in the next major.\n *\n * Server-side, pass `options.visitorIp` (from the incoming request, see\n * `visitorIpFromHeaders`): base-api keys this route's per-IP limit on it\n * and, without it, throttles every visitor of the SITE in one shared\n * tenant-level bucket (REB-1006).\n */\n checkEmail: (\n body: ShopCheckEmailInput,\n options?: RebluMutationOptions,\n ): Promise<ShopEmailCheck> =>\n requestMutation<ShopCheckEmailInput, ShopEmailCheck>(\n ctx,\n '/api/site/shop/check-email',\n body,\n options,\n ),\n\n /**\n * Look up a confirmed (or still-pending) order by its Stripe Checkout\n * session id — the order-confirmation flow (REB-829). `GET`, so it goes\n * through `requestOne` like every other read, not `requestMutation`.\n *\n * Never cached by default (REB-1245): an order belongs to one buyer, and\n * \"not found yet\" right after the Stripe redirect must not be remembered.\n * Pass an explicit `cache` policy only if you know you want one.\n */\n order: (sessionId: string, cache?: RebluCachePolicy): Promise<ShopOrder> =>\n requestOne<ShopOrder>(ctx, `/api/site/shop/order/${encodeURIComponent(sessionId)}`, {\n cachePolicy: cache ?? { revalidate: false },\n }),\n }\n}\n","import type { FeaturedReviews, Review } from '@reblu/site-contracts'\nimport { buildUrl } from '../build-url'\nimport type { RebluCachePolicy } from '../cache'\nimport { requestList, requestOne, type RebluClientContext } from '../client'\nimport type { ListResult } from '../types'\n\n/** Filters for {@link makeReviewsNamespace}'s `list` (REB-1272). */\nexport interface ReviewListParams {\n /** Only the reviews linked to this page (its `id`). Omit for every published review. */\n pageId?: string\n /** At most this many reviews (1..100). Omit for all of them. */\n limit?: number\n}\n\n/** Options for {@link makeReviewsNamespace}'s `featured` (REB-1272). */\nexport interface FeaturedReviewsParams {\n /** At most this many featured reviews (1..100). Omit for all of them. */\n limit?: number\n}\n\n/**\n * `GET /api/site/reviews` — the tenant's PUBLISHED reviews in the order set in\n * nexus, each with its source and the https link to the original. Publishing,\n * hiding or reordering in nexus needs no SITE change. Requires the tenant's\n * `reviews` module: without it the API answers 404 (an `ApiError`).\n */\nexport function makeReviewsNamespace(ctx: RebluClientContext) {\n function list(\n params: ReviewListParams = {},\n cache?: RebluCachePolicy,\n ): Promise<ListResult<Review>> {\n return requestList<Review>(\n ctx,\n buildUrl('/api/site/reviews', { pageId: params.pageId, limit: params.limit }),\n { cachePolicy: cache },\n )\n }\n /**\n * `GET /api/site/reviews/featured` (REB-1272, owner 8-Nov) — the reviews the\n * tenant featured for its home, published only, in the featured order, and\n * the `layout` it chose in nexus (`grid` | `carousel`). Render both with\n * `FeaturedReviews` from `@reblu/site-blocks/client`. 404 without the\n * tenant's `reviews` module (an `ApiError`).\n */\n function featured(\n params: FeaturedReviewsParams = {},\n cache?: RebluCachePolicy,\n ): Promise<FeaturedReviews> {\n return requestOne<FeaturedReviews>(\n ctx,\n buildUrl('/api/site/reviews/featured', { limit: params.limit }),\n { cachePolicy: cache },\n )\n }\n return { list, featured }\n}\n","import type { TrustBadge, TrustBadgeType } from '@reblu/site-contracts'\nimport { buildUrl } from '../build-url'\nimport type { RebluCachePolicy } from '../cache'\nimport { requestList, type RebluClientContext } from '../client'\nimport type { ListResult } from '../types'\n\n/** Filters for {@link makeTrustBadgesNamespace}'s `list` (REB-996). */\nexport interface TrustBadgeListParams {\n /** Only badges of this type (e.g. `'SUBVENTION'`). Omit for every type. */\n type?: TrustBadgeType\n}\n\nfunction isCachePolicy(value: TrustBadgeListParams | RebluCachePolicy): value is RebluCachePolicy {\n return Object.prototype.hasOwnProperty.call(value, 'revalidate')\n}\n\n/**\n * `GET /api/site/trust-badges` — the tenant's PUBLIC trust badges (no\n * pagination): active in nexus and inside their optional validity window, so\n * expiring or switching one off needs no SITE change.\n */\nexport function makeTrustBadgesNamespace(ctx: RebluClientContext) {\n function list(cache?: RebluCachePolicy): Promise<ListResult<TrustBadge>>\n function list(\n params: TrustBadgeListParams,\n cache?: RebluCachePolicy,\n ): Promise<ListResult<TrustBadge>>\n function list(\n paramsOrCache?: TrustBadgeListParams | RebluCachePolicy,\n cache?: RebluCachePolicy,\n ): Promise<ListResult<TrustBadge>> {\n // `list(cache)` is the pre-REB-996 call shape — kept so this stays a minor.\n const legacyCache = paramsOrCache && isCachePolicy(paramsOrCache) ? paramsOrCache : undefined\n const params = legacyCache ? undefined : (paramsOrCache as TrustBadgeListParams | undefined)\n return requestList<TrustBadge>(\n ctx,\n buildUrl('/api/site/trust-badges', { type: params?.type }),\n { cachePolicy: legacyCache ?? cache },\n )\n }\n return { list }\n}\n","import type { WithdrawalRequestInput, WithdrawalRequestSubmission } from '@reblu/site-contracts'\nimport { requestMutation, type RebluClientContext, type RebluMutationOptions } from '../client'\n\n/**\n * Withdrawal requests domain (REB-1201) — WRITE only\n * (`POST /api/site/withdrawal-requests`). A buyer communicates that they\n * withdraw from a purchase: base-api stores it, emails the tenant team and\n * acknowledges it to the buyer. Returns the request id, when it was received\n * and whether the buyer's acknowledgement email went out.\n *\n * base-api requires a reCAPTCHA v3 token (`captcha`) and rate-limits per tenant\n * and per visitor IP; a rejection surfaces as an `ApiError` carrying the\n * upstream `status` (400 validation, 422 captcha, 429 rate limit).\n */\nexport function makeWithdrawalRequestsNamespace(ctx: RebluClientContext) {\n return {\n /**\n * Submit a withdrawal communication. Server-side, pass `options.visitorIp`\n * (see `visitorIpFromHeaders`) so base-api's per-IP limit sees the visitor.\n */\n submit: (\n body: WithdrawalRequestInput,\n options?: RebluMutationOptions,\n ): Promise<WithdrawalRequestSubmission> =>\n requestMutation<WithdrawalRequestInput, WithdrawalRequestSubmission>(\n ctx,\n '/api/site/withdrawal-requests',\n body,\n options,\n ),\n }\n}\n","import type { ContactFormInput } from '@reblu/site-contracts'\n\n/**\n * REB-1077 — the `metadata` block of a contact-form submission, built from the\n * page the visitor is on so a SITE never hand-codes `fromPage`.\n *\n * Call it in the browser, at submit time (inside the form's submit handler):\n * it reads `globalThis.location` unless a `location` is passed.\n */\n\n/** The part of `window.location` these helpers read. */\nexport interface PageLocation {\n pathname: string\n href?: string\n}\n\nexport type ContactFormMetadata = ContactFormInput['metadata']\n\nexport interface ContactFormMetadataOptions {\n /**\n * Explicit page path, overriding the one read from the location (e.g. a\n * modal form that wants to report the landing it belongs to).\n */\n fromPage?: string\n /** @default { platform: 'organic' } */\n leadSource?: ContactFormMetadata['leadSource']\n /** @default globalThis.location */\n location?: PageLocation | null\n /** Submission time. @default new Date() */\n now?: Date\n}\n\nfunction browserLocation(): PageLocation | null {\n const location = (globalThis as { location?: PageLocation }).location\n return location && typeof location.pathname === 'string' ? location : null\n}\n\n/**\n * Path of the page the visitor is on — `location.pathname`, never with a query\n * string or hash. `'/'` when there is no location (server render).\n */\nexport function currentPagePath(location: PageLocation | null = browserLocation()): string {\n const path = location?.pathname.split(/[?#]/, 1)[0] ?? ''\n return path.startsWith('/') ? path : '/'\n}\n\n/**\n * The `metadata` for `contactForm.submit()` / `POST /api/site/contact-form`:\n * `fromPage` is the current page path, `currentUrl` the full href (base-api\n * reads the UTMs from it), `timestamp` the submission time.\n *\n * ```ts\n * body: JSON.stringify({ ...fields, captcha, metadata: buildContactFormMetadata() })\n * ```\n */\nexport function buildContactFormMetadata(\n options: ContactFormMetadataOptions = {},\n): ContactFormMetadata {\n const location = options.location === undefined ? browserLocation() : options.location\n return {\n fromPage: options.fromPage ?? currentPagePath(location),\n timestamp: (options.now ?? new Date()).toISOString(),\n currentUrl: location?.href ?? '',\n leadSource: options.leadSource ?? { platform: 'organic' },\n }\n}\n","/**\n * `@reblu/site-client` — framework-agnostic HTTP client for the Reblu SITE API\n * (REB-415 / design doc §3).\n *\n * The consumer owns the environment: `baseUrl`/`apiKey` are passed in, never read\n * from `process.env`. Caching is delegated to an injectable `RebluCacheAdapter`\n * (default `PlainCacheAdapter`), which severs any coupling to a specific\n * framework's cache model — `@reblu/site-next` supplies the ISR-aware adapter.\n * Response DTOs are typed by the peer, type-only `@reblu/site-contracts`.\n */\nimport { PlainCacheAdapter, type RebluCacheAdapter } from './cache'\nimport type { RebluClientContext, RequestScope } from './client'\nimport { DEFAULT_CHECKOUT_REDIRECT_HOSTS } from './checkout-redirect'\nimport { makeAcademyNamespace } from './domains/academy'\nimport { makeAnalyticsNamespace } from './domains/analytics'\nimport { makeBlogNamespace } from './domains/blog'\nimport { makeConfigNamespace } from './domains/config'\nimport { makeContactFormNamespace } from './domains/contact-form'\nimport { makeCtasNamespace } from './domains/ctas'\nimport { makeDataRequestsNamespace } from './domains/data-requests'\nimport { makeLeadsNamespace } from './domains/leads'\nimport { makeLegalNamespace } from './domains/legal'\nimport { makePagesNamespace } from './domains/pages'\nimport { makeShopNamespace } from './domains/shop'\nimport { makeReviewsNamespace } from './domains/reviews'\nimport { makeTrustBadgesNamespace } from './domains/trust-badges'\nimport { makeWithdrawalRequestsNamespace } from './domains/withdrawal-requests'\n\nconst DEFAULT_API_VERSION = '1'\n/** Parity with demo-site's upstream timeout (client.ts:36). */\nconst DEFAULT_TIMEOUT_MS = 8000\n\nexport interface RebluClientConfig {\n /** Absolute origin of the Reblu SITE API, e.g. `https://api.reblu.app`. */\n baseUrl: string\n /** Tenant SITE API key (`rbl_…`), sent as `X-Reblu-Api-Key`. */\n apiKey: string\n /** Contract version sent as `X-Reblu-Api-Version`. Defaults to `'1'`. */\n apiVersion?: string\n /** Cache strategy. Defaults to `new PlainCacheAdapter()` (no-store). */\n cache?: RebluCacheAdapter\n /** Per-request hard timeout in ms. Defaults to `8000`. */\n timeoutMs?: number\n /** Injectable `fetch` — for tests, custom runtimes, or instrumentation. */\n fetch?: typeof fetch\n /**\n * Allowed hosts for the Stripe Checkout URL `shop.checkout()` returns\n * (REB-836, defence in depth — see `./checkout-redirect`). Defaults to\n * `['checkout.stripe.com']`. Override only to point at a test double (e.g.\n * a Stripe CLI mock host) — never to widen production trust.\n */\n checkoutRedirectHosts?: string[]\n /**\n * REB-1214 — memo store of the current server request, so identical GET\n * reads within it are fetched once. In Next.js pass `rebluRequestScope`\n * from `@reblu/site-next` (React `cache()`); see the README. Without it,\n * identical GETs share one fetch only while in flight.\n */\n requestScope?: RequestScope\n}\n\n/**\n * Build a Reblu SITE API client. Each domain is a small namespace of bound\n * methods assembled from a separate module, so a consumer importing only a\n * couple of domains can tree-shake the rest.\n */\nexport function createRebluClient(config: RebluClientConfig) {\n // Wrap global fetch so calling `ctx.fetch()` never loses its `this` binding\n // (browsers throw \"Illegal invocation\" for an unbound `window.fetch`).\n const boundFetch: typeof fetch = config.fetch ?? ((input, init) => fetch(input, init))\n\n const ctx: RebluClientContext = {\n baseUrl: config.baseUrl,\n apiKey: config.apiKey,\n apiVersion: config.apiVersion ?? DEFAULT_API_VERSION,\n cache: config.cache ?? new PlainCacheAdapter(),\n timeoutMs: config.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n fetch: boundFetch,\n checkoutRedirectHosts: config.checkoutRedirectHosts ?? DEFAULT_CHECKOUT_REDIRECT_HOSTS,\n requestScope: config.requestScope,\n inFlight: new Map(),\n }\n\n return {\n config: makeConfigNamespace(ctx),\n analytics: makeAnalyticsNamespace(ctx),\n legal: makeLegalNamespace(ctx),\n trustBadges: makeTrustBadgesNamespace(ctx),\n ctas: makeCtasNamespace(ctx),\n blog: makeBlogNamespace(ctx),\n pages: makePagesNamespace(ctx),\n shop: makeShopNamespace(ctx),\n academy: makeAcademyNamespace(ctx),\n contactForm: makeContactFormNamespace(ctx),\n leads: makeLeadsNamespace(ctx),\n dataRequests: makeDataRequestsNamespace(ctx),\n withdrawalRequests: makeWithdrawalRequestsNamespace(ctx),\n reviews: makeReviewsNamespace(ctx),\n }\n}\n\n/** The assembled client returned by {@link createRebluClient}. */\nexport type RebluClient = ReturnType<typeof createRebluClient>\n\nexport { ApiError, AmbiguousPageSlugError, AcademyCheckoutError } from './errors'\nexport type {\n ApiErrorBody,\n ApiErrorDetails,\n PageSlugCandidate,\n AcademyCheckoutErrorDetails,\n} from './errors'\nexport type { PageBySlugOptions, FeaturedPagesOptions } from './domains/pages'\nexport type { TrustBadgeListParams } from './domains/trust-badges'\nexport type { ReviewListParams, FeaturedReviewsParams } from './domains/reviews'\nexport {\n DEFAULT_CHECKOUT_REDIRECT_HOSTS,\n isSafeCheckoutRedirectUrl,\n UnsafeCheckoutRedirectError,\n} from './checkout-redirect'\nexport { PlainCacheAdapter } from './cache'\nexport type { RebluCacheAdapter, RebluCachePolicy } from './cache'\nexport type {\n ListResult,\n BlogListParams,\n PagesListParams,\n ShopProductsParams,\n AcademyCourseListParams,\n AcademyEnrollmentRequestInput,\n AcademyEnrollmentRequestResult,\n} from './types'\nexport {\n VISITOR_IP_HEADER,\n visitorIpFromHeaders,\n RETRY_AFTER_CAP_MS,\n type RebluMutationOptions,\n type RequestScope,\n} from './client'\nexport {\n buildContactFormMetadata,\n currentPagePath,\n type ContactFormMetadata,\n type ContactFormMetadataOptions,\n type PageLocation,\n} from './contact-form-metadata'\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -333,6 +333,11 @@ interface ReviewListParams {
|
|
|
333
333
|
/** At most this many reviews (1..100). Omit for all of them. */
|
|
334
334
|
limit?: number;
|
|
335
335
|
}
|
|
336
|
+
/** Options for {@link makeReviewsNamespace}'s `featured` (REB-1272). */
|
|
337
|
+
interface FeaturedReviewsParams {
|
|
338
|
+
/** At most this many featured reviews (1..100). Omit for all of them. */
|
|
339
|
+
limit?: number;
|
|
340
|
+
}
|
|
336
341
|
|
|
337
342
|
/**
|
|
338
343
|
* Stripe Checkout redirect-origin guard (REB-836, defence in depth).
|
|
@@ -512,9 +517,10 @@ declare function createRebluClient(config: RebluClientConfig): {
|
|
|
512
517
|
};
|
|
513
518
|
reviews: {
|
|
514
519
|
list: (params?: ReviewListParams, cache?: RebluCachePolicy) => Promise<ListResult<_reblu_site_contracts.Review>>;
|
|
520
|
+
featured: (params?: FeaturedReviewsParams, cache?: RebluCachePolicy) => Promise<_reblu_site_contracts.FeaturedReviews>;
|
|
515
521
|
};
|
|
516
522
|
};
|
|
517
523
|
/** The assembled client returned by {@link createRebluClient}. */
|
|
518
524
|
type RebluClient = ReturnType<typeof createRebluClient>;
|
|
519
525
|
|
|
520
|
-
export { AcademyCheckoutError, type AcademyCheckoutErrorDetails, type AcademyCourseListParams, type AcademyEnrollmentRequestInput, type AcademyEnrollmentRequestResult, AmbiguousPageSlugError, ApiError, type ApiErrorBody, type ApiErrorDetails, type BlogListParams, type ContactFormMetadata, type ContactFormMetadataOptions, DEFAULT_CHECKOUT_REDIRECT_HOSTS, type FeaturedPagesOptions, type ListResult, type PageBySlugOptions, type PageLocation, type PageSlugCandidate, type PagesListParams, PlainCacheAdapter, RETRY_AFTER_CAP_MS, type RebluCacheAdapter, type RebluCachePolicy, type RebluClient, type RebluClientConfig, type RebluMutationOptions, type RequestScope, type ReviewListParams, type ShopProductsParams, type TrustBadgeListParams, UnsafeCheckoutRedirectError, VISITOR_IP_HEADER, buildContactFormMetadata, createRebluClient, currentPagePath, isSafeCheckoutRedirectUrl, visitorIpFromHeaders };
|
|
526
|
+
export { AcademyCheckoutError, type AcademyCheckoutErrorDetails, type AcademyCourseListParams, type AcademyEnrollmentRequestInput, type AcademyEnrollmentRequestResult, AmbiguousPageSlugError, ApiError, type ApiErrorBody, type ApiErrorDetails, type BlogListParams, type ContactFormMetadata, type ContactFormMetadataOptions, DEFAULT_CHECKOUT_REDIRECT_HOSTS, type FeaturedPagesOptions, type FeaturedReviewsParams, type ListResult, type PageBySlugOptions, type PageLocation, type PageSlugCandidate, type PagesListParams, PlainCacheAdapter, RETRY_AFTER_CAP_MS, type RebluCacheAdapter, type RebluCachePolicy, type RebluClient, type RebluClientConfig, type RebluMutationOptions, type RequestScope, type ReviewListParams, type ShopProductsParams, type TrustBadgeListParams, UnsafeCheckoutRedirectError, VISITOR_IP_HEADER, buildContactFormMetadata, createRebluClient, currentPagePath, isSafeCheckoutRedirectUrl, visitorIpFromHeaders };
|
package/dist/index.d.ts
CHANGED
|
@@ -333,6 +333,11 @@ interface ReviewListParams {
|
|
|
333
333
|
/** At most this many reviews (1..100). Omit for all of them. */
|
|
334
334
|
limit?: number;
|
|
335
335
|
}
|
|
336
|
+
/** Options for {@link makeReviewsNamespace}'s `featured` (REB-1272). */
|
|
337
|
+
interface FeaturedReviewsParams {
|
|
338
|
+
/** At most this many featured reviews (1..100). Omit for all of them. */
|
|
339
|
+
limit?: number;
|
|
340
|
+
}
|
|
336
341
|
|
|
337
342
|
/**
|
|
338
343
|
* Stripe Checkout redirect-origin guard (REB-836, defence in depth).
|
|
@@ -512,9 +517,10 @@ declare function createRebluClient(config: RebluClientConfig): {
|
|
|
512
517
|
};
|
|
513
518
|
reviews: {
|
|
514
519
|
list: (params?: ReviewListParams, cache?: RebluCachePolicy) => Promise<ListResult<_reblu_site_contracts.Review>>;
|
|
520
|
+
featured: (params?: FeaturedReviewsParams, cache?: RebluCachePolicy) => Promise<_reblu_site_contracts.FeaturedReviews>;
|
|
515
521
|
};
|
|
516
522
|
};
|
|
517
523
|
/** The assembled client returned by {@link createRebluClient}. */
|
|
518
524
|
type RebluClient = ReturnType<typeof createRebluClient>;
|
|
519
525
|
|
|
520
|
-
export { AcademyCheckoutError, type AcademyCheckoutErrorDetails, type AcademyCourseListParams, type AcademyEnrollmentRequestInput, type AcademyEnrollmentRequestResult, AmbiguousPageSlugError, ApiError, type ApiErrorBody, type ApiErrorDetails, type BlogListParams, type ContactFormMetadata, type ContactFormMetadataOptions, DEFAULT_CHECKOUT_REDIRECT_HOSTS, type FeaturedPagesOptions, type ListResult, type PageBySlugOptions, type PageLocation, type PageSlugCandidate, type PagesListParams, PlainCacheAdapter, RETRY_AFTER_CAP_MS, type RebluCacheAdapter, type RebluCachePolicy, type RebluClient, type RebluClientConfig, type RebluMutationOptions, type RequestScope, type ReviewListParams, type ShopProductsParams, type TrustBadgeListParams, UnsafeCheckoutRedirectError, VISITOR_IP_HEADER, buildContactFormMetadata, createRebluClient, currentPagePath, isSafeCheckoutRedirectUrl, visitorIpFromHeaders };
|
|
526
|
+
export { AcademyCheckoutError, type AcademyCheckoutErrorDetails, type AcademyCourseListParams, type AcademyEnrollmentRequestInput, type AcademyEnrollmentRequestResult, AmbiguousPageSlugError, ApiError, type ApiErrorBody, type ApiErrorDetails, type BlogListParams, type ContactFormMetadata, type ContactFormMetadataOptions, DEFAULT_CHECKOUT_REDIRECT_HOSTS, type FeaturedPagesOptions, type FeaturedReviewsParams, type ListResult, type PageBySlugOptions, type PageLocation, type PageSlugCandidate, type PagesListParams, PlainCacheAdapter, RETRY_AFTER_CAP_MS, type RebluCacheAdapter, type RebluCachePolicy, type RebluClient, type RebluClientConfig, type RebluMutationOptions, type RequestScope, type ReviewListParams, type ShopProductsParams, type TrustBadgeListParams, UnsafeCheckoutRedirectError, VISITOR_IP_HEADER, buildContactFormMetadata, createRebluClient, currentPagePath, isSafeCheckoutRedirectUrl, visitorIpFromHeaders };
|
package/dist/index.js
CHANGED
|
@@ -679,7 +679,14 @@ function makeReviewsNamespace(ctx) {
|
|
|
679
679
|
{ cachePolicy: cache }
|
|
680
680
|
);
|
|
681
681
|
}
|
|
682
|
-
|
|
682
|
+
function featured(params = {}, cache) {
|
|
683
|
+
return requestOne(
|
|
684
|
+
ctx,
|
|
685
|
+
buildUrl("/api/site/reviews/featured", { limit: params.limit }),
|
|
686
|
+
{ cachePolicy: cache }
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
return { list, featured };
|
|
683
690
|
}
|
|
684
691
|
|
|
685
692
|
// src/domains/trust-badges.ts
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cache.ts","../src/checkout-redirect.ts","../src/build-url.ts","../src/errors.ts","../src/client.ts","../src/domains/academy.ts","../src/domains/analytics.ts","../src/domains/blog.ts","../src/domains/config.ts","../src/domains/contact-form.ts","../src/domains/ctas.ts","../src/domains/data-requests.ts","../src/domains/leads.ts","../src/domains/legal.ts","../src/domains/pages.ts","../src/domains/shop.ts","../src/domains/reviews.ts","../src/domains/trust-badges.ts","../src/domains/withdrawal-requests.ts","../src/contact-form-metadata.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Cache adapter — the seam that severs the SDK from any framework's caching\n * model (design doc D4 / §3.2). The client owns *when* to cache (a per-call\n * `RebluCachePolicy`); the adapter owns *how* (the framework-specific\n * `RequestInit` fields). `@reblu/site-client` ships the framework-agnostic\n * `PlainCacheAdapter`; `@reblu/site-next` (REB-418) supplies a `NextCacheAdapter`\n * that returns `{ next: { revalidate } }`.\n */\n\nexport interface RebluCachePolicy {\n /**\n * Seconds to revalidate. `undefined` → use the adapter's default; `0`/`false`\n * → never cache (always fetch fresh).\n */\n revalidate?: number | false\n}\n\nexport interface RebluCacheAdapter {\n /** Map a per-call cache policy to framework-specific `fetch` `RequestInit` fields. */\n resolveRequestInit(policy: RebluCachePolicy): RequestInit\n /**\n * Optional (REB-1245): perform the HTTP call yourself. When present, the\n * client calls it for every attempt instead of `transport(url, init)`, with\n * `init` already carrying `resolveRequestInit`'s fields. An adapter uses it to\n * cache the read's OUTCOME (a 404 included), which a framework fetch cache\n * may refuse to store. Return a `Response` exactly as `fetch` would: the\n * client still maps non-2xx to `ApiError` and retries 429/503 reads.\n */\n fetch?(url: string, init: RequestInit, transport: typeof fetch): Promise<Response>\n}\n\n/**\n * Default adapter — plain `fetch` cache semantics that work in Node, edge,\n * browser and tests. No `next`, no `server-only`.\n */\nexport class PlainCacheAdapter implements RebluCacheAdapter {\n constructor(private readonly defaultRevalidate: number | false = false) {}\n\n resolveRequestInit(policy: RebluCachePolicy): RequestInit {\n const revalidate = policy.revalidate ?? this.defaultRevalidate\n return revalidate === false || revalidate === 0\n ? { cache: 'no-store' }\n : { cache: 'force-cache' }\n }\n}\n","/**\n * Stripe Checkout redirect-origin guard (REB-836, defence in depth).\n *\n * `shop.checkout()` returns a URL every SITE immediately hands to\n * `window.location.assign(url)`. Nothing upstream of this SDK validated it\n * before REB-836 — a compromised/misconfigured base-api, a tampered\n * response, or a MITM proxy could have sent a buyer's browser anywhere.\n * `isSafeCheckoutRedirectUrl` is the shared predicate `domains/shop.ts` uses\n * to refuse an unsafe URL before ever returning it to the caller; a SITE's\n * `@reblu/site-next` Route Handler re-checks the SAME property server-side\n * (`packages/site-next/src/shop/checkout-redirect.ts`, kept as an\n * independent copy — see that file's docblock for why it isn't a shared\n * import). Update both together if Stripe ever changes its Checkout host.\n */\n\n/** The only Checkout redirect host trusted by default. */\nexport const DEFAULT_CHECKOUT_REDIRECT_HOSTS: readonly string[] = ['checkout.stripe.com']\n\n/**\n * True only when `url` parses as an absolute URL, uses `https:`, and its\n * hostname is in `allowedHosts` (default: {@link DEFAULT_CHECKOUT_REDIRECT_HOSTS}).\n * Never throws — an unparsable `url` is simply unsafe.\n */\nexport function isSafeCheckoutRedirectUrl(\n url: string,\n allowedHosts: readonly string[] = DEFAULT_CHECKOUT_REDIRECT_HOSTS,\n): boolean {\n let parsed: URL\n try {\n parsed = new URL(url)\n } catch {\n return false\n }\n return parsed.protocol === 'https:' && allowedHosts.includes(parsed.hostname)\n}\n\n/**\n * Thrown by `shop.checkout()` when base-api's Checkout URL fails\n * {@link isSafeCheckoutRedirectUrl} — the SDK never hands an unsafe URL back\n * to a caller that would otherwise `window.location.assign` it.\n */\nexport class UnsafeCheckoutRedirectError extends Error {\n constructor(\n public readonly url: string,\n public readonly allowedHosts: readonly string[],\n ) {\n super(\n `Refusing to return an unsafe Stripe Checkout redirect URL: ${JSON.stringify(url)} ` +\n `(allowed hosts: ${allowedHosts.join(', ')})`,\n )\n this.name = 'UnsafeCheckoutRedirectError'\n }\n}\n","/** A value that can be serialized into a query string. */\nexport type QueryValue = string | number | boolean | undefined | null\n\n/**\n * Append a query string to `base`, dropping `undefined`/`null` values.\n *\n * Single definition for the whole client (design doc §3.1 dedupes the two copies\n * that lived in demo-site's `client.ts` and `index.ts`). Falsy-but-meaningful\n * values (`0`, `false`, `''`) are preserved; only `undefined`/`null` are omitted.\n * Array values (REB-475 faceted `attr[key]`) emit one repeated param per element.\n */\nexport function buildUrl(\n base: string,\n params?: Record<string, QueryValue | QueryValue[]>,\n): string {\n if (!params) return base\n const pairs: [string, string][] = []\n for (const [key, value] of Object.entries(params)) {\n const values = Array.isArray(value) ? value : [value]\n for (const v of values) {\n if (v === undefined || v === null) continue\n pairs.push([key, String(v)])\n }\n }\n if (pairs.length === 0) return base\n const qs = new URLSearchParams(pairs).toString()\n return `${base}?${qs}`\n}\n","import type { AcademyCheckoutErrorCode } from '@reblu/site-contracts'\n\n/**\n * The `details` shape `/api/site/contact-form` and `/api/site/leads` attach on a\n * 400/422, straight from zod's `.flatten()` (see\n * `details: parsed.error.flatten()` in\n * `apps/base-api/src/lib/server/services/contact-form.service.ts:114` and\n * `apiError('Validation error', 422, parsed.error.flatten())` in\n * `apps/base-api/src/app/api/site/leads/route.ts:40`). `fieldErrors` is what a\n * SITE actually needs — REB-659's `details.fieldErrors.phone` — `formErrors` is\n * kept alongside it because `.flatten()` always emits both and dropping it would\n * silently discard form-level (non-field) messages.\n */\nexport interface ApiErrorDetails {\n fieldErrors?: Record<string, string[]>\n formErrors?: string[]\n /**\n * REB-836: the affected product ids on a shop checkout `STOCK_CHANGED` /\n * `PRICE_CHANGED` error — see `ApiErrorBody.code`.\n */\n productIds?: string[]\n /** REB-842: optional request-field identifier for checkout validation errors. */\n field?: string\n}\n\n/**\n * The parsed shape of a non-2xx SITE API response body: `{ error, details? }`\n * (see `apiError()` in `apps/base-api/src/lib/api/response.ts:33` — the general\n * shape both routes above build on).\n */\nexport interface ApiErrorBody {\n error: string\n details?: ApiErrorDetails\n /**\n * REB-836: stable, machine-readable error code (e.g. `ShopErrorCode` from\n * `@reblu/site-contracts`) the checkout/validate-cart/apply-discount write\n * endpoints may set — additive and OPTIONAL, so a consumer that only reads\n * `error`/`message` keeps working unchanged. `string` rather than\n * `ShopErrorCode` here because `ApiError` is a generic transport error used\n * by every `/api/site/*` domain, not just shop.\n */\n code?: string\n}\n\nfunction isStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((item) => typeof item === 'string')\n}\n\n/**\n * Validates `ApiErrorDetails` deeply — `fieldErrors` must be a record whose\n * values are `string[] | undefined`, `formErrors` (if present) must be\n * `string[]`, and `field` (if present) must be a string. Without this, a body like\n * `{ fieldErrors: { phone: \"abc\" } }` passed the old shallow check, so\n * `body.details?.fieldErrors?.phone?.[0]` silently returned the character\n * `\"a\"` instead of a validation message (REB-764 review finding).\n */\nfunction isApiErrorDetails(value: unknown): value is ApiErrorDetails {\n if (typeof value !== 'object' || value === null) return false\n const details = value as {\n fieldErrors?: unknown\n formErrors?: unknown\n productIds?: unknown\n field?: unknown\n }\n\n if (details.fieldErrors !== undefined) {\n if (\n typeof details.fieldErrors !== 'object' ||\n details.fieldErrors === null ||\n Array.isArray(details.fieldErrors)\n ) {\n return false\n }\n for (const fieldValue of Object.values(details.fieldErrors as Record<string, unknown>)) {\n if (fieldValue !== undefined && !isStringArray(fieldValue)) return false\n }\n }\n\n if (details.formErrors !== undefined && !isStringArray(details.formErrors)) return false\n\n // REB-836: STOCK_CHANGED/PRICE_CHANGED carry the affected product ids here.\n if (details.productIds !== undefined && !isStringArray(details.productIds)) return false\n if ('field' in details && typeof details.field !== 'string') return false\n\n return true\n}\n\n/**\n * `details`, when present, must match `ApiErrorDetails` deeply — an\n * unrecognised `details` shape makes the whole body unrecognised (`body`\n * stays `undefined`), the stricter reading consistent with\n * `parseApiErrorBody`'s existing all-or-nothing semantics: a body is either\n * shaped like `ApiErrorBody` or it isn't. Silently dropping a malformed\n * `details` instead would let a consumer treat a schema-drifted or corrupted\n * response as merely detail-less, hiding the mismatch rather than surfacing\n * it as \"unparseable\" (REB-764 review finding).\n */\nfunction isApiErrorBody(value: unknown): value is ApiErrorBody {\n if (typeof value !== 'object' || value === null) return false\n const body = value as { error?: unknown; details?: unknown; code?: unknown }\n if (typeof body.error !== 'string') return false\n if (body.details !== undefined && !isApiErrorDetails(body.details)) return false\n // REB-836: `code` must be a string when present — same all-or-nothing\n // reading as `details` above (an unrecognised shape makes the whole body\n // unrecognised, never silently dropped).\n if (body.code !== undefined && typeof body.code !== 'string') return false\n return true\n}\n\n/**\n * Parses `rawBody` (the exact text `message` is built from) into a typed\n * `ApiErrorBody`, or `undefined` when it is not JSON, or JSON that does not\n * match the expected shape — an edge proxy 502 returns HTML, and this must\n * never throw while constructing the error it is reporting.\n */\nfunction parseApiErrorBody(rawBody: string): ApiErrorBody | undefined {\n if (!rawBody) return undefined\n try {\n const parsed: unknown = JSON.parse(rawBody)\n return isApiErrorBody(parsed) ? parsed : undefined\n } catch {\n return undefined\n }\n}\n\n/**\n * Typed transport error for the Reblu SITE API.\n *\n * Every non-2xx response throws an `ApiError` (never a bare `fetch` rejection),\n * so consumers can `catch (e) { if (e instanceof ApiError) … }` and fall back\n * gracefully (e.g. render an empty section) — the one behavioural contract the\n * SITE relies on. Moved verbatim from demo-site's `client.ts` (design doc §3.4).\n */\nexport class ApiError extends Error {\n /**\n * The parsed response body, when it was JSON shaped like `{ error, details? }`\n * (REB-764). `undefined` when the body was not JSON or didn't match — parsing\n * is defensive and never throws. Read `body.details?.fieldErrors?.phone`\n * instead of regex-stripping this error's `message`.\n */\n public readonly body: ApiErrorBody | undefined\n\n constructor(\n public readonly status: number,\n public readonly path: string,\n message: string,\n ) {\n super(`API ${status} @ ${path}: ${message}`)\n this.name = 'ApiError'\n this.body = parseApiErrorBody(message)\n }\n}\n\n/** One page a slug could mean (REB-867) — `null` = the uncategorised page. */\nexport interface PageSlugCandidate {\n categorySlug: string | null\n}\n\nfunction parseCandidates(rawBody: string): PageSlugCandidate[] {\n try {\n const parsed = JSON.parse(rawBody) as { candidates?: unknown }\n if (!Array.isArray(parsed.candidates)) return []\n return parsed.candidates.flatMap((c: unknown) => {\n const slug = (c as { categorySlug?: unknown } | null)?.categorySlug\n return typeof slug === 'string' || slug === null ? [{ categorySlug: slug }] : []\n })\n } catch {\n return []\n }\n}\n\n/**\n * REB-867 — `GET /api/site/pages/[slug]` answered 409 `ambiguous_slug`: page\n * slugs are unique PER CATEGORY and this one exists in more than one, so the\n * server refused to pick. Retry with `pages.bySlug(slug, { category })` using\n * one of `candidates`. Extends `ApiError`, so an existing\n * `catch (e) { if (e instanceof ApiError) … }` keeps handling it.\n */\nexport class AmbiguousPageSlugError extends ApiError {\n public readonly candidates: PageSlugCandidate[]\n\n constructor(source: ApiError, rawBody: string) {\n super(source.status, source.path, rawBody)\n this.name = 'AmbiguousPageSlugError'\n this.candidates = parseCandidates(rawBody)\n }\n}\n\n/** The raw body an `ApiError` was built from (its message minus the prefix). */\nexport function apiErrorRawBody(error: ApiError): string {\n const prefix = `API ${error.status} @ ${error.path}: `\n return error.message.startsWith(prefix) ? error.message.slice(prefix.length) : error.message\n}\n\n/**\n * `details` of a guest course checkout refusal (REB-1264): `field` on\n * `INVALID_INPUT`, `reason` on `ENROLLMENT_REQUEST_REQUIRED`\n * (`VALIDATION_REQUIRED` | `FREE_COURSE`), and `fieldErrors` — one message\n * per field, a plain string — on the terms, consent and billing refusals.\n */\nexport interface AcademyCheckoutErrorDetails {\n field?: string\n reason?: string\n fieldErrors?: Record<string, string>\n /** `INVOICING_NOT_READY`: what the academy still lacks to invoice (e.g. `taxId`, `series`). */\n missing?: string[]\n}\n\n/** Keeps only the well-typed parts of `details` (strings, and string-valued `fieldErrors`). */\nfunction toAcademyCheckoutDetails(value: unknown): AcademyCheckoutErrorDetails | undefined {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return undefined\n const raw = value as {\n field?: unknown\n reason?: unknown\n fieldErrors?: unknown\n missing?: unknown\n }\n const details: AcademyCheckoutErrorDetails = {}\n if (typeof raw.field === 'string') details.field = raw.field\n if (typeof raw.reason === 'string') details.reason = raw.reason\n if (Array.isArray(raw.missing) && raw.missing.every((item) => typeof item === 'string')) {\n details.missing = raw.missing as string[]\n }\n if (\n typeof raw.fieldErrors === 'object' &&\n raw.fieldErrors !== null &&\n !Array.isArray(raw.fieldErrors)\n ) {\n const fieldErrors = Object.fromEntries(\n Object.entries(raw.fieldErrors).filter(\n (entry): entry is [string, string] => typeof entry[1] === 'string',\n ),\n )\n if (Object.keys(fieldErrors).length > 0) details.fieldErrors = fieldErrors\n }\n return Object.keys(details).length > 0 ? details : undefined\n}\n\nfunction parseAcademyCheckoutBody(rawBody: string): {\n error?: string\n code?: AcademyCheckoutErrorCode\n details?: AcademyCheckoutErrorDetails\n} {\n try {\n const parsed = JSON.parse(rawBody) as { error?: unknown; code?: unknown; details?: unknown }\n if (typeof parsed !== 'object' || parsed === null) return {}\n const details = toAcademyCheckoutDetails(parsed.details)\n return {\n ...(typeof parsed.error === 'string' ? { error: parsed.error } : {}),\n ...(isAcademyCheckoutErrorCode(parsed.code) ? { code: parsed.code } : {}),\n ...(details ? { details } : {}),\n }\n } catch {\n return {}\n }\n}\n\n// A `Record` so the compiler rejects a code base-api adds and this list lacks.\nconst ACADEMY_CHECKOUT_CODE_SET: Record<AcademyCheckoutErrorCode, true> = {\n INVALID_INPUT: true,\n RATE_LIMITED: true,\n COURSE_NOT_FOUND: true,\n SALES_CLOSED: true,\n ENROLLMENT_REQUEST_REQUIRED: true,\n INVOICING_NOT_READY: true,\n TERMS_NOT_ACCEPTED: true,\n TERMS_VERSION_CHANGED: true,\n CONSENT_REQUIRED: true,\n CONSENT_VERSION_CHANGED: true,\n INVALID_BILLING: true,\n}\n\nfunction isAcademyCheckoutErrorCode(value: unknown): value is AcademyCheckoutErrorCode {\n return typeof value === 'string' && Object.hasOwn(ACADEMY_CHECKOUT_CODE_SET, value)\n}\n\n/**\n * REB-1264 — `academy.checkout()` was refused. `code` is the stable\n * `AcademyCheckoutErrorCode` (`undefined` when the answer carried none, e.g.\n * an edge 502), `details` its extra data, `errorMessage` the server's text.\n * On `ENROLLMENT_REQUEST_REQUIRED` the course is not bought in one step:\n * show the enrollment request form instead. Extends `ApiError`, so an\n * existing `catch (e) { if (e instanceof ApiError) … }` keeps handling it.\n *\n * Its own parser because these refusals send `details.fieldErrors` as one\n * string per field, a shape `ApiError.body` (string arrays) does not accept.\n */\nexport class AcademyCheckoutError extends ApiError {\n public readonly code: AcademyCheckoutErrorCode | undefined\n public readonly details: AcademyCheckoutErrorDetails | undefined\n public readonly errorMessage: string | undefined\n\n constructor(source: ApiError) {\n const rawBody = apiErrorRawBody(source)\n super(source.status, source.path, rawBody)\n this.name = 'AcademyCheckoutError'\n const parsed = parseAcademyCheckoutBody(rawBody)\n this.code = parsed.code\n this.details = parsed.details\n this.errorMessage = parsed.error\n }\n}\n","import type { PaginationMeta } from '@reblu/site-contracts'\nimport type { RebluCacheAdapter, RebluCachePolicy } from './cache'\nimport { ApiError } from './errors'\nimport type { ListResult } from './types'\n\n/**\n * Resolved, immutable client context threaded into every domain namespace. Built\n * once by `createRebluClient` from the consumer's config — the SDK never reads\n * `process.env`, so the consumer owns `baseUrl`/`apiKey` (design doc §3.3).\n */\nexport interface RebluClientContext {\n baseUrl: string\n apiKey: string\n apiVersion: string\n cache: RebluCacheAdapter\n timeoutMs: number\n fetch: typeof fetch\n /**\n * Allowed hosts for the Stripe Checkout URL `shop.checkout()` returns\n * (REB-836, defence in depth). Defaults to\n * `DEFAULT_CHECKOUT_REDIRECT_HOSTS` (`['checkout.stripe.com']`) — tests\n * inject a different value via `RebluClientConfig.checkoutRedirectHosts`.\n */\n checkoutRedirectHosts: readonly string[]\n /**\n * REB-1214 — returns the memo store of the CURRENT server request, so\n * identical GET reads within it are fetched once (e.g. `generateMetadata`\n * and the page body). `@reblu/site-next` supplies one backed by React\n * `cache()` (`rebluRequestScope`). Absent, or returning `undefined`,\n * identical GETs still share one fetch while they are in flight.\n */\n requestScope?: RequestScope\n /** In-flight GETs when no request scope is active. Created by `createRebluClient`. */\n inFlight?: Map<string, Promise<unknown>>\n}\n\n/** Memo store of one server request, keyed by read identity (REB-1214). */\nexport type RequestScope = () => Map<string, Promise<unknown>> | undefined\n\n/** Upper bound for a `Retry-After` wait before the single retry (REB-1214). */\nexport const RETRY_AFTER_CAP_MS = 2000\n/** Wait before the single retry when `Retry-After` is absent or unparseable. */\nexport const DEFAULT_RETRY_DELAY_MS = 250\n/** Statuses that mean \"try again shortly\" for a read. */\nconst RETRYABLE_READ_STATUSES = new Set([429, 503])\n\n/** `Retry-After` (seconds or HTTP date) → ms, capped; default when unusable. */\nfunction retryDelayMs(header: string | null): number {\n if (header !== null && header.trim() !== '') {\n const value = header.trim()\n const seconds = /^\\d+$/.test(value) ? Number(value) * 1000 : NaN\n const ms = Number.isFinite(seconds) ? seconds : Date.parse(value) - Date.now()\n if (Number.isFinite(ms)) return Math.min(Math.max(0, ms), RETRY_AFTER_CAP_MS)\n }\n return DEFAULT_RETRY_DELAY_MS\n}\n\nconst sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms))\n\n/** Per-call request options. `cachePolicy` is interpreted by the cache adapter. */\nexport interface RequestOptions {\n method?: string\n body?: string\n headers?: Record<string, string>\n cachePolicy?: RebluCachePolicy\n}\n\n/**\n * Core transport (design doc §3.4). Injects the auth + contract-version headers,\n * merges the adapter's cache `RequestInit`, enforces a hard timeout per attempt,\n * throws a typed `ApiError` on non-2xx, and returns the parsed JSON body verbatim\n * (envelope unwrapping is the caller's concern).\n *\n * REB-1214 — GET reads only: identical reads are deduped (see\n * `RebluClientContext.requestScope`) and a 429/503 is retried once after\n * `Retry-After`, capped at {@link RETRY_AFTER_CAP_MS}. If the retry fails too,\n * the second response's `ApiError` is thrown — the same type as before.\n * Writes are never deduped nor retried.\n */\nexport async function request<T>(\n ctx: RebluClientContext,\n path: string,\n opts: RequestOptions,\n): Promise<T> {\n const method = opts.method ?? 'GET'\n const init: RequestInit = {\n method,\n body: opts.body,\n headers: {\n 'X-Reblu-Api-Key': ctx.apiKey,\n 'X-Reblu-Api-Version': ctx.apiVersion,\n 'Content-Type': 'application/json',\n ...opts.headers,\n },\n ...ctx.cache.resolveRequestInit(opts.cachePolicy ?? {}),\n }\n if (method !== 'GET' || opts.body !== undefined) {\n return attempt<T>(ctx, path, init)\n }\n\n // REB-1214 — a read is identified by everything that shapes its response:\n // URL, headers and the resolved cache init. Kept for the whole request when\n // a scope is active; otherwise only while in flight.\n const key = JSON.stringify([ctx.baseUrl + path, init])\n const scope = ctx.requestScope?.()\n const store = scope ?? ctx.inFlight\n const existing = store?.get(key)\n if (existing) return existing as Promise<T>\n\n const pending = readWithRetry<T>(ctx, path, init)\n if (store) {\n store.set(key, pending)\n // A failure is never memoised; without a scope nothing outlives the fetch.\n pending.then(\n () => {\n if (!scope) store.delete(key)\n },\n () => {\n if (store.get(key) === pending) store.delete(key)\n },\n )\n }\n return pending\n}\n\n/** A GET answered 429/503 is retried once after `Retry-After` (capped). */\nasync function readWithRetry<T>(\n ctx: RebluClientContext,\n path: string,\n init: RequestInit,\n): Promise<T> {\n try {\n return await attempt<T>(ctx, path, init)\n } catch (error) {\n if (!(error instanceof RetryableReadError)) throw error\n await sleep(retryDelayMs(error.retryAfter))\n try {\n return await attempt<T>(ctx, path, init)\n } catch (retryError) {\n throw retryError instanceof RetryableReadError ? retryError.apiError : retryError\n }\n }\n}\n\n/** Internal carrier: a retryable read status plus the `ApiError` to throw if it persists. */\nclass RetryableReadError extends Error {\n constructor(\n readonly apiError: ApiError,\n readonly retryAfter: string | null,\n ) {\n super(apiError.message)\n }\n}\n\n/**\n * One HTTP attempt: enforces the hard timeout via `AbortController`, throws a\n * typed `ApiError` on non-2xx, and returns the parsed JSON body verbatim\n * (envelope unwrapping is the caller's concern).\n */\nasync function attempt<T>(ctx: RebluClientContext, path: string, init: RequestInit): Promise<T> {\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), ctx.timeoutMs)\n\n try {\n const url = ctx.baseUrl + path\n const attemptInit = { ...init, signal: controller.signal }\n // REB-1245 — an adapter may own the call (e.g. to cache a 404 outcome).\n const res = ctx.cache.fetch\n ? await ctx.cache.fetch(url, attemptInit, ctx.fetch)\n : await ctx.fetch(url, attemptInit)\n\n if (!res.ok) {\n const error = new ApiError(res.status, path, await res.text())\n if (init.method === 'GET' && RETRYABLE_READ_STATUSES.has(res.status)) {\n throw new RetryableReadError(error, res.headers.get('retry-after'))\n }\n throw error\n }\n return (await res.json()) as T\n } finally {\n clearTimeout(timer)\n }\n}\n\n/** Fetch a single resource and unwrap the `{ data: T }` envelope. */\nexport async function requestOne<T>(\n ctx: RebluClientContext,\n path: string,\n opts: RequestOptions,\n): Promise<T> {\n const body = await request<{ data: T }>(ctx, path, opts)\n return body.data\n}\n\n/** Fetch a list resource, returning the `{ data, meta }` envelope (meta preserved). */\nexport async function requestList<T>(\n ctx: RebluClientContext,\n path: string,\n opts: RequestOptions,\n): Promise<ListResult<T>> {\n const body = await request<{ data: T[]; meta?: PaginationMeta }>(ctx, path, opts)\n return { data: body.data, meta: body.meta }\n}\n\n/**\n * Header carrying the visitor IP from a SITE server to base-api (REB-1005).\n * base-api keys its per-visitor limit on public writes on it, and trusts it\n * only behind a valid API key.\n */\nexport const VISITOR_IP_HEADER = 'x-reblu-visitor-ip'\n\n/**\n * Per-call options for a write (REB-1005).\n *\n * - `visitorIp` — the visitor's IP when a SITE calls the SDK server-side, sent\n * as `x-reblu-visitor-ip`. Do NOT rely on forwarding `x-forwarded-for`: on\n * Vercel the SITE→base-api hop gets it overwritten with the SITE function's\n * egress IP. Read it from the INCOMING request with\n * {@link visitorIpFromHeaders}.\n * - `headers` — other extra headers (e.g. `user-agent`). They can never set\n * `x-reblu-visitor-ip` nor replace `X-Reblu-Api-Key`, `X-Reblu-Api-Version`\n * or `Content-Type`.\n */\nexport interface RebluMutationOptions {\n visitorIp?: string\n headers?: Record<string, string>\n}\n\n/** The visitor IP of an incoming request: first `x-forwarded-for` entry, else `x-real-ip`. */\nexport function visitorIpFromHeaders(headers: {\n get(name: string): string | null\n}): string | undefined {\n return (\n headers.get('x-forwarded-for')?.split(',')[0]?.trim() ||\n headers.get('x-real-ip')?.trim() ||\n undefined\n )\n}\n\n/** Headers the SDK owns on every request — an extra header never sets or overrides these. */\nconst RESERVED_HEADERS = new Set([\n 'x-reblu-api-key',\n 'x-reblu-api-version',\n 'content-type',\n VISITOR_IP_HEADER,\n])\n\n/**\n * The extra headers a write sends for `options`: caller headers minus the\n * reserved ones, plus `x-reblu-visitor-ip` from `visitorIp`. Shared by\n * {@link requestMutation} and the writes that cannot use it (academy).\n */\nexport function extraHeaders(\n options: RebluMutationOptions | undefined,\n): Record<string, string> | undefined {\n if (!options?.headers && !options?.visitorIp) return undefined\n const safe: Record<string, string> = {}\n for (const [name, value] of Object.entries(options.headers ?? {})) {\n if (!RESERVED_HEADERS.has(name.toLowerCase())) safe[name] = value\n }\n const visitorIp = options.visitorIp?.trim()\n if (visitorIp) safe[VISITOR_IP_HEADER] = visitorIp\n return safe\n}\n\n/**\n * POST a mutation: serialize `body` to JSON, force a cache bypass (`revalidate:\n * false` → `no-store` regardless of the injected adapter, since a mutation must\n * never be served from cache), then unwrap the `{ data: T }` envelope. Mirrors\n * every `/api/site/*` write route, which answers with `apiOk`/`{ data }`.\n */\nexport async function requestMutation<Req, Res>(\n ctx: RebluClientContext,\n path: string,\n body: Req,\n options?: RebluMutationOptions,\n): Promise<Res> {\n const headers = extraHeaders(options)\n const envelope = await request<{ data: Res }>(ctx, path, {\n method: 'POST',\n body: JSON.stringify(body),\n cachePolicy: { revalidate: false },\n ...(headers ? { headers } : {}),\n })\n return envelope.data\n}\n","import type {\n AcademyCategoryPublic,\n AcademyCheckoutRequest,\n AcademyCheckoutSession,\n AcademyConfigPublic,\n AcademyCourseDetail,\n AcademyCourseSummary,\n AcademyInstructorPublic,\n AcademyLessonPreview,\n} from '@reblu/site-contracts'\nimport { buildUrl, type QueryValue } from '../build-url'\nimport type { RebluCachePolicy } from '../cache'\nimport { isSafeCheckoutRedirectUrl, UnsafeCheckoutRedirectError } from '../checkout-redirect'\nimport { AcademyCheckoutError, ApiError } from '../errors'\nimport {\n extraHeaders,\n request,\n requestList,\n requestMutation,\n requestOne,\n type RebluClientContext,\n type RebluMutationOptions,\n} from '../client'\nimport type {\n AcademyCourseListParams,\n AcademyEnrollmentRequestInput,\n AcademyEnrollmentRequestResult,\n ListResult,\n} from '../types'\n\n/**\n * Academy domain — the public course catalogue (`GET /api/site/academy/*`,\n * REB-528 / REB-534) plus the anonymous enrollment request (REB-496). The reads\n * are published-filtered server-side, so nothing here needs an entitlement\n * notion; the one write is unauthenticated by design.\n *\n * `enrollmentRequest` drops to the raw `request()` transport rather than\n * `requestMutation` (REB-584). The route answers with a BARE `{ message }`, not\n * the `{ data }` envelope every other write uses, so `requestMutation` would\n * unwrap a missing `.data` and hand the caller `undefined`. Normalizing the\n * route's envelope instead was rejected: its response shape is load-bearing\n * anti-enumeration behaviour, and changing it would ripple into base-api tests\n * for no gain on the SITE side.\n */\nexport function makeAcademyNamespace(ctx: RebluClientContext) {\n return {\n /** Published courses, filtered + paginated. `meta` carries the pagination. */\n courses: (\n params?: AcademyCourseListParams,\n cache?: RebluCachePolicy,\n ): Promise<ListResult<AcademyCourseSummary>> =>\n requestList<AcademyCourseSummary>(\n ctx,\n buildUrl('/api/site/academy/courses', params as Record<string, QueryValue> | undefined),\n { cachePolicy: cache },\n ),\n\n /** A single published course by slug, with its modules, lessons and CTA gate. */\n bySlug: (slug: string, cache?: RebluCachePolicy): Promise<AcademyCourseDetail> =>\n requestOne<AcademyCourseDetail>(ctx, `/api/site/academy/courses/${slug}`, {\n cachePolicy: cache,\n }),\n\n /**\n * A free-preview lesson, viewable by the public (REB-1135): title, duration,\n * Markdown body and a ready-to-embed video. Any lesson the public may not see\n * is a uniform 404 (`ApiError`). Never cached: the payload carries a\n * short-lived signed playback token. Server-side, prefer `getLessonPreview`\n * from `@reblu/site-next/academy-server`, which maps that 404 to `null`.\n */\n lessonPreview: (courseSlug: string, lessonSlug: string): Promise<AcademyLessonPreview> =>\n requestOne<AcademyLessonPreview>(\n ctx,\n `/api/site/academy/courses/${encodeURIComponent(courseSlug)}/lessons/${encodeURIComponent(lessonSlug)}`,\n { cachePolicy: { revalidate: false } },\n ),\n\n /**\n * Instructors of published courses, sorted by name, backing the catalog\n * instructor filter (REB-547). Pass an `id` as `courses({ instructor })`.\n * Not paginated.\n */\n instructors: (cache?: RebluCachePolicy): Promise<ListResult<AcademyInstructorPublic>> =>\n requestList<AcademyInstructorPublic>(ctx, '/api/site/academy/instructors', {\n cachePolicy: cache,\n }),\n\n /**\n * Tenant academy settings for the SITE (REB-1234). `catalogFilters` says\n * which catalogue filters the tenant switched on in nexus — pass it as\n * `enabled` to `CourseCatalogFilters` (`@reblu/site-blocks`). A filter the\n * tenant never set comes back `true`.\n */\n config: (cache?: RebluCachePolicy): Promise<AcademyConfigPublic> =>\n requestOne<AcademyConfigPublic>(ctx, '/api/site/academy/config', { cachePolicy: cache }),\n\n /** Active course categories backing the catalog filter. Not paginated. */\n categories: (cache?: RebluCachePolicy): Promise<ListResult<AcademyCategoryPublic>> =>\n requestList<AcademyCategoryPublic>(ctx, '/api/site/academy/categories', {\n cachePolicy: cache,\n }),\n\n /**\n * REB-1264 — guest checkout of a course that needs no validation: name,\n * email, the accepted terms version, the course's consent box version\n * and, optionally, full-invoice data. Returns the Stripe Checkout `url`\n * to send the buyer to; the account is created after payment. The\n * answer never depends on whether the email has an account.\n *\n * The `url` is re-checked like `shop.checkout()` (`https:` on\n * `ctx.checkoutRedirectHosts`, else {@link UnsafeCheckoutRedirectError}).\n * A refusal throws {@link AcademyCheckoutError} with its stable `code`;\n * on `ENROLLMENT_REQUEST_REQUIRED` use `enrollmentRequest` instead.\n *\n * Server-side, pass `options.visitorIp` (see `visitorIpFromHeaders`):\n * base-api keys this route's per-visitor limit on it.\n */\n checkout: async (\n body: AcademyCheckoutRequest,\n options?: RebluMutationOptions,\n ): Promise<AcademyCheckoutSession> => {\n let session: AcademyCheckoutSession\n try {\n session = await requestMutation<AcademyCheckoutRequest, AcademyCheckoutSession>(\n ctx,\n '/api/site/academy/checkout',\n body,\n options,\n )\n } catch (error) {\n throw error instanceof ApiError ? new AcademyCheckoutError(error) : error\n }\n if (!isSafeCheckoutRedirectUrl(session.url, ctx.checkoutRedirectHosts)) {\n throw new UnsafeCheckoutRedirectError(session.url, ctx.checkoutRedirectHosts)\n }\n return session\n },\n\n /**\n * Submit an anonymous enrollment request for a course.\n *\n * The reply is UNIFORM by design: the caller cannot tell whether the email\n * was new (a request row was created) or already known (a sign-in link was\n * sent and NO request row exists). Never present the returned message as\n * confirmation that a request was recorded — it is not one.\n *\n * Server-side, pass `options.visitorIp` (from the incoming request, see\n * `visitorIpFromHeaders`): base-api keys this route's per-IP limit on it\n * and, without it, throttles every visitor of the SITE in one shared\n * tenant-level bucket (REB-1006).\n */\n enrollmentRequest: (\n body: AcademyEnrollmentRequestInput,\n options?: RebluMutationOptions,\n ): Promise<AcademyEnrollmentRequestResult> => {\n const headers = extraHeaders(options)\n return request<AcademyEnrollmentRequestResult>(ctx, '/api/site/academy/enrollment-requests', {\n method: 'POST',\n body: JSON.stringify(body),\n cachePolicy: { revalidate: false },\n ...(headers ? { headers } : {}),\n })\n },\n }\n}\n","import type { Analytics } from '@reblu/site-contracts'\nimport type { RebluCachePolicy } from '../cache'\nimport { requestOne, type RebluClientContext } from '../client'\n\n/** `GET /api/site/analytics` — the tenant's analytics provider configuration. */\nexport function makeAnalyticsNamespace(ctx: RebluClientContext) {\n return {\n get: (cache?: RebluCachePolicy): Promise<Analytics> =>\n requestOne<Analytics>(ctx, '/api/site/analytics', { cachePolicy: cache }),\n }\n}\n","import type {\n BlogCategoryWithCount,\n BlogPostDetail,\n BlogPostListItem,\n BlogPostPreview,\n BlogSlug,\n} from '@reblu/site-contracts'\nimport { buildUrl, type QueryValue } from '../build-url'\nimport type { RebluCachePolicy } from '../cache'\nimport { requestList, requestOne, type RebluClientContext } from '../client'\nimport type { BlogListParams, ListResult } from '../types'\n\n/** Blog domain — `GET /api/site/blog/*`. */\nexport function makeBlogNamespace(ctx: RebluClientContext) {\n return {\n list: (\n params?: BlogListParams,\n cache?: RebluCachePolicy,\n ): Promise<ListResult<BlogPostListItem>> =>\n requestList<BlogPostListItem>(\n ctx,\n buildUrl('/api/site/blog', params as Record<string, QueryValue> | undefined),\n { cachePolicy: cache },\n ),\n\n bySlug: (slug: string, cache?: RebluCachePolicy): Promise<BlogPostDetail> =>\n requestOne<BlogPostDetail>(ctx, `/api/site/blog/${slug}`, { cachePolicy: cache }),\n\n related: (slug: string, cache?: RebluCachePolicy): Promise<ListResult<BlogPostListItem>> =>\n requestList<BlogPostListItem>(ctx, `/api/site/blog/${slug}/related`, { cachePolicy: cache }),\n\n categories: (cache?: RebluCachePolicy): Promise<ListResult<BlogCategoryWithCount>> =>\n requestList<BlogCategoryWithCount>(ctx, '/api/site/blog/categories', { cachePolicy: cache }),\n\n slugs: (cache?: RebluCachePolicy): Promise<ListResult<BlogSlug>> =>\n requestList<BlogSlug>(ctx, '/api/site/blog/slugs', { cachePolicy: cache }),\n\n /** Draft preview — never cached (unpublished content). */\n preview: (id: string): Promise<BlogPostPreview> =>\n requestOne<BlogPostPreview>(ctx, `/api/site/blog/preview/${id}`, {\n cachePolicy: { revalidate: false },\n }),\n }\n}\n","import type { Config } from '@reblu/site-contracts'\nimport type { RebluCachePolicy } from '../cache'\nimport { requestOne, type RebluClientContext } from '../client'\n\n/** `GET /api/site/config` — the tenant's public site configuration. */\nexport function makeConfigNamespace(ctx: RebluClientContext) {\n return {\n get: (cache?: RebluCachePolicy): Promise<Config> =>\n requestOne<Config>(ctx, '/api/site/config', { cachePolicy: cache }),\n }\n}\n","import type { ContactFormInput, ContactFormSubmission } from '@reblu/site-contracts'\nimport { requestMutation, type RebluClientContext, type RebluMutationOptions } from '../client'\n\n/**\n * Contact form domain — WRITE only (`POST /api/site/contact-form`, REB-438).\n * Posts a body typed by `@reblu/site-contracts` and bypasses the cache; returns\n * the created lead's id + creation timestamp.\n */\nexport function makeContactFormNamespace(ctx: RebluClientContext) {\n return {\n /**\n * Submit a contact form. Returns the created lead acknowledgement.\n * Server-side (a Server Action / Route Handler), pass `options.visitorIp`\n * (from the incoming request, see `visitorIpFromHeaders`) so base-api's\n * per-IP limit (REB-1005) applies to the real visitor.\n */\n submit: (\n body: ContactFormInput,\n options?: RebluMutationOptions,\n ): Promise<ContactFormSubmission> =>\n requestMutation<ContactFormInput, ContactFormSubmission>(\n ctx,\n '/api/site/contact-form',\n body,\n options,\n ),\n }\n}\n","import type { Cta } from '@reblu/site-contracts'\nimport { buildUrl } from '../build-url'\nimport type { RebluCachePolicy } from '../cache'\nimport { requestList, type RebluClientContext } from '../client'\nimport type { ListResult } from '../types'\n\n/**\n * `GET /api/site/ctas?slot=…` — editable CTAs for a given slot.\n *\n * `slot` is typed as `string` on purpose: the slot enum lives in base-api's\n * `validations/` (not the response contracts), so `@reblu/site-contracts` does\n * not export it. Keeping it a `string` avoids duplicating (and drifting from)\n * the server-owned enum. The server validates the slot.\n */\nexport function makeCtasNamespace(ctx: RebluClientContext) {\n return {\n list: (slot: string, cache?: RebluCachePolicy): Promise<ListResult<Cta>> =>\n requestList<Cta>(ctx, buildUrl('/api/site/ctas', { slot }), { cachePolicy: cache }),\n }\n}\n","import type { DataRequestInput, DataRequestSubmission } from '@reblu/site-contracts'\nimport { requestMutation, type RebluClientContext, type RebluMutationOptions } from '../client'\n\n/**\n * GDPR data-subject requests domain — WRITE only (`POST /api/site/data-requests`,\n * REB-77 endpoint / REB-980 SDK). Posts the public rights-form body typed by\n * `@reblu/site-contracts`, bypasses the cache, and returns the request id, when\n * it was received and the 72h deadline (`slaDueAt`) it is now held to.\n *\n * base-api requires a reCAPTCHA v3 token (`captcha`) and rate-limits per tenant\n * and per visitor IP (REB-1005); a rejection surfaces as an `ApiError` carrying\n * the upstream `status` (400 validation, 422 captcha, 429 rate limit).\n */\nexport function makeDataRequestsNamespace(ctx: RebluClientContext) {\n return {\n /**\n * Submit a data-subject request. Returns its acknowledgement with the 72h\n * deadline. Server-side, pass `options.visitorIp` (from the incoming\n * request, see `visitorIpFromHeaders`) so base-api's per-IP limit\n * (REB-1005) sees the real visitor.\n */\n submit: (\n body: DataRequestInput,\n options?: RebluMutationOptions,\n ): Promise<DataRequestSubmission> =>\n requestMutation<DataRequestInput, DataRequestSubmission>(\n ctx,\n '/api/site/data-requests',\n body,\n options,\n ),\n }\n}\n","import type { LeadInput, LeadSubmission } from '@reblu/site-contracts'\nimport { requestMutation, type RebluClientContext } from '../client'\n\n/**\n * Leads domain — WRITE only (`POST /api/site/leads`, REB-438). The generic\n * lead-ingestion endpoint: posts a body typed by `@reblu/site-contracts`,\n * bypasses the cache, and returns the created lead's id + creation timestamp.\n */\nexport function makeLeadsNamespace(ctx: RebluClientContext) {\n return {\n /** Create a lead. Returns the created lead acknowledgement. */\n create: (body: LeadInput): Promise<LeadSubmission> =>\n requestMutation<LeadInput, LeadSubmission>(ctx, '/api/site/leads', body),\n }\n}\n","import type { Legal } from '@reblu/site-contracts'\nimport type { RebluCachePolicy } from '../cache'\nimport { requestOne, type RebluClientContext } from '../client'\n\n/**\n * `GET /api/site/legal` — the tenant's legal content (privacy, terms, cookies,\n * and since REB-1201 `legalNotice` and `withdrawalPolicy`). Render each with\n * `LegalDocument` from `@reblu/site-blocks`.\n */\nexport function makeLegalNamespace(ctx: RebluClientContext) {\n return {\n get: (cache?: RebluCachePolicy): Promise<Legal> =>\n requestOne<Legal>(ctx, '/api/site/legal', { cachePolicy: cache }),\n }\n}\n","import type { PageDetail, PageListItem, PageSlug } from '@reblu/site-contracts'\nimport { buildUrl, type QueryValue } from '../build-url'\nimport type { RebluCachePolicy } from '../cache'\nimport { requestList, requestOne, type RebluClientContext } from '../client'\nimport { AmbiguousPageSlugError, ApiError, apiErrorRawBody } from '../errors'\nimport type { ListResult, PagesListParams } from '../types'\n\n/**\n * REB-867 — options for `pages.bySlug`. Page slugs are unique PER CATEGORY:\n * - `category: '<categorySlug>'` → the page with that slug in that category;\n * - `category: null` → the UNCATEGORISED page with that slug;\n * - omitted → the unique page with that slug; throws\n * `AmbiguousPageSlugError` when it exists in more than one category.\n */\nexport interface PageBySlugOptions {\n category?: string | null\n cache?: RebluCachePolicy\n}\n\n/**\n * The two second-argument shapes share no key: a `RebluCachePolicy` only ever\n * has `revalidate`, `PageBySlugOptions` only `category`/`cache`. So the\n * presence of `category` or `cache` (even `category: undefined`) selects the\n * options form; anything else — including `{}` — is a cache policy, exactly as\n * before REB-867.\n */\n/** REB-869 — options for `pages.featured`. */\nexport interface FeaturedPagesOptions {\n /**\n * How many pages at most: an integer >= 1. Default 3. Values above the\n * server's cap (3) are clamped to 3; anything else invalid (0, negative,\n * fractional, NaN) throws a `RangeError` before any request is made.\n */\n limit?: number\n /**\n * When the category has nothing featured, return its first `limit` pages in\n * normal order instead of `[]`. Default `true`, so a teaser is never empty\n * just because nobody picked pages yet.\n */\n fallback?: boolean\n cache?: RebluCachePolicy\n}\n\n/** The server caps a featured list at 3 (REB-869). */\nconst FEATURED_PAGES_MAX = 3\n\nfunction isBySlugOptions(value: RebluCachePolicy | PageBySlugOptions): value is PageBySlugOptions {\n return 'category' in value || 'cache' in value\n}\n\n/** Dynamic pages domain — `GET /api/site/pages/*`. */\nexport function makePagesNamespace(ctx: RebluClientContext) {\n const namespace = {\n list: (\n params?: PagesListParams,\n cache?: RebluCachePolicy,\n ): Promise<ListResult<PageListItem>> =>\n requestList<PageListItem>(\n ctx,\n // REB-63: `type` can now be a single PageType or an array (repeated\n // `?type=` params) — widen the cast alongside `PagesListParams`.\n buildUrl('/api/site/pages', params as Record<string, QueryValue | QueryValue[]> | undefined),\n { cachePolicy: cache },\n ),\n\n /**\n * A published page by slug. The second argument is either a cache policy\n * (backward-compatible) or {@link PageBySlugOptions} (REB-867).\n */\n bySlug: async (\n slug: string,\n cacheOrOptions?: RebluCachePolicy | PageBySlugOptions,\n ): Promise<PageDetail> => {\n const options: PageBySlugOptions =\n cacheOrOptions && isBySlugOptions(cacheOrOptions)\n ? cacheOrOptions\n : { cache: cacheOrOptions }\n const path =\n options.category === undefined\n ? `/api/site/pages/${slug}`\n : buildUrl(`/api/site/pages/${slug}`, { category: options.category ?? '' })\n try {\n return await requestOne<PageDetail>(ctx, path, { cachePolicy: options.cache })\n } catch (error) {\n if (error instanceof ApiError && error.status === 409 && error.body?.code === 'ambiguous_slug') {\n throw new AmbiguousPageSlugError(error, apiErrorRawBody(error))\n }\n throw error\n }\n },\n\n /**\n * REB-869 — the pages to show in a category teaser: the category's\n * featured pages in the tenant's order; when none are featured (and\n * `fallback` is not `false`), the first `limit` pages of the category in\n * normal order. Returns the items only (no pagination applies).\n */\n featured: async (\n category: string,\n { limit: requested = FEATURED_PAGES_MAX, fallback = true, cache }: FeaturedPagesOptions = {},\n ): Promise<PageListItem[]> => {\n if (!Number.isInteger(requested) || requested < 1) {\n throw new RangeError(`pages.featured: limit must be an integer >= 1 (got ${requested})`)\n }\n const limit = Math.min(requested, FEATURED_PAGES_MAX)\n const featured = await namespace.list({ category, featured: true }, cache)\n if (featured.data.length > 0 || !fallback) return featured.data.slice(0, limit)\n const first = await namespace.list({ category, pageSize: limit }, cache)\n return first.data.slice(0, limit)\n },\n\n slugs: (cache?: RebluCachePolicy): Promise<ListResult<PageSlug>> =>\n requestList<PageSlug>(ctx, '/api/site/pages/slugs', { cachePolicy: cache }),\n }\n return namespace\n}\n","import type {\n ShopApplyDiscountInput,\n ShopCartValidation,\n ShopCategory,\n ShopCheckEmailInput,\n ShopCheckoutInput,\n ShopCheckoutSession,\n ShopConfig,\n ShopDiscountApplication,\n ShopEmailCheck,\n ShopFacets,\n ShopOrder,\n ShopProductDetail,\n ShopProductListItem,\n ShopQuote,\n ShopQuoteInput,\n ShopValidateCartInput,\n} from '@reblu/site-contracts'\nimport { buildUrl, type QueryValue } from '../build-url'\nimport type { RebluCachePolicy } from '../cache'\nimport {\n requestList,\n requestMutation,\n requestOne,\n type RebluClientContext,\n type RebluMutationOptions,\n} from '../client'\nimport { isSafeCheckoutRedirectUrl, UnsafeCheckoutRedirectError } from '../checkout-redirect'\nimport type { ListResult, ShopProductsParams } from '../types'\n\n/**\n * Flatten `ShopProductsParams` into a query record: scalars pass through; the\n * `taxonomy`/`attr` maps become repeatable `taxonomy[key]`/`attr[key]` params.\n * Array attr values repeat the same bracket key.\n */\nfunction flattenShopParams(params?: ShopProductsParams): Record<string, QueryValue | QueryValue[]> {\n if (!params) return {}\n const { taxonomy, attr, ...scalars } = params\n const out: Record<string, QueryValue | QueryValue[]> = { ...scalars }\n if (taxonomy) {\n for (const [key, value] of Object.entries(taxonomy)) out[`taxonomy[${key}]`] = value\n }\n if (attr) {\n for (const [key, value] of Object.entries(attr)) out[`attr[${key}]`] = value\n }\n return out\n}\n\n/**\n * Shop domain — READ surface (`GET /api/site/shop/*`) plus the mutation flow\n * (`POST` checkout, validate-cart, apply-discount, and the deprecated\n * check-email) added in REB-438. Every write posts a body typed by `@reblu/site-contracts` and bypasses\n * the cache. Request/response DTOs live in the contracts package (zero Zod).\n */\nexport function makeShopNamespace(ctx: RebluClientContext) {\n return {\n products: (\n params?: ShopProductsParams,\n cache?: RebluCachePolicy,\n ): Promise<ListResult<ShopProductListItem>> =>\n requestList<ShopProductListItem>(\n ctx,\n buildUrl('/api/site/shop/products', flattenShopParams(params)),\n { cachePolicy: cache },\n ),\n\n /** Available facets (brand/attributes/price/on-sale) for the given filters. */\n facets: (params?: ShopProductsParams, cache?: RebluCachePolicy): Promise<ShopFacets> =>\n requestOne<ShopFacets>(ctx, buildUrl('/api/site/shop/facets', flattenShopParams(params)), {\n cachePolicy: cache,\n }),\n\n bySlug: (slug: string, cache?: RebluCachePolicy): Promise<ShopProductDetail> =>\n requestOne<ShopProductDetail>(ctx, `/api/site/shop/products/${slug}`, { cachePolicy: cache }),\n\n categories: (cache?: RebluCachePolicy): Promise<ListResult<ShopCategory>> =>\n requestList<ShopCategory>(ctx, '/api/site/shop/categories', { cachePolicy: cache }),\n\n config: (cache?: RebluCachePolicy): Promise<ShopConfig> =>\n requestOne<ShopConfig>(ctx, '/api/site/shop/config', { cachePolicy: cache }),\n\n // ── Mutations (REB-438) ──────────────────────────────────────────────────\n\n /**\n * Start a hosted checkout session. Returns the Stripe URL + session id.\n *\n * REB-836 (defence in depth): every SITE hands `url` straight to\n * `window.location.assign`, so this validates it is `https:` on an\n * allowed host (`ctx.checkoutRedirectHosts`, default\n * `checkout.stripe.com`) BEFORE returning — throwing\n * {@link UnsafeCheckoutRedirectError} instead of ever handing back a URL\n * that could redirect a buyer's browser off Stripe's domain.\n */\n checkout: async (body: ShopCheckoutInput): Promise<ShopCheckoutSession> => {\n const session = await requestMutation<ShopCheckoutInput, ShopCheckoutSession>(\n ctx,\n '/api/site/shop/checkout',\n body,\n )\n if (!isSafeCheckoutRedirectUrl(session.url, ctx.checkoutRedirectHosts)) {\n throw new UnsafeCheckoutRedirectError(session.url, ctx.checkoutRedirectHosts)\n }\n return session\n },\n\n /** Validate a cart against live product/stock/price state before checkout. */\n validateCart: (body: ShopValidateCartInput): Promise<ShopCartValidation> =>\n requestMutation<ShopValidateCartInput, ShopCartValidation>(\n ctx,\n '/api/site/shop/validate-cart',\n body,\n ),\n\n /**\n * REB-1197 — the pre-payment summary of the single-step checkout: lines,\n * discount, delivery, shipping, tax, weight, delivery term and the final\n * total, priced by the same function as `checkout` (the total shown is the\n * total Stripe charges). Holds nothing: no discount slot, no session.\n *\n * Server-side, pass `options.visitorIp` (see `visitorIpFromHeaders`): a\n * quote with a `discountCode` counts against the same per-visitor limit\n * as `applyDiscount`.\n */\n quote: (body: ShopQuoteInput, options?: RebluMutationOptions): Promise<ShopQuote> =>\n requestMutation<ShopQuoteInput, ShopQuote>(ctx, '/api/site/shop/quote', body, options),\n\n /** Validate a discount code against the current cart subtotal. */\n applyDiscount: (body: ShopApplyDiscountInput): Promise<ShopDiscountApplication> =>\n requestMutation<ShopApplyDiscountInput, ShopDiscountApplication>(\n ctx,\n '/api/site/shop/apply-discount',\n body,\n ),\n\n /**\n * @deprecated REB-834 — the endpoint no longer reveals whether an account\n * exists: it always resolves `{ exists: false }`. Do not call it and never\n * branch UI on `exists`; the checkout always offers «Iniciar sesión o\n * continuar como invitado» and the server links the order to an existing\n * account after payment. Removed in the next major.\n *\n * Server-side, pass `options.visitorIp` (from the incoming request, see\n * `visitorIpFromHeaders`): base-api keys this route's per-IP limit on it\n * and, without it, throttles every visitor of the SITE in one shared\n * tenant-level bucket (REB-1006).\n */\n checkEmail: (\n body: ShopCheckEmailInput,\n options?: RebluMutationOptions,\n ): Promise<ShopEmailCheck> =>\n requestMutation<ShopCheckEmailInput, ShopEmailCheck>(\n ctx,\n '/api/site/shop/check-email',\n body,\n options,\n ),\n\n /**\n * Look up a confirmed (or still-pending) order by its Stripe Checkout\n * session id — the order-confirmation flow (REB-829). `GET`, so it goes\n * through `requestOne` like every other read, not `requestMutation`.\n *\n * Never cached by default (REB-1245): an order belongs to one buyer, and\n * \"not found yet\" right after the Stripe redirect must not be remembered.\n * Pass an explicit `cache` policy only if you know you want one.\n */\n order: (sessionId: string, cache?: RebluCachePolicy): Promise<ShopOrder> =>\n requestOne<ShopOrder>(ctx, `/api/site/shop/order/${encodeURIComponent(sessionId)}`, {\n cachePolicy: cache ?? { revalidate: false },\n }),\n }\n}\n","import type { Review } from '@reblu/site-contracts'\nimport { buildUrl } from '../build-url'\nimport type { RebluCachePolicy } from '../cache'\nimport { requestList, type RebluClientContext } from '../client'\nimport type { ListResult } from '../types'\n\n/** Filters for {@link makeReviewsNamespace}'s `list` (REB-1272). */\nexport interface ReviewListParams {\n /** Only the reviews linked to this page (its `id`). Omit for every published review. */\n pageId?: string\n /** At most this many reviews (1..100). Omit for all of them. */\n limit?: number\n}\n\n/**\n * `GET /api/site/reviews` — the tenant's PUBLISHED reviews in the order set in\n * nexus, each with its source and the https link to the original. Publishing,\n * hiding or reordering in nexus needs no SITE change. Requires the tenant's\n * `reviews` module: without it the API answers 404 (an `ApiError`).\n */\nexport function makeReviewsNamespace(ctx: RebluClientContext) {\n function list(\n params: ReviewListParams = {},\n cache?: RebluCachePolicy,\n ): Promise<ListResult<Review>> {\n return requestList<Review>(\n ctx,\n buildUrl('/api/site/reviews', { pageId: params.pageId, limit: params.limit }),\n { cachePolicy: cache },\n )\n }\n return { list }\n}\n","import type { TrustBadge, TrustBadgeType } from '@reblu/site-contracts'\nimport { buildUrl } from '../build-url'\nimport type { RebluCachePolicy } from '../cache'\nimport { requestList, type RebluClientContext } from '../client'\nimport type { ListResult } from '../types'\n\n/** Filters for {@link makeTrustBadgesNamespace}'s `list` (REB-996). */\nexport interface TrustBadgeListParams {\n /** Only badges of this type (e.g. `'SUBVENTION'`). Omit for every type. */\n type?: TrustBadgeType\n}\n\nfunction isCachePolicy(value: TrustBadgeListParams | RebluCachePolicy): value is RebluCachePolicy {\n return Object.prototype.hasOwnProperty.call(value, 'revalidate')\n}\n\n/**\n * `GET /api/site/trust-badges` — the tenant's PUBLIC trust badges (no\n * pagination): active in nexus and inside their optional validity window, so\n * expiring or switching one off needs no SITE change.\n */\nexport function makeTrustBadgesNamespace(ctx: RebluClientContext) {\n function list(cache?: RebluCachePolicy): Promise<ListResult<TrustBadge>>\n function list(\n params: TrustBadgeListParams,\n cache?: RebluCachePolicy,\n ): Promise<ListResult<TrustBadge>>\n function list(\n paramsOrCache?: TrustBadgeListParams | RebluCachePolicy,\n cache?: RebluCachePolicy,\n ): Promise<ListResult<TrustBadge>> {\n // `list(cache)` is the pre-REB-996 call shape — kept so this stays a minor.\n const legacyCache = paramsOrCache && isCachePolicy(paramsOrCache) ? paramsOrCache : undefined\n const params = legacyCache ? undefined : (paramsOrCache as TrustBadgeListParams | undefined)\n return requestList<TrustBadge>(\n ctx,\n buildUrl('/api/site/trust-badges', { type: params?.type }),\n { cachePolicy: legacyCache ?? cache },\n )\n }\n return { list }\n}\n","import type { WithdrawalRequestInput, WithdrawalRequestSubmission } from '@reblu/site-contracts'\nimport { requestMutation, type RebluClientContext, type RebluMutationOptions } from '../client'\n\n/**\n * Withdrawal requests domain (REB-1201) — WRITE only\n * (`POST /api/site/withdrawal-requests`). A buyer communicates that they\n * withdraw from a purchase: base-api stores it, emails the tenant team and\n * acknowledges it to the buyer. Returns the request id, when it was received\n * and whether the buyer's acknowledgement email went out.\n *\n * base-api requires a reCAPTCHA v3 token (`captcha`) and rate-limits per tenant\n * and per visitor IP; a rejection surfaces as an `ApiError` carrying the\n * upstream `status` (400 validation, 422 captcha, 429 rate limit).\n */\nexport function makeWithdrawalRequestsNamespace(ctx: RebluClientContext) {\n return {\n /**\n * Submit a withdrawal communication. Server-side, pass `options.visitorIp`\n * (see `visitorIpFromHeaders`) so base-api's per-IP limit sees the visitor.\n */\n submit: (\n body: WithdrawalRequestInput,\n options?: RebluMutationOptions,\n ): Promise<WithdrawalRequestSubmission> =>\n requestMutation<WithdrawalRequestInput, WithdrawalRequestSubmission>(\n ctx,\n '/api/site/withdrawal-requests',\n body,\n options,\n ),\n }\n}\n","import type { ContactFormInput } from '@reblu/site-contracts'\n\n/**\n * REB-1077 — the `metadata` block of a contact-form submission, built from the\n * page the visitor is on so a SITE never hand-codes `fromPage`.\n *\n * Call it in the browser, at submit time (inside the form's submit handler):\n * it reads `globalThis.location` unless a `location` is passed.\n */\n\n/** The part of `window.location` these helpers read. */\nexport interface PageLocation {\n pathname: string\n href?: string\n}\n\nexport type ContactFormMetadata = ContactFormInput['metadata']\n\nexport interface ContactFormMetadataOptions {\n /**\n * Explicit page path, overriding the one read from the location (e.g. a\n * modal form that wants to report the landing it belongs to).\n */\n fromPage?: string\n /** @default { platform: 'organic' } */\n leadSource?: ContactFormMetadata['leadSource']\n /** @default globalThis.location */\n location?: PageLocation | null\n /** Submission time. @default new Date() */\n now?: Date\n}\n\nfunction browserLocation(): PageLocation | null {\n const location = (globalThis as { location?: PageLocation }).location\n return location && typeof location.pathname === 'string' ? location : null\n}\n\n/**\n * Path of the page the visitor is on — `location.pathname`, never with a query\n * string or hash. `'/'` when there is no location (server render).\n */\nexport function currentPagePath(location: PageLocation | null = browserLocation()): string {\n const path = location?.pathname.split(/[?#]/, 1)[0] ?? ''\n return path.startsWith('/') ? path : '/'\n}\n\n/**\n * The `metadata` for `contactForm.submit()` / `POST /api/site/contact-form`:\n * `fromPage` is the current page path, `currentUrl` the full href (base-api\n * reads the UTMs from it), `timestamp` the submission time.\n *\n * ```ts\n * body: JSON.stringify({ ...fields, captcha, metadata: buildContactFormMetadata() })\n * ```\n */\nexport function buildContactFormMetadata(\n options: ContactFormMetadataOptions = {},\n): ContactFormMetadata {\n const location = options.location === undefined ? browserLocation() : options.location\n return {\n fromPage: options.fromPage ?? currentPagePath(location),\n timestamp: (options.now ?? new Date()).toISOString(),\n currentUrl: location?.href ?? '',\n leadSource: options.leadSource ?? { platform: 'organic' },\n }\n}\n","/**\n * `@reblu/site-client` — framework-agnostic HTTP client for the Reblu SITE API\n * (REB-415 / design doc §3).\n *\n * The consumer owns the environment: `baseUrl`/`apiKey` are passed in, never read\n * from `process.env`. Caching is delegated to an injectable `RebluCacheAdapter`\n * (default `PlainCacheAdapter`), which severs any coupling to a specific\n * framework's cache model — `@reblu/site-next` supplies the ISR-aware adapter.\n * Response DTOs are typed by the peer, type-only `@reblu/site-contracts`.\n */\nimport { PlainCacheAdapter, type RebluCacheAdapter } from './cache'\nimport type { RebluClientContext, RequestScope } from './client'\nimport { DEFAULT_CHECKOUT_REDIRECT_HOSTS } from './checkout-redirect'\nimport { makeAcademyNamespace } from './domains/academy'\nimport { makeAnalyticsNamespace } from './domains/analytics'\nimport { makeBlogNamespace } from './domains/blog'\nimport { makeConfigNamespace } from './domains/config'\nimport { makeContactFormNamespace } from './domains/contact-form'\nimport { makeCtasNamespace } from './domains/ctas'\nimport { makeDataRequestsNamespace } from './domains/data-requests'\nimport { makeLeadsNamespace } from './domains/leads'\nimport { makeLegalNamespace } from './domains/legal'\nimport { makePagesNamespace } from './domains/pages'\nimport { makeShopNamespace } from './domains/shop'\nimport { makeReviewsNamespace } from './domains/reviews'\nimport { makeTrustBadgesNamespace } from './domains/trust-badges'\nimport { makeWithdrawalRequestsNamespace } from './domains/withdrawal-requests'\n\nconst DEFAULT_API_VERSION = '1'\n/** Parity with demo-site's upstream timeout (client.ts:36). */\nconst DEFAULT_TIMEOUT_MS = 8000\n\nexport interface RebluClientConfig {\n /** Absolute origin of the Reblu SITE API, e.g. `https://api.reblu.app`. */\n baseUrl: string\n /** Tenant SITE API key (`rbl_…`), sent as `X-Reblu-Api-Key`. */\n apiKey: string\n /** Contract version sent as `X-Reblu-Api-Version`. Defaults to `'1'`. */\n apiVersion?: string\n /** Cache strategy. Defaults to `new PlainCacheAdapter()` (no-store). */\n cache?: RebluCacheAdapter\n /** Per-request hard timeout in ms. Defaults to `8000`. */\n timeoutMs?: number\n /** Injectable `fetch` — for tests, custom runtimes, or instrumentation. */\n fetch?: typeof fetch\n /**\n * Allowed hosts for the Stripe Checkout URL `shop.checkout()` returns\n * (REB-836, defence in depth — see `./checkout-redirect`). Defaults to\n * `['checkout.stripe.com']`. Override only to point at a test double (e.g.\n * a Stripe CLI mock host) — never to widen production trust.\n */\n checkoutRedirectHosts?: string[]\n /**\n * REB-1214 — memo store of the current server request, so identical GET\n * reads within it are fetched once. In Next.js pass `rebluRequestScope`\n * from `@reblu/site-next` (React `cache()`); see the README. Without it,\n * identical GETs share one fetch only while in flight.\n */\n requestScope?: RequestScope\n}\n\n/**\n * Build a Reblu SITE API client. Each domain is a small namespace of bound\n * methods assembled from a separate module, so a consumer importing only a\n * couple of domains can tree-shake the rest.\n */\nexport function createRebluClient(config: RebluClientConfig) {\n // Wrap global fetch so calling `ctx.fetch()` never loses its `this` binding\n // (browsers throw \"Illegal invocation\" for an unbound `window.fetch`).\n const boundFetch: typeof fetch = config.fetch ?? ((input, init) => fetch(input, init))\n\n const ctx: RebluClientContext = {\n baseUrl: config.baseUrl,\n apiKey: config.apiKey,\n apiVersion: config.apiVersion ?? DEFAULT_API_VERSION,\n cache: config.cache ?? new PlainCacheAdapter(),\n timeoutMs: config.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n fetch: boundFetch,\n checkoutRedirectHosts: config.checkoutRedirectHosts ?? DEFAULT_CHECKOUT_REDIRECT_HOSTS,\n requestScope: config.requestScope,\n inFlight: new Map(),\n }\n\n return {\n config: makeConfigNamespace(ctx),\n analytics: makeAnalyticsNamespace(ctx),\n legal: makeLegalNamespace(ctx),\n trustBadges: makeTrustBadgesNamespace(ctx),\n ctas: makeCtasNamespace(ctx),\n blog: makeBlogNamespace(ctx),\n pages: makePagesNamespace(ctx),\n shop: makeShopNamespace(ctx),\n academy: makeAcademyNamespace(ctx),\n contactForm: makeContactFormNamespace(ctx),\n leads: makeLeadsNamespace(ctx),\n dataRequests: makeDataRequestsNamespace(ctx),\n withdrawalRequests: makeWithdrawalRequestsNamespace(ctx),\n reviews: makeReviewsNamespace(ctx),\n }\n}\n\n/** The assembled client returned by {@link createRebluClient}. */\nexport type RebluClient = ReturnType<typeof createRebluClient>\n\nexport { ApiError, AmbiguousPageSlugError, AcademyCheckoutError } from './errors'\nexport type {\n ApiErrorBody,\n ApiErrorDetails,\n PageSlugCandidate,\n AcademyCheckoutErrorDetails,\n} from './errors'\nexport type { PageBySlugOptions, FeaturedPagesOptions } from './domains/pages'\nexport type { TrustBadgeListParams } from './domains/trust-badges'\nexport type { ReviewListParams } from './domains/reviews'\nexport {\n DEFAULT_CHECKOUT_REDIRECT_HOSTS,\n isSafeCheckoutRedirectUrl,\n UnsafeCheckoutRedirectError,\n} from './checkout-redirect'\nexport { PlainCacheAdapter } from './cache'\nexport type { RebluCacheAdapter, RebluCachePolicy } from './cache'\nexport type {\n ListResult,\n BlogListParams,\n PagesListParams,\n ShopProductsParams,\n AcademyCourseListParams,\n AcademyEnrollmentRequestInput,\n AcademyEnrollmentRequestResult,\n} from './types'\nexport {\n VISITOR_IP_HEADER,\n visitorIpFromHeaders,\n RETRY_AFTER_CAP_MS,\n type RebluMutationOptions,\n type RequestScope,\n} from './client'\nexport {\n buildContactFormMetadata,\n currentPagePath,\n type ContactFormMetadata,\n type ContactFormMetadataOptions,\n type PageLocation,\n} from './contact-form-metadata'\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/cache.ts","../src/checkout-redirect.ts","../src/build-url.ts","../src/errors.ts","../src/client.ts","../src/domains/academy.ts","../src/domains/analytics.ts","../src/domains/blog.ts","../src/domains/config.ts","../src/domains/contact-form.ts","../src/domains/ctas.ts","../src/domains/data-requests.ts","../src/domains/leads.ts","../src/domains/legal.ts","../src/domains/pages.ts","../src/domains/shop.ts","../src/domains/reviews.ts","../src/domains/trust-badges.ts","../src/domains/withdrawal-requests.ts","../src/contact-form-metadata.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Cache adapter — the seam that severs the SDK from any framework's caching\n * model (design doc D4 / §3.2). The client owns *when* to cache (a per-call\n * `RebluCachePolicy`); the adapter owns *how* (the framework-specific\n * `RequestInit` fields). `@reblu/site-client` ships the framework-agnostic\n * `PlainCacheAdapter`; `@reblu/site-next` (REB-418) supplies a `NextCacheAdapter`\n * that returns `{ next: { revalidate } }`.\n */\n\nexport interface RebluCachePolicy {\n /**\n * Seconds to revalidate. `undefined` → use the adapter's default; `0`/`false`\n * → never cache (always fetch fresh).\n */\n revalidate?: number | false\n}\n\nexport interface RebluCacheAdapter {\n /** Map a per-call cache policy to framework-specific `fetch` `RequestInit` fields. */\n resolveRequestInit(policy: RebluCachePolicy): RequestInit\n /**\n * Optional (REB-1245): perform the HTTP call yourself. When present, the\n * client calls it for every attempt instead of `transport(url, init)`, with\n * `init` already carrying `resolveRequestInit`'s fields. An adapter uses it to\n * cache the read's OUTCOME (a 404 included), which a framework fetch cache\n * may refuse to store. Return a `Response` exactly as `fetch` would: the\n * client still maps non-2xx to `ApiError` and retries 429/503 reads.\n */\n fetch?(url: string, init: RequestInit, transport: typeof fetch): Promise<Response>\n}\n\n/**\n * Default adapter — plain `fetch` cache semantics that work in Node, edge,\n * browser and tests. No `next`, no `server-only`.\n */\nexport class PlainCacheAdapter implements RebluCacheAdapter {\n constructor(private readonly defaultRevalidate: number | false = false) {}\n\n resolveRequestInit(policy: RebluCachePolicy): RequestInit {\n const revalidate = policy.revalidate ?? this.defaultRevalidate\n return revalidate === false || revalidate === 0\n ? { cache: 'no-store' }\n : { cache: 'force-cache' }\n }\n}\n","/**\n * Stripe Checkout redirect-origin guard (REB-836, defence in depth).\n *\n * `shop.checkout()` returns a URL every SITE immediately hands to\n * `window.location.assign(url)`. Nothing upstream of this SDK validated it\n * before REB-836 — a compromised/misconfigured base-api, a tampered\n * response, or a MITM proxy could have sent a buyer's browser anywhere.\n * `isSafeCheckoutRedirectUrl` is the shared predicate `domains/shop.ts` uses\n * to refuse an unsafe URL before ever returning it to the caller; a SITE's\n * `@reblu/site-next` Route Handler re-checks the SAME property server-side\n * (`packages/site-next/src/shop/checkout-redirect.ts`, kept as an\n * independent copy — see that file's docblock for why it isn't a shared\n * import). Update both together if Stripe ever changes its Checkout host.\n */\n\n/** The only Checkout redirect host trusted by default. */\nexport const DEFAULT_CHECKOUT_REDIRECT_HOSTS: readonly string[] = ['checkout.stripe.com']\n\n/**\n * True only when `url` parses as an absolute URL, uses `https:`, and its\n * hostname is in `allowedHosts` (default: {@link DEFAULT_CHECKOUT_REDIRECT_HOSTS}).\n * Never throws — an unparsable `url` is simply unsafe.\n */\nexport function isSafeCheckoutRedirectUrl(\n url: string,\n allowedHosts: readonly string[] = DEFAULT_CHECKOUT_REDIRECT_HOSTS,\n): boolean {\n let parsed: URL\n try {\n parsed = new URL(url)\n } catch {\n return false\n }\n return parsed.protocol === 'https:' && allowedHosts.includes(parsed.hostname)\n}\n\n/**\n * Thrown by `shop.checkout()` when base-api's Checkout URL fails\n * {@link isSafeCheckoutRedirectUrl} — the SDK never hands an unsafe URL back\n * to a caller that would otherwise `window.location.assign` it.\n */\nexport class UnsafeCheckoutRedirectError extends Error {\n constructor(\n public readonly url: string,\n public readonly allowedHosts: readonly string[],\n ) {\n super(\n `Refusing to return an unsafe Stripe Checkout redirect URL: ${JSON.stringify(url)} ` +\n `(allowed hosts: ${allowedHosts.join(', ')})`,\n )\n this.name = 'UnsafeCheckoutRedirectError'\n }\n}\n","/** A value that can be serialized into a query string. */\nexport type QueryValue = string | number | boolean | undefined | null\n\n/**\n * Append a query string to `base`, dropping `undefined`/`null` values.\n *\n * Single definition for the whole client (design doc §3.1 dedupes the two copies\n * that lived in demo-site's `client.ts` and `index.ts`). Falsy-but-meaningful\n * values (`0`, `false`, `''`) are preserved; only `undefined`/`null` are omitted.\n * Array values (REB-475 faceted `attr[key]`) emit one repeated param per element.\n */\nexport function buildUrl(\n base: string,\n params?: Record<string, QueryValue | QueryValue[]>,\n): string {\n if (!params) return base\n const pairs: [string, string][] = []\n for (const [key, value] of Object.entries(params)) {\n const values = Array.isArray(value) ? value : [value]\n for (const v of values) {\n if (v === undefined || v === null) continue\n pairs.push([key, String(v)])\n }\n }\n if (pairs.length === 0) return base\n const qs = new URLSearchParams(pairs).toString()\n return `${base}?${qs}`\n}\n","import type { AcademyCheckoutErrorCode } from '@reblu/site-contracts'\n\n/**\n * The `details` shape `/api/site/contact-form` and `/api/site/leads` attach on a\n * 400/422, straight from zod's `.flatten()` (see\n * `details: parsed.error.flatten()` in\n * `apps/base-api/src/lib/server/services/contact-form.service.ts:114` and\n * `apiError('Validation error', 422, parsed.error.flatten())` in\n * `apps/base-api/src/app/api/site/leads/route.ts:40`). `fieldErrors` is what a\n * SITE actually needs — REB-659's `details.fieldErrors.phone` — `formErrors` is\n * kept alongside it because `.flatten()` always emits both and dropping it would\n * silently discard form-level (non-field) messages.\n */\nexport interface ApiErrorDetails {\n fieldErrors?: Record<string, string[]>\n formErrors?: string[]\n /**\n * REB-836: the affected product ids on a shop checkout `STOCK_CHANGED` /\n * `PRICE_CHANGED` error — see `ApiErrorBody.code`.\n */\n productIds?: string[]\n /** REB-842: optional request-field identifier for checkout validation errors. */\n field?: string\n}\n\n/**\n * The parsed shape of a non-2xx SITE API response body: `{ error, details? }`\n * (see `apiError()` in `apps/base-api/src/lib/api/response.ts:33` — the general\n * shape both routes above build on).\n */\nexport interface ApiErrorBody {\n error: string\n details?: ApiErrorDetails\n /**\n * REB-836: stable, machine-readable error code (e.g. `ShopErrorCode` from\n * `@reblu/site-contracts`) the checkout/validate-cart/apply-discount write\n * endpoints may set — additive and OPTIONAL, so a consumer that only reads\n * `error`/`message` keeps working unchanged. `string` rather than\n * `ShopErrorCode` here because `ApiError` is a generic transport error used\n * by every `/api/site/*` domain, not just shop.\n */\n code?: string\n}\n\nfunction isStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((item) => typeof item === 'string')\n}\n\n/**\n * Validates `ApiErrorDetails` deeply — `fieldErrors` must be a record whose\n * values are `string[] | undefined`, `formErrors` (if present) must be\n * `string[]`, and `field` (if present) must be a string. Without this, a body like\n * `{ fieldErrors: { phone: \"abc\" } }` passed the old shallow check, so\n * `body.details?.fieldErrors?.phone?.[0]` silently returned the character\n * `\"a\"` instead of a validation message (REB-764 review finding).\n */\nfunction isApiErrorDetails(value: unknown): value is ApiErrorDetails {\n if (typeof value !== 'object' || value === null) return false\n const details = value as {\n fieldErrors?: unknown\n formErrors?: unknown\n productIds?: unknown\n field?: unknown\n }\n\n if (details.fieldErrors !== undefined) {\n if (\n typeof details.fieldErrors !== 'object' ||\n details.fieldErrors === null ||\n Array.isArray(details.fieldErrors)\n ) {\n return false\n }\n for (const fieldValue of Object.values(details.fieldErrors as Record<string, unknown>)) {\n if (fieldValue !== undefined && !isStringArray(fieldValue)) return false\n }\n }\n\n if (details.formErrors !== undefined && !isStringArray(details.formErrors)) return false\n\n // REB-836: STOCK_CHANGED/PRICE_CHANGED carry the affected product ids here.\n if (details.productIds !== undefined && !isStringArray(details.productIds)) return false\n if ('field' in details && typeof details.field !== 'string') return false\n\n return true\n}\n\n/**\n * `details`, when present, must match `ApiErrorDetails` deeply — an\n * unrecognised `details` shape makes the whole body unrecognised (`body`\n * stays `undefined`), the stricter reading consistent with\n * `parseApiErrorBody`'s existing all-or-nothing semantics: a body is either\n * shaped like `ApiErrorBody` or it isn't. Silently dropping a malformed\n * `details` instead would let a consumer treat a schema-drifted or corrupted\n * response as merely detail-less, hiding the mismatch rather than surfacing\n * it as \"unparseable\" (REB-764 review finding).\n */\nfunction isApiErrorBody(value: unknown): value is ApiErrorBody {\n if (typeof value !== 'object' || value === null) return false\n const body = value as { error?: unknown; details?: unknown; code?: unknown }\n if (typeof body.error !== 'string') return false\n if (body.details !== undefined && !isApiErrorDetails(body.details)) return false\n // REB-836: `code` must be a string when present — same all-or-nothing\n // reading as `details` above (an unrecognised shape makes the whole body\n // unrecognised, never silently dropped).\n if (body.code !== undefined && typeof body.code !== 'string') return false\n return true\n}\n\n/**\n * Parses `rawBody` (the exact text `message` is built from) into a typed\n * `ApiErrorBody`, or `undefined` when it is not JSON, or JSON that does not\n * match the expected shape — an edge proxy 502 returns HTML, and this must\n * never throw while constructing the error it is reporting.\n */\nfunction parseApiErrorBody(rawBody: string): ApiErrorBody | undefined {\n if (!rawBody) return undefined\n try {\n const parsed: unknown = JSON.parse(rawBody)\n return isApiErrorBody(parsed) ? parsed : undefined\n } catch {\n return undefined\n }\n}\n\n/**\n * Typed transport error for the Reblu SITE API.\n *\n * Every non-2xx response throws an `ApiError` (never a bare `fetch` rejection),\n * so consumers can `catch (e) { if (e instanceof ApiError) … }` and fall back\n * gracefully (e.g. render an empty section) — the one behavioural contract the\n * SITE relies on. Moved verbatim from demo-site's `client.ts` (design doc §3.4).\n */\nexport class ApiError extends Error {\n /**\n * The parsed response body, when it was JSON shaped like `{ error, details? }`\n * (REB-764). `undefined` when the body was not JSON or didn't match — parsing\n * is defensive and never throws. Read `body.details?.fieldErrors?.phone`\n * instead of regex-stripping this error's `message`.\n */\n public readonly body: ApiErrorBody | undefined\n\n constructor(\n public readonly status: number,\n public readonly path: string,\n message: string,\n ) {\n super(`API ${status} @ ${path}: ${message}`)\n this.name = 'ApiError'\n this.body = parseApiErrorBody(message)\n }\n}\n\n/** One page a slug could mean (REB-867) — `null` = the uncategorised page. */\nexport interface PageSlugCandidate {\n categorySlug: string | null\n}\n\nfunction parseCandidates(rawBody: string): PageSlugCandidate[] {\n try {\n const parsed = JSON.parse(rawBody) as { candidates?: unknown }\n if (!Array.isArray(parsed.candidates)) return []\n return parsed.candidates.flatMap((c: unknown) => {\n const slug = (c as { categorySlug?: unknown } | null)?.categorySlug\n return typeof slug === 'string' || slug === null ? [{ categorySlug: slug }] : []\n })\n } catch {\n return []\n }\n}\n\n/**\n * REB-867 — `GET /api/site/pages/[slug]` answered 409 `ambiguous_slug`: page\n * slugs are unique PER CATEGORY and this one exists in more than one, so the\n * server refused to pick. Retry with `pages.bySlug(slug, { category })` using\n * one of `candidates`. Extends `ApiError`, so an existing\n * `catch (e) { if (e instanceof ApiError) … }` keeps handling it.\n */\nexport class AmbiguousPageSlugError extends ApiError {\n public readonly candidates: PageSlugCandidate[]\n\n constructor(source: ApiError, rawBody: string) {\n super(source.status, source.path, rawBody)\n this.name = 'AmbiguousPageSlugError'\n this.candidates = parseCandidates(rawBody)\n }\n}\n\n/** The raw body an `ApiError` was built from (its message minus the prefix). */\nexport function apiErrorRawBody(error: ApiError): string {\n const prefix = `API ${error.status} @ ${error.path}: `\n return error.message.startsWith(prefix) ? error.message.slice(prefix.length) : error.message\n}\n\n/**\n * `details` of a guest course checkout refusal (REB-1264): `field` on\n * `INVALID_INPUT`, `reason` on `ENROLLMENT_REQUEST_REQUIRED`\n * (`VALIDATION_REQUIRED` | `FREE_COURSE`), and `fieldErrors` — one message\n * per field, a plain string — on the terms, consent and billing refusals.\n */\nexport interface AcademyCheckoutErrorDetails {\n field?: string\n reason?: string\n fieldErrors?: Record<string, string>\n /** `INVOICING_NOT_READY`: what the academy still lacks to invoice (e.g. `taxId`, `series`). */\n missing?: string[]\n}\n\n/** Keeps only the well-typed parts of `details` (strings, and string-valued `fieldErrors`). */\nfunction toAcademyCheckoutDetails(value: unknown): AcademyCheckoutErrorDetails | undefined {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return undefined\n const raw = value as {\n field?: unknown\n reason?: unknown\n fieldErrors?: unknown\n missing?: unknown\n }\n const details: AcademyCheckoutErrorDetails = {}\n if (typeof raw.field === 'string') details.field = raw.field\n if (typeof raw.reason === 'string') details.reason = raw.reason\n if (Array.isArray(raw.missing) && raw.missing.every((item) => typeof item === 'string')) {\n details.missing = raw.missing as string[]\n }\n if (\n typeof raw.fieldErrors === 'object' &&\n raw.fieldErrors !== null &&\n !Array.isArray(raw.fieldErrors)\n ) {\n const fieldErrors = Object.fromEntries(\n Object.entries(raw.fieldErrors).filter(\n (entry): entry is [string, string] => typeof entry[1] === 'string',\n ),\n )\n if (Object.keys(fieldErrors).length > 0) details.fieldErrors = fieldErrors\n }\n return Object.keys(details).length > 0 ? details : undefined\n}\n\nfunction parseAcademyCheckoutBody(rawBody: string): {\n error?: string\n code?: AcademyCheckoutErrorCode\n details?: AcademyCheckoutErrorDetails\n} {\n try {\n const parsed = JSON.parse(rawBody) as { error?: unknown; code?: unknown; details?: unknown }\n if (typeof parsed !== 'object' || parsed === null) return {}\n const details = toAcademyCheckoutDetails(parsed.details)\n return {\n ...(typeof parsed.error === 'string' ? { error: parsed.error } : {}),\n ...(isAcademyCheckoutErrorCode(parsed.code) ? { code: parsed.code } : {}),\n ...(details ? { details } : {}),\n }\n } catch {\n return {}\n }\n}\n\n// A `Record` so the compiler rejects a code base-api adds and this list lacks.\nconst ACADEMY_CHECKOUT_CODE_SET: Record<AcademyCheckoutErrorCode, true> = {\n INVALID_INPUT: true,\n RATE_LIMITED: true,\n COURSE_NOT_FOUND: true,\n SALES_CLOSED: true,\n ENROLLMENT_REQUEST_REQUIRED: true,\n INVOICING_NOT_READY: true,\n TERMS_NOT_ACCEPTED: true,\n TERMS_VERSION_CHANGED: true,\n CONSENT_REQUIRED: true,\n CONSENT_VERSION_CHANGED: true,\n INVALID_BILLING: true,\n}\n\nfunction isAcademyCheckoutErrorCode(value: unknown): value is AcademyCheckoutErrorCode {\n return typeof value === 'string' && Object.hasOwn(ACADEMY_CHECKOUT_CODE_SET, value)\n}\n\n/**\n * REB-1264 — `academy.checkout()` was refused. `code` is the stable\n * `AcademyCheckoutErrorCode` (`undefined` when the answer carried none, e.g.\n * an edge 502), `details` its extra data, `errorMessage` the server's text.\n * On `ENROLLMENT_REQUEST_REQUIRED` the course is not bought in one step:\n * show the enrollment request form instead. Extends `ApiError`, so an\n * existing `catch (e) { if (e instanceof ApiError) … }` keeps handling it.\n *\n * Its own parser because these refusals send `details.fieldErrors` as one\n * string per field, a shape `ApiError.body` (string arrays) does not accept.\n */\nexport class AcademyCheckoutError extends ApiError {\n public readonly code: AcademyCheckoutErrorCode | undefined\n public readonly details: AcademyCheckoutErrorDetails | undefined\n public readonly errorMessage: string | undefined\n\n constructor(source: ApiError) {\n const rawBody = apiErrorRawBody(source)\n super(source.status, source.path, rawBody)\n this.name = 'AcademyCheckoutError'\n const parsed = parseAcademyCheckoutBody(rawBody)\n this.code = parsed.code\n this.details = parsed.details\n this.errorMessage = parsed.error\n }\n}\n","import type { PaginationMeta } from '@reblu/site-contracts'\nimport type { RebluCacheAdapter, RebluCachePolicy } from './cache'\nimport { ApiError } from './errors'\nimport type { ListResult } from './types'\n\n/**\n * Resolved, immutable client context threaded into every domain namespace. Built\n * once by `createRebluClient` from the consumer's config — the SDK never reads\n * `process.env`, so the consumer owns `baseUrl`/`apiKey` (design doc §3.3).\n */\nexport interface RebluClientContext {\n baseUrl: string\n apiKey: string\n apiVersion: string\n cache: RebluCacheAdapter\n timeoutMs: number\n fetch: typeof fetch\n /**\n * Allowed hosts for the Stripe Checkout URL `shop.checkout()` returns\n * (REB-836, defence in depth). Defaults to\n * `DEFAULT_CHECKOUT_REDIRECT_HOSTS` (`['checkout.stripe.com']`) — tests\n * inject a different value via `RebluClientConfig.checkoutRedirectHosts`.\n */\n checkoutRedirectHosts: readonly string[]\n /**\n * REB-1214 — returns the memo store of the CURRENT server request, so\n * identical GET reads within it are fetched once (e.g. `generateMetadata`\n * and the page body). `@reblu/site-next` supplies one backed by React\n * `cache()` (`rebluRequestScope`). Absent, or returning `undefined`,\n * identical GETs still share one fetch while they are in flight.\n */\n requestScope?: RequestScope\n /** In-flight GETs when no request scope is active. Created by `createRebluClient`. */\n inFlight?: Map<string, Promise<unknown>>\n}\n\n/** Memo store of one server request, keyed by read identity (REB-1214). */\nexport type RequestScope = () => Map<string, Promise<unknown>> | undefined\n\n/** Upper bound for a `Retry-After` wait before the single retry (REB-1214). */\nexport const RETRY_AFTER_CAP_MS = 2000\n/** Wait before the single retry when `Retry-After` is absent or unparseable. */\nexport const DEFAULT_RETRY_DELAY_MS = 250\n/** Statuses that mean \"try again shortly\" for a read. */\nconst RETRYABLE_READ_STATUSES = new Set([429, 503])\n\n/** `Retry-After` (seconds or HTTP date) → ms, capped; default when unusable. */\nfunction retryDelayMs(header: string | null): number {\n if (header !== null && header.trim() !== '') {\n const value = header.trim()\n const seconds = /^\\d+$/.test(value) ? Number(value) * 1000 : NaN\n const ms = Number.isFinite(seconds) ? seconds : Date.parse(value) - Date.now()\n if (Number.isFinite(ms)) return Math.min(Math.max(0, ms), RETRY_AFTER_CAP_MS)\n }\n return DEFAULT_RETRY_DELAY_MS\n}\n\nconst sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms))\n\n/** Per-call request options. `cachePolicy` is interpreted by the cache adapter. */\nexport interface RequestOptions {\n method?: string\n body?: string\n headers?: Record<string, string>\n cachePolicy?: RebluCachePolicy\n}\n\n/**\n * Core transport (design doc §3.4). Injects the auth + contract-version headers,\n * merges the adapter's cache `RequestInit`, enforces a hard timeout per attempt,\n * throws a typed `ApiError` on non-2xx, and returns the parsed JSON body verbatim\n * (envelope unwrapping is the caller's concern).\n *\n * REB-1214 — GET reads only: identical reads are deduped (see\n * `RebluClientContext.requestScope`) and a 429/503 is retried once after\n * `Retry-After`, capped at {@link RETRY_AFTER_CAP_MS}. If the retry fails too,\n * the second response's `ApiError` is thrown — the same type as before.\n * Writes are never deduped nor retried.\n */\nexport async function request<T>(\n ctx: RebluClientContext,\n path: string,\n opts: RequestOptions,\n): Promise<T> {\n const method = opts.method ?? 'GET'\n const init: RequestInit = {\n method,\n body: opts.body,\n headers: {\n 'X-Reblu-Api-Key': ctx.apiKey,\n 'X-Reblu-Api-Version': ctx.apiVersion,\n 'Content-Type': 'application/json',\n ...opts.headers,\n },\n ...ctx.cache.resolveRequestInit(opts.cachePolicy ?? {}),\n }\n if (method !== 'GET' || opts.body !== undefined) {\n return attempt<T>(ctx, path, init)\n }\n\n // REB-1214 — a read is identified by everything that shapes its response:\n // URL, headers and the resolved cache init. Kept for the whole request when\n // a scope is active; otherwise only while in flight.\n const key = JSON.stringify([ctx.baseUrl + path, init])\n const scope = ctx.requestScope?.()\n const store = scope ?? ctx.inFlight\n const existing = store?.get(key)\n if (existing) return existing as Promise<T>\n\n const pending = readWithRetry<T>(ctx, path, init)\n if (store) {\n store.set(key, pending)\n // A failure is never memoised; without a scope nothing outlives the fetch.\n pending.then(\n () => {\n if (!scope) store.delete(key)\n },\n () => {\n if (store.get(key) === pending) store.delete(key)\n },\n )\n }\n return pending\n}\n\n/** A GET answered 429/503 is retried once after `Retry-After` (capped). */\nasync function readWithRetry<T>(\n ctx: RebluClientContext,\n path: string,\n init: RequestInit,\n): Promise<T> {\n try {\n return await attempt<T>(ctx, path, init)\n } catch (error) {\n if (!(error instanceof RetryableReadError)) throw error\n await sleep(retryDelayMs(error.retryAfter))\n try {\n return await attempt<T>(ctx, path, init)\n } catch (retryError) {\n throw retryError instanceof RetryableReadError ? retryError.apiError : retryError\n }\n }\n}\n\n/** Internal carrier: a retryable read status plus the `ApiError` to throw if it persists. */\nclass RetryableReadError extends Error {\n constructor(\n readonly apiError: ApiError,\n readonly retryAfter: string | null,\n ) {\n super(apiError.message)\n }\n}\n\n/**\n * One HTTP attempt: enforces the hard timeout via `AbortController`, throws a\n * typed `ApiError` on non-2xx, and returns the parsed JSON body verbatim\n * (envelope unwrapping is the caller's concern).\n */\nasync function attempt<T>(ctx: RebluClientContext, path: string, init: RequestInit): Promise<T> {\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), ctx.timeoutMs)\n\n try {\n const url = ctx.baseUrl + path\n const attemptInit = { ...init, signal: controller.signal }\n // REB-1245 — an adapter may own the call (e.g. to cache a 404 outcome).\n const res = ctx.cache.fetch\n ? await ctx.cache.fetch(url, attemptInit, ctx.fetch)\n : await ctx.fetch(url, attemptInit)\n\n if (!res.ok) {\n const error = new ApiError(res.status, path, await res.text())\n if (init.method === 'GET' && RETRYABLE_READ_STATUSES.has(res.status)) {\n throw new RetryableReadError(error, res.headers.get('retry-after'))\n }\n throw error\n }\n return (await res.json()) as T\n } finally {\n clearTimeout(timer)\n }\n}\n\n/** Fetch a single resource and unwrap the `{ data: T }` envelope. */\nexport async function requestOne<T>(\n ctx: RebluClientContext,\n path: string,\n opts: RequestOptions,\n): Promise<T> {\n const body = await request<{ data: T }>(ctx, path, opts)\n return body.data\n}\n\n/** Fetch a list resource, returning the `{ data, meta }` envelope (meta preserved). */\nexport async function requestList<T>(\n ctx: RebluClientContext,\n path: string,\n opts: RequestOptions,\n): Promise<ListResult<T>> {\n const body = await request<{ data: T[]; meta?: PaginationMeta }>(ctx, path, opts)\n return { data: body.data, meta: body.meta }\n}\n\n/**\n * Header carrying the visitor IP from a SITE server to base-api (REB-1005).\n * base-api keys its per-visitor limit on public writes on it, and trusts it\n * only behind a valid API key.\n */\nexport const VISITOR_IP_HEADER = 'x-reblu-visitor-ip'\n\n/**\n * Per-call options for a write (REB-1005).\n *\n * - `visitorIp` — the visitor's IP when a SITE calls the SDK server-side, sent\n * as `x-reblu-visitor-ip`. Do NOT rely on forwarding `x-forwarded-for`: on\n * Vercel the SITE→base-api hop gets it overwritten with the SITE function's\n * egress IP. Read it from the INCOMING request with\n * {@link visitorIpFromHeaders}.\n * - `headers` — other extra headers (e.g. `user-agent`). They can never set\n * `x-reblu-visitor-ip` nor replace `X-Reblu-Api-Key`, `X-Reblu-Api-Version`\n * or `Content-Type`.\n */\nexport interface RebluMutationOptions {\n visitorIp?: string\n headers?: Record<string, string>\n}\n\n/** The visitor IP of an incoming request: first `x-forwarded-for` entry, else `x-real-ip`. */\nexport function visitorIpFromHeaders(headers: {\n get(name: string): string | null\n}): string | undefined {\n return (\n headers.get('x-forwarded-for')?.split(',')[0]?.trim() ||\n headers.get('x-real-ip')?.trim() ||\n undefined\n )\n}\n\n/** Headers the SDK owns on every request — an extra header never sets or overrides these. */\nconst RESERVED_HEADERS = new Set([\n 'x-reblu-api-key',\n 'x-reblu-api-version',\n 'content-type',\n VISITOR_IP_HEADER,\n])\n\n/**\n * The extra headers a write sends for `options`: caller headers minus the\n * reserved ones, plus `x-reblu-visitor-ip` from `visitorIp`. Shared by\n * {@link requestMutation} and the writes that cannot use it (academy).\n */\nexport function extraHeaders(\n options: RebluMutationOptions | undefined,\n): Record<string, string> | undefined {\n if (!options?.headers && !options?.visitorIp) return undefined\n const safe: Record<string, string> = {}\n for (const [name, value] of Object.entries(options.headers ?? {})) {\n if (!RESERVED_HEADERS.has(name.toLowerCase())) safe[name] = value\n }\n const visitorIp = options.visitorIp?.trim()\n if (visitorIp) safe[VISITOR_IP_HEADER] = visitorIp\n return safe\n}\n\n/**\n * POST a mutation: serialize `body` to JSON, force a cache bypass (`revalidate:\n * false` → `no-store` regardless of the injected adapter, since a mutation must\n * never be served from cache), then unwrap the `{ data: T }` envelope. Mirrors\n * every `/api/site/*` write route, which answers with `apiOk`/`{ data }`.\n */\nexport async function requestMutation<Req, Res>(\n ctx: RebluClientContext,\n path: string,\n body: Req,\n options?: RebluMutationOptions,\n): Promise<Res> {\n const headers = extraHeaders(options)\n const envelope = await request<{ data: Res }>(ctx, path, {\n method: 'POST',\n body: JSON.stringify(body),\n cachePolicy: { revalidate: false },\n ...(headers ? { headers } : {}),\n })\n return envelope.data\n}\n","import type {\n AcademyCategoryPublic,\n AcademyCheckoutRequest,\n AcademyCheckoutSession,\n AcademyConfigPublic,\n AcademyCourseDetail,\n AcademyCourseSummary,\n AcademyInstructorPublic,\n AcademyLessonPreview,\n} from '@reblu/site-contracts'\nimport { buildUrl, type QueryValue } from '../build-url'\nimport type { RebluCachePolicy } from '../cache'\nimport { isSafeCheckoutRedirectUrl, UnsafeCheckoutRedirectError } from '../checkout-redirect'\nimport { AcademyCheckoutError, ApiError } from '../errors'\nimport {\n extraHeaders,\n request,\n requestList,\n requestMutation,\n requestOne,\n type RebluClientContext,\n type RebluMutationOptions,\n} from '../client'\nimport type {\n AcademyCourseListParams,\n AcademyEnrollmentRequestInput,\n AcademyEnrollmentRequestResult,\n ListResult,\n} from '../types'\n\n/**\n * Academy domain — the public course catalogue (`GET /api/site/academy/*`,\n * REB-528 / REB-534) plus the anonymous enrollment request (REB-496). The reads\n * are published-filtered server-side, so nothing here needs an entitlement\n * notion; the one write is unauthenticated by design.\n *\n * `enrollmentRequest` drops to the raw `request()` transport rather than\n * `requestMutation` (REB-584). The route answers with a BARE `{ message }`, not\n * the `{ data }` envelope every other write uses, so `requestMutation` would\n * unwrap a missing `.data` and hand the caller `undefined`. Normalizing the\n * route's envelope instead was rejected: its response shape is load-bearing\n * anti-enumeration behaviour, and changing it would ripple into base-api tests\n * for no gain on the SITE side.\n */\nexport function makeAcademyNamespace(ctx: RebluClientContext) {\n return {\n /** Published courses, filtered + paginated. `meta` carries the pagination. */\n courses: (\n params?: AcademyCourseListParams,\n cache?: RebluCachePolicy,\n ): Promise<ListResult<AcademyCourseSummary>> =>\n requestList<AcademyCourseSummary>(\n ctx,\n buildUrl('/api/site/academy/courses', params as Record<string, QueryValue> | undefined),\n { cachePolicy: cache },\n ),\n\n /** A single published course by slug, with its modules, lessons and CTA gate. */\n bySlug: (slug: string, cache?: RebluCachePolicy): Promise<AcademyCourseDetail> =>\n requestOne<AcademyCourseDetail>(ctx, `/api/site/academy/courses/${slug}`, {\n cachePolicy: cache,\n }),\n\n /**\n * A free-preview lesson, viewable by the public (REB-1135): title, duration,\n * Markdown body and a ready-to-embed video. Any lesson the public may not see\n * is a uniform 404 (`ApiError`). Never cached: the payload carries a\n * short-lived signed playback token. Server-side, prefer `getLessonPreview`\n * from `@reblu/site-next/academy-server`, which maps that 404 to `null`.\n */\n lessonPreview: (courseSlug: string, lessonSlug: string): Promise<AcademyLessonPreview> =>\n requestOne<AcademyLessonPreview>(\n ctx,\n `/api/site/academy/courses/${encodeURIComponent(courseSlug)}/lessons/${encodeURIComponent(lessonSlug)}`,\n { cachePolicy: { revalidate: false } },\n ),\n\n /**\n * Instructors of published courses, sorted by name, backing the catalog\n * instructor filter (REB-547). Pass an `id` as `courses({ instructor })`.\n * Not paginated.\n */\n instructors: (cache?: RebluCachePolicy): Promise<ListResult<AcademyInstructorPublic>> =>\n requestList<AcademyInstructorPublic>(ctx, '/api/site/academy/instructors', {\n cachePolicy: cache,\n }),\n\n /**\n * Tenant academy settings for the SITE (REB-1234). `catalogFilters` says\n * which catalogue filters the tenant switched on in nexus — pass it as\n * `enabled` to `CourseCatalogFilters` (`@reblu/site-blocks`). A filter the\n * tenant never set comes back `true`.\n */\n config: (cache?: RebluCachePolicy): Promise<AcademyConfigPublic> =>\n requestOne<AcademyConfigPublic>(ctx, '/api/site/academy/config', { cachePolicy: cache }),\n\n /** Active course categories backing the catalog filter. Not paginated. */\n categories: (cache?: RebluCachePolicy): Promise<ListResult<AcademyCategoryPublic>> =>\n requestList<AcademyCategoryPublic>(ctx, '/api/site/academy/categories', {\n cachePolicy: cache,\n }),\n\n /**\n * REB-1264 — guest checkout of a course that needs no validation: name,\n * email, the accepted terms version, the course's consent box version\n * and, optionally, full-invoice data. Returns the Stripe Checkout `url`\n * to send the buyer to; the account is created after payment. The\n * answer never depends on whether the email has an account.\n *\n * The `url` is re-checked like `shop.checkout()` (`https:` on\n * `ctx.checkoutRedirectHosts`, else {@link UnsafeCheckoutRedirectError}).\n * A refusal throws {@link AcademyCheckoutError} with its stable `code`;\n * on `ENROLLMENT_REQUEST_REQUIRED` use `enrollmentRequest` instead.\n *\n * Server-side, pass `options.visitorIp` (see `visitorIpFromHeaders`):\n * base-api keys this route's per-visitor limit on it.\n */\n checkout: async (\n body: AcademyCheckoutRequest,\n options?: RebluMutationOptions,\n ): Promise<AcademyCheckoutSession> => {\n let session: AcademyCheckoutSession\n try {\n session = await requestMutation<AcademyCheckoutRequest, AcademyCheckoutSession>(\n ctx,\n '/api/site/academy/checkout',\n body,\n options,\n )\n } catch (error) {\n throw error instanceof ApiError ? new AcademyCheckoutError(error) : error\n }\n if (!isSafeCheckoutRedirectUrl(session.url, ctx.checkoutRedirectHosts)) {\n throw new UnsafeCheckoutRedirectError(session.url, ctx.checkoutRedirectHosts)\n }\n return session\n },\n\n /**\n * Submit an anonymous enrollment request for a course.\n *\n * The reply is UNIFORM by design: the caller cannot tell whether the email\n * was new (a request row was created) or already known (a sign-in link was\n * sent and NO request row exists). Never present the returned message as\n * confirmation that a request was recorded — it is not one.\n *\n * Server-side, pass `options.visitorIp` (from the incoming request, see\n * `visitorIpFromHeaders`): base-api keys this route's per-IP limit on it\n * and, without it, throttles every visitor of the SITE in one shared\n * tenant-level bucket (REB-1006).\n */\n enrollmentRequest: (\n body: AcademyEnrollmentRequestInput,\n options?: RebluMutationOptions,\n ): Promise<AcademyEnrollmentRequestResult> => {\n const headers = extraHeaders(options)\n return request<AcademyEnrollmentRequestResult>(ctx, '/api/site/academy/enrollment-requests', {\n method: 'POST',\n body: JSON.stringify(body),\n cachePolicy: { revalidate: false },\n ...(headers ? { headers } : {}),\n })\n },\n }\n}\n","import type { Analytics } from '@reblu/site-contracts'\nimport type { RebluCachePolicy } from '../cache'\nimport { requestOne, type RebluClientContext } from '../client'\n\n/** `GET /api/site/analytics` — the tenant's analytics provider configuration. */\nexport function makeAnalyticsNamespace(ctx: RebluClientContext) {\n return {\n get: (cache?: RebluCachePolicy): Promise<Analytics> =>\n requestOne<Analytics>(ctx, '/api/site/analytics', { cachePolicy: cache }),\n }\n}\n","import type {\n BlogCategoryWithCount,\n BlogPostDetail,\n BlogPostListItem,\n BlogPostPreview,\n BlogSlug,\n} from '@reblu/site-contracts'\nimport { buildUrl, type QueryValue } from '../build-url'\nimport type { RebluCachePolicy } from '../cache'\nimport { requestList, requestOne, type RebluClientContext } from '../client'\nimport type { BlogListParams, ListResult } from '../types'\n\n/** Blog domain — `GET /api/site/blog/*`. */\nexport function makeBlogNamespace(ctx: RebluClientContext) {\n return {\n list: (\n params?: BlogListParams,\n cache?: RebluCachePolicy,\n ): Promise<ListResult<BlogPostListItem>> =>\n requestList<BlogPostListItem>(\n ctx,\n buildUrl('/api/site/blog', params as Record<string, QueryValue> | undefined),\n { cachePolicy: cache },\n ),\n\n bySlug: (slug: string, cache?: RebluCachePolicy): Promise<BlogPostDetail> =>\n requestOne<BlogPostDetail>(ctx, `/api/site/blog/${slug}`, { cachePolicy: cache }),\n\n related: (slug: string, cache?: RebluCachePolicy): Promise<ListResult<BlogPostListItem>> =>\n requestList<BlogPostListItem>(ctx, `/api/site/blog/${slug}/related`, { cachePolicy: cache }),\n\n categories: (cache?: RebluCachePolicy): Promise<ListResult<BlogCategoryWithCount>> =>\n requestList<BlogCategoryWithCount>(ctx, '/api/site/blog/categories', { cachePolicy: cache }),\n\n slugs: (cache?: RebluCachePolicy): Promise<ListResult<BlogSlug>> =>\n requestList<BlogSlug>(ctx, '/api/site/blog/slugs', { cachePolicy: cache }),\n\n /** Draft preview — never cached (unpublished content). */\n preview: (id: string): Promise<BlogPostPreview> =>\n requestOne<BlogPostPreview>(ctx, `/api/site/blog/preview/${id}`, {\n cachePolicy: { revalidate: false },\n }),\n }\n}\n","import type { Config } from '@reblu/site-contracts'\nimport type { RebluCachePolicy } from '../cache'\nimport { requestOne, type RebluClientContext } from '../client'\n\n/** `GET /api/site/config` — the tenant's public site configuration. */\nexport function makeConfigNamespace(ctx: RebluClientContext) {\n return {\n get: (cache?: RebluCachePolicy): Promise<Config> =>\n requestOne<Config>(ctx, '/api/site/config', { cachePolicy: cache }),\n }\n}\n","import type { ContactFormInput, ContactFormSubmission } from '@reblu/site-contracts'\nimport { requestMutation, type RebluClientContext, type RebluMutationOptions } from '../client'\n\n/**\n * Contact form domain — WRITE only (`POST /api/site/contact-form`, REB-438).\n * Posts a body typed by `@reblu/site-contracts` and bypasses the cache; returns\n * the created lead's id + creation timestamp.\n */\nexport function makeContactFormNamespace(ctx: RebluClientContext) {\n return {\n /**\n * Submit a contact form. Returns the created lead acknowledgement.\n * Server-side (a Server Action / Route Handler), pass `options.visitorIp`\n * (from the incoming request, see `visitorIpFromHeaders`) so base-api's\n * per-IP limit (REB-1005) applies to the real visitor.\n */\n submit: (\n body: ContactFormInput,\n options?: RebluMutationOptions,\n ): Promise<ContactFormSubmission> =>\n requestMutation<ContactFormInput, ContactFormSubmission>(\n ctx,\n '/api/site/contact-form',\n body,\n options,\n ),\n }\n}\n","import type { Cta } from '@reblu/site-contracts'\nimport { buildUrl } from '../build-url'\nimport type { RebluCachePolicy } from '../cache'\nimport { requestList, type RebluClientContext } from '../client'\nimport type { ListResult } from '../types'\n\n/**\n * `GET /api/site/ctas?slot=…` — editable CTAs for a given slot.\n *\n * `slot` is typed as `string` on purpose: the slot enum lives in base-api's\n * `validations/` (not the response contracts), so `@reblu/site-contracts` does\n * not export it. Keeping it a `string` avoids duplicating (and drifting from)\n * the server-owned enum. The server validates the slot.\n */\nexport function makeCtasNamespace(ctx: RebluClientContext) {\n return {\n list: (slot: string, cache?: RebluCachePolicy): Promise<ListResult<Cta>> =>\n requestList<Cta>(ctx, buildUrl('/api/site/ctas', { slot }), { cachePolicy: cache }),\n }\n}\n","import type { DataRequestInput, DataRequestSubmission } from '@reblu/site-contracts'\nimport { requestMutation, type RebluClientContext, type RebluMutationOptions } from '../client'\n\n/**\n * GDPR data-subject requests domain — WRITE only (`POST /api/site/data-requests`,\n * REB-77 endpoint / REB-980 SDK). Posts the public rights-form body typed by\n * `@reblu/site-contracts`, bypasses the cache, and returns the request id, when\n * it was received and the 72h deadline (`slaDueAt`) it is now held to.\n *\n * base-api requires a reCAPTCHA v3 token (`captcha`) and rate-limits per tenant\n * and per visitor IP (REB-1005); a rejection surfaces as an `ApiError` carrying\n * the upstream `status` (400 validation, 422 captcha, 429 rate limit).\n */\nexport function makeDataRequestsNamespace(ctx: RebluClientContext) {\n return {\n /**\n * Submit a data-subject request. Returns its acknowledgement with the 72h\n * deadline. Server-side, pass `options.visitorIp` (from the incoming\n * request, see `visitorIpFromHeaders`) so base-api's per-IP limit\n * (REB-1005) sees the real visitor.\n */\n submit: (\n body: DataRequestInput,\n options?: RebluMutationOptions,\n ): Promise<DataRequestSubmission> =>\n requestMutation<DataRequestInput, DataRequestSubmission>(\n ctx,\n '/api/site/data-requests',\n body,\n options,\n ),\n }\n}\n","import type { LeadInput, LeadSubmission } from '@reblu/site-contracts'\nimport { requestMutation, type RebluClientContext } from '../client'\n\n/**\n * Leads domain — WRITE only (`POST /api/site/leads`, REB-438). The generic\n * lead-ingestion endpoint: posts a body typed by `@reblu/site-contracts`,\n * bypasses the cache, and returns the created lead's id + creation timestamp.\n */\nexport function makeLeadsNamespace(ctx: RebluClientContext) {\n return {\n /** Create a lead. Returns the created lead acknowledgement. */\n create: (body: LeadInput): Promise<LeadSubmission> =>\n requestMutation<LeadInput, LeadSubmission>(ctx, '/api/site/leads', body),\n }\n}\n","import type { Legal } from '@reblu/site-contracts'\nimport type { RebluCachePolicy } from '../cache'\nimport { requestOne, type RebluClientContext } from '../client'\n\n/**\n * `GET /api/site/legal` — the tenant's legal content (privacy, terms, cookies,\n * and since REB-1201 `legalNotice` and `withdrawalPolicy`). Render each with\n * `LegalDocument` from `@reblu/site-blocks`.\n */\nexport function makeLegalNamespace(ctx: RebluClientContext) {\n return {\n get: (cache?: RebluCachePolicy): Promise<Legal> =>\n requestOne<Legal>(ctx, '/api/site/legal', { cachePolicy: cache }),\n }\n}\n","import type { PageDetail, PageListItem, PageSlug } from '@reblu/site-contracts'\nimport { buildUrl, type QueryValue } from '../build-url'\nimport type { RebluCachePolicy } from '../cache'\nimport { requestList, requestOne, type RebluClientContext } from '../client'\nimport { AmbiguousPageSlugError, ApiError, apiErrorRawBody } from '../errors'\nimport type { ListResult, PagesListParams } from '../types'\n\n/**\n * REB-867 — options for `pages.bySlug`. Page slugs are unique PER CATEGORY:\n * - `category: '<categorySlug>'` → the page with that slug in that category;\n * - `category: null` → the UNCATEGORISED page with that slug;\n * - omitted → the unique page with that slug; throws\n * `AmbiguousPageSlugError` when it exists in more than one category.\n */\nexport interface PageBySlugOptions {\n category?: string | null\n cache?: RebluCachePolicy\n}\n\n/**\n * The two second-argument shapes share no key: a `RebluCachePolicy` only ever\n * has `revalidate`, `PageBySlugOptions` only `category`/`cache`. So the\n * presence of `category` or `cache` (even `category: undefined`) selects the\n * options form; anything else — including `{}` — is a cache policy, exactly as\n * before REB-867.\n */\n/** REB-869 — options for `pages.featured`. */\nexport interface FeaturedPagesOptions {\n /**\n * How many pages at most: an integer >= 1. Default 3. Values above the\n * server's cap (3) are clamped to 3; anything else invalid (0, negative,\n * fractional, NaN) throws a `RangeError` before any request is made.\n */\n limit?: number\n /**\n * When the category has nothing featured, return its first `limit` pages in\n * normal order instead of `[]`. Default `true`, so a teaser is never empty\n * just because nobody picked pages yet.\n */\n fallback?: boolean\n cache?: RebluCachePolicy\n}\n\n/** The server caps a featured list at 3 (REB-869). */\nconst FEATURED_PAGES_MAX = 3\n\nfunction isBySlugOptions(value: RebluCachePolicy | PageBySlugOptions): value is PageBySlugOptions {\n return 'category' in value || 'cache' in value\n}\n\n/** Dynamic pages domain — `GET /api/site/pages/*`. */\nexport function makePagesNamespace(ctx: RebluClientContext) {\n const namespace = {\n list: (\n params?: PagesListParams,\n cache?: RebluCachePolicy,\n ): Promise<ListResult<PageListItem>> =>\n requestList<PageListItem>(\n ctx,\n // REB-63: `type` can now be a single PageType or an array (repeated\n // `?type=` params) — widen the cast alongside `PagesListParams`.\n buildUrl('/api/site/pages', params as Record<string, QueryValue | QueryValue[]> | undefined),\n { cachePolicy: cache },\n ),\n\n /**\n * A published page by slug. The second argument is either a cache policy\n * (backward-compatible) or {@link PageBySlugOptions} (REB-867).\n */\n bySlug: async (\n slug: string,\n cacheOrOptions?: RebluCachePolicy | PageBySlugOptions,\n ): Promise<PageDetail> => {\n const options: PageBySlugOptions =\n cacheOrOptions && isBySlugOptions(cacheOrOptions)\n ? cacheOrOptions\n : { cache: cacheOrOptions }\n const path =\n options.category === undefined\n ? `/api/site/pages/${slug}`\n : buildUrl(`/api/site/pages/${slug}`, { category: options.category ?? '' })\n try {\n return await requestOne<PageDetail>(ctx, path, { cachePolicy: options.cache })\n } catch (error) {\n if (error instanceof ApiError && error.status === 409 && error.body?.code === 'ambiguous_slug') {\n throw new AmbiguousPageSlugError(error, apiErrorRawBody(error))\n }\n throw error\n }\n },\n\n /**\n * REB-869 — the pages to show in a category teaser: the category's\n * featured pages in the tenant's order; when none are featured (and\n * `fallback` is not `false`), the first `limit` pages of the category in\n * normal order. Returns the items only (no pagination applies).\n */\n featured: async (\n category: string,\n { limit: requested = FEATURED_PAGES_MAX, fallback = true, cache }: FeaturedPagesOptions = {},\n ): Promise<PageListItem[]> => {\n if (!Number.isInteger(requested) || requested < 1) {\n throw new RangeError(`pages.featured: limit must be an integer >= 1 (got ${requested})`)\n }\n const limit = Math.min(requested, FEATURED_PAGES_MAX)\n const featured = await namespace.list({ category, featured: true }, cache)\n if (featured.data.length > 0 || !fallback) return featured.data.slice(0, limit)\n const first = await namespace.list({ category, pageSize: limit }, cache)\n return first.data.slice(0, limit)\n },\n\n slugs: (cache?: RebluCachePolicy): Promise<ListResult<PageSlug>> =>\n requestList<PageSlug>(ctx, '/api/site/pages/slugs', { cachePolicy: cache }),\n }\n return namespace\n}\n","import type {\n ShopApplyDiscountInput,\n ShopCartValidation,\n ShopCategory,\n ShopCheckEmailInput,\n ShopCheckoutInput,\n ShopCheckoutSession,\n ShopConfig,\n ShopDiscountApplication,\n ShopEmailCheck,\n ShopFacets,\n ShopOrder,\n ShopProductDetail,\n ShopProductListItem,\n ShopQuote,\n ShopQuoteInput,\n ShopValidateCartInput,\n} from '@reblu/site-contracts'\nimport { buildUrl, type QueryValue } from '../build-url'\nimport type { RebluCachePolicy } from '../cache'\nimport {\n requestList,\n requestMutation,\n requestOne,\n type RebluClientContext,\n type RebluMutationOptions,\n} from '../client'\nimport { isSafeCheckoutRedirectUrl, UnsafeCheckoutRedirectError } from '../checkout-redirect'\nimport type { ListResult, ShopProductsParams } from '../types'\n\n/**\n * Flatten `ShopProductsParams` into a query record: scalars pass through; the\n * `taxonomy`/`attr` maps become repeatable `taxonomy[key]`/`attr[key]` params.\n * Array attr values repeat the same bracket key.\n */\nfunction flattenShopParams(params?: ShopProductsParams): Record<string, QueryValue | QueryValue[]> {\n if (!params) return {}\n const { taxonomy, attr, ...scalars } = params\n const out: Record<string, QueryValue | QueryValue[]> = { ...scalars }\n if (taxonomy) {\n for (const [key, value] of Object.entries(taxonomy)) out[`taxonomy[${key}]`] = value\n }\n if (attr) {\n for (const [key, value] of Object.entries(attr)) out[`attr[${key}]`] = value\n }\n return out\n}\n\n/**\n * Shop domain — READ surface (`GET /api/site/shop/*`) plus the mutation flow\n * (`POST` checkout, validate-cart, apply-discount, and the deprecated\n * check-email) added in REB-438. Every write posts a body typed by `@reblu/site-contracts` and bypasses\n * the cache. Request/response DTOs live in the contracts package (zero Zod).\n */\nexport function makeShopNamespace(ctx: RebluClientContext) {\n return {\n products: (\n params?: ShopProductsParams,\n cache?: RebluCachePolicy,\n ): Promise<ListResult<ShopProductListItem>> =>\n requestList<ShopProductListItem>(\n ctx,\n buildUrl('/api/site/shop/products', flattenShopParams(params)),\n { cachePolicy: cache },\n ),\n\n /** Available facets (brand/attributes/price/on-sale) for the given filters. */\n facets: (params?: ShopProductsParams, cache?: RebluCachePolicy): Promise<ShopFacets> =>\n requestOne<ShopFacets>(ctx, buildUrl('/api/site/shop/facets', flattenShopParams(params)), {\n cachePolicy: cache,\n }),\n\n bySlug: (slug: string, cache?: RebluCachePolicy): Promise<ShopProductDetail> =>\n requestOne<ShopProductDetail>(ctx, `/api/site/shop/products/${slug}`, { cachePolicy: cache }),\n\n categories: (cache?: RebluCachePolicy): Promise<ListResult<ShopCategory>> =>\n requestList<ShopCategory>(ctx, '/api/site/shop/categories', { cachePolicy: cache }),\n\n config: (cache?: RebluCachePolicy): Promise<ShopConfig> =>\n requestOne<ShopConfig>(ctx, '/api/site/shop/config', { cachePolicy: cache }),\n\n // ── Mutations (REB-438) ──────────────────────────────────────────────────\n\n /**\n * Start a hosted checkout session. Returns the Stripe URL + session id.\n *\n * REB-836 (defence in depth): every SITE hands `url` straight to\n * `window.location.assign`, so this validates it is `https:` on an\n * allowed host (`ctx.checkoutRedirectHosts`, default\n * `checkout.stripe.com`) BEFORE returning — throwing\n * {@link UnsafeCheckoutRedirectError} instead of ever handing back a URL\n * that could redirect a buyer's browser off Stripe's domain.\n */\n checkout: async (body: ShopCheckoutInput): Promise<ShopCheckoutSession> => {\n const session = await requestMutation<ShopCheckoutInput, ShopCheckoutSession>(\n ctx,\n '/api/site/shop/checkout',\n body,\n )\n if (!isSafeCheckoutRedirectUrl(session.url, ctx.checkoutRedirectHosts)) {\n throw new UnsafeCheckoutRedirectError(session.url, ctx.checkoutRedirectHosts)\n }\n return session\n },\n\n /** Validate a cart against live product/stock/price state before checkout. */\n validateCart: (body: ShopValidateCartInput): Promise<ShopCartValidation> =>\n requestMutation<ShopValidateCartInput, ShopCartValidation>(\n ctx,\n '/api/site/shop/validate-cart',\n body,\n ),\n\n /**\n * REB-1197 — the pre-payment summary of the single-step checkout: lines,\n * discount, delivery, shipping, tax, weight, delivery term and the final\n * total, priced by the same function as `checkout` (the total shown is the\n * total Stripe charges). Holds nothing: no discount slot, no session.\n *\n * Server-side, pass `options.visitorIp` (see `visitorIpFromHeaders`): a\n * quote with a `discountCode` counts against the same per-visitor limit\n * as `applyDiscount`.\n */\n quote: (body: ShopQuoteInput, options?: RebluMutationOptions): Promise<ShopQuote> =>\n requestMutation<ShopQuoteInput, ShopQuote>(ctx, '/api/site/shop/quote', body, options),\n\n /** Validate a discount code against the current cart subtotal. */\n applyDiscount: (body: ShopApplyDiscountInput): Promise<ShopDiscountApplication> =>\n requestMutation<ShopApplyDiscountInput, ShopDiscountApplication>(\n ctx,\n '/api/site/shop/apply-discount',\n body,\n ),\n\n /**\n * @deprecated REB-834 — the endpoint no longer reveals whether an account\n * exists: it always resolves `{ exists: false }`. Do not call it and never\n * branch UI on `exists`; the checkout always offers «Iniciar sesión o\n * continuar como invitado» and the server links the order to an existing\n * account after payment. Removed in the next major.\n *\n * Server-side, pass `options.visitorIp` (from the incoming request, see\n * `visitorIpFromHeaders`): base-api keys this route's per-IP limit on it\n * and, without it, throttles every visitor of the SITE in one shared\n * tenant-level bucket (REB-1006).\n */\n checkEmail: (\n body: ShopCheckEmailInput,\n options?: RebluMutationOptions,\n ): Promise<ShopEmailCheck> =>\n requestMutation<ShopCheckEmailInput, ShopEmailCheck>(\n ctx,\n '/api/site/shop/check-email',\n body,\n options,\n ),\n\n /**\n * Look up a confirmed (or still-pending) order by its Stripe Checkout\n * session id — the order-confirmation flow (REB-829). `GET`, so it goes\n * through `requestOne` like every other read, not `requestMutation`.\n *\n * Never cached by default (REB-1245): an order belongs to one buyer, and\n * \"not found yet\" right after the Stripe redirect must not be remembered.\n * Pass an explicit `cache` policy only if you know you want one.\n */\n order: (sessionId: string, cache?: RebluCachePolicy): Promise<ShopOrder> =>\n requestOne<ShopOrder>(ctx, `/api/site/shop/order/${encodeURIComponent(sessionId)}`, {\n cachePolicy: cache ?? { revalidate: false },\n }),\n }\n}\n","import type { FeaturedReviews, Review } from '@reblu/site-contracts'\nimport { buildUrl } from '../build-url'\nimport type { RebluCachePolicy } from '../cache'\nimport { requestList, requestOne, type RebluClientContext } from '../client'\nimport type { ListResult } from '../types'\n\n/** Filters for {@link makeReviewsNamespace}'s `list` (REB-1272). */\nexport interface ReviewListParams {\n /** Only the reviews linked to this page (its `id`). Omit for every published review. */\n pageId?: string\n /** At most this many reviews (1..100). Omit for all of them. */\n limit?: number\n}\n\n/** Options for {@link makeReviewsNamespace}'s `featured` (REB-1272). */\nexport interface FeaturedReviewsParams {\n /** At most this many featured reviews (1..100). Omit for all of them. */\n limit?: number\n}\n\n/**\n * `GET /api/site/reviews` — the tenant's PUBLISHED reviews in the order set in\n * nexus, each with its source and the https link to the original. Publishing,\n * hiding or reordering in nexus needs no SITE change. Requires the tenant's\n * `reviews` module: without it the API answers 404 (an `ApiError`).\n */\nexport function makeReviewsNamespace(ctx: RebluClientContext) {\n function list(\n params: ReviewListParams = {},\n cache?: RebluCachePolicy,\n ): Promise<ListResult<Review>> {\n return requestList<Review>(\n ctx,\n buildUrl('/api/site/reviews', { pageId: params.pageId, limit: params.limit }),\n { cachePolicy: cache },\n )\n }\n /**\n * `GET /api/site/reviews/featured` (REB-1272, owner 8-Nov) — the reviews the\n * tenant featured for its home, published only, in the featured order, and\n * the `layout` it chose in nexus (`grid` | `carousel`). Render both with\n * `FeaturedReviews` from `@reblu/site-blocks/client`. 404 without the\n * tenant's `reviews` module (an `ApiError`).\n */\n function featured(\n params: FeaturedReviewsParams = {},\n cache?: RebluCachePolicy,\n ): Promise<FeaturedReviews> {\n return requestOne<FeaturedReviews>(\n ctx,\n buildUrl('/api/site/reviews/featured', { limit: params.limit }),\n { cachePolicy: cache },\n )\n }\n return { list, featured }\n}\n","import type { TrustBadge, TrustBadgeType } from '@reblu/site-contracts'\nimport { buildUrl } from '../build-url'\nimport type { RebluCachePolicy } from '../cache'\nimport { requestList, type RebluClientContext } from '../client'\nimport type { ListResult } from '../types'\n\n/** Filters for {@link makeTrustBadgesNamespace}'s `list` (REB-996). */\nexport interface TrustBadgeListParams {\n /** Only badges of this type (e.g. `'SUBVENTION'`). Omit for every type. */\n type?: TrustBadgeType\n}\n\nfunction isCachePolicy(value: TrustBadgeListParams | RebluCachePolicy): value is RebluCachePolicy {\n return Object.prototype.hasOwnProperty.call(value, 'revalidate')\n}\n\n/**\n * `GET /api/site/trust-badges` — the tenant's PUBLIC trust badges (no\n * pagination): active in nexus and inside their optional validity window, so\n * expiring or switching one off needs no SITE change.\n */\nexport function makeTrustBadgesNamespace(ctx: RebluClientContext) {\n function list(cache?: RebluCachePolicy): Promise<ListResult<TrustBadge>>\n function list(\n params: TrustBadgeListParams,\n cache?: RebluCachePolicy,\n ): Promise<ListResult<TrustBadge>>\n function list(\n paramsOrCache?: TrustBadgeListParams | RebluCachePolicy,\n cache?: RebluCachePolicy,\n ): Promise<ListResult<TrustBadge>> {\n // `list(cache)` is the pre-REB-996 call shape — kept so this stays a minor.\n const legacyCache = paramsOrCache && isCachePolicy(paramsOrCache) ? paramsOrCache : undefined\n const params = legacyCache ? undefined : (paramsOrCache as TrustBadgeListParams | undefined)\n return requestList<TrustBadge>(\n ctx,\n buildUrl('/api/site/trust-badges', { type: params?.type }),\n { cachePolicy: legacyCache ?? cache },\n )\n }\n return { list }\n}\n","import type { WithdrawalRequestInput, WithdrawalRequestSubmission } from '@reblu/site-contracts'\nimport { requestMutation, type RebluClientContext, type RebluMutationOptions } from '../client'\n\n/**\n * Withdrawal requests domain (REB-1201) — WRITE only\n * (`POST /api/site/withdrawal-requests`). A buyer communicates that they\n * withdraw from a purchase: base-api stores it, emails the tenant team and\n * acknowledges it to the buyer. Returns the request id, when it was received\n * and whether the buyer's acknowledgement email went out.\n *\n * base-api requires a reCAPTCHA v3 token (`captcha`) and rate-limits per tenant\n * and per visitor IP; a rejection surfaces as an `ApiError` carrying the\n * upstream `status` (400 validation, 422 captcha, 429 rate limit).\n */\nexport function makeWithdrawalRequestsNamespace(ctx: RebluClientContext) {\n return {\n /**\n * Submit a withdrawal communication. Server-side, pass `options.visitorIp`\n * (see `visitorIpFromHeaders`) so base-api's per-IP limit sees the visitor.\n */\n submit: (\n body: WithdrawalRequestInput,\n options?: RebluMutationOptions,\n ): Promise<WithdrawalRequestSubmission> =>\n requestMutation<WithdrawalRequestInput, WithdrawalRequestSubmission>(\n ctx,\n '/api/site/withdrawal-requests',\n body,\n options,\n ),\n }\n}\n","import type { ContactFormInput } from '@reblu/site-contracts'\n\n/**\n * REB-1077 — the `metadata` block of a contact-form submission, built from the\n * page the visitor is on so a SITE never hand-codes `fromPage`.\n *\n * Call it in the browser, at submit time (inside the form's submit handler):\n * it reads `globalThis.location` unless a `location` is passed.\n */\n\n/** The part of `window.location` these helpers read. */\nexport interface PageLocation {\n pathname: string\n href?: string\n}\n\nexport type ContactFormMetadata = ContactFormInput['metadata']\n\nexport interface ContactFormMetadataOptions {\n /**\n * Explicit page path, overriding the one read from the location (e.g. a\n * modal form that wants to report the landing it belongs to).\n */\n fromPage?: string\n /** @default { platform: 'organic' } */\n leadSource?: ContactFormMetadata['leadSource']\n /** @default globalThis.location */\n location?: PageLocation | null\n /** Submission time. @default new Date() */\n now?: Date\n}\n\nfunction browserLocation(): PageLocation | null {\n const location = (globalThis as { location?: PageLocation }).location\n return location && typeof location.pathname === 'string' ? location : null\n}\n\n/**\n * Path of the page the visitor is on — `location.pathname`, never with a query\n * string or hash. `'/'` when there is no location (server render).\n */\nexport function currentPagePath(location: PageLocation | null = browserLocation()): string {\n const path = location?.pathname.split(/[?#]/, 1)[0] ?? ''\n return path.startsWith('/') ? path : '/'\n}\n\n/**\n * The `metadata` for `contactForm.submit()` / `POST /api/site/contact-form`:\n * `fromPage` is the current page path, `currentUrl` the full href (base-api\n * reads the UTMs from it), `timestamp` the submission time.\n *\n * ```ts\n * body: JSON.stringify({ ...fields, captcha, metadata: buildContactFormMetadata() })\n * ```\n */\nexport function buildContactFormMetadata(\n options: ContactFormMetadataOptions = {},\n): ContactFormMetadata {\n const location = options.location === undefined ? browserLocation() : options.location\n return {\n fromPage: options.fromPage ?? currentPagePath(location),\n timestamp: (options.now ?? new Date()).toISOString(),\n currentUrl: location?.href ?? '',\n leadSource: options.leadSource ?? { platform: 'organic' },\n }\n}\n","/**\n * `@reblu/site-client` — framework-agnostic HTTP client for the Reblu SITE API\n * (REB-415 / design doc §3).\n *\n * The consumer owns the environment: `baseUrl`/`apiKey` are passed in, never read\n * from `process.env`. Caching is delegated to an injectable `RebluCacheAdapter`\n * (default `PlainCacheAdapter`), which severs any coupling to a specific\n * framework's cache model — `@reblu/site-next` supplies the ISR-aware adapter.\n * Response DTOs are typed by the peer, type-only `@reblu/site-contracts`.\n */\nimport { PlainCacheAdapter, type RebluCacheAdapter } from './cache'\nimport type { RebluClientContext, RequestScope } from './client'\nimport { DEFAULT_CHECKOUT_REDIRECT_HOSTS } from './checkout-redirect'\nimport { makeAcademyNamespace } from './domains/academy'\nimport { makeAnalyticsNamespace } from './domains/analytics'\nimport { makeBlogNamespace } from './domains/blog'\nimport { makeConfigNamespace } from './domains/config'\nimport { makeContactFormNamespace } from './domains/contact-form'\nimport { makeCtasNamespace } from './domains/ctas'\nimport { makeDataRequestsNamespace } from './domains/data-requests'\nimport { makeLeadsNamespace } from './domains/leads'\nimport { makeLegalNamespace } from './domains/legal'\nimport { makePagesNamespace } from './domains/pages'\nimport { makeShopNamespace } from './domains/shop'\nimport { makeReviewsNamespace } from './domains/reviews'\nimport { makeTrustBadgesNamespace } from './domains/trust-badges'\nimport { makeWithdrawalRequestsNamespace } from './domains/withdrawal-requests'\n\nconst DEFAULT_API_VERSION = '1'\n/** Parity with demo-site's upstream timeout (client.ts:36). */\nconst DEFAULT_TIMEOUT_MS = 8000\n\nexport interface RebluClientConfig {\n /** Absolute origin of the Reblu SITE API, e.g. `https://api.reblu.app`. */\n baseUrl: string\n /** Tenant SITE API key (`rbl_…`), sent as `X-Reblu-Api-Key`. */\n apiKey: string\n /** Contract version sent as `X-Reblu-Api-Version`. Defaults to `'1'`. */\n apiVersion?: string\n /** Cache strategy. Defaults to `new PlainCacheAdapter()` (no-store). */\n cache?: RebluCacheAdapter\n /** Per-request hard timeout in ms. Defaults to `8000`. */\n timeoutMs?: number\n /** Injectable `fetch` — for tests, custom runtimes, or instrumentation. */\n fetch?: typeof fetch\n /**\n * Allowed hosts for the Stripe Checkout URL `shop.checkout()` returns\n * (REB-836, defence in depth — see `./checkout-redirect`). Defaults to\n * `['checkout.stripe.com']`. Override only to point at a test double (e.g.\n * a Stripe CLI mock host) — never to widen production trust.\n */\n checkoutRedirectHosts?: string[]\n /**\n * REB-1214 — memo store of the current server request, so identical GET\n * reads within it are fetched once. In Next.js pass `rebluRequestScope`\n * from `@reblu/site-next` (React `cache()`); see the README. Without it,\n * identical GETs share one fetch only while in flight.\n */\n requestScope?: RequestScope\n}\n\n/**\n * Build a Reblu SITE API client. Each domain is a small namespace of bound\n * methods assembled from a separate module, so a consumer importing only a\n * couple of domains can tree-shake the rest.\n */\nexport function createRebluClient(config: RebluClientConfig) {\n // Wrap global fetch so calling `ctx.fetch()` never loses its `this` binding\n // (browsers throw \"Illegal invocation\" for an unbound `window.fetch`).\n const boundFetch: typeof fetch = config.fetch ?? ((input, init) => fetch(input, init))\n\n const ctx: RebluClientContext = {\n baseUrl: config.baseUrl,\n apiKey: config.apiKey,\n apiVersion: config.apiVersion ?? DEFAULT_API_VERSION,\n cache: config.cache ?? new PlainCacheAdapter(),\n timeoutMs: config.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n fetch: boundFetch,\n checkoutRedirectHosts: config.checkoutRedirectHosts ?? DEFAULT_CHECKOUT_REDIRECT_HOSTS,\n requestScope: config.requestScope,\n inFlight: new Map(),\n }\n\n return {\n config: makeConfigNamespace(ctx),\n analytics: makeAnalyticsNamespace(ctx),\n legal: makeLegalNamespace(ctx),\n trustBadges: makeTrustBadgesNamespace(ctx),\n ctas: makeCtasNamespace(ctx),\n blog: makeBlogNamespace(ctx),\n pages: makePagesNamespace(ctx),\n shop: makeShopNamespace(ctx),\n academy: makeAcademyNamespace(ctx),\n contactForm: makeContactFormNamespace(ctx),\n leads: makeLeadsNamespace(ctx),\n dataRequests: makeDataRequestsNamespace(ctx),\n withdrawalRequests: makeWithdrawalRequestsNamespace(ctx),\n reviews: makeReviewsNamespace(ctx),\n }\n}\n\n/** The assembled client returned by {@link createRebluClient}. */\nexport type RebluClient = ReturnType<typeof createRebluClient>\n\nexport { ApiError, AmbiguousPageSlugError, AcademyCheckoutError } from './errors'\nexport type {\n ApiErrorBody,\n ApiErrorDetails,\n PageSlugCandidate,\n AcademyCheckoutErrorDetails,\n} from './errors'\nexport type { PageBySlugOptions, FeaturedPagesOptions } from './domains/pages'\nexport type { TrustBadgeListParams } from './domains/trust-badges'\nexport type { ReviewListParams, FeaturedReviewsParams } from './domains/reviews'\nexport {\n DEFAULT_CHECKOUT_REDIRECT_HOSTS,\n isSafeCheckoutRedirectUrl,\n UnsafeCheckoutRedirectError,\n} from './checkout-redirect'\nexport { PlainCacheAdapter } from './cache'\nexport type { RebluCacheAdapter, RebluCachePolicy } from './cache'\nexport type {\n ListResult,\n BlogListParams,\n PagesListParams,\n ShopProductsParams,\n AcademyCourseListParams,\n AcademyEnrollmentRequestInput,\n AcademyEnrollmentRequestResult,\n} from './types'\nexport {\n VISITOR_IP_HEADER,\n visitorIpFromHeaders,\n RETRY_AFTER_CAP_MS,\n type RebluMutationOptions,\n type RequestScope,\n} from './client'\nexport {\n buildContactFormMetadata,\n currentPagePath,\n type ContactFormMetadata,\n type ContactFormMetadataOptions,\n type PageLocation,\n} from './contact-form-metadata'\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reblu/site-client",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.21.0",
|
|
4
4
|
"description": "Framework-agnostic HTTP client for the Reblu SITE API — dependency-free, injectable cache adapter, typed by @reblu/site-contracts.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Reblu — Adolfo Unturbe",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"access": "public"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
|
-
"@reblu/site-contracts": "^1.
|
|
50
|
+
"@reblu/site-contracts": "^1.32.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@size-limit/preset-small-lib": "^11.1.6",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"typescript": "^5.8.3",
|
|
59
59
|
"vitest": "^4.0.15",
|
|
60
60
|
"@reblu/config": "0.0.1",
|
|
61
|
-
"@reblu/site-contracts": "^1.
|
|
61
|
+
"@reblu/site-contracts": "^1.32.0"
|
|
62
62
|
},
|
|
63
63
|
"size-limit": [
|
|
64
64
|
{
|