@reblu/site-client 5.0.1 → 5.2.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 +32 -0
- package/README.md +41 -0
- package/dist/index.cjs +50 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +33 -1
- package/dist/index.d.ts +33 -1
- package/dist/index.js +50 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# @reblu/site-client
|
|
2
2
|
|
|
3
|
+
## 5.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 74a94ff: Add the order-lookup contract (REB-829). `ShopOrder`/`ShopOrderItem` type the
|
|
8
|
+
`GET /api/site/shop/order/[sessionId]` response — the order-confirmation
|
|
9
|
+
flow's missing piece: every SITE previously hand-rolled a local type for it
|
|
10
|
+
(demo-site's own comment: "base-api /api/site/shop/order is not yet in the
|
|
11
|
+
SDK surface"). `@reblu/site-client`'s `shop.order(sessionId)` reads it.
|
|
12
|
+
|
|
13
|
+
## 5.1.0
|
|
14
|
+
|
|
15
|
+
### Minor Changes
|
|
16
|
+
|
|
17
|
+
- d1ab2ad: Expose the parsed response body on `ApiError` (REB-764).
|
|
18
|
+
|
|
19
|
+
`ApiError` previously flattened a non-2xx response body into the `message`
|
|
20
|
+
string (`` `API ${status} @ ${path}: ${body}` ``), so a consumer could not reach
|
|
21
|
+
`/api/site/contact-form`'s or `/api/site/leads`'s `details.fieldErrors.phone`
|
|
22
|
+
without regex-stripping that template literal from our own source. `ApiError`
|
|
23
|
+
now also carries `body: ApiErrorBody | undefined` — the response parsed and
|
|
24
|
+
typed as `{ error, details?: { fieldErrors?, formErrors? } }` — so a consumer
|
|
25
|
+
reads `err.body?.details?.fieldErrors?.phone` directly. `message` is unchanged,
|
|
26
|
+
byte-for-byte, so anything that logs it keeps working. Parsing is defensive:
|
|
27
|
+
a non-JSON body (an edge proxy's HTML 502, say) leaves `body` `undefined`
|
|
28
|
+
instead of throwing.
|
|
29
|
+
|
|
30
|
+
Additive: a new readonly field and two new exported types, nothing removed or
|
|
31
|
+
reshaped. A `^4.x` consumer with a caret range picks this up on their next
|
|
32
|
+
install; a consumer pinned to an exact version or a locked lockfile needs an
|
|
33
|
+
explicit update.
|
|
34
|
+
|
|
3
35
|
## 5.0.1
|
|
4
36
|
|
|
5
37
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -35,6 +35,47 @@ try {
|
|
|
35
35
|
}
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
+
### `ApiError` — reading field-level errors
|
|
39
|
+
|
|
40
|
+
Every non-2xx response throws a typed `ApiError` (`status`, `path`, `message`).
|
|
41
|
+
`message` stays a human-readable `` `API ${status} @ ${path}: ${body}` `` string —
|
|
42
|
+
unchanged, so anything that logs it keeps working — but the response body is
|
|
43
|
+
**also** available parsed and typed on `body`, so a consumer never needs to
|
|
44
|
+
regex-strip `message` to reach it:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { createRebluClient, ApiError } from '@reblu/site-client'
|
|
48
|
+
import type { ApiErrorBody } from '@reblu/site-client'
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
await client.contactForm.submit(payload)
|
|
52
|
+
} catch (e) {
|
|
53
|
+
if (e instanceof ApiError) {
|
|
54
|
+
const phoneError = e.body?.details?.fieldErrors?.phone?.[0]
|
|
55
|
+
if (phoneError) {
|
|
56
|
+
// render it against the phone input
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`body: ApiErrorBody | undefined`:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
interface ApiErrorBody {
|
|
66
|
+
error: string
|
|
67
|
+
details?: {
|
|
68
|
+
fieldErrors?: Record<string, string[]> // per-field messages, e.g. details.fieldErrors.phone
|
|
69
|
+
formErrors?: string[] // form-level messages not tied to one field
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
This matches what `/api/site/contact-form` and `/api/site/leads` actually send on
|
|
75
|
+
a 400/422 (zod's `.flatten()` under the hood). `body` is `undefined` whenever the
|
|
76
|
+
response was not JSON shaped like this — e.g. an edge proxy's HTML error page on
|
|
77
|
+
a 502 — parsing is defensive and never throws while constructing the error.
|
|
78
|
+
|
|
38
79
|
### Caching — the injectable adapter
|
|
39
80
|
|
|
40
81
|
The client owns *when* to cache (a per-call policy); a `RebluCacheAdapter` owns
|
package/dist/index.cjs
CHANGED
|
@@ -29,15 +29,56 @@ function buildUrl(base, params) {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
// src/errors.ts
|
|
32
|
+
function isStringArray(value) {
|
|
33
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
34
|
+
}
|
|
35
|
+
function isApiErrorDetails(value) {
|
|
36
|
+
if (typeof value !== "object" || value === null) return false;
|
|
37
|
+
const details = value;
|
|
38
|
+
if (details.fieldErrors !== void 0) {
|
|
39
|
+
if (typeof details.fieldErrors !== "object" || details.fieldErrors === null || Array.isArray(details.fieldErrors)) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
for (const fieldValue of Object.values(details.fieldErrors)) {
|
|
43
|
+
if (fieldValue !== void 0 && !isStringArray(fieldValue)) return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (details.formErrors !== void 0 && !isStringArray(details.formErrors)) return false;
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
function isApiErrorBody(value) {
|
|
50
|
+
if (typeof value !== "object" || value === null) return false;
|
|
51
|
+
const body = value;
|
|
52
|
+
if (typeof body.error !== "string") return false;
|
|
53
|
+
if (body.details !== void 0 && !isApiErrorDetails(body.details)) return false;
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
function parseApiErrorBody(rawBody) {
|
|
57
|
+
if (!rawBody) return void 0;
|
|
58
|
+
try {
|
|
59
|
+
const parsed = JSON.parse(rawBody);
|
|
60
|
+
return isApiErrorBody(parsed) ? parsed : void 0;
|
|
61
|
+
} catch {
|
|
62
|
+
return void 0;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
32
65
|
var ApiError = class extends Error {
|
|
33
66
|
constructor(status, path, message) {
|
|
34
67
|
super(`API ${status} @ ${path}: ${message}`);
|
|
35
68
|
this.status = status;
|
|
36
69
|
this.path = path;
|
|
37
70
|
this.name = "ApiError";
|
|
71
|
+
this.body = parseApiErrorBody(message);
|
|
38
72
|
}
|
|
39
73
|
status;
|
|
40
74
|
path;
|
|
75
|
+
/**
|
|
76
|
+
* The parsed response body, when it was JSON shaped like `{ error, details? }`
|
|
77
|
+
* (REB-764). `undefined` when the body was not JSON or didn't match — parsing
|
|
78
|
+
* is defensive and never throws. Read `body.details?.fieldErrors?.phone`
|
|
79
|
+
* instead of regex-stripping this error's `message`.
|
|
80
|
+
*/
|
|
81
|
+
body;
|
|
41
82
|
};
|
|
42
83
|
|
|
43
84
|
// src/client.ts
|
|
@@ -237,7 +278,15 @@ function makeShopNamespace(ctx) {
|
|
|
237
278
|
body
|
|
238
279
|
),
|
|
239
280
|
/** Check whether a customer with the given email already exists. */
|
|
240
|
-
checkEmail: (body) => requestMutation(ctx, "/api/site/shop/check-email", body)
|
|
281
|
+
checkEmail: (body) => requestMutation(ctx, "/api/site/shop/check-email", body),
|
|
282
|
+
/**
|
|
283
|
+
* Look up a confirmed (or still-pending) order by its Stripe Checkout
|
|
284
|
+
* session id — the order-confirmation flow (REB-829). `GET`, so it goes
|
|
285
|
+
* through `requestOne` like every other read, not `requestMutation`.
|
|
286
|
+
*/
|
|
287
|
+
order: (sessionId, cache) => requestOne(ctx, `/api/site/shop/order/${encodeURIComponent(sessionId)}`, {
|
|
288
|
+
cachePolicy: cache
|
|
289
|
+
})
|
|
241
290
|
};
|
|
242
291
|
}
|
|
243
292
|
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cache.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/leads.ts","../src/domains/legal.ts","../src/domains/pages.ts","../src/domains/shop.ts","../src/domains/trust-badges.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\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","/** 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","/**\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 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 }\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\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 via\n * `AbortController`, throws a typed `ApiError` on non-2xx, and returns the parsed\n * JSON body verbatim (envelope unwrapping is the caller's concern).\n */\nexport async function request<T>(\n ctx: RebluClientContext,\n path: string,\n opts: RequestOptions,\n): Promise<T> {\n const url = ctx.baseUrl + path\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), ctx.timeoutMs)\n\n try {\n const res = await ctx.fetch(url, {\n method: opts.method ?? 'GET',\n body: opts.body,\n signal: controller.signal,\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\n if (!res.ok) {\n throw new ApiError(res.status, path, await res.text())\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 * 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): Promise<Res> {\n const envelope = await request<{ data: Res }>(ctx, path, {\n method: 'POST',\n body: JSON.stringify(body),\n cachePolicy: { revalidate: false },\n })\n return envelope.data\n}\n","import type {\n AcademyCategoryPublic,\n AcademyCourseDetail,\n AcademyCourseSummary,\n} from '@reblu/site-contracts'\nimport { buildUrl, type QueryValue } from '../build-url'\nimport type { RebluCachePolicy } from '../cache'\nimport { request, requestList, requestOne, type RebluClientContext } 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 /** 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 * 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 enrollmentRequest: (\n body: AcademyEnrollmentRequestInput,\n ): Promise<AcademyEnrollmentRequestResult> =>\n request<AcademyEnrollmentRequestResult>(ctx, '/api/site/academy/enrollment-requests', {\n method: 'POST',\n body: JSON.stringify(body),\n cachePolicy: { revalidate: false },\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 } 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 /** Submit a contact form. Returns the created lead acknowledgement. */\n submit: (body: ContactFormInput): Promise<ContactFormSubmission> =>\n requestMutation<ContactFormInput, ContactFormSubmission>(ctx, '/api/site/contact-form', body),\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 { 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/** `GET /api/site/legal` — the tenant's legal content (privacy, terms, cookies). */\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 type { ListResult, PagesListParams } from '../types'\n\n/** Dynamic pages domain — `GET /api/site/pages/*`. */\nexport function makePagesNamespace(ctx: RebluClientContext) {\n return {\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 bySlug: (slug: string, cache?: RebluCachePolicy): Promise<PageDetail> =>\n requestOne<PageDetail>(ctx, `/api/site/pages/${slug}`, { cachePolicy: cache }),\n\n slugs: (cache?: RebluCachePolicy): Promise<ListResult<PageSlug>> =>\n requestList<PageSlug>(ctx, '/api/site/pages/slugs', { cachePolicy: cache }),\n }\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 ShopProductDetail,\n ShopProductListItem,\n ShopValidateCartInput,\n} from '@reblu/site-contracts'\nimport { buildUrl, type QueryValue } from '../build-url'\nimport type { RebluCachePolicy } from '../cache'\nimport { requestList, requestMutation, requestOne, type RebluClientContext } from '../client'\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, check-email) added in\n * 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 /** Start a hosted checkout session. Returns the Stripe URL + session id. */\n checkout: (body: ShopCheckoutInput): Promise<ShopCheckoutSession> =>\n requestMutation<ShopCheckoutInput, ShopCheckoutSession>(ctx, '/api/site/shop/checkout', body),\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 /** 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 /** Check whether a customer with the given email already exists. */\n checkEmail: (body: ShopCheckEmailInput): Promise<ShopEmailCheck> =>\n requestMutation<ShopCheckEmailInput, ShopEmailCheck>(ctx, '/api/site/shop/check-email', body),\n }\n}\n","import type { TrustBadge } from '@reblu/site-contracts'\nimport type { RebluCachePolicy } from '../cache'\nimport { requestList, type RebluClientContext } from '../client'\nimport type { ListResult } from '../types'\n\n/** `GET /api/site/trust-badges` — the tenant's trust badges (no pagination). */\nexport function makeTrustBadgesNamespace(ctx: RebluClientContext) {\n return {\n list: (cache?: RebluCachePolicy): Promise<ListResult<TrustBadge>> =>\n requestList<TrustBadge>(ctx, '/api/site/trust-badges', { cachePolicy: cache }),\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 } from './client'\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 { makeLeadsNamespace } from './domains/leads'\nimport { makeLegalNamespace } from './domains/legal'\nimport { makePagesNamespace } from './domains/pages'\nimport { makeShopNamespace } from './domains/shop'\nimport { makeTrustBadgesNamespace } from './domains/trust-badges'\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\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 }\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 }\n}\n\n/** The assembled client returned by {@link createRebluClient}. */\nexport type RebluClient = ReturnType<typeof createRebluClient>\n\nexport { ApiError } from './errors'\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'\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/cache.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/leads.ts","../src/domains/legal.ts","../src/domains/pages.ts","../src/domains/shop.ts","../src/domains/trust-badges.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\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","/** 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","/**\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\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\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[]`. 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 { fieldErrors?: unknown; formErrors?: unknown }\n\n if (details.fieldErrors !== undefined) {\n if (typeof details.fieldErrors !== 'object' || details.fieldErrors === null || Array.isArray(details.fieldErrors)) {\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 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 }\n if (typeof body.error !== 'string') return false\n if (body.details !== undefined && !isApiErrorDetails(body.details)) 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","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\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 via\n * `AbortController`, throws a typed `ApiError` on non-2xx, and returns the parsed\n * JSON body verbatim (envelope unwrapping is the caller's concern).\n */\nexport async function request<T>(\n ctx: RebluClientContext,\n path: string,\n opts: RequestOptions,\n): Promise<T> {\n const url = ctx.baseUrl + path\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), ctx.timeoutMs)\n\n try {\n const res = await ctx.fetch(url, {\n method: opts.method ?? 'GET',\n body: opts.body,\n signal: controller.signal,\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\n if (!res.ok) {\n throw new ApiError(res.status, path, await res.text())\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 * 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): Promise<Res> {\n const envelope = await request<{ data: Res }>(ctx, path, {\n method: 'POST',\n body: JSON.stringify(body),\n cachePolicy: { revalidate: false },\n })\n return envelope.data\n}\n","import type {\n AcademyCategoryPublic,\n AcademyCourseDetail,\n AcademyCourseSummary,\n} from '@reblu/site-contracts'\nimport { buildUrl, type QueryValue } from '../build-url'\nimport type { RebluCachePolicy } from '../cache'\nimport { request, requestList, requestOne, type RebluClientContext } 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 /** 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 * 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 enrollmentRequest: (\n body: AcademyEnrollmentRequestInput,\n ): Promise<AcademyEnrollmentRequestResult> =>\n request<AcademyEnrollmentRequestResult>(ctx, '/api/site/academy/enrollment-requests', {\n method: 'POST',\n body: JSON.stringify(body),\n cachePolicy: { revalidate: false },\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 } 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 /** Submit a contact form. Returns the created lead acknowledgement. */\n submit: (body: ContactFormInput): Promise<ContactFormSubmission> =>\n requestMutation<ContactFormInput, ContactFormSubmission>(ctx, '/api/site/contact-form', body),\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 { 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/** `GET /api/site/legal` — the tenant's legal content (privacy, terms, cookies). */\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 type { ListResult, PagesListParams } from '../types'\n\n/** Dynamic pages domain — `GET /api/site/pages/*`. */\nexport function makePagesNamespace(ctx: RebluClientContext) {\n return {\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 bySlug: (slug: string, cache?: RebluCachePolicy): Promise<PageDetail> =>\n requestOne<PageDetail>(ctx, `/api/site/pages/${slug}`, { cachePolicy: cache }),\n\n slugs: (cache?: RebluCachePolicy): Promise<ListResult<PageSlug>> =>\n requestList<PageSlug>(ctx, '/api/site/pages/slugs', { cachePolicy: cache }),\n }\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 ShopValidateCartInput,\n} from '@reblu/site-contracts'\nimport { buildUrl, type QueryValue } from '../build-url'\nimport type { RebluCachePolicy } from '../cache'\nimport { requestList, requestMutation, requestOne, type RebluClientContext } from '../client'\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, check-email) added in\n * 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 /** Start a hosted checkout session. Returns the Stripe URL + session id. */\n checkout: (body: ShopCheckoutInput): Promise<ShopCheckoutSession> =>\n requestMutation<ShopCheckoutInput, ShopCheckoutSession>(ctx, '/api/site/shop/checkout', body),\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 /** 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 /** Check whether a customer with the given email already exists. */\n checkEmail: (body: ShopCheckEmailInput): Promise<ShopEmailCheck> =>\n requestMutation<ShopCheckEmailInput, ShopEmailCheck>(ctx, '/api/site/shop/check-email', body),\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 order: (sessionId: string, cache?: RebluCachePolicy): Promise<ShopOrder> =>\n requestOne<ShopOrder>(ctx, `/api/site/shop/order/${encodeURIComponent(sessionId)}`, {\n cachePolicy: cache,\n }),\n }\n}\n","import type { TrustBadge } from '@reblu/site-contracts'\nimport type { RebluCachePolicy } from '../cache'\nimport { requestList, type RebluClientContext } from '../client'\nimport type { ListResult } from '../types'\n\n/** `GET /api/site/trust-badges` — the tenant's trust badges (no pagination). */\nexport function makeTrustBadgesNamespace(ctx: RebluClientContext) {\n return {\n list: (cache?: RebluCachePolicy): Promise<ListResult<TrustBadge>> =>\n requestList<TrustBadge>(ctx, '/api/site/trust-badges', { cachePolicy: cache }),\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 } from './client'\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 { makeLeadsNamespace } from './domains/leads'\nimport { makeLegalNamespace } from './domains/legal'\nimport { makePagesNamespace } from './domains/pages'\nimport { makeShopNamespace } from './domains/shop'\nimport { makeTrustBadgesNamespace } from './domains/trust-badges'\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\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 }\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 }\n}\n\n/** The assembled client returned by {@link createRebluClient}. */\nexport type RebluClient = ReturnType<typeof createRebluClient>\n\nexport { ApiError } from './errors'\nexport type { ApiErrorBody, ApiErrorDetails } from './errors'\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'\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -139,6 +139,30 @@ declare class PlainCacheAdapter implements RebluCacheAdapter {
|
|
|
139
139
|
resolveRequestInit(policy: RebluCachePolicy): RequestInit;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
/**
|
|
143
|
+
* The `details` shape `/api/site/contact-form` and `/api/site/leads` attach on a
|
|
144
|
+
* 400/422, straight from zod's `.flatten()` (see
|
|
145
|
+
* `details: parsed.error.flatten()` in
|
|
146
|
+
* `apps/base-api/src/lib/server/services/contact-form.service.ts:114` and
|
|
147
|
+
* `apiError('Validation error', 422, parsed.error.flatten())` in
|
|
148
|
+
* `apps/base-api/src/app/api/site/leads/route.ts:40`). `fieldErrors` is what a
|
|
149
|
+
* SITE actually needs — REB-659's `details.fieldErrors.phone` — `formErrors` is
|
|
150
|
+
* kept alongside it because `.flatten()` always emits both and dropping it would
|
|
151
|
+
* silently discard form-level (non-field) messages.
|
|
152
|
+
*/
|
|
153
|
+
interface ApiErrorDetails {
|
|
154
|
+
fieldErrors?: Record<string, string[]>;
|
|
155
|
+
formErrors?: string[];
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* The parsed shape of a non-2xx SITE API response body: `{ error, details? }`
|
|
159
|
+
* (see `apiError()` in `apps/base-api/src/lib/api/response.ts:33` — the general
|
|
160
|
+
* shape both routes above build on).
|
|
161
|
+
*/
|
|
162
|
+
interface ApiErrorBody {
|
|
163
|
+
error: string;
|
|
164
|
+
details?: ApiErrorDetails;
|
|
165
|
+
}
|
|
142
166
|
/**
|
|
143
167
|
* Typed transport error for the Reblu SITE API.
|
|
144
168
|
*
|
|
@@ -150,6 +174,13 @@ declare class PlainCacheAdapter implements RebluCacheAdapter {
|
|
|
150
174
|
declare class ApiError extends Error {
|
|
151
175
|
readonly status: number;
|
|
152
176
|
readonly path: string;
|
|
177
|
+
/**
|
|
178
|
+
* The parsed response body, when it was JSON shaped like `{ error, details? }`
|
|
179
|
+
* (REB-764). `undefined` when the body was not JSON or didn't match — parsing
|
|
180
|
+
* is defensive and never throws. Read `body.details?.fieldErrors?.phone`
|
|
181
|
+
* instead of regex-stripping this error's `message`.
|
|
182
|
+
*/
|
|
183
|
+
readonly body: ApiErrorBody | undefined;
|
|
153
184
|
constructor(status: number, path: string, message: string);
|
|
154
185
|
}
|
|
155
186
|
|
|
@@ -211,6 +242,7 @@ declare function createRebluClient(config: RebluClientConfig): {
|
|
|
211
242
|
validateCart: (body: _reblu_site_contracts.ShopValidateCartInput) => Promise<_reblu_site_contracts.ShopCartValidation>;
|
|
212
243
|
applyDiscount: (body: _reblu_site_contracts.ShopApplyDiscountInput) => Promise<_reblu_site_contracts.ShopDiscountApplication>;
|
|
213
244
|
checkEmail: (body: _reblu_site_contracts.ShopCheckEmailInput) => Promise<_reblu_site_contracts.ShopEmailCheck>;
|
|
245
|
+
order: (sessionId: string, cache?: RebluCachePolicy) => Promise<_reblu_site_contracts.ShopOrder>;
|
|
214
246
|
};
|
|
215
247
|
academy: {
|
|
216
248
|
courses: (params?: AcademyCourseListParams, cache?: RebluCachePolicy) => Promise<ListResult<_reblu_site_contracts.AcademyCourseSummary>>;
|
|
@@ -228,4 +260,4 @@ declare function createRebluClient(config: RebluClientConfig): {
|
|
|
228
260
|
/** The assembled client returned by {@link createRebluClient}. */
|
|
229
261
|
type RebluClient = ReturnType<typeof createRebluClient>;
|
|
230
262
|
|
|
231
|
-
export { type AcademyCourseListParams, type AcademyEnrollmentRequestInput, type AcademyEnrollmentRequestResult, ApiError, type BlogListParams, type ListResult, type PagesListParams, PlainCacheAdapter, type RebluCacheAdapter, type RebluCachePolicy, type RebluClient, type RebluClientConfig, type ShopProductsParams, createRebluClient };
|
|
263
|
+
export { type AcademyCourseListParams, type AcademyEnrollmentRequestInput, type AcademyEnrollmentRequestResult, ApiError, type ApiErrorBody, type ApiErrorDetails, type BlogListParams, type ListResult, type PagesListParams, PlainCacheAdapter, type RebluCacheAdapter, type RebluCachePolicy, type RebluClient, type RebluClientConfig, type ShopProductsParams, createRebluClient };
|
package/dist/index.d.ts
CHANGED
|
@@ -139,6 +139,30 @@ declare class PlainCacheAdapter implements RebluCacheAdapter {
|
|
|
139
139
|
resolveRequestInit(policy: RebluCachePolicy): RequestInit;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
/**
|
|
143
|
+
* The `details` shape `/api/site/contact-form` and `/api/site/leads` attach on a
|
|
144
|
+
* 400/422, straight from zod's `.flatten()` (see
|
|
145
|
+
* `details: parsed.error.flatten()` in
|
|
146
|
+
* `apps/base-api/src/lib/server/services/contact-form.service.ts:114` and
|
|
147
|
+
* `apiError('Validation error', 422, parsed.error.flatten())` in
|
|
148
|
+
* `apps/base-api/src/app/api/site/leads/route.ts:40`). `fieldErrors` is what a
|
|
149
|
+
* SITE actually needs — REB-659's `details.fieldErrors.phone` — `formErrors` is
|
|
150
|
+
* kept alongside it because `.flatten()` always emits both and dropping it would
|
|
151
|
+
* silently discard form-level (non-field) messages.
|
|
152
|
+
*/
|
|
153
|
+
interface ApiErrorDetails {
|
|
154
|
+
fieldErrors?: Record<string, string[]>;
|
|
155
|
+
formErrors?: string[];
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* The parsed shape of a non-2xx SITE API response body: `{ error, details? }`
|
|
159
|
+
* (see `apiError()` in `apps/base-api/src/lib/api/response.ts:33` — the general
|
|
160
|
+
* shape both routes above build on).
|
|
161
|
+
*/
|
|
162
|
+
interface ApiErrorBody {
|
|
163
|
+
error: string;
|
|
164
|
+
details?: ApiErrorDetails;
|
|
165
|
+
}
|
|
142
166
|
/**
|
|
143
167
|
* Typed transport error for the Reblu SITE API.
|
|
144
168
|
*
|
|
@@ -150,6 +174,13 @@ declare class PlainCacheAdapter implements RebluCacheAdapter {
|
|
|
150
174
|
declare class ApiError extends Error {
|
|
151
175
|
readonly status: number;
|
|
152
176
|
readonly path: string;
|
|
177
|
+
/**
|
|
178
|
+
* The parsed response body, when it was JSON shaped like `{ error, details? }`
|
|
179
|
+
* (REB-764). `undefined` when the body was not JSON or didn't match — parsing
|
|
180
|
+
* is defensive and never throws. Read `body.details?.fieldErrors?.phone`
|
|
181
|
+
* instead of regex-stripping this error's `message`.
|
|
182
|
+
*/
|
|
183
|
+
readonly body: ApiErrorBody | undefined;
|
|
153
184
|
constructor(status: number, path: string, message: string);
|
|
154
185
|
}
|
|
155
186
|
|
|
@@ -211,6 +242,7 @@ declare function createRebluClient(config: RebluClientConfig): {
|
|
|
211
242
|
validateCart: (body: _reblu_site_contracts.ShopValidateCartInput) => Promise<_reblu_site_contracts.ShopCartValidation>;
|
|
212
243
|
applyDiscount: (body: _reblu_site_contracts.ShopApplyDiscountInput) => Promise<_reblu_site_contracts.ShopDiscountApplication>;
|
|
213
244
|
checkEmail: (body: _reblu_site_contracts.ShopCheckEmailInput) => Promise<_reblu_site_contracts.ShopEmailCheck>;
|
|
245
|
+
order: (sessionId: string, cache?: RebluCachePolicy) => Promise<_reblu_site_contracts.ShopOrder>;
|
|
214
246
|
};
|
|
215
247
|
academy: {
|
|
216
248
|
courses: (params?: AcademyCourseListParams, cache?: RebluCachePolicy) => Promise<ListResult<_reblu_site_contracts.AcademyCourseSummary>>;
|
|
@@ -228,4 +260,4 @@ declare function createRebluClient(config: RebluClientConfig): {
|
|
|
228
260
|
/** The assembled client returned by {@link createRebluClient}. */
|
|
229
261
|
type RebluClient = ReturnType<typeof createRebluClient>;
|
|
230
262
|
|
|
231
|
-
export { type AcademyCourseListParams, type AcademyEnrollmentRequestInput, type AcademyEnrollmentRequestResult, ApiError, type BlogListParams, type ListResult, type PagesListParams, PlainCacheAdapter, type RebluCacheAdapter, type RebluCachePolicy, type RebluClient, type RebluClientConfig, type ShopProductsParams, createRebluClient };
|
|
263
|
+
export { type AcademyCourseListParams, type AcademyEnrollmentRequestInput, type AcademyEnrollmentRequestResult, ApiError, type ApiErrorBody, type ApiErrorDetails, type BlogListParams, type ListResult, type PagesListParams, PlainCacheAdapter, type RebluCacheAdapter, type RebluCachePolicy, type RebluClient, type RebluClientConfig, type ShopProductsParams, createRebluClient };
|
package/dist/index.js
CHANGED
|
@@ -27,15 +27,56 @@ function buildUrl(base, params) {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
// src/errors.ts
|
|
30
|
+
function isStringArray(value) {
|
|
31
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
32
|
+
}
|
|
33
|
+
function isApiErrorDetails(value) {
|
|
34
|
+
if (typeof value !== "object" || value === null) return false;
|
|
35
|
+
const details = value;
|
|
36
|
+
if (details.fieldErrors !== void 0) {
|
|
37
|
+
if (typeof details.fieldErrors !== "object" || details.fieldErrors === null || Array.isArray(details.fieldErrors)) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
for (const fieldValue of Object.values(details.fieldErrors)) {
|
|
41
|
+
if (fieldValue !== void 0 && !isStringArray(fieldValue)) return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (details.formErrors !== void 0 && !isStringArray(details.formErrors)) return false;
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
function isApiErrorBody(value) {
|
|
48
|
+
if (typeof value !== "object" || value === null) return false;
|
|
49
|
+
const body = value;
|
|
50
|
+
if (typeof body.error !== "string") return false;
|
|
51
|
+
if (body.details !== void 0 && !isApiErrorDetails(body.details)) return false;
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
function parseApiErrorBody(rawBody) {
|
|
55
|
+
if (!rawBody) return void 0;
|
|
56
|
+
try {
|
|
57
|
+
const parsed = JSON.parse(rawBody);
|
|
58
|
+
return isApiErrorBody(parsed) ? parsed : void 0;
|
|
59
|
+
} catch {
|
|
60
|
+
return void 0;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
30
63
|
var ApiError = class extends Error {
|
|
31
64
|
constructor(status, path, message) {
|
|
32
65
|
super(`API ${status} @ ${path}: ${message}`);
|
|
33
66
|
this.status = status;
|
|
34
67
|
this.path = path;
|
|
35
68
|
this.name = "ApiError";
|
|
69
|
+
this.body = parseApiErrorBody(message);
|
|
36
70
|
}
|
|
37
71
|
status;
|
|
38
72
|
path;
|
|
73
|
+
/**
|
|
74
|
+
* The parsed response body, when it was JSON shaped like `{ error, details? }`
|
|
75
|
+
* (REB-764). `undefined` when the body was not JSON or didn't match — parsing
|
|
76
|
+
* is defensive and never throws. Read `body.details?.fieldErrors?.phone`
|
|
77
|
+
* instead of regex-stripping this error's `message`.
|
|
78
|
+
*/
|
|
79
|
+
body;
|
|
39
80
|
};
|
|
40
81
|
|
|
41
82
|
// src/client.ts
|
|
@@ -235,7 +276,15 @@ function makeShopNamespace(ctx) {
|
|
|
235
276
|
body
|
|
236
277
|
),
|
|
237
278
|
/** Check whether a customer with the given email already exists. */
|
|
238
|
-
checkEmail: (body) => requestMutation(ctx, "/api/site/shop/check-email", body)
|
|
279
|
+
checkEmail: (body) => requestMutation(ctx, "/api/site/shop/check-email", body),
|
|
280
|
+
/**
|
|
281
|
+
* Look up a confirmed (or still-pending) order by its Stripe Checkout
|
|
282
|
+
* session id — the order-confirmation flow (REB-829). `GET`, so it goes
|
|
283
|
+
* through `requestOne` like every other read, not `requestMutation`.
|
|
284
|
+
*/
|
|
285
|
+
order: (sessionId, cache) => requestOne(ctx, `/api/site/shop/order/${encodeURIComponent(sessionId)}`, {
|
|
286
|
+
cachePolicy: cache
|
|
287
|
+
})
|
|
239
288
|
};
|
|
240
289
|
}
|
|
241
290
|
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cache.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/leads.ts","../src/domains/legal.ts","../src/domains/pages.ts","../src/domains/shop.ts","../src/domains/trust-badges.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\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","/** 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","/**\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 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 }\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\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 via\n * `AbortController`, throws a typed `ApiError` on non-2xx, and returns the parsed\n * JSON body verbatim (envelope unwrapping is the caller's concern).\n */\nexport async function request<T>(\n ctx: RebluClientContext,\n path: string,\n opts: RequestOptions,\n): Promise<T> {\n const url = ctx.baseUrl + path\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), ctx.timeoutMs)\n\n try {\n const res = await ctx.fetch(url, {\n method: opts.method ?? 'GET',\n body: opts.body,\n signal: controller.signal,\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\n if (!res.ok) {\n throw new ApiError(res.status, path, await res.text())\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 * 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): Promise<Res> {\n const envelope = await request<{ data: Res }>(ctx, path, {\n method: 'POST',\n body: JSON.stringify(body),\n cachePolicy: { revalidate: false },\n })\n return envelope.data\n}\n","import type {\n AcademyCategoryPublic,\n AcademyCourseDetail,\n AcademyCourseSummary,\n} from '@reblu/site-contracts'\nimport { buildUrl, type QueryValue } from '../build-url'\nimport type { RebluCachePolicy } from '../cache'\nimport { request, requestList, requestOne, type RebluClientContext } 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 /** 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 * 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 enrollmentRequest: (\n body: AcademyEnrollmentRequestInput,\n ): Promise<AcademyEnrollmentRequestResult> =>\n request<AcademyEnrollmentRequestResult>(ctx, '/api/site/academy/enrollment-requests', {\n method: 'POST',\n body: JSON.stringify(body),\n cachePolicy: { revalidate: false },\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 } 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 /** Submit a contact form. Returns the created lead acknowledgement. */\n submit: (body: ContactFormInput): Promise<ContactFormSubmission> =>\n requestMutation<ContactFormInput, ContactFormSubmission>(ctx, '/api/site/contact-form', body),\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 { 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/** `GET /api/site/legal` — the tenant's legal content (privacy, terms, cookies). */\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 type { ListResult, PagesListParams } from '../types'\n\n/** Dynamic pages domain — `GET /api/site/pages/*`. */\nexport function makePagesNamespace(ctx: RebluClientContext) {\n return {\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 bySlug: (slug: string, cache?: RebluCachePolicy): Promise<PageDetail> =>\n requestOne<PageDetail>(ctx, `/api/site/pages/${slug}`, { cachePolicy: cache }),\n\n slugs: (cache?: RebluCachePolicy): Promise<ListResult<PageSlug>> =>\n requestList<PageSlug>(ctx, '/api/site/pages/slugs', { cachePolicy: cache }),\n }\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 ShopProductDetail,\n ShopProductListItem,\n ShopValidateCartInput,\n} from '@reblu/site-contracts'\nimport { buildUrl, type QueryValue } from '../build-url'\nimport type { RebluCachePolicy } from '../cache'\nimport { requestList, requestMutation, requestOne, type RebluClientContext } from '../client'\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, check-email) added in\n * 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 /** Start a hosted checkout session. Returns the Stripe URL + session id. */\n checkout: (body: ShopCheckoutInput): Promise<ShopCheckoutSession> =>\n requestMutation<ShopCheckoutInput, ShopCheckoutSession>(ctx, '/api/site/shop/checkout', body),\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 /** 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 /** Check whether a customer with the given email already exists. */\n checkEmail: (body: ShopCheckEmailInput): Promise<ShopEmailCheck> =>\n requestMutation<ShopCheckEmailInput, ShopEmailCheck>(ctx, '/api/site/shop/check-email', body),\n }\n}\n","import type { TrustBadge } from '@reblu/site-contracts'\nimport type { RebluCachePolicy } from '../cache'\nimport { requestList, type RebluClientContext } from '../client'\nimport type { ListResult } from '../types'\n\n/** `GET /api/site/trust-badges` — the tenant's trust badges (no pagination). */\nexport function makeTrustBadgesNamespace(ctx: RebluClientContext) {\n return {\n list: (cache?: RebluCachePolicy): Promise<ListResult<TrustBadge>> =>\n requestList<TrustBadge>(ctx, '/api/site/trust-badges', { cachePolicy: cache }),\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 } from './client'\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 { makeLeadsNamespace } from './domains/leads'\nimport { makeLegalNamespace } from './domains/legal'\nimport { makePagesNamespace } from './domains/pages'\nimport { makeShopNamespace } from './domains/shop'\nimport { makeTrustBadgesNamespace } from './domains/trust-badges'\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\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 }\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 }\n}\n\n/** The assembled client returned by {@link createRebluClient}. */\nexport type RebluClient = ReturnType<typeof createRebluClient>\n\nexport { ApiError } from './errors'\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'\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/cache.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/leads.ts","../src/domains/legal.ts","../src/domains/pages.ts","../src/domains/shop.ts","../src/domains/trust-badges.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\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","/** 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","/**\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\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\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[]`. 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 { fieldErrors?: unknown; formErrors?: unknown }\n\n if (details.fieldErrors !== undefined) {\n if (typeof details.fieldErrors !== 'object' || details.fieldErrors === null || Array.isArray(details.fieldErrors)) {\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 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 }\n if (typeof body.error !== 'string') return false\n if (body.details !== undefined && !isApiErrorDetails(body.details)) 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","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\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 via\n * `AbortController`, throws a typed `ApiError` on non-2xx, and returns the parsed\n * JSON body verbatim (envelope unwrapping is the caller's concern).\n */\nexport async function request<T>(\n ctx: RebluClientContext,\n path: string,\n opts: RequestOptions,\n): Promise<T> {\n const url = ctx.baseUrl + path\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), ctx.timeoutMs)\n\n try {\n const res = await ctx.fetch(url, {\n method: opts.method ?? 'GET',\n body: opts.body,\n signal: controller.signal,\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\n if (!res.ok) {\n throw new ApiError(res.status, path, await res.text())\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 * 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): Promise<Res> {\n const envelope = await request<{ data: Res }>(ctx, path, {\n method: 'POST',\n body: JSON.stringify(body),\n cachePolicy: { revalidate: false },\n })\n return envelope.data\n}\n","import type {\n AcademyCategoryPublic,\n AcademyCourseDetail,\n AcademyCourseSummary,\n} from '@reblu/site-contracts'\nimport { buildUrl, type QueryValue } from '../build-url'\nimport type { RebluCachePolicy } from '../cache'\nimport { request, requestList, requestOne, type RebluClientContext } 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 /** 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 * 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 enrollmentRequest: (\n body: AcademyEnrollmentRequestInput,\n ): Promise<AcademyEnrollmentRequestResult> =>\n request<AcademyEnrollmentRequestResult>(ctx, '/api/site/academy/enrollment-requests', {\n method: 'POST',\n body: JSON.stringify(body),\n cachePolicy: { revalidate: false },\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 } 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 /** Submit a contact form. Returns the created lead acknowledgement. */\n submit: (body: ContactFormInput): Promise<ContactFormSubmission> =>\n requestMutation<ContactFormInput, ContactFormSubmission>(ctx, '/api/site/contact-form', body),\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 { 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/** `GET /api/site/legal` — the tenant's legal content (privacy, terms, cookies). */\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 type { ListResult, PagesListParams } from '../types'\n\n/** Dynamic pages domain — `GET /api/site/pages/*`. */\nexport function makePagesNamespace(ctx: RebluClientContext) {\n return {\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 bySlug: (slug: string, cache?: RebluCachePolicy): Promise<PageDetail> =>\n requestOne<PageDetail>(ctx, `/api/site/pages/${slug}`, { cachePolicy: cache }),\n\n slugs: (cache?: RebluCachePolicy): Promise<ListResult<PageSlug>> =>\n requestList<PageSlug>(ctx, '/api/site/pages/slugs', { cachePolicy: cache }),\n }\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 ShopValidateCartInput,\n} from '@reblu/site-contracts'\nimport { buildUrl, type QueryValue } from '../build-url'\nimport type { RebluCachePolicy } from '../cache'\nimport { requestList, requestMutation, requestOne, type RebluClientContext } from '../client'\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, check-email) added in\n * 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 /** Start a hosted checkout session. Returns the Stripe URL + session id. */\n checkout: (body: ShopCheckoutInput): Promise<ShopCheckoutSession> =>\n requestMutation<ShopCheckoutInput, ShopCheckoutSession>(ctx, '/api/site/shop/checkout', body),\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 /** 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 /** Check whether a customer with the given email already exists. */\n checkEmail: (body: ShopCheckEmailInput): Promise<ShopEmailCheck> =>\n requestMutation<ShopCheckEmailInput, ShopEmailCheck>(ctx, '/api/site/shop/check-email', body),\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 order: (sessionId: string, cache?: RebluCachePolicy): Promise<ShopOrder> =>\n requestOne<ShopOrder>(ctx, `/api/site/shop/order/${encodeURIComponent(sessionId)}`, {\n cachePolicy: cache,\n }),\n }\n}\n","import type { TrustBadge } from '@reblu/site-contracts'\nimport type { RebluCachePolicy } from '../cache'\nimport { requestList, type RebluClientContext } from '../client'\nimport type { ListResult } from '../types'\n\n/** `GET /api/site/trust-badges` — the tenant's trust badges (no pagination). */\nexport function makeTrustBadgesNamespace(ctx: RebluClientContext) {\n return {\n list: (cache?: RebluCachePolicy): Promise<ListResult<TrustBadge>> =>\n requestList<TrustBadge>(ctx, '/api/site/trust-badges', { cachePolicy: cache }),\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 } from './client'\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 { makeLeadsNamespace } from './domains/leads'\nimport { makeLegalNamespace } from './domains/legal'\nimport { makePagesNamespace } from './domains/pages'\nimport { makeShopNamespace } from './domains/shop'\nimport { makeTrustBadgesNamespace } from './domains/trust-badges'\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\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 }\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 }\n}\n\n/** The assembled client returned by {@link createRebluClient}. */\nexport type RebluClient = ReturnType<typeof createRebluClient>\n\nexport { ApiError } from './errors'\nexport type { ApiErrorBody, ApiErrorDetails } from './errors'\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'\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reblu/site-client",
|
|
3
|
-
"version": "5.0
|
|
3
|
+
"version": "5.2.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.0
|
|
50
|
+
"@reblu/site-contracts": "^1.5.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.0
|
|
61
|
+
"@reblu/site-contracts": "^1.5.0"
|
|
62
62
|
},
|
|
63
63
|
"size-limit": [
|
|
64
64
|
{
|