@brandfine/client 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/dist/{index-_XBy9Y81.d.cts → index-CnR601Y2.d.cts} +31 -1
- package/dist/{index-_XBy9Y81.d.ts → index-CnR601Y2.d.ts} +31 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js.map +1 -1
- package/dist/resolvers/index.d.cts +1 -1
- package/dist/resolvers/index.d.ts +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @brandfine/client
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- d486975: Type `BrandfinePost.translations[]` (returned by `bf.posts.getBySlug`) and add
|
|
8
|
+
`publishedAt` to each translation entry. Consumers can now compute a true
|
|
9
|
+
cross-locale sitemap `<lastmod>` (`max(publishedAt)` across siblings) without
|
|
10
|
+
extra fetches. Also clarifies in the `BrandfinePost` JSDoc that `publishedAt`
|
|
11
|
+
is the last-published timestamp — re-stamped on every republish with a new
|
|
12
|
+
contentHash, untouched on no-op republishes — so it's the right field to feed
|
|
13
|
+
into a sitemap `<lastmod>`.
|
|
14
|
+
|
|
15
|
+
New named export: `BrandfinePostTranslation`.
|
|
16
|
+
|
|
17
|
+
Additive, no breaking changes.
|
|
18
|
+
|
|
3
19
|
## 0.1.0
|
|
4
20
|
|
|
5
21
|
### Minor Changes
|
|
@@ -35,12 +35,42 @@ type BrandfinePost<TConfig = unknown> = {
|
|
|
35
35
|
category: BrandfineCategory | null;
|
|
36
36
|
authorName: string | null;
|
|
37
37
|
customConfig: TConfig | null;
|
|
38
|
+
/**
|
|
39
|
+
* ISO 8601 timestamp — when the *served content* was last
|
|
40
|
+
* published. Re-stamped on every republish that actually changes
|
|
41
|
+
* content (no-op republishes are skipped server-side), so this is
|
|
42
|
+
* the correct value to use for a sitemap `<lastmod>`.
|
|
43
|
+
*/
|
|
38
44
|
publishedAt: string;
|
|
39
45
|
contentHtml: string | null;
|
|
40
46
|
contentMarkdown: string | null;
|
|
41
47
|
/** Optional per-post JSON-LD payload. Shape is consumer-defined;
|
|
42
48
|
* Brandfine round-trips it untouched. */
|
|
43
49
|
jsonLd: unknown;
|
|
50
|
+
/**
|
|
51
|
+
* Sibling translations of this post (every published locale
|
|
52
|
+
* variant in the same translation group, including this post
|
|
53
|
+
* itself). Only present on the singular detail response
|
|
54
|
+
* (`bf.posts.getBySlug`), not on list rows. Sorted by locale.
|
|
55
|
+
*
|
|
56
|
+
* Use cases:
|
|
57
|
+
* - hreflang `<link>` tags for SEO
|
|
58
|
+
* - per-locale URL switcher in the UI
|
|
59
|
+
* - cross-locale sitemap `<lastmod>` (take `max(publishedAt)`
|
|
60
|
+
* across siblings when a single URL represents the article
|
|
61
|
+
* family)
|
|
62
|
+
*/
|
|
63
|
+
translations?: BrandfinePostTranslation[];
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* One sibling translation row, as returned in
|
|
67
|
+
* `BrandfinePost.translations[]`. `publishedAt` is the per-locale
|
|
68
|
+
* last-published timestamp (same semantics as the top-level field).
|
|
69
|
+
*/
|
|
70
|
+
type BrandfinePostTranslation = {
|
|
71
|
+
locale: string;
|
|
72
|
+
slug: string;
|
|
73
|
+
publishedAt: string;
|
|
44
74
|
};
|
|
45
75
|
type BrandfinePostListResponse<TConfig = unknown> = {
|
|
46
76
|
items: BrandfinePost<TConfig>[];
|
|
@@ -259,4 +289,4 @@ type ResolveNavigationOptions = {
|
|
|
259
289
|
};
|
|
260
290
|
declare function resolveNavigation(nav: BrandfineNavigation, locale: string, opts: ResolveNavigationOptions): HydratedNav;
|
|
261
291
|
|
|
262
|
-
export { type BrandfinePost as B, type HydratedNav as H, type ListPostsOptions as L, type ResolveNavigationOptions as R, type ListCategoriesOptions as a, type BrandfineCategory as b, type BrandfineWorkspace as c, type BrandfineNavigation as d, type BrandfineNavItem as e, type BrandfineNavItemType as f, type BrandfineNavPost as g, type BrandfinePostListResponse as h, type
|
|
292
|
+
export { type BrandfinePost as B, type HydratedNav as H, type ListPostsOptions as L, type ResolveNavigationOptions as R, type ListCategoriesOptions as a, type BrandfineCategory as b, type BrandfineWorkspace as c, type BrandfineNavigation as d, type BrandfineNavItem as e, type BrandfineNavItemType as f, type BrandfineNavPost as g, type BrandfinePostListResponse as h, type BrandfinePostTranslation as i, type HydratedNavItem as j, type LocaleOptions as k, isLocale as l, localizePath as m, pickLocale as p, resolveNavigation as r, stripLocalePrefix as s };
|
|
@@ -35,12 +35,42 @@ type BrandfinePost<TConfig = unknown> = {
|
|
|
35
35
|
category: BrandfineCategory | null;
|
|
36
36
|
authorName: string | null;
|
|
37
37
|
customConfig: TConfig | null;
|
|
38
|
+
/**
|
|
39
|
+
* ISO 8601 timestamp — when the *served content* was last
|
|
40
|
+
* published. Re-stamped on every republish that actually changes
|
|
41
|
+
* content (no-op republishes are skipped server-side), so this is
|
|
42
|
+
* the correct value to use for a sitemap `<lastmod>`.
|
|
43
|
+
*/
|
|
38
44
|
publishedAt: string;
|
|
39
45
|
contentHtml: string | null;
|
|
40
46
|
contentMarkdown: string | null;
|
|
41
47
|
/** Optional per-post JSON-LD payload. Shape is consumer-defined;
|
|
42
48
|
* Brandfine round-trips it untouched. */
|
|
43
49
|
jsonLd: unknown;
|
|
50
|
+
/**
|
|
51
|
+
* Sibling translations of this post (every published locale
|
|
52
|
+
* variant in the same translation group, including this post
|
|
53
|
+
* itself). Only present on the singular detail response
|
|
54
|
+
* (`bf.posts.getBySlug`), not on list rows. Sorted by locale.
|
|
55
|
+
*
|
|
56
|
+
* Use cases:
|
|
57
|
+
* - hreflang `<link>` tags for SEO
|
|
58
|
+
* - per-locale URL switcher in the UI
|
|
59
|
+
* - cross-locale sitemap `<lastmod>` (take `max(publishedAt)`
|
|
60
|
+
* across siblings when a single URL represents the article
|
|
61
|
+
* family)
|
|
62
|
+
*/
|
|
63
|
+
translations?: BrandfinePostTranslation[];
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* One sibling translation row, as returned in
|
|
67
|
+
* `BrandfinePost.translations[]`. `publishedAt` is the per-locale
|
|
68
|
+
* last-published timestamp (same semantics as the top-level field).
|
|
69
|
+
*/
|
|
70
|
+
type BrandfinePostTranslation = {
|
|
71
|
+
locale: string;
|
|
72
|
+
slug: string;
|
|
73
|
+
publishedAt: string;
|
|
44
74
|
};
|
|
45
75
|
type BrandfinePostListResponse<TConfig = unknown> = {
|
|
46
76
|
items: BrandfinePost<TConfig>[];
|
|
@@ -259,4 +289,4 @@ type ResolveNavigationOptions = {
|
|
|
259
289
|
};
|
|
260
290
|
declare function resolveNavigation(nav: BrandfineNavigation, locale: string, opts: ResolveNavigationOptions): HydratedNav;
|
|
261
291
|
|
|
262
|
-
export { type BrandfinePost as B, type HydratedNav as H, type ListPostsOptions as L, type ResolveNavigationOptions as R, type ListCategoriesOptions as a, type BrandfineCategory as b, type BrandfineWorkspace as c, type BrandfineNavigation as d, type BrandfineNavItem as e, type BrandfineNavItemType as f, type BrandfineNavPost as g, type BrandfinePostListResponse as h, type
|
|
292
|
+
export { type BrandfinePost as B, type HydratedNav as H, type ListPostsOptions as L, type ResolveNavigationOptions as R, type ListCategoriesOptions as a, type BrandfineCategory as b, type BrandfineWorkspace as c, type BrandfineNavigation as d, type BrandfineNavItem as e, type BrandfineNavItemType as f, type BrandfineNavPost as g, type BrandfinePostListResponse as h, type BrandfinePostTranslation as i, type HydratedNavItem as j, type LocaleOptions as k, isLocale as l, localizePath as m, pickLocale as p, resolveNavigation as r, stripLocalePrefix as s };
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","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"]}
|
|
1
|
+
{"version":3,"sources":["../src/client.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","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 BrandfinePostTranslation,\n BrandfineWorkspace,\n ListCategoriesOptions,\n ListPostsOptions,\n} from './types'\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { L as ListPostsOptions, B as BrandfinePost, a as ListCategoriesOptions, b as BrandfineCategory, c as BrandfineWorkspace, d as BrandfineNavigation } from './index-
|
|
2
|
-
export { e as BrandfineNavItem, f as BrandfineNavItemType, g as BrandfineNavPost, h as BrandfinePostListResponse, H as HydratedNav,
|
|
1
|
+
import { L as ListPostsOptions, B as BrandfinePost, a as ListCategoriesOptions, b as BrandfineCategory, c as BrandfineWorkspace, d as BrandfineNavigation } from './index-CnR601Y2.cjs';
|
|
2
|
+
export { e as BrandfineNavItem, f as BrandfineNavItemType, g as BrandfineNavPost, h as BrandfinePostListResponse, i as BrandfinePostTranslation, H as HydratedNav, j as HydratedNavItem, k as LocaleOptions, R as ResolveNavigationOptions, l as isLocale, m as localizePath, p as pickLocale, r as resolveNavigation, s as stripLocalePrefix } from './index-CnR601Y2.cjs';
|
|
3
3
|
export { Cache, CacheOptions, KeyedCache, KeyedCacheOptions, createCache, createKeyedCache } from './cache/index.cjs';
|
|
4
4
|
export { BrandfineWebhookEvent, BrandfineWebhookHandlerOptions, BrandfineWebhookPayload, createBrandfineWebhookHandler, parseWebhookPayload, verifyWebhookSecret } from './webhook/index.cjs';
|
|
5
5
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { L as ListPostsOptions, B as BrandfinePost, a as ListCategoriesOptions, b as BrandfineCategory, c as BrandfineWorkspace, d as BrandfineNavigation } from './index-
|
|
2
|
-
export { e as BrandfineNavItem, f as BrandfineNavItemType, g as BrandfineNavPost, h as BrandfinePostListResponse, H as HydratedNav,
|
|
1
|
+
import { L as ListPostsOptions, B as BrandfinePost, a as ListCategoriesOptions, b as BrandfineCategory, c as BrandfineWorkspace, d as BrandfineNavigation } from './index-CnR601Y2.js';
|
|
2
|
+
export { e as BrandfineNavItem, f as BrandfineNavItemType, g as BrandfineNavPost, h as BrandfinePostListResponse, i as BrandfinePostTranslation, H as HydratedNav, j as HydratedNavItem, k as LocaleOptions, R as ResolveNavigationOptions, l as isLocale, m as localizePath, p as pickLocale, r as resolveNavigation, s as stripLocalePrefix } from './index-CnR601Y2.js';
|
|
3
3
|
export { Cache, CacheOptions, KeyedCache, KeyedCacheOptions, createCache, createKeyedCache } from './cache/index.js';
|
|
4
4
|
export { BrandfineWebhookEvent, BrandfineWebhookHandlerOptions, BrandfineWebhookPayload, createBrandfineWebhookHandler, parseWebhookPayload, verifyWebhookSecret } from './webhook/index.js';
|
|
5
5
|
|
package/dist/index.js.map
CHANGED
|
@@ -1 +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"]}
|
|
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 BrandfinePostTranslation,\n BrandfineWorkspace,\n ListCategoriesOptions,\n ListPostsOptions,\n} from './types'\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { H as HydratedNav,
|
|
1
|
+
export { H as HydratedNav, j as HydratedNavItem, k as LocaleOptions, R as ResolveNavigationOptions, l as isLocale, m as localizePath, p as pickLocale, r as resolveNavigation, s as stripLocalePrefix } from '../index-CnR601Y2.cjs';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { H as HydratedNav,
|
|
1
|
+
export { H as HydratedNav, j as HydratedNavItem, k as LocaleOptions, R as ResolveNavigationOptions, l as isLocale, m as localizePath, p as pickLocale, r as resolveNavigation, s as stripLocalePrefix } from '../index-CnR601Y2.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brandfine/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Brandfine consumer SDK — typed HTTP client, server-side caches, locale + navigation resolvers, and webhook helpers for landing-page integrations.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|