@reblu/site-client 0.1.0 → 0.1.2

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/dist/index.cjs ADDED
@@ -0,0 +1,247 @@
1
+ 'use strict';
2
+
3
+ // src/cache.ts
4
+ var PlainCacheAdapter = class {
5
+ constructor(defaultRevalidate = false) {
6
+ this.defaultRevalidate = defaultRevalidate;
7
+ }
8
+ defaultRevalidate;
9
+ resolveRequestInit(policy) {
10
+ const revalidate = policy.revalidate ?? this.defaultRevalidate;
11
+ return revalidate === false || revalidate === 0 ? { cache: "no-store" } : { cache: "force-cache" };
12
+ }
13
+ };
14
+
15
+ // src/errors.ts
16
+ var ApiError = class extends Error {
17
+ constructor(status, path, message) {
18
+ super(`API ${status} @ ${path}: ${message}`);
19
+ this.status = status;
20
+ this.path = path;
21
+ this.name = "ApiError";
22
+ }
23
+ status;
24
+ path;
25
+ };
26
+
27
+ // src/client.ts
28
+ async function request(ctx, path, opts) {
29
+ const url = ctx.baseUrl + path;
30
+ const controller = new AbortController();
31
+ const timer = setTimeout(() => controller.abort(), ctx.timeoutMs);
32
+ try {
33
+ const res = await ctx.fetch(url, {
34
+ method: opts.method ?? "GET",
35
+ body: opts.body,
36
+ signal: controller.signal,
37
+ headers: {
38
+ "X-Reblu-Api-Key": ctx.apiKey,
39
+ "X-Reblu-Api-Version": ctx.apiVersion,
40
+ "Content-Type": "application/json",
41
+ ...opts.headers
42
+ },
43
+ ...ctx.cache.resolveRequestInit(opts.cachePolicy ?? {})
44
+ });
45
+ if (!res.ok) {
46
+ throw new ApiError(res.status, path, await res.text());
47
+ }
48
+ return await res.json();
49
+ } finally {
50
+ clearTimeout(timer);
51
+ }
52
+ }
53
+ async function requestOne(ctx, path, opts) {
54
+ const body = await request(ctx, path, opts);
55
+ return body.data;
56
+ }
57
+ async function requestList(ctx, path, opts) {
58
+ const body = await request(ctx, path, opts);
59
+ return { data: body.data, meta: body.meta };
60
+ }
61
+ async function requestMutation(ctx, path, body) {
62
+ const envelope = await request(ctx, path, {
63
+ method: "POST",
64
+ body: JSON.stringify(body),
65
+ cachePolicy: { revalidate: false }
66
+ });
67
+ return envelope.data;
68
+ }
69
+
70
+ // src/domains/analytics.ts
71
+ function makeAnalyticsNamespace(ctx) {
72
+ return {
73
+ get: (cache) => requestOne(ctx, "/api/site/analytics", { cachePolicy: cache })
74
+ };
75
+ }
76
+
77
+ // src/build-url.ts
78
+ function buildUrl(base, params) {
79
+ if (!params) return base;
80
+ const pairs = [];
81
+ for (const [key, value] of Object.entries(params)) {
82
+ const values = Array.isArray(value) ? value : [value];
83
+ for (const v of values) {
84
+ if (v === void 0 || v === null) continue;
85
+ pairs.push([key, String(v)]);
86
+ }
87
+ }
88
+ if (pairs.length === 0) return base;
89
+ const qs = new URLSearchParams(pairs).toString();
90
+ return `${base}?${qs}`;
91
+ }
92
+
93
+ // src/domains/blog.ts
94
+ function makeBlogNamespace(ctx) {
95
+ return {
96
+ list: (params, cache) => requestList(
97
+ ctx,
98
+ buildUrl("/api/site/blog", params),
99
+ { cachePolicy: cache }
100
+ ),
101
+ bySlug: (slug, cache) => requestOne(ctx, `/api/site/blog/${slug}`, { cachePolicy: cache }),
102
+ related: (slug, cache) => requestList(ctx, `/api/site/blog/${slug}/related`, { cachePolicy: cache }),
103
+ categories: (cache) => requestList(ctx, "/api/site/blog/categories", { cachePolicy: cache }),
104
+ slugs: (cache) => requestList(ctx, "/api/site/blog/slugs", { cachePolicy: cache }),
105
+ /** Draft preview — never cached (unpublished content). */
106
+ preview: (id) => requestOne(ctx, `/api/site/blog/preview/${id}`, {
107
+ cachePolicy: { revalidate: false }
108
+ })
109
+ };
110
+ }
111
+
112
+ // src/domains/config.ts
113
+ function makeConfigNamespace(ctx) {
114
+ return {
115
+ get: (cache) => requestOne(ctx, "/api/site/config", { cachePolicy: cache })
116
+ };
117
+ }
118
+
119
+ // src/domains/contact-form.ts
120
+ function makeContactFormNamespace(ctx) {
121
+ return {
122
+ /** Submit a contact form. Returns the created lead acknowledgement. */
123
+ submit: (body) => requestMutation(ctx, "/api/site/contact-form", body)
124
+ };
125
+ }
126
+
127
+ // src/domains/ctas.ts
128
+ function makeCtasNamespace(ctx) {
129
+ return {
130
+ list: (slot, cache) => requestList(ctx, buildUrl("/api/site/ctas", { slot }), { cachePolicy: cache })
131
+ };
132
+ }
133
+
134
+ // src/domains/leads.ts
135
+ function makeLeadsNamespace(ctx) {
136
+ return {
137
+ /** Create a lead. Returns the created lead acknowledgement. */
138
+ create: (body) => requestMutation(ctx, "/api/site/leads", body)
139
+ };
140
+ }
141
+
142
+ // src/domains/legal.ts
143
+ function makeLegalNamespace(ctx) {
144
+ return {
145
+ get: (cache) => requestOne(ctx, "/api/site/legal", { cachePolicy: cache })
146
+ };
147
+ }
148
+
149
+ // src/domains/pages.ts
150
+ function makePagesNamespace(ctx) {
151
+ return {
152
+ list: (params, cache) => requestList(
153
+ ctx,
154
+ buildUrl("/api/site/pages", params),
155
+ { cachePolicy: cache }
156
+ ),
157
+ bySlug: (slug, cache) => requestOne(ctx, `/api/site/pages/${slug}`, { cachePolicy: cache }),
158
+ slugs: (cache) => requestList(ctx, "/api/site/pages/slugs", { cachePolicy: cache })
159
+ };
160
+ }
161
+
162
+ // src/domains/shop.ts
163
+ function flattenShopParams(params) {
164
+ if (!params) return {};
165
+ const { taxonomy, attr, ...scalars } = params;
166
+ const out = { ...scalars };
167
+ if (taxonomy) {
168
+ for (const [key, value] of Object.entries(taxonomy)) out[`taxonomy[${key}]`] = value;
169
+ }
170
+ if (attr) {
171
+ for (const [key, value] of Object.entries(attr)) out[`attr[${key}]`] = value;
172
+ }
173
+ return out;
174
+ }
175
+ function makeShopNamespace(ctx) {
176
+ return {
177
+ products: (params, cache) => requestList(
178
+ ctx,
179
+ buildUrl("/api/site/shop/products", flattenShopParams(params)),
180
+ { cachePolicy: cache }
181
+ ),
182
+ /** Available facets (brand/attributes/price/on-sale) for the given filters. */
183
+ facets: (params, cache) => requestOne(ctx, buildUrl("/api/site/shop/facets", flattenShopParams(params)), {
184
+ cachePolicy: cache
185
+ }),
186
+ bySlug: (slug, cache) => requestOne(ctx, `/api/site/shop/products/${slug}`, { cachePolicy: cache }),
187
+ categories: (cache) => requestList(ctx, "/api/site/shop/categories", { cachePolicy: cache }),
188
+ config: (cache) => requestOne(ctx, "/api/site/shop/config", { cachePolicy: cache }),
189
+ // ── Mutations (REB-438) ──────────────────────────────────────────────────
190
+ /** Start a hosted checkout session. Returns the Stripe URL + session id. */
191
+ checkout: (body) => requestMutation(ctx, "/api/site/shop/checkout", body),
192
+ /** Validate a cart against live product/stock/price state before checkout. */
193
+ validateCart: (body) => requestMutation(
194
+ ctx,
195
+ "/api/site/shop/validate-cart",
196
+ body
197
+ ),
198
+ /** Validate a discount code against the current cart subtotal. */
199
+ applyDiscount: (body) => requestMutation(
200
+ ctx,
201
+ "/api/site/shop/apply-discount",
202
+ body
203
+ ),
204
+ /** Check whether a customer with the given email already exists. */
205
+ checkEmail: (body) => requestMutation(ctx, "/api/site/shop/check-email", body)
206
+ };
207
+ }
208
+
209
+ // src/domains/trust-badges.ts
210
+ function makeTrustBadgesNamespace(ctx) {
211
+ return {
212
+ list: (cache) => requestList(ctx, "/api/site/trust-badges", { cachePolicy: cache })
213
+ };
214
+ }
215
+
216
+ // src/index.ts
217
+ var DEFAULT_API_VERSION = "1";
218
+ var DEFAULT_TIMEOUT_MS = 8e3;
219
+ function createRebluClient(config) {
220
+ const boundFetch = config.fetch ?? ((input, init) => fetch(input, init));
221
+ const ctx = {
222
+ baseUrl: config.baseUrl,
223
+ apiKey: config.apiKey,
224
+ apiVersion: config.apiVersion ?? DEFAULT_API_VERSION,
225
+ cache: config.cache ?? new PlainCacheAdapter(),
226
+ timeoutMs: config.timeoutMs ?? DEFAULT_TIMEOUT_MS,
227
+ fetch: boundFetch
228
+ };
229
+ return {
230
+ config: makeConfigNamespace(ctx),
231
+ analytics: makeAnalyticsNamespace(ctx),
232
+ legal: makeLegalNamespace(ctx),
233
+ trustBadges: makeTrustBadgesNamespace(ctx),
234
+ ctas: makeCtasNamespace(ctx),
235
+ blog: makeBlogNamespace(ctx),
236
+ pages: makePagesNamespace(ctx),
237
+ shop: makeShopNamespace(ctx),
238
+ contactForm: makeContactFormNamespace(ctx),
239
+ leads: makeLeadsNamespace(ctx)
240
+ };
241
+ }
242
+
243
+ exports.ApiError = ApiError;
244
+ exports.PlainCacheAdapter = PlainCacheAdapter;
245
+ exports.createRebluClient = createRebluClient;
246
+ //# sourceMappingURL=index.cjs.map
247
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cache.ts","../src/errors.ts","../src/client.ts","../src/domains/analytics.ts","../src/build-url.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","/**\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 { 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","/** A value that can be serialized into a query string. */\nexport type QueryValue = string | number | boolean | undefined | null\n\n/**\n * Append a query string to `base`, dropping `undefined`/`null` values.\n *\n * Single definition for the whole client (design doc §3.1 dedupes the two copies\n * that lived in demo-site's `client.ts` and `index.ts`). Falsy-but-meaningful\n * values (`0`, `false`, `''`) are preserved; only `undefined`/`null` are omitted.\n * Array values (REB-475 faceted `attr[key]`) emit one repeated param per element.\n */\nexport function buildUrl(\n base: string,\n params?: Record<string, QueryValue | QueryValue[]>,\n): string {\n if (!params) return base\n const pairs: [string, string][] = []\n for (const [key, value] of Object.entries(params)) {\n const values = Array.isArray(value) ? value : [value]\n for (const v of values) {\n if (v === undefined || v === null) continue\n pairs.push([key, String(v)])\n }\n }\n if (pairs.length === 0) return base\n const qs = new URLSearchParams(pairs).toString()\n return `${base}?${qs}`\n}\n","import type {\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 buildUrl('/api/site/pages', params as Record<string, 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 { 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 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 { ListResult, BlogListParams, PagesListParams, ShopProductsParams } from './types'\n"]}
@@ -0,0 +1,157 @@
1
+ import * as _reblu_site_contracts from '@reblu/site-contracts';
2
+ import { PaginationMeta, LegacyPageType, ShopSort } from '@reblu/site-contracts';
3
+
4
+ /**
5
+ * The shape every list method returns. Unlike demo-site's old bare-array return,
6
+ * the SDK surfaces `meta` so pagination is never silently dropped (design doc
7
+ * §3.4, fixes the §3.1 bug). `meta` is absent for non-paginated list endpoints
8
+ * (categories, slugs, ctas, trust-badges, related).
9
+ */
10
+ interface ListResult<T> {
11
+ data: T[];
12
+ meta?: PaginationMeta;
13
+ }
14
+ /** Query params for `blog.list` (grounded on `GET /api/site/blog`). */
15
+ interface BlogListParams {
16
+ page?: number;
17
+ limit?: number;
18
+ category?: string;
19
+ tag?: string;
20
+ }
21
+ /** Query params for `pages.list` (grounded on `GET /api/site/pages`). */
22
+ interface PagesListParams {
23
+ type?: LegacyPageType;
24
+ category?: string;
25
+ }
26
+ /**
27
+ * Query params for `shop.products` / `shop.facets` (REB-474/475). All agnostic
28
+ * primitives. `taxonomy` maps an axis key to a term slug (`{ brand: "acme" }`);
29
+ * `attr` maps a field key to value(s). The SDK flattens both into repeatable
30
+ * `taxonomy[key]` / `attr[key]` query params.
31
+ */
32
+ interface ShopProductsParams {
33
+ page?: number;
34
+ limit?: number;
35
+ category?: string;
36
+ q?: string;
37
+ sort?: ShopSort;
38
+ priceMin?: number;
39
+ priceMax?: number;
40
+ onSale?: boolean;
41
+ taxonomy?: Record<string, string>;
42
+ attr?: Record<string, string | string[]>;
43
+ }
44
+
45
+ /**
46
+ * Cache adapter — the seam that severs the SDK from any framework's caching
47
+ * model (design doc D4 / §3.2). The client owns *when* to cache (a per-call
48
+ * `RebluCachePolicy`); the adapter owns *how* (the framework-specific
49
+ * `RequestInit` fields). `@reblu/site-client` ships the framework-agnostic
50
+ * `PlainCacheAdapter`; `@reblu/site-next` (REB-418) supplies a `NextCacheAdapter`
51
+ * that returns `{ next: { revalidate } }`.
52
+ */
53
+ interface RebluCachePolicy {
54
+ /**
55
+ * Seconds to revalidate. `undefined` → use the adapter's default; `0`/`false`
56
+ * → never cache (always fetch fresh).
57
+ */
58
+ revalidate?: number | false;
59
+ }
60
+ interface RebluCacheAdapter {
61
+ /** Map a per-call cache policy to framework-specific `fetch` `RequestInit` fields. */
62
+ resolveRequestInit(policy: RebluCachePolicy): RequestInit;
63
+ }
64
+ /**
65
+ * Default adapter — plain `fetch` cache semantics that work in Node, edge,
66
+ * browser and tests. No `next`, no `server-only`.
67
+ */
68
+ declare class PlainCacheAdapter implements RebluCacheAdapter {
69
+ private readonly defaultRevalidate;
70
+ constructor(defaultRevalidate?: number | false);
71
+ resolveRequestInit(policy: RebluCachePolicy): RequestInit;
72
+ }
73
+
74
+ /**
75
+ * Typed transport error for the Reblu SITE API.
76
+ *
77
+ * Every non-2xx response throws an `ApiError` (never a bare `fetch` rejection),
78
+ * so consumers can `catch (e) { if (e instanceof ApiError) … }` and fall back
79
+ * gracefully (e.g. render an empty section) — the one behavioural contract the
80
+ * SITE relies on. Moved verbatim from demo-site's `client.ts` (design doc §3.4).
81
+ */
82
+ declare class ApiError extends Error {
83
+ readonly status: number;
84
+ readonly path: string;
85
+ constructor(status: number, path: string, message: string);
86
+ }
87
+
88
+ interface RebluClientConfig {
89
+ /** Absolute origin of the Reblu SITE API, e.g. `https://api.reblu.app`. */
90
+ baseUrl: string;
91
+ /** Tenant SITE API key (`rbl_…`), sent as `X-Reblu-Api-Key`. */
92
+ apiKey: string;
93
+ /** Contract version sent as `X-Reblu-Api-Version`. Defaults to `'1'`. */
94
+ apiVersion?: string;
95
+ /** Cache strategy. Defaults to `new PlainCacheAdapter()` (no-store). */
96
+ cache?: RebluCacheAdapter;
97
+ /** Per-request hard timeout in ms. Defaults to `8000`. */
98
+ timeoutMs?: number;
99
+ /** Injectable `fetch` — for tests, custom runtimes, or instrumentation. */
100
+ fetch?: typeof fetch;
101
+ }
102
+ /**
103
+ * Build a Reblu SITE API client. Each domain is a small namespace of bound
104
+ * methods assembled from a separate module, so a consumer importing only a
105
+ * couple of domains can tree-shake the rest.
106
+ */
107
+ declare function createRebluClient(config: RebluClientConfig): {
108
+ config: {
109
+ get: (cache?: RebluCachePolicy) => Promise<_reblu_site_contracts.Config>;
110
+ };
111
+ analytics: {
112
+ get: (cache?: RebluCachePolicy) => Promise<_reblu_site_contracts.Analytics>;
113
+ };
114
+ legal: {
115
+ get: (cache?: RebluCachePolicy) => Promise<_reblu_site_contracts.Legal>;
116
+ };
117
+ trustBadges: {
118
+ list: (cache?: RebluCachePolicy) => Promise<ListResult<_reblu_site_contracts.TrustBadge>>;
119
+ };
120
+ ctas: {
121
+ list: (slot: string, cache?: RebluCachePolicy) => Promise<ListResult<_reblu_site_contracts.Cta>>;
122
+ };
123
+ blog: {
124
+ list: (params?: BlogListParams, cache?: RebluCachePolicy) => Promise<ListResult<_reblu_site_contracts.BlogPostListItem>>;
125
+ bySlug: (slug: string, cache?: RebluCachePolicy) => Promise<_reblu_site_contracts.BlogPostDetail>;
126
+ related: (slug: string, cache?: RebluCachePolicy) => Promise<ListResult<_reblu_site_contracts.BlogPostListItem>>;
127
+ categories: (cache?: RebluCachePolicy) => Promise<ListResult<_reblu_site_contracts.BlogCategoryWithCount>>;
128
+ slugs: (cache?: RebluCachePolicy) => Promise<ListResult<_reblu_site_contracts.BlogSlug>>;
129
+ preview: (id: string) => Promise<_reblu_site_contracts.BlogPostPreview>;
130
+ };
131
+ pages: {
132
+ list: (params?: PagesListParams, cache?: RebluCachePolicy) => Promise<ListResult<_reblu_site_contracts.PageListItem>>;
133
+ bySlug: (slug: string, cache?: RebluCachePolicy) => Promise<_reblu_site_contracts.PageDetail>;
134
+ slugs: (cache?: RebluCachePolicy) => Promise<ListResult<_reblu_site_contracts.PageSlug>>;
135
+ };
136
+ shop: {
137
+ products: (params?: ShopProductsParams, cache?: RebluCachePolicy) => Promise<ListResult<_reblu_site_contracts.ShopProductListItem>>;
138
+ facets: (params?: ShopProductsParams, cache?: RebluCachePolicy) => Promise<_reblu_site_contracts.ShopFacets>;
139
+ bySlug: (slug: string, cache?: RebluCachePolicy) => Promise<_reblu_site_contracts.ShopProductDetail>;
140
+ categories: (cache?: RebluCachePolicy) => Promise<ListResult<_reblu_site_contracts.ShopCategory>>;
141
+ config: (cache?: RebluCachePolicy) => Promise<_reblu_site_contracts.ShopConfig>;
142
+ checkout: (body: _reblu_site_contracts.ShopCheckoutInput) => Promise<_reblu_site_contracts.ShopCheckoutSession>;
143
+ validateCart: (body: _reblu_site_contracts.ShopValidateCartInput) => Promise<_reblu_site_contracts.ShopCartValidation>;
144
+ applyDiscount: (body: _reblu_site_contracts.ShopApplyDiscountInput) => Promise<_reblu_site_contracts.ShopDiscountApplication>;
145
+ checkEmail: (body: _reblu_site_contracts.ShopCheckEmailInput) => Promise<_reblu_site_contracts.ShopEmailCheck>;
146
+ };
147
+ contactForm: {
148
+ submit: (body: _reblu_site_contracts.ContactFormInput) => Promise<_reblu_site_contracts.ContactFormSubmission>;
149
+ };
150
+ leads: {
151
+ create: (body: _reblu_site_contracts.LeadInput) => Promise<_reblu_site_contracts.LeadSubmission>;
152
+ };
153
+ };
154
+ /** The assembled client returned by {@link createRebluClient}. */
155
+ type RebluClient = ReturnType<typeof createRebluClient>;
156
+
157
+ export { ApiError, type BlogListParams, type ListResult, type PagesListParams, PlainCacheAdapter, type RebluCacheAdapter, type RebluCachePolicy, type RebluClient, type RebluClientConfig, type ShopProductsParams, createRebluClient };
@@ -0,0 +1,157 @@
1
+ import * as _reblu_site_contracts from '@reblu/site-contracts';
2
+ import { PaginationMeta, LegacyPageType, ShopSort } from '@reblu/site-contracts';
3
+
4
+ /**
5
+ * The shape every list method returns. Unlike demo-site's old bare-array return,
6
+ * the SDK surfaces `meta` so pagination is never silently dropped (design doc
7
+ * §3.4, fixes the §3.1 bug). `meta` is absent for non-paginated list endpoints
8
+ * (categories, slugs, ctas, trust-badges, related).
9
+ */
10
+ interface ListResult<T> {
11
+ data: T[];
12
+ meta?: PaginationMeta;
13
+ }
14
+ /** Query params for `blog.list` (grounded on `GET /api/site/blog`). */
15
+ interface BlogListParams {
16
+ page?: number;
17
+ limit?: number;
18
+ category?: string;
19
+ tag?: string;
20
+ }
21
+ /** Query params for `pages.list` (grounded on `GET /api/site/pages`). */
22
+ interface PagesListParams {
23
+ type?: LegacyPageType;
24
+ category?: string;
25
+ }
26
+ /**
27
+ * Query params for `shop.products` / `shop.facets` (REB-474/475). All agnostic
28
+ * primitives. `taxonomy` maps an axis key to a term slug (`{ brand: "acme" }`);
29
+ * `attr` maps a field key to value(s). The SDK flattens both into repeatable
30
+ * `taxonomy[key]` / `attr[key]` query params.
31
+ */
32
+ interface ShopProductsParams {
33
+ page?: number;
34
+ limit?: number;
35
+ category?: string;
36
+ q?: string;
37
+ sort?: ShopSort;
38
+ priceMin?: number;
39
+ priceMax?: number;
40
+ onSale?: boolean;
41
+ taxonomy?: Record<string, string>;
42
+ attr?: Record<string, string | string[]>;
43
+ }
44
+
45
+ /**
46
+ * Cache adapter — the seam that severs the SDK from any framework's caching
47
+ * model (design doc D4 / §3.2). The client owns *when* to cache (a per-call
48
+ * `RebluCachePolicy`); the adapter owns *how* (the framework-specific
49
+ * `RequestInit` fields). `@reblu/site-client` ships the framework-agnostic
50
+ * `PlainCacheAdapter`; `@reblu/site-next` (REB-418) supplies a `NextCacheAdapter`
51
+ * that returns `{ next: { revalidate } }`.
52
+ */
53
+ interface RebluCachePolicy {
54
+ /**
55
+ * Seconds to revalidate. `undefined` → use the adapter's default; `0`/`false`
56
+ * → never cache (always fetch fresh).
57
+ */
58
+ revalidate?: number | false;
59
+ }
60
+ interface RebluCacheAdapter {
61
+ /** Map a per-call cache policy to framework-specific `fetch` `RequestInit` fields. */
62
+ resolveRequestInit(policy: RebluCachePolicy): RequestInit;
63
+ }
64
+ /**
65
+ * Default adapter — plain `fetch` cache semantics that work in Node, edge,
66
+ * browser and tests. No `next`, no `server-only`.
67
+ */
68
+ declare class PlainCacheAdapter implements RebluCacheAdapter {
69
+ private readonly defaultRevalidate;
70
+ constructor(defaultRevalidate?: number | false);
71
+ resolveRequestInit(policy: RebluCachePolicy): RequestInit;
72
+ }
73
+
74
+ /**
75
+ * Typed transport error for the Reblu SITE API.
76
+ *
77
+ * Every non-2xx response throws an `ApiError` (never a bare `fetch` rejection),
78
+ * so consumers can `catch (e) { if (e instanceof ApiError) … }` and fall back
79
+ * gracefully (e.g. render an empty section) — the one behavioural contract the
80
+ * SITE relies on. Moved verbatim from demo-site's `client.ts` (design doc §3.4).
81
+ */
82
+ declare class ApiError extends Error {
83
+ readonly status: number;
84
+ readonly path: string;
85
+ constructor(status: number, path: string, message: string);
86
+ }
87
+
88
+ interface RebluClientConfig {
89
+ /** Absolute origin of the Reblu SITE API, e.g. `https://api.reblu.app`. */
90
+ baseUrl: string;
91
+ /** Tenant SITE API key (`rbl_…`), sent as `X-Reblu-Api-Key`. */
92
+ apiKey: string;
93
+ /** Contract version sent as `X-Reblu-Api-Version`. Defaults to `'1'`. */
94
+ apiVersion?: string;
95
+ /** Cache strategy. Defaults to `new PlainCacheAdapter()` (no-store). */
96
+ cache?: RebluCacheAdapter;
97
+ /** Per-request hard timeout in ms. Defaults to `8000`. */
98
+ timeoutMs?: number;
99
+ /** Injectable `fetch` — for tests, custom runtimes, or instrumentation. */
100
+ fetch?: typeof fetch;
101
+ }
102
+ /**
103
+ * Build a Reblu SITE API client. Each domain is a small namespace of bound
104
+ * methods assembled from a separate module, so a consumer importing only a
105
+ * couple of domains can tree-shake the rest.
106
+ */
107
+ declare function createRebluClient(config: RebluClientConfig): {
108
+ config: {
109
+ get: (cache?: RebluCachePolicy) => Promise<_reblu_site_contracts.Config>;
110
+ };
111
+ analytics: {
112
+ get: (cache?: RebluCachePolicy) => Promise<_reblu_site_contracts.Analytics>;
113
+ };
114
+ legal: {
115
+ get: (cache?: RebluCachePolicy) => Promise<_reblu_site_contracts.Legal>;
116
+ };
117
+ trustBadges: {
118
+ list: (cache?: RebluCachePolicy) => Promise<ListResult<_reblu_site_contracts.TrustBadge>>;
119
+ };
120
+ ctas: {
121
+ list: (slot: string, cache?: RebluCachePolicy) => Promise<ListResult<_reblu_site_contracts.Cta>>;
122
+ };
123
+ blog: {
124
+ list: (params?: BlogListParams, cache?: RebluCachePolicy) => Promise<ListResult<_reblu_site_contracts.BlogPostListItem>>;
125
+ bySlug: (slug: string, cache?: RebluCachePolicy) => Promise<_reblu_site_contracts.BlogPostDetail>;
126
+ related: (slug: string, cache?: RebluCachePolicy) => Promise<ListResult<_reblu_site_contracts.BlogPostListItem>>;
127
+ categories: (cache?: RebluCachePolicy) => Promise<ListResult<_reblu_site_contracts.BlogCategoryWithCount>>;
128
+ slugs: (cache?: RebluCachePolicy) => Promise<ListResult<_reblu_site_contracts.BlogSlug>>;
129
+ preview: (id: string) => Promise<_reblu_site_contracts.BlogPostPreview>;
130
+ };
131
+ pages: {
132
+ list: (params?: PagesListParams, cache?: RebluCachePolicy) => Promise<ListResult<_reblu_site_contracts.PageListItem>>;
133
+ bySlug: (slug: string, cache?: RebluCachePolicy) => Promise<_reblu_site_contracts.PageDetail>;
134
+ slugs: (cache?: RebluCachePolicy) => Promise<ListResult<_reblu_site_contracts.PageSlug>>;
135
+ };
136
+ shop: {
137
+ products: (params?: ShopProductsParams, cache?: RebluCachePolicy) => Promise<ListResult<_reblu_site_contracts.ShopProductListItem>>;
138
+ facets: (params?: ShopProductsParams, cache?: RebluCachePolicy) => Promise<_reblu_site_contracts.ShopFacets>;
139
+ bySlug: (slug: string, cache?: RebluCachePolicy) => Promise<_reblu_site_contracts.ShopProductDetail>;
140
+ categories: (cache?: RebluCachePolicy) => Promise<ListResult<_reblu_site_contracts.ShopCategory>>;
141
+ config: (cache?: RebluCachePolicy) => Promise<_reblu_site_contracts.ShopConfig>;
142
+ checkout: (body: _reblu_site_contracts.ShopCheckoutInput) => Promise<_reblu_site_contracts.ShopCheckoutSession>;
143
+ validateCart: (body: _reblu_site_contracts.ShopValidateCartInput) => Promise<_reblu_site_contracts.ShopCartValidation>;
144
+ applyDiscount: (body: _reblu_site_contracts.ShopApplyDiscountInput) => Promise<_reblu_site_contracts.ShopDiscountApplication>;
145
+ checkEmail: (body: _reblu_site_contracts.ShopCheckEmailInput) => Promise<_reblu_site_contracts.ShopEmailCheck>;
146
+ };
147
+ contactForm: {
148
+ submit: (body: _reblu_site_contracts.ContactFormInput) => Promise<_reblu_site_contracts.ContactFormSubmission>;
149
+ };
150
+ leads: {
151
+ create: (body: _reblu_site_contracts.LeadInput) => Promise<_reblu_site_contracts.LeadSubmission>;
152
+ };
153
+ };
154
+ /** The assembled client returned by {@link createRebluClient}. */
155
+ type RebluClient = ReturnType<typeof createRebluClient>;
156
+
157
+ export { ApiError, type BlogListParams, type ListResult, type PagesListParams, PlainCacheAdapter, type RebluCacheAdapter, type RebluCachePolicy, type RebluClient, type RebluClientConfig, type ShopProductsParams, createRebluClient };
package/dist/index.js ADDED
@@ -0,0 +1,243 @@
1
+ // src/cache.ts
2
+ var PlainCacheAdapter = class {
3
+ constructor(defaultRevalidate = false) {
4
+ this.defaultRevalidate = defaultRevalidate;
5
+ }
6
+ defaultRevalidate;
7
+ resolveRequestInit(policy) {
8
+ const revalidate = policy.revalidate ?? this.defaultRevalidate;
9
+ return revalidate === false || revalidate === 0 ? { cache: "no-store" } : { cache: "force-cache" };
10
+ }
11
+ };
12
+
13
+ // src/errors.ts
14
+ var ApiError = class extends Error {
15
+ constructor(status, path, message) {
16
+ super(`API ${status} @ ${path}: ${message}`);
17
+ this.status = status;
18
+ this.path = path;
19
+ this.name = "ApiError";
20
+ }
21
+ status;
22
+ path;
23
+ };
24
+
25
+ // src/client.ts
26
+ async function request(ctx, path, opts) {
27
+ const url = ctx.baseUrl + path;
28
+ const controller = new AbortController();
29
+ const timer = setTimeout(() => controller.abort(), ctx.timeoutMs);
30
+ try {
31
+ const res = await ctx.fetch(url, {
32
+ method: opts.method ?? "GET",
33
+ body: opts.body,
34
+ signal: controller.signal,
35
+ headers: {
36
+ "X-Reblu-Api-Key": ctx.apiKey,
37
+ "X-Reblu-Api-Version": ctx.apiVersion,
38
+ "Content-Type": "application/json",
39
+ ...opts.headers
40
+ },
41
+ ...ctx.cache.resolveRequestInit(opts.cachePolicy ?? {})
42
+ });
43
+ if (!res.ok) {
44
+ throw new ApiError(res.status, path, await res.text());
45
+ }
46
+ return await res.json();
47
+ } finally {
48
+ clearTimeout(timer);
49
+ }
50
+ }
51
+ async function requestOne(ctx, path, opts) {
52
+ const body = await request(ctx, path, opts);
53
+ return body.data;
54
+ }
55
+ async function requestList(ctx, path, opts) {
56
+ const body = await request(ctx, path, opts);
57
+ return { data: body.data, meta: body.meta };
58
+ }
59
+ async function requestMutation(ctx, path, body) {
60
+ const envelope = await request(ctx, path, {
61
+ method: "POST",
62
+ body: JSON.stringify(body),
63
+ cachePolicy: { revalidate: false }
64
+ });
65
+ return envelope.data;
66
+ }
67
+
68
+ // src/domains/analytics.ts
69
+ function makeAnalyticsNamespace(ctx) {
70
+ return {
71
+ get: (cache) => requestOne(ctx, "/api/site/analytics", { cachePolicy: cache })
72
+ };
73
+ }
74
+
75
+ // src/build-url.ts
76
+ function buildUrl(base, params) {
77
+ if (!params) return base;
78
+ const pairs = [];
79
+ for (const [key, value] of Object.entries(params)) {
80
+ const values = Array.isArray(value) ? value : [value];
81
+ for (const v of values) {
82
+ if (v === void 0 || v === null) continue;
83
+ pairs.push([key, String(v)]);
84
+ }
85
+ }
86
+ if (pairs.length === 0) return base;
87
+ const qs = new URLSearchParams(pairs).toString();
88
+ return `${base}?${qs}`;
89
+ }
90
+
91
+ // src/domains/blog.ts
92
+ function makeBlogNamespace(ctx) {
93
+ return {
94
+ list: (params, cache) => requestList(
95
+ ctx,
96
+ buildUrl("/api/site/blog", params),
97
+ { cachePolicy: cache }
98
+ ),
99
+ bySlug: (slug, cache) => requestOne(ctx, `/api/site/blog/${slug}`, { cachePolicy: cache }),
100
+ related: (slug, cache) => requestList(ctx, `/api/site/blog/${slug}/related`, { cachePolicy: cache }),
101
+ categories: (cache) => requestList(ctx, "/api/site/blog/categories", { cachePolicy: cache }),
102
+ slugs: (cache) => requestList(ctx, "/api/site/blog/slugs", { cachePolicy: cache }),
103
+ /** Draft preview — never cached (unpublished content). */
104
+ preview: (id) => requestOne(ctx, `/api/site/blog/preview/${id}`, {
105
+ cachePolicy: { revalidate: false }
106
+ })
107
+ };
108
+ }
109
+
110
+ // src/domains/config.ts
111
+ function makeConfigNamespace(ctx) {
112
+ return {
113
+ get: (cache) => requestOne(ctx, "/api/site/config", { cachePolicy: cache })
114
+ };
115
+ }
116
+
117
+ // src/domains/contact-form.ts
118
+ function makeContactFormNamespace(ctx) {
119
+ return {
120
+ /** Submit a contact form. Returns the created lead acknowledgement. */
121
+ submit: (body) => requestMutation(ctx, "/api/site/contact-form", body)
122
+ };
123
+ }
124
+
125
+ // src/domains/ctas.ts
126
+ function makeCtasNamespace(ctx) {
127
+ return {
128
+ list: (slot, cache) => requestList(ctx, buildUrl("/api/site/ctas", { slot }), { cachePolicy: cache })
129
+ };
130
+ }
131
+
132
+ // src/domains/leads.ts
133
+ function makeLeadsNamespace(ctx) {
134
+ return {
135
+ /** Create a lead. Returns the created lead acknowledgement. */
136
+ create: (body) => requestMutation(ctx, "/api/site/leads", body)
137
+ };
138
+ }
139
+
140
+ // src/domains/legal.ts
141
+ function makeLegalNamespace(ctx) {
142
+ return {
143
+ get: (cache) => requestOne(ctx, "/api/site/legal", { cachePolicy: cache })
144
+ };
145
+ }
146
+
147
+ // src/domains/pages.ts
148
+ function makePagesNamespace(ctx) {
149
+ return {
150
+ list: (params, cache) => requestList(
151
+ ctx,
152
+ buildUrl("/api/site/pages", params),
153
+ { cachePolicy: cache }
154
+ ),
155
+ bySlug: (slug, cache) => requestOne(ctx, `/api/site/pages/${slug}`, { cachePolicy: cache }),
156
+ slugs: (cache) => requestList(ctx, "/api/site/pages/slugs", { cachePolicy: cache })
157
+ };
158
+ }
159
+
160
+ // src/domains/shop.ts
161
+ function flattenShopParams(params) {
162
+ if (!params) return {};
163
+ const { taxonomy, attr, ...scalars } = params;
164
+ const out = { ...scalars };
165
+ if (taxonomy) {
166
+ for (const [key, value] of Object.entries(taxonomy)) out[`taxonomy[${key}]`] = value;
167
+ }
168
+ if (attr) {
169
+ for (const [key, value] of Object.entries(attr)) out[`attr[${key}]`] = value;
170
+ }
171
+ return out;
172
+ }
173
+ function makeShopNamespace(ctx) {
174
+ return {
175
+ products: (params, cache) => requestList(
176
+ ctx,
177
+ buildUrl("/api/site/shop/products", flattenShopParams(params)),
178
+ { cachePolicy: cache }
179
+ ),
180
+ /** Available facets (brand/attributes/price/on-sale) for the given filters. */
181
+ facets: (params, cache) => requestOne(ctx, buildUrl("/api/site/shop/facets", flattenShopParams(params)), {
182
+ cachePolicy: cache
183
+ }),
184
+ bySlug: (slug, cache) => requestOne(ctx, `/api/site/shop/products/${slug}`, { cachePolicy: cache }),
185
+ categories: (cache) => requestList(ctx, "/api/site/shop/categories", { cachePolicy: cache }),
186
+ config: (cache) => requestOne(ctx, "/api/site/shop/config", { cachePolicy: cache }),
187
+ // ── Mutations (REB-438) ──────────────────────────────────────────────────
188
+ /** Start a hosted checkout session. Returns the Stripe URL + session id. */
189
+ checkout: (body) => requestMutation(ctx, "/api/site/shop/checkout", body),
190
+ /** Validate a cart against live product/stock/price state before checkout. */
191
+ validateCart: (body) => requestMutation(
192
+ ctx,
193
+ "/api/site/shop/validate-cart",
194
+ body
195
+ ),
196
+ /** Validate a discount code against the current cart subtotal. */
197
+ applyDiscount: (body) => requestMutation(
198
+ ctx,
199
+ "/api/site/shop/apply-discount",
200
+ body
201
+ ),
202
+ /** Check whether a customer with the given email already exists. */
203
+ checkEmail: (body) => requestMutation(ctx, "/api/site/shop/check-email", body)
204
+ };
205
+ }
206
+
207
+ // src/domains/trust-badges.ts
208
+ function makeTrustBadgesNamespace(ctx) {
209
+ return {
210
+ list: (cache) => requestList(ctx, "/api/site/trust-badges", { cachePolicy: cache })
211
+ };
212
+ }
213
+
214
+ // src/index.ts
215
+ var DEFAULT_API_VERSION = "1";
216
+ var DEFAULT_TIMEOUT_MS = 8e3;
217
+ function createRebluClient(config) {
218
+ const boundFetch = config.fetch ?? ((input, init) => fetch(input, init));
219
+ const ctx = {
220
+ baseUrl: config.baseUrl,
221
+ apiKey: config.apiKey,
222
+ apiVersion: config.apiVersion ?? DEFAULT_API_VERSION,
223
+ cache: config.cache ?? new PlainCacheAdapter(),
224
+ timeoutMs: config.timeoutMs ?? DEFAULT_TIMEOUT_MS,
225
+ fetch: boundFetch
226
+ };
227
+ return {
228
+ config: makeConfigNamespace(ctx),
229
+ analytics: makeAnalyticsNamespace(ctx),
230
+ legal: makeLegalNamespace(ctx),
231
+ trustBadges: makeTrustBadgesNamespace(ctx),
232
+ ctas: makeCtasNamespace(ctx),
233
+ blog: makeBlogNamespace(ctx),
234
+ pages: makePagesNamespace(ctx),
235
+ shop: makeShopNamespace(ctx),
236
+ contactForm: makeContactFormNamespace(ctx),
237
+ leads: makeLeadsNamespace(ctx)
238
+ };
239
+ }
240
+
241
+ export { ApiError, PlainCacheAdapter, createRebluClient };
242
+ //# sourceMappingURL=index.js.map
243
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cache.ts","../src/errors.ts","../src/client.ts","../src/domains/analytics.ts","../src/build-url.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","/**\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 { 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","/** A value that can be serialized into a query string. */\nexport type QueryValue = string | number | boolean | undefined | null\n\n/**\n * Append a query string to `base`, dropping `undefined`/`null` values.\n *\n * Single definition for the whole client (design doc §3.1 dedupes the two copies\n * that lived in demo-site's `client.ts` and `index.ts`). Falsy-but-meaningful\n * values (`0`, `false`, `''`) are preserved; only `undefined`/`null` are omitted.\n * Array values (REB-475 faceted `attr[key]`) emit one repeated param per element.\n */\nexport function buildUrl(\n base: string,\n params?: Record<string, QueryValue | QueryValue[]>,\n): string {\n if (!params) return base\n const pairs: [string, string][] = []\n for (const [key, value] of Object.entries(params)) {\n const values = Array.isArray(value) ? value : [value]\n for (const v of values) {\n if (v === undefined || v === null) continue\n pairs.push([key, String(v)])\n }\n }\n if (pairs.length === 0) return base\n const qs = new URLSearchParams(pairs).toString()\n return `${base}?${qs}`\n}\n","import type {\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 buildUrl('/api/site/pages', params as Record<string, 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 { 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 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 { ListResult, BlogListParams, PagesListParams, ShopProductsParams } from './types'\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reblu/site-client",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
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",
@@ -46,7 +46,7 @@
46
46
  "access": "public"
47
47
  },
48
48
  "peerDependencies": {
49
- "@reblu/site-contracts": "0.1.0"
49
+ "@reblu/site-contracts": "0.1.2"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@size-limit/preset-small-lib": "^11.1.6",
@@ -57,7 +57,7 @@
57
57
  "typescript": "^5.8.3",
58
58
  "vitest": "^4.0.15",
59
59
  "@reblu/config": "0.0.1",
60
- "@reblu/site-contracts": "0.1.0"
60
+ "@reblu/site-contracts": "0.1.2"
61
61
  },
62
62
  "size-limit": [
63
63
  {