@brandfine/client 0.1.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.
Files changed (41) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +41 -0
  3. package/dist/cache/index.cjs +16 -0
  4. package/dist/cache/index.cjs.map +1 -0
  5. package/dist/cache/index.d.cts +61 -0
  6. package/dist/cache/index.d.ts +61 -0
  7. package/dist/cache/index.js +3 -0
  8. package/dist/cache/index.js.map +1 -0
  9. package/dist/chunk-DHQHUIFO.js +95 -0
  10. package/dist/chunk-DHQHUIFO.js.map +1 -0
  11. package/dist/chunk-KHHMR2NX.cjs +75 -0
  12. package/dist/chunk-KHHMR2NX.cjs.map +1 -0
  13. package/dist/chunk-MTBSSTTG.js +101 -0
  14. package/dist/chunk-MTBSSTTG.js.map +1 -0
  15. package/dist/chunk-OKIEA3AD.cjs +107 -0
  16. package/dist/chunk-OKIEA3AD.cjs.map +1 -0
  17. package/dist/chunk-QQLAYITF.js +71 -0
  18. package/dist/chunk-QQLAYITF.js.map +1 -0
  19. package/dist/chunk-XJFKL2HU.cjs +98 -0
  20. package/dist/chunk-XJFKL2HU.cjs.map +1 -0
  21. package/dist/index-_XBy9Y81.d.cts +262 -0
  22. package/dist/index-_XBy9Y81.d.ts +262 -0
  23. package/dist/index.cjs +159 -0
  24. package/dist/index.cjs.map +1 -0
  25. package/dist/index.d.cts +107 -0
  26. package/dist/index.d.ts +107 -0
  27. package/dist/index.js +115 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/resolvers/index.cjs +28 -0
  30. package/dist/resolvers/index.cjs.map +1 -0
  31. package/dist/resolvers/index.d.cts +1 -0
  32. package/dist/resolvers/index.d.ts +1 -0
  33. package/dist/resolvers/index.js +3 -0
  34. package/dist/resolvers/index.js.map +1 -0
  35. package/dist/webhook/index.cjs +20 -0
  36. package/dist/webhook/index.cjs.map +1 -0
  37. package/dist/webhook/index.d.cts +117 -0
  38. package/dist/webhook/index.d.ts +117 -0
  39. package/dist/webhook/index.js +3 -0
  40. package/dist/webhook/index.js.map +1 -0
  41. package/package.json +82 -0
package/dist/index.js ADDED
@@ -0,0 +1,115 @@
1
+ export { createCache, createKeyedCache } from './chunk-DHQHUIFO.js';
2
+ export { isLocale, localizePath, pickLocale, resolveNavigation, stripLocalePrefix } from './chunk-MTBSSTTG.js';
3
+ export { createBrandfineWebhookHandler, parseWebhookPayload, verifyWebhookSecret } from './chunk-QQLAYITF.js';
4
+
5
+ // src/client.ts
6
+ var BrandfineApiError = class extends Error {
7
+ name = "BrandfineApiError";
8
+ status;
9
+ statusText;
10
+ body;
11
+ url;
12
+ constructor(args) {
13
+ super(
14
+ `[brandfine] ${args.status} ${args.statusText} on ${args.url} \u2014 ${args.body.slice(0, 200)}`
15
+ );
16
+ this.status = args.status;
17
+ this.statusText = args.statusText;
18
+ this.body = args.body;
19
+ this.url = args.url;
20
+ }
21
+ };
22
+ var DEFAULT_USER_AGENT = "@brandfine/client";
23
+ function createBrandfineClient(config) {
24
+ if (!config.baseUrl)
25
+ throw new Error("createBrandfineClient: `baseUrl` is required");
26
+ if (!config.apiKey)
27
+ throw new Error("createBrandfineClient: `apiKey` is required");
28
+ const baseUrl = config.baseUrl.replace(/\/$/, "");
29
+ const apiKey = config.apiKey;
30
+ const fetchImpl = config.fetch ?? globalThis.fetch;
31
+ const userAgent = config.userAgent ?? DEFAULT_USER_AGENT;
32
+ async function get(path, opts = {}) {
33
+ const url = `${baseUrl}${path}`;
34
+ const res = await fetchImpl(url, {
35
+ method: "GET",
36
+ headers: {
37
+ "X-Api-Key": apiKey,
38
+ Accept: "application/json",
39
+ "User-Agent": userAgent
40
+ },
41
+ signal: opts.signal
42
+ });
43
+ if (res.status === 404 && opts.nullable404) {
44
+ await res.text().catch(() => "");
45
+ return null;
46
+ }
47
+ if (!res.ok) {
48
+ const body = await res.text().catch(() => "");
49
+ throw new BrandfineApiError({
50
+ status: res.status,
51
+ statusText: res.statusText,
52
+ body,
53
+ url
54
+ });
55
+ }
56
+ return await res.json();
57
+ }
58
+ const posts = {
59
+ async list(opts = {}) {
60
+ const out = [];
61
+ let page = 1;
62
+ const typeQuery = opts.type ? `&type=${encodeURIComponent(opts.type)}` : "";
63
+ const localeQuery = opts.locale ? `&locale=${encodeURIComponent(opts.locale)}` : "";
64
+ const sizeQuery = opts.forceLimit ? `&force_limit=${opts.forceLimit}` : "&limit=50";
65
+ const MAX_PAGES = 200;
66
+ while (page <= MAX_PAGES) {
67
+ const data = await get(
68
+ `/external/posts?include=content${sizeQuery}&page=${page}${typeQuery}${localeQuery}`
69
+ );
70
+ out.push(...data.items);
71
+ if (!data.pageInfo.hasNext) break;
72
+ page += 1;
73
+ }
74
+ return out;
75
+ },
76
+ async getBySlug(slug) {
77
+ return get(
78
+ `/external/posts/${encodeURIComponent(slug)}`,
79
+ { nullable404: true }
80
+ );
81
+ }
82
+ };
83
+ const categories = {
84
+ async list(opts = {}) {
85
+ const qs = opts.locale ? `?locale=${encodeURIComponent(opts.locale)}` : "";
86
+ const data = await get(
87
+ `/external/categories${qs}`
88
+ );
89
+ return data.items;
90
+ }
91
+ };
92
+ const workspace = {
93
+ get() {
94
+ return get(
95
+ "/external/workspace"
96
+ );
97
+ }
98
+ };
99
+ const navigations = {
100
+ get(key) {
101
+ return get(
102
+ `/external/navigations/${encodeURIComponent(key)}`,
103
+ { nullable404: true }
104
+ );
105
+ }
106
+ };
107
+ return { get, posts, categories, workspace, navigations };
108
+ }
109
+
110
+ // src/index.ts
111
+ var SDK_VERSION = "0.0.0";
112
+
113
+ export { BrandfineApiError, SDK_VERSION, createBrandfineClient };
114
+ //# sourceMappingURL=index.js.map
115
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * `createBrandfineClient` — the SDK's entry point.\n *\n * Returns a stateless, multi-instance-safe handle scoped to a\n * single `(baseUrl, apiKey)` pair. Pattern follows the Stripe /\n * Algolia / OpenAI SDKs — explicit construction with config,\n * namespaced methods (`bf.posts.list(...)`, `bf.workspace.get()`),\n * no module-level singletons.\n *\n * Why factory not module-level state: multi-tenant consumers\n * sometimes need two clients in the same process (e.g. main site\n * + admin preview). Module-level env reading makes that impossible\n * without monkey-patching.\n */\n\nimport type {\n BrandfineCategory,\n BrandfineNavigation,\n BrandfinePost,\n BrandfinePostListResponse,\n BrandfineWorkspace,\n ListCategoriesOptions,\n ListPostsOptions,\n} from './types'\n\nexport type BrandfineClientConfig = {\n /** Base URL of the Brandfine API. No trailing slash — the client\n * trims one if you pass it anyway. e.g. `https://api.brandfine.co` */\n baseUrl: string\n /** Workspace-scoped API key. Generated from the cms's Workspace\n * settings; identifies which workspace the client talks to. */\n apiKey: string\n /** Optional fetch override. Useful for tests (inject a stub),\n * for runtimes that need a custom implementation (edge workers\n * with non-standard fetch), or to add cross-cutting concerns\n * like tracing / retries. Defaults to `globalThis.fetch`. */\n fetch?: typeof globalThis.fetch\n /** Optional User-Agent header. Falls back to a generic SDK tag. */\n userAgent?: string\n}\n\n/**\n * Structured error thrown by every request helper on non-2xx\n * responses. Carries the raw body so consumers can log it for\n * debugging without re-fetching.\n */\nexport class BrandfineApiError extends Error {\n override readonly name = 'BrandfineApiError'\n readonly status: number\n readonly statusText: string\n readonly body: string\n readonly url: string\n\n constructor(args: {\n status: number\n statusText: string\n body: string\n url: string\n }) {\n super(\n `[brandfine] ${args.status} ${args.statusText} on ${args.url} — ${args.body.slice(0, 200)}`,\n )\n this.status = args.status\n this.statusText = args.statusText\n this.body = args.body\n this.url = args.url\n }\n}\n\ntype RequestOptions = {\n /** When true and the response is 404, return `null` instead of\n * throwing. Used by endpoints where 404 is a meaningful empty\n * state (navigation by key, single post by slug). */\n nullable404?: boolean\n signal?: AbortSignal\n}\n\nexport type BrandfineClient = {\n /** Low-level GET. Reserved for endpoints we don't have a typed\n * helper for yet. Adds the X-Api-Key header automatically. */\n get: <T>(path: string, opts?: RequestOptions) => Promise<T>\n posts: PostsApi\n categories: CategoriesApi\n workspace: WorkspaceApi\n navigations: NavigationsApi\n}\n\ntype PostsApi = {\n /** Paginated list of published posts. Handles the cms's\n * pagination transparently — caller gets a flat array. */\n list: <TConfig = unknown>(\n opts?: ListPostsOptions,\n ) => Promise<BrandfinePost<TConfig>[]>\n /** Single post by per-locale URL slug, scoped to the active\n * locale on the workspace's content. Returns `null` for 404 so\n * callers can render their own \"not found\" page without try/catch. */\n getBySlug: <TConfig = unknown>(\n slug: string,\n ) => Promise<BrandfinePost<TConfig> | null>\n}\n\ntype CategoriesApi = {\n list: (opts?: ListCategoriesOptions) => Promise<BrandfineCategory[]>\n}\n\ntype WorkspaceApi = {\n get: <\n TCustomConfig = Record<string, unknown>,\n TSchemaOrg = Record<string, unknown>,\n >() => Promise<BrandfineWorkspace<TCustomConfig, TSchemaOrg>>\n}\n\ntype NavigationsApi = {\n /** Navigation by its workspace-scoped `key` (e.g. `'header'`).\n * Returns `null` for 404 so consumers can fall back to a\n * hardcoded default without try/catch. */\n get: (key: string) => Promise<BrandfineNavigation | null>\n}\n\nconst DEFAULT_USER_AGENT = '@brandfine/client'\n\nexport function createBrandfineClient(\n config: BrandfineClientConfig,\n): BrandfineClient {\n if (!config.baseUrl)\n throw new Error('createBrandfineClient: `baseUrl` is required')\n if (!config.apiKey)\n throw new Error('createBrandfineClient: `apiKey` is required')\n\n const baseUrl = config.baseUrl.replace(/\\/$/, '')\n const apiKey = config.apiKey\n // Resolve fetch lazily so consumers in environments without a\n // global fetch can polyfill before constructing the client.\n const fetchImpl: typeof fetch = config.fetch ?? globalThis.fetch\n const userAgent = config.userAgent ?? DEFAULT_USER_AGENT\n\n async function get<T>(path: string, opts: RequestOptions = {}): Promise<T> {\n const url = `${baseUrl}${path}`\n const res = await fetchImpl(url, {\n method: 'GET',\n headers: {\n 'X-Api-Key': apiKey,\n Accept: 'application/json',\n 'User-Agent': userAgent,\n },\n signal: opts.signal,\n })\n if (res.status === 404 && opts.nullable404) {\n // Drain the body so the underlying socket can be reused —\n // fetch implementations that don't auto-drain (older Node)\n // can leak otherwise.\n await res.text().catch(() => '')\n return null as T\n }\n if (!res.ok) {\n const body = await res.text().catch(() => '')\n throw new BrandfineApiError({\n status: res.status,\n statusText: res.statusText,\n body,\n url,\n })\n }\n return (await res.json()) as T\n }\n\n const posts: PostsApi = {\n async list<TConfig = unknown>(opts: ListPostsOptions = {}) {\n const out: BrandfinePost<TConfig>[] = []\n let page = 1\n const typeQuery = opts.type ? `&type=${encodeURIComponent(opts.type)}` : ''\n const localeQuery = opts.locale\n ? `&locale=${encodeURIComponent(opts.locale)}`\n : ''\n // Default pagination at the cms's 50-per-page cap. `forceLimit`\n // opts past it for content types that would otherwise need\n // many round-trips.\n const sizeQuery = opts.forceLimit\n ? `&force_limit=${opts.forceLimit}`\n : '&limit=50'\n // Pathological safety brake — 200 pages × 50 = 10k posts. If\n // a workspace ever needs more, callers should hit the API\n // directly with their own pagination logic.\n const MAX_PAGES = 200\n while (page <= MAX_PAGES) {\n const data = await get<BrandfinePostListResponse<TConfig>>(\n `/external/posts?include=content${sizeQuery}&page=${page}${typeQuery}${localeQuery}`,\n )\n out.push(...data.items)\n if (!data.pageInfo.hasNext) break\n page += 1\n }\n return out\n },\n async getBySlug<TConfig = unknown>(slug: string) {\n return get<BrandfinePost<TConfig> | null>(\n `/external/posts/${encodeURIComponent(slug)}`,\n { nullable404: true },\n )\n },\n }\n\n const categories: CategoriesApi = {\n async list(opts: ListCategoriesOptions = {}) {\n const qs = opts.locale ? `?locale=${encodeURIComponent(opts.locale)}` : ''\n const data = await get<{ items: BrandfineCategory[] }>(\n `/external/categories${qs}`,\n )\n return data.items\n },\n }\n\n const workspace: WorkspaceApi = {\n get<\n TCustomConfig = Record<string, unknown>,\n TSchemaOrg = Record<string, unknown>,\n >() {\n return get<BrandfineWorkspace<TCustomConfig, TSchemaOrg>>(\n '/external/workspace',\n )\n },\n }\n\n const navigations: NavigationsApi = {\n get(key: string) {\n return get<BrandfineNavigation | null>(\n `/external/navigations/${encodeURIComponent(key)}`,\n { nullable404: true },\n )\n },\n }\n\n return { get, posts, categories, workspace, navigations }\n}\n","/**\n * @brandfine/client — root entry.\n *\n * The full SDK surface is exposed here for \"import everything from\n * one place\" usage. Tree-shaking + `sideEffects: false` mean\n * consumers don't pay a bundle cost for what they don't import.\n *\n * Heavier or framework-coupled pieces still live under subpath\n * exports (`@brandfine/client/cache`, `/resolvers`, `/webhook`) so\n * consumers with poor tree-shaking — or who only need one slice —\n * can scope their imports.\n */\n\nexport const SDK_VERSION = '0.0.0' as const\n\nexport {\n BrandfineApiError,\n createBrandfineClient,\n type BrandfineClient,\n type BrandfineClientConfig,\n} from './client'\n\nexport {\n createCache,\n createKeyedCache,\n type Cache,\n type CacheOptions,\n type KeyedCache,\n type KeyedCacheOptions,\n} from './cache/index'\n\nexport {\n isLocale,\n localizePath,\n pickLocale,\n resolveNavigation,\n stripLocalePrefix,\n type HydratedNav,\n type HydratedNavItem,\n type LocaleOptions,\n type ResolveNavigationOptions,\n} from './resolvers/index'\n\nexport {\n createBrandfineWebhookHandler,\n parseWebhookPayload,\n verifyWebhookSecret,\n type BrandfineWebhookEvent,\n type BrandfineWebhookHandlerOptions,\n type BrandfineWebhookPayload,\n} from './webhook/index'\n\nexport type {\n BrandfineCategory,\n BrandfineNavItem,\n BrandfineNavItemType,\n BrandfineNavPost,\n BrandfineNavigation,\n BrandfinePost,\n BrandfinePostListResponse,\n BrandfineWorkspace,\n ListCategoriesOptions,\n ListPostsOptions,\n} from './types'\n"]}
@@ -0,0 +1,28 @@
1
+ 'use strict';
2
+
3
+ var chunkOKIEA3AD_cjs = require('../chunk-OKIEA3AD.cjs');
4
+
5
+
6
+
7
+ Object.defineProperty(exports, "isLocale", {
8
+ enumerable: true,
9
+ get: function () { return chunkOKIEA3AD_cjs.isLocale; }
10
+ });
11
+ Object.defineProperty(exports, "localizePath", {
12
+ enumerable: true,
13
+ get: function () { return chunkOKIEA3AD_cjs.localizePath; }
14
+ });
15
+ Object.defineProperty(exports, "pickLocale", {
16
+ enumerable: true,
17
+ get: function () { return chunkOKIEA3AD_cjs.pickLocale; }
18
+ });
19
+ Object.defineProperty(exports, "resolveNavigation", {
20
+ enumerable: true,
21
+ get: function () { return chunkOKIEA3AD_cjs.resolveNavigation; }
22
+ });
23
+ Object.defineProperty(exports, "stripLocalePrefix", {
24
+ enumerable: true,
25
+ get: function () { return chunkOKIEA3AD_cjs.stripLocalePrefix; }
26
+ });
27
+ //# sourceMappingURL=index.cjs.map
28
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.cjs"}
@@ -0,0 +1 @@
1
+ export { H as HydratedNav, i as HydratedNavItem, j as LocaleOptions, R as ResolveNavigationOptions, k as isLocale, l as localizePath, p as pickLocale, r as resolveNavigation, s as stripLocalePrefix } from '../index-_XBy9Y81.cjs';
@@ -0,0 +1 @@
1
+ export { H as HydratedNav, i as HydratedNavItem, j as LocaleOptions, R as ResolveNavigationOptions, k as isLocale, l as localizePath, p as pickLocale, r as resolveNavigation, s as stripLocalePrefix } from '../index-_XBy9Y81.js';
@@ -0,0 +1,3 @@
1
+ export { isLocale, localizePath, pickLocale, resolveNavigation, stripLocalePrefix } from '../chunk-MTBSSTTG.js';
2
+ //# sourceMappingURL=index.js.map
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
@@ -0,0 +1,20 @@
1
+ 'use strict';
2
+
3
+ var chunkKHHMR2NX_cjs = require('../chunk-KHHMR2NX.cjs');
4
+
5
+
6
+
7
+ Object.defineProperty(exports, "createBrandfineWebhookHandler", {
8
+ enumerable: true,
9
+ get: function () { return chunkKHHMR2NX_cjs.createBrandfineWebhookHandler; }
10
+ });
11
+ Object.defineProperty(exports, "parseWebhookPayload", {
12
+ enumerable: true,
13
+ get: function () { return chunkKHHMR2NX_cjs.parseWebhookPayload; }
14
+ });
15
+ Object.defineProperty(exports, "verifyWebhookSecret", {
16
+ enumerable: true,
17
+ get: function () { return chunkKHHMR2NX_cjs.verifyWebhookSecret; }
18
+ });
19
+ //# sourceMappingURL=index.cjs.map
20
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.cjs"}
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Webhook payload types + parser.
3
+ *
4
+ * The cms POSTs a small JSON envelope every time content changes.
5
+ * The discriminator is `event` — a string we recognise (the
6
+ * literal union below) or arbitrary new strings for
7
+ * forward-compatibility. The `& {}` trick keeps autocomplete on
8
+ * the known events while still typing the field as `string`.
9
+ */
10
+ /** Known event types emitted by the cms today. Add to this union
11
+ * as new events ship — consumers who switch on `event` get
12
+ * exhaustiveness checks. */
13
+ type BrandfineWebhookEvent = 'post.published' | 'post.unpublished' | 'navigation.created' | 'navigation.updated' | 'navigation.deleted' | 'navigation.items.replaced';
14
+ /**
15
+ * Envelope shape. Optional event-specific fields (`postId`,
16
+ * `navigationId`, …) are typed as optional so consumers can
17
+ * narrow with `if (payload.event === 'post.published' && payload.postId)`.
18
+ *
19
+ * Unknown event types still parse into the same shape so handlers
20
+ * never throw on new server-side events. Forward-compatible by
21
+ * default — the cms can add events and the package keeps working.
22
+ */
23
+ type BrandfineWebhookPayload = {
24
+ /** Recognised event or any new event string. */
25
+ event: BrandfineWebhookEvent | (string & {});
26
+ workspaceId: string;
27
+ /** ISO timestamp of when the event was generated, server-side. */
28
+ at?: string;
29
+ postId?: string;
30
+ slug?: string;
31
+ title?: string;
32
+ publishedAt?: string;
33
+ navigationId?: string;
34
+ /** Kebab-case nav identifier (e.g. `'header'`). Null when the
35
+ * cms emits the event without a specific key (rare). */
36
+ key?: string | null;
37
+ };
38
+ /**
39
+ * Read + parse a webhook request body. Tolerant — accepts any
40
+ * JSON object with at minimum a `workspaceId` string, since the
41
+ * `event` field may be missing or unknown and we don't want to
42
+ * reject those (we want consumers to be able to inspect the
43
+ * payload and decide).
44
+ *
45
+ * Throws on:
46
+ * - non-JSON body
47
+ * - non-object root (string, array, null)
48
+ * - missing `workspaceId`
49
+ *
50
+ * Callers in the framework adapter layer turn these into 400
51
+ * responses — the cms doesn't retry on 400, which is correct
52
+ * since malformed bodies aren't transient.
53
+ */
54
+ declare function parseWebhookPayload(request: Request): Promise<BrandfineWebhookPayload>;
55
+
56
+ /**
57
+ * Framework-agnostic webhook handler.
58
+ *
59
+ * `createBrandfineWebhookHandler` takes a config and returns a
60
+ * `(request: Request) => Promise<Response>` — the standardised
61
+ * Web Fetch API shape that Astro, Next App Router, Remix,
62
+ * SvelteKit, Bun, and Cloudflare Workers all share.
63
+ *
64
+ * Framework adapters (`@brandfine/client-astro`) re-export this
65
+ * with the framework's route convention applied — e.g. exposing
66
+ * `POST = createBrandfineWebhookHandler(...)` directly.
67
+ *
68
+ * Response codes:
69
+ * - 401 — secret mismatch or no secret configured. The cms
70
+ * does not retry on 401, so a misconfigured consumer fails
71
+ * loudly rather than racking up retries.
72
+ * - 400 — body is missing / malformed. Also not retried.
73
+ * - 500 — `onEvent` callback threw. The cms retries with
74
+ * backoff; the consumer's `onError` (if set) gets the
75
+ * original error.
76
+ * - 200 — accepted. Body echoes `event` for debugging.
77
+ */
78
+
79
+ type BrandfineWebhookHandlerOptions = {
80
+ /** Shared secret. Compared against the `?secret=` query param
81
+ * on every request (constant-time). When empty, every request
82
+ * is rejected with 401 — consumers shouldn't construct a
83
+ * handler without a secret. */
84
+ secret: string;
85
+ /** Called once the request is authenticated and parsed. Wire
86
+ * cache invalidations here. May be async; the handler waits
87
+ * for it before responding so the cms's "delivered" status
88
+ * reflects whether the consumer actually processed the event. */
89
+ onEvent: (payload: BrandfineWebhookPayload) => void | Promise<void>;
90
+ /** Optional error logger for failures inside `onEvent`. The
91
+ * package's default falls back to `console.error` — override
92
+ * to route into a structured logger. */
93
+ onError?: (err: unknown) => void;
94
+ };
95
+ declare function createBrandfineWebhookHandler(opts: BrandfineWebhookHandlerOptions): (request: Request) => Promise<Response>;
96
+
97
+ /**
98
+ * Constant-time secret comparison.
99
+ *
100
+ * The webhook receiver compares the URL `?secret=` query param
101
+ * against the shared secret. Naive `===` leaks information via
102
+ * timing — early-exit on first byte mismatch means an attacker
103
+ * can brute-force the secret one byte at a time.
104
+ *
105
+ * This implementation XORs every byte and ORs the results, so
106
+ * the loop runs in O(n) regardless of where the mismatch is.
107
+ *
108
+ * Note on length: we early-return on length mismatch. The
109
+ * `expected` length is set by configuration (not secret data),
110
+ * so leaking the length is acceptable in practice — and skipping
111
+ * the loop entirely beats the cost of running it for a guaranteed
112
+ * mismatch. Cryptographic-grade primitives (e.g. WebCrypto's
113
+ * `timingSafeEqual` on Buffers) require fixed-length inputs.
114
+ */
115
+ declare function verifyWebhookSecret(provided: string, expected: string): boolean;
116
+
117
+ export { type BrandfineWebhookEvent, type BrandfineWebhookHandlerOptions, type BrandfineWebhookPayload, createBrandfineWebhookHandler, parseWebhookPayload, verifyWebhookSecret };
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Webhook payload types + parser.
3
+ *
4
+ * The cms POSTs a small JSON envelope every time content changes.
5
+ * The discriminator is `event` — a string we recognise (the
6
+ * literal union below) or arbitrary new strings for
7
+ * forward-compatibility. The `& {}` trick keeps autocomplete on
8
+ * the known events while still typing the field as `string`.
9
+ */
10
+ /** Known event types emitted by the cms today. Add to this union
11
+ * as new events ship — consumers who switch on `event` get
12
+ * exhaustiveness checks. */
13
+ type BrandfineWebhookEvent = 'post.published' | 'post.unpublished' | 'navigation.created' | 'navigation.updated' | 'navigation.deleted' | 'navigation.items.replaced';
14
+ /**
15
+ * Envelope shape. Optional event-specific fields (`postId`,
16
+ * `navigationId`, …) are typed as optional so consumers can
17
+ * narrow with `if (payload.event === 'post.published' && payload.postId)`.
18
+ *
19
+ * Unknown event types still parse into the same shape so handlers
20
+ * never throw on new server-side events. Forward-compatible by
21
+ * default — the cms can add events and the package keeps working.
22
+ */
23
+ type BrandfineWebhookPayload = {
24
+ /** Recognised event or any new event string. */
25
+ event: BrandfineWebhookEvent | (string & {});
26
+ workspaceId: string;
27
+ /** ISO timestamp of when the event was generated, server-side. */
28
+ at?: string;
29
+ postId?: string;
30
+ slug?: string;
31
+ title?: string;
32
+ publishedAt?: string;
33
+ navigationId?: string;
34
+ /** Kebab-case nav identifier (e.g. `'header'`). Null when the
35
+ * cms emits the event without a specific key (rare). */
36
+ key?: string | null;
37
+ };
38
+ /**
39
+ * Read + parse a webhook request body. Tolerant — accepts any
40
+ * JSON object with at minimum a `workspaceId` string, since the
41
+ * `event` field may be missing or unknown and we don't want to
42
+ * reject those (we want consumers to be able to inspect the
43
+ * payload and decide).
44
+ *
45
+ * Throws on:
46
+ * - non-JSON body
47
+ * - non-object root (string, array, null)
48
+ * - missing `workspaceId`
49
+ *
50
+ * Callers in the framework adapter layer turn these into 400
51
+ * responses — the cms doesn't retry on 400, which is correct
52
+ * since malformed bodies aren't transient.
53
+ */
54
+ declare function parseWebhookPayload(request: Request): Promise<BrandfineWebhookPayload>;
55
+
56
+ /**
57
+ * Framework-agnostic webhook handler.
58
+ *
59
+ * `createBrandfineWebhookHandler` takes a config and returns a
60
+ * `(request: Request) => Promise<Response>` — the standardised
61
+ * Web Fetch API shape that Astro, Next App Router, Remix,
62
+ * SvelteKit, Bun, and Cloudflare Workers all share.
63
+ *
64
+ * Framework adapters (`@brandfine/client-astro`) re-export this
65
+ * with the framework's route convention applied — e.g. exposing
66
+ * `POST = createBrandfineWebhookHandler(...)` directly.
67
+ *
68
+ * Response codes:
69
+ * - 401 — secret mismatch or no secret configured. The cms
70
+ * does not retry on 401, so a misconfigured consumer fails
71
+ * loudly rather than racking up retries.
72
+ * - 400 — body is missing / malformed. Also not retried.
73
+ * - 500 — `onEvent` callback threw. The cms retries with
74
+ * backoff; the consumer's `onError` (if set) gets the
75
+ * original error.
76
+ * - 200 — accepted. Body echoes `event` for debugging.
77
+ */
78
+
79
+ type BrandfineWebhookHandlerOptions = {
80
+ /** Shared secret. Compared against the `?secret=` query param
81
+ * on every request (constant-time). When empty, every request
82
+ * is rejected with 401 — consumers shouldn't construct a
83
+ * handler without a secret. */
84
+ secret: string;
85
+ /** Called once the request is authenticated and parsed. Wire
86
+ * cache invalidations here. May be async; the handler waits
87
+ * for it before responding so the cms's "delivered" status
88
+ * reflects whether the consumer actually processed the event. */
89
+ onEvent: (payload: BrandfineWebhookPayload) => void | Promise<void>;
90
+ /** Optional error logger for failures inside `onEvent`. The
91
+ * package's default falls back to `console.error` — override
92
+ * to route into a structured logger. */
93
+ onError?: (err: unknown) => void;
94
+ };
95
+ declare function createBrandfineWebhookHandler(opts: BrandfineWebhookHandlerOptions): (request: Request) => Promise<Response>;
96
+
97
+ /**
98
+ * Constant-time secret comparison.
99
+ *
100
+ * The webhook receiver compares the URL `?secret=` query param
101
+ * against the shared secret. Naive `===` leaks information via
102
+ * timing — early-exit on first byte mismatch means an attacker
103
+ * can brute-force the secret one byte at a time.
104
+ *
105
+ * This implementation XORs every byte and ORs the results, so
106
+ * the loop runs in O(n) regardless of where the mismatch is.
107
+ *
108
+ * Note on length: we early-return on length mismatch. The
109
+ * `expected` length is set by configuration (not secret data),
110
+ * so leaking the length is acceptable in practice — and skipping
111
+ * the loop entirely beats the cost of running it for a guaranteed
112
+ * mismatch. Cryptographic-grade primitives (e.g. WebCrypto's
113
+ * `timingSafeEqual` on Buffers) require fixed-length inputs.
114
+ */
115
+ declare function verifyWebhookSecret(provided: string, expected: string): boolean;
116
+
117
+ export { type BrandfineWebhookEvent, type BrandfineWebhookHandlerOptions, type BrandfineWebhookPayload, createBrandfineWebhookHandler, parseWebhookPayload, verifyWebhookSecret };
@@ -0,0 +1,3 @@
1
+ export { createBrandfineWebhookHandler, parseWebhookPayload, verifyWebhookSecret } from '../chunk-QQLAYITF.js';
2
+ //# sourceMappingURL=index.js.map
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
package/package.json ADDED
@@ -0,0 +1,82 @@
1
+ {
2
+ "name": "@brandfine/client",
3
+ "version": "0.1.0",
4
+ "description": "Brandfine consumer SDK — typed HTTP client, server-side caches, locale + navigation resolvers, and webhook helpers for landing-page integrations.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "sideEffects": false,
8
+ "main": "./dist/index.cjs",
9
+ "module": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "import": {
14
+ "types": "./dist/index.d.ts",
15
+ "default": "./dist/index.js"
16
+ },
17
+ "require": {
18
+ "types": "./dist/index.d.cts",
19
+ "default": "./dist/index.cjs"
20
+ }
21
+ },
22
+ "./cache": {
23
+ "import": {
24
+ "types": "./dist/cache/index.d.ts",
25
+ "default": "./dist/cache/index.js"
26
+ },
27
+ "require": {
28
+ "types": "./dist/cache/index.d.cts",
29
+ "default": "./dist/cache/index.cjs"
30
+ }
31
+ },
32
+ "./resolvers": {
33
+ "import": {
34
+ "types": "./dist/resolvers/index.d.ts",
35
+ "default": "./dist/resolvers/index.js"
36
+ },
37
+ "require": {
38
+ "types": "./dist/resolvers/index.d.cts",
39
+ "default": "./dist/resolvers/index.cjs"
40
+ }
41
+ },
42
+ "./webhook": {
43
+ "import": {
44
+ "types": "./dist/webhook/index.d.ts",
45
+ "default": "./dist/webhook/index.js"
46
+ },
47
+ "require": {
48
+ "types": "./dist/webhook/index.d.cts",
49
+ "default": "./dist/webhook/index.cjs"
50
+ }
51
+ }
52
+ },
53
+ "files": [
54
+ "dist",
55
+ "README.md",
56
+ "CHANGELOG.md"
57
+ ],
58
+ "scripts": {
59
+ "build": "tsup",
60
+ "dev": "tsup --watch",
61
+ "typecheck": "tsc --noEmit",
62
+ "test": "vitest run",
63
+ "test:watch": "vitest",
64
+ "lint:pkg": "publint",
65
+ "lint:types": "attw --pack --profile node16",
66
+ "validate": "npm run build && npm run typecheck && npm run test && npm run lint:pkg && npm run lint:types",
67
+ "prepublishOnly": "npm run build && npm run lint:pkg"
68
+ },
69
+ "engines": {
70
+ "node": ">=20"
71
+ },
72
+ "devDependencies": {
73
+ "@arethetypeswrong/cli": "0.18.2",
74
+ "publint": "0.3.20",
75
+ "tsup": "^8.3.0",
76
+ "typescript": "^6.0.0",
77
+ "vitest": "^2.1.1"
78
+ },
79
+ "publishConfig": {
80
+ "access": "public"
81
+ }
82
+ }