@riverbankcms/sdk 0.7.4 → 0.8.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/README.md +88 -1
- package/dist/cli/index.js +3693 -39
- package/dist/cli/index.js.map +1 -1
- package/dist/client/client.d.mts +2 -2
- package/dist/client/client.d.ts +2 -2
- package/dist/client/client.js +167 -4
- package/dist/client/client.js.map +1 -1
- package/dist/client/client.mjs +167 -4
- package/dist/client/client.mjs.map +1 -1
- package/dist/client/hooks.d.mts +2 -2
- package/dist/client/hooks.d.ts +2 -2
- package/dist/client/usePage-CdnO2CP5.d.mts +6875 -0
- package/dist/client/usePage-_ksKXlUF.d.ts +6875 -0
- package/dist/server/{Layout-qWLdVm5-.d.mts → Layout-D4J009eS.d.mts} +1 -1
- package/dist/server/{Layout-Yluyb6sK.d.ts → Layout-l2v4Qa6E.d.ts} +1 -1
- package/dist/server/{chunk-74XUVNOO.mjs → chunk-4YQJUL5W.mjs} +4 -2
- package/dist/server/{chunk-74XUVNOO.mjs.map → chunk-4YQJUL5W.mjs.map} +1 -1
- package/dist/server/{chunk-JNU7ZS2V.mjs → chunk-65A5HAUZ.mjs} +168 -5
- package/dist/server/chunk-65A5HAUZ.mjs.map +1 -0
- package/dist/server/{chunk-JWRNMNWI.js → chunk-EIJ27EZQ.js} +4 -2
- package/dist/server/chunk-EIJ27EZQ.js.map +1 -0
- package/dist/server/{chunk-6Z4MQG47.js → chunk-WM646WI3.js} +168 -5
- package/dist/server/chunk-WM646WI3.js.map +1 -0
- package/dist/server/{components-Di5ME6He.d.ts → components-D2uCKCj7.d.ts} +3 -3
- package/dist/server/{components-DNHfSCML.d.mts → components-vtYEmmPF.d.mts} +3 -3
- package/dist/server/components.d.mts +5 -5
- package/dist/server/components.d.ts +5 -5
- package/dist/server/config-validation.d.mts +2 -2
- package/dist/server/config-validation.d.ts +2 -2
- package/dist/server/config.d.mts +3 -3
- package/dist/server/config.d.ts +3 -3
- package/dist/server/data.d.mts +2 -2
- package/dist/server/data.d.ts +2 -2
- package/dist/server/{index-Clm3skz_.d.mts → index-2qnY7VH_.d.mts} +1 -1
- package/dist/server/{index-DLvNddi-.d.ts → index-BxrAuL9K.d.ts} +1 -1
- package/dist/server/{index-C9Ra8dza.d.ts → index-CH_dvF6n.d.ts} +2 -2
- package/dist/server/{index--Oyunk_B.d.mts → index-DfWg1Qle.d.mts} +2 -2
- package/dist/server/index.d.mts +13 -5
- package/dist/server/index.d.ts +13 -5
- package/dist/server/index.js +10 -10
- package/dist/server/index.mjs +1 -1
- package/dist/server/{loadContent-D7LQwI0o.d.ts → loadContent-DECnsp4k.d.ts} +3 -3
- package/dist/server/{loadContent-DVfuBLiZ.d.mts → loadContent-Du5kS8UM.d.mts} +3 -3
- package/dist/server/{loadPage-BmYJCe_V.d.ts → loadPage-BZohBxxf.d.ts} +2 -2
- package/dist/server/{loadPage-BucnLHmE.d.mts → loadPage-VBorKlWv.d.mts} +2 -2
- package/dist/server/metadata.d.mts +4 -4
- package/dist/server/metadata.d.ts +4 -4
- package/dist/server/navigation.d.mts +2 -2
- package/dist/server/navigation.d.ts +2 -2
- package/dist/server/next.d.mts +38 -7
- package/dist/server/next.d.ts +38 -7
- package/dist/server/next.js +35 -17
- package/dist/server/next.js.map +1 -1
- package/dist/server/next.mjs +30 -12
- package/dist/server/next.mjs.map +1 -1
- package/dist/server/rendering/server.d.mts +4 -4
- package/dist/server/rendering/server.d.ts +4 -4
- package/dist/server/rendering.d.mts +7 -7
- package/dist/server/rendering.d.ts +7 -7
- package/dist/server/rendering.js +2 -2
- package/dist/server/rendering.mjs +1 -1
- package/dist/server/routing.d.mts +4 -4
- package/dist/server/routing.d.ts +4 -4
- package/dist/server/server.d.mts +5 -5
- package/dist/server/server.d.ts +5 -5
- package/dist/server/server.js +3 -3
- package/dist/server/server.mjs +2 -2
- package/dist/server/{types-C-LShyIg.d.mts → types-BRQ_6yOc.d.mts} +43 -1
- package/dist/server/{types-BjgZt8xJ.d.mts → types-CJfJwcuL.d.mts} +37 -0
- package/dist/server/{types-Dt98DeYa.d.ts → types-CgSO0yxg.d.ts} +8 -0
- package/dist/server/{types-BRQyLrQU.d.ts → types-D0rPF8l5.d.ts} +43 -1
- package/dist/server/{types-DLBhEPSt.d.ts → types-D8XqwoVd.d.ts} +37 -0
- package/dist/server/{types-BSV6Vc-P.d.mts → types-DT30Qy7x.d.mts} +8 -0
- package/dist/server/{validation-DU2YE7u5.d.ts → validation-D1LaY1kQ.d.ts} +1 -1
- package/dist/server/{validation-BGuRo8P1.d.mts → validation-Pv3Zs6dP.d.mts} +1 -1
- package/package.json +2 -1
- package/dist/server/chunk-6Z4MQG47.js.map +0 -1
- package/dist/server/chunk-JNU7ZS2V.mjs.map +0 -1
- package/dist/server/chunk-JWRNMNWI.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { buildMenu, buildLogo } from './navigation.mjs';
|
|
3
|
-
import { S as SiteResponse, R as RiverbankClient } from './types-
|
|
3
|
+
import { S as SiteResponse, R as RiverbankClient } from './types-BRQ_6yOc.mjs';
|
|
4
4
|
|
|
5
5
|
type HeaderData = {
|
|
6
6
|
menu: ReturnType<typeof buildMenu>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { buildMenu, buildLogo } from './navigation.js';
|
|
3
|
-
import { S as SiteResponse, R as RiverbankClient } from './types-
|
|
3
|
+
import { S as SiteResponse, R as RiverbankClient } from './types-D0rPF8l5.js';
|
|
4
4
|
|
|
5
5
|
type HeaderData = {
|
|
6
6
|
menu: ReturnType<typeof buildMenu>;
|
|
@@ -95,10 +95,12 @@ function validateAndConvertBlock(block, source) {
|
|
|
95
95
|
if (typeof blockRecord.purpose !== "string") {
|
|
96
96
|
throw new Error(`Invalid block purpose in ${source}: expected string, got ${typeof blockRecord.purpose}`);
|
|
97
97
|
}
|
|
98
|
+
const content2 = blockRecord.content ?? {};
|
|
98
99
|
return {
|
|
99
100
|
id: blockRecord.id,
|
|
100
101
|
kind: kindValue,
|
|
101
|
-
purpose: blockRecord.purpose
|
|
102
|
+
purpose: blockRecord.purpose,
|
|
103
|
+
content: content2
|
|
102
104
|
};
|
|
103
105
|
}
|
|
104
106
|
const scope = blockRecord.scope;
|
|
@@ -139,4 +141,4 @@ export {
|
|
|
139
141
|
isEntryContent,
|
|
140
142
|
loadContent
|
|
141
143
|
};
|
|
142
|
-
//# sourceMappingURL=chunk-
|
|
144
|
+
//# sourceMappingURL=chunk-4YQJUL5W.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/rendering/helpers/loadContent.ts"],"sourcesContent":["/**\n * Server-side helper to fetch content (page or entry) by path.\n *\n * Use this for dynamic routing where a path could resolve to either\n * a page or a content entry.\n */\n\nimport type { Theme } from '@riverbankcms/blocks';\nimport type { RiverbankClient, PageResponse, SiteResponse } from '../../client/types';\nimport type { PageProps } from '../components/Page';\nimport { prefetchBlockData } from '../../data/prefetchBlockData';\nimport type { ResolvedBlockData } from '../../data/prefetchBlockData';\n\n/**\n * Site data included in content results for metadata generation.\n */\nexport type SiteData = SiteResponse['site'];\n\nexport type LoadContentParams = {\n client: RiverbankClient;\n siteId: string;\n path: string;\n /**\n * If true, fetches draft/unpublished content instead of published content.\n * This affects both pages and entries.\n * Requires API key with site access.\n *\n * @default false\n */\n preview?: boolean;\n};\n\n/**\n * Content entry data returned when a path resolves to an entry\n */\nexport type ContentEntryData = {\n id: string;\n /** Content type key (e.g., 'blog-post', 'product') */\n type: string | null;\n title: string;\n slug: string | null;\n path: string | null;\n status: string;\n publishAt: string | null;\n /** The raw content fields - use these to render your own UI */\n content: Record<string, unknown>;\n metaTitle: string | null;\n metaDescription: string | null;\n createdAt: string;\n updatedAt: string;\n};\n\n/**\n * Result when path resolves to a page\n */\nexport type PageContentResult = {\n type: 'page';\n /** Page outline ready for rendering with <Page> component */\n page: PageProps['page'];\n /** Site theme for styling */\n theme: Theme;\n /** Site ID */\n siteId: string;\n /** Pre-fetched block data for data loaders */\n resolvedData: ResolvedBlockData;\n /** Site data for metadata generation */\n site: SiteData;\n};\n\n/**\n * Result when path resolves to a content entry\n */\nexport type EntryContentResult = {\n type: 'entry';\n /** Raw entry data - render this however you want */\n entry: ContentEntryData;\n /** Template page for rendering the entry (if available) */\n templatePage: PageProps['page'] | null;\n /** Pre-fetched block data for template page data loaders */\n resolvedData: ResolvedBlockData;\n /** Data context for template blocks (includes entry content for bindings) */\n dataContext: { contentEntry: Record<string, unknown> };\n /** Site theme for styling (useful if rendering with SDK components) */\n theme: Theme;\n /** Site ID */\n siteId: string;\n /** Site data for metadata generation */\n site: SiteData;\n};\n\n/**\n * Discriminated union result from loadContent\n */\nexport type LoadContentResult = PageContentResult | EntryContentResult;\n\n/**\n * Type guard to check if result is a page\n */\nexport function isPageContent(result: LoadContentResult): result is PageContentResult {\n return result.type === 'page';\n}\n\n/**\n * Type guard to check if result is an entry\n */\nexport function isEntryContent(result: LoadContentResult): result is EntryContentResult {\n return result.type === 'entry';\n}\n\n/**\n * Server-side helper to fetch content by path.\n *\n * Returns a discriminated union - either page data (ready for `<Page>` component)\n * or raw entry data (for custom rendering).\n *\n * @example Dynamic routing with both pages and entries\n * ```tsx\n * import { loadContent, Page, isPageContent } from '@riverbankcms/sdk';\n *\n * export default async function DynamicRoute({ params }) {\n * const content = await loadContent({\n * client,\n * siteId: 'site-123',\n * path: `/${params.slug?.join('/') || ''}`,\n * });\n *\n * if (isPageContent(content)) {\n * return <Page {...content} />;\n * }\n *\n * // Render entry with custom UI\n * return (\n * <article>\n * <h1>{content.entry.title}</h1>\n * <div>{content.entry.content.body}</div>\n * </article>\n * );\n * }\n * ```\n *\n * @example Entry-specific rendering based on content type\n * ```tsx\n * const content = await loadContent({ client, siteId, path });\n *\n * if (content.type === 'entry') {\n * switch (content.entry.type) {\n * case 'blog-post':\n * return <BlogPost entry={content.entry} theme={content.theme} />;\n * case 'product':\n * return <ProductPage entry={content.entry} theme={content.theme} />;\n * default:\n * return <GenericEntry entry={content.entry} />;\n * }\n * }\n *\n * return <Page {...content} />;\n * ```\n *\n * @example Preview mode for draft content\n * ```tsx\n * const content = await loadContent({\n * client,\n * siteId,\n * path,\n * preview: true, // Fetches draft content for both pages and entries\n * });\n * ```\n */\nexport async function loadContent(params: LoadContentParams): Promise<LoadContentResult> {\n const { client, siteId, path, preview = false } = params;\n\n // Fetch site and content in parallel\n const [site, contentResponse] = await Promise.all([\n client.getSite({ id: siteId }),\n client.getPage({ siteId, path, preview }),\n ]);\n\n // Check if response is an entry\n if (isEntryResponse(contentResponse)) {\n const entryData = contentResponse.entry;\n\n const entry: ContentEntryData = {\n id: entryData.id,\n type: entryData.type,\n title: entryData.title,\n slug: entryData.slug,\n path: entryData.path,\n status: entryData.status,\n publishAt: entryData.publishAt,\n // Use draft content in preview mode, otherwise use published content\n content: preview\n ? (entryData.draftContent ?? entryData.content)\n : entryData.content,\n metaTitle: preview\n ? (entryData.draftMetaTitle ?? entryData.metaTitle)\n : entryData.metaTitle,\n metaDescription: preview\n ? (entryData.draftMetaDescription ?? entryData.metaDescription)\n : entryData.metaDescription,\n createdAt: entryData.createdAt,\n updatedAt: entryData.updatedAt,\n };\n\n // Process template if available (uses first template from content type)\n const { templatePage, resolvedData } = await processEntryTemplate(\n contentResponse.templates as Template[] | undefined,\n entry,\n { siteId, preview },\n client\n );\n\n return {\n type: 'entry',\n entry,\n templatePage,\n resolvedData,\n dataContext: { contentEntry: entry.content },\n theme: site.theme,\n siteId,\n site: site.site,\n };\n }\n\n // Handle page response\n const { page: pageData } = contentResponse;\n\n // Convert API response blocks to PageOutline format with validation\n const blocks = pageData.blocks.map((block) => validateAndConvertBlock(block, 'page'));\n\n const pageOutline = {\n name: pageData.name,\n path: pageData.path,\n purpose: pageData.purpose,\n blocks,\n };\n\n // Prefetch block data loaders for pages\n const resolvedData = await prefetchBlockData(\n pageOutline,\n {\n siteId,\n pageId: pageData.id,\n previewStage: preview ? 'preview' : 'published',\n },\n client\n );\n\n return {\n type: 'page',\n page: pageOutline,\n theme: site.theme,\n siteId,\n resolvedData,\n site: site.site,\n };\n}\n\n/**\n * Type guard to check if API response is an entry\n */\nfunction isEntryResponse(response: PageResponse): response is Extract<PageResponse, { type: 'entry' }> {\n return 'type' in response && response.type === 'entry';\n}\n\n/**\n * Validates and converts a raw block from API response to PageOutline block format.\n * Used for both page blocks and template blocks to ensure consistent validation.\n */\nfunction validateAndConvertBlock(\n block: unknown,\n source: 'page' | 'template'\n): { id: string | null; kind: string; purpose: string; content?: Record<string, unknown> } {\n if (!block || typeof block !== 'object') {\n throw new Error(`Invalid block format in ${source} API response`);\n }\n\n const blockRecord = block as Record<string, unknown>;\n\n // Template blocks use 'blockKind', page blocks use 'kind'\n const kindField = source === 'template' ? 'blockKind' : 'kind';\n const kindValue = blockRecord[kindField];\n\n if (typeof blockRecord.id !== 'string' && blockRecord.id !== null) {\n throw new Error(`Invalid block id in ${source}: expected string or null, got ${typeof blockRecord.id}`);\n }\n if (typeof kindValue !== 'string') {\n throw new Error(`Invalid block ${kindField} in ${source}: expected string, got ${typeof kindValue}`);\n }\n\n // Template blocks derive purpose from scope, page blocks have explicit purpose\n if (source === 'page') {\n if (typeof blockRecord.purpose !== 'string') {\n throw new Error(`Invalid block purpose in ${source}: expected string, got ${typeof blockRecord.purpose}`);\n }\n return {\n id: blockRecord.id as string | null,\n kind: kindValue,\n purpose: blockRecord.purpose,\n };\n }\n\n // Template block: derive purpose from scope, include content\n const scope = blockRecord.scope as 'entry' | 'template' | undefined;\n const content = (blockRecord.content as Record<string, unknown> | null) ?? {};\n\n return {\n id: blockRecord.id as string | null,\n kind: kindValue,\n purpose: scope === 'entry' ? 'entry-content' : 'template-layout',\n content,\n };\n}\n\n/** Template block structure from API response */\ntype TemplateBlock = {\n id: string;\n blockKind: string;\n scope: 'entry' | 'template';\n content: Record<string, unknown> | null;\n};\n\n/** Template structure from API response */\ntype Template = {\n id: string;\n name: string;\n templateKey: string;\n blocks: TemplateBlock[];\n};\n\n/**\n * Processes an entry's template into a PageOutline format and prefetches block data.\n * Returns null templatePage if no valid template with blocks is available.\n */\nasync function processEntryTemplate(\n templates: Template[] | undefined,\n entry: ContentEntryData,\n context: { siteId: string; preview: boolean },\n client: RiverbankClient\n): Promise<{ templatePage: PageProps['page'] | null; resolvedData: ResolvedBlockData }> {\n const template = templates?.[0];\n\n // Templates without blocks are treated as \"no template\" - the entry should be\n // rendered with custom UI rather than an empty template page\n if (!template || !template.blocks?.length) {\n return { templatePage: null, resolvedData: {} };\n }\n\n // Convert template blocks to PageOutline format with validation\n const blocks = template.blocks.map((block) => validateAndConvertBlock(block, 'template'));\n\n const templatePage: PageProps['page'] = {\n name: template.name || 'Entry Template',\n path: entry.path || '/',\n purpose: 'entry-template',\n blocks,\n };\n\n // Prefetch block data for template\n const resolvedData = await prefetchBlockData(\n templatePage,\n {\n siteId: context.siteId,\n pageId: template.id,\n previewStage: context.preview ? 'preview' : 'published',\n },\n client\n );\n\n return { templatePage, resolvedData };\n}\n"],"mappings":"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","names":["resolvedData"]}
|
|
1
|
+
{"version":3,"sources":["../../src/rendering/helpers/loadContent.ts"],"sourcesContent":["/**\n * Server-side helper to fetch content (page or entry) by path.\n *\n * Use this for dynamic routing where a path could resolve to either\n * a page or a content entry.\n */\n\nimport type { Theme } from '@riverbankcms/blocks';\nimport type { RiverbankClient, PageResponse, SiteResponse } from '../../client/types';\nimport type { PageProps } from '../components/Page';\nimport { prefetchBlockData } from '../../data/prefetchBlockData';\nimport type { ResolvedBlockData } from '../../data/prefetchBlockData';\n\n/**\n * Site data included in content results for metadata generation.\n */\nexport type SiteData = SiteResponse['site'];\n\nexport type LoadContentParams = {\n client: RiverbankClient;\n siteId: string;\n path: string;\n /**\n * If true, fetches draft/unpublished content instead of published content.\n * This affects both pages and entries.\n * Requires API key with site access.\n *\n * @default false\n */\n preview?: boolean;\n};\n\n/**\n * Content entry data returned when a path resolves to an entry\n */\nexport type ContentEntryData = {\n id: string;\n /** Content type key (e.g., 'blog-post', 'product') */\n type: string | null;\n title: string;\n slug: string | null;\n path: string | null;\n status: string;\n publishAt: string | null;\n /** The raw content fields - use these to render your own UI */\n content: Record<string, unknown>;\n metaTitle: string | null;\n metaDescription: string | null;\n createdAt: string;\n updatedAt: string;\n};\n\n/**\n * Result when path resolves to a page\n */\nexport type PageContentResult = {\n type: 'page';\n /** Page outline ready for rendering with <Page> component */\n page: PageProps['page'];\n /** Site theme for styling */\n theme: Theme;\n /** Site ID */\n siteId: string;\n /** Pre-fetched block data for data loaders */\n resolvedData: ResolvedBlockData;\n /** Site data for metadata generation */\n site: SiteData;\n};\n\n/**\n * Result when path resolves to a content entry\n */\nexport type EntryContentResult = {\n type: 'entry';\n /** Raw entry data - render this however you want */\n entry: ContentEntryData;\n /** Template page for rendering the entry (if available) */\n templatePage: PageProps['page'] | null;\n /** Pre-fetched block data for template page data loaders */\n resolvedData: ResolvedBlockData;\n /** Data context for template blocks (includes entry content for bindings) */\n dataContext: { contentEntry: Record<string, unknown> };\n /** Site theme for styling (useful if rendering with SDK components) */\n theme: Theme;\n /** Site ID */\n siteId: string;\n /** Site data for metadata generation */\n site: SiteData;\n};\n\n/**\n * Discriminated union result from loadContent\n */\nexport type LoadContentResult = PageContentResult | EntryContentResult;\n\n/**\n * Type guard to check if result is a page\n */\nexport function isPageContent(result: LoadContentResult): result is PageContentResult {\n return result.type === 'page';\n}\n\n/**\n * Type guard to check if result is an entry\n */\nexport function isEntryContent(result: LoadContentResult): result is EntryContentResult {\n return result.type === 'entry';\n}\n\n/**\n * Server-side helper to fetch content by path.\n *\n * Returns a discriminated union - either page data (ready for `<Page>` component)\n * or raw entry data (for custom rendering).\n *\n * @example Dynamic routing with both pages and entries\n * ```tsx\n * import { loadContent, Page, isPageContent } from '@riverbankcms/sdk';\n *\n * export default async function DynamicRoute({ params }) {\n * const content = await loadContent({\n * client,\n * siteId: 'site-123',\n * path: `/${params.slug?.join('/') || ''}`,\n * });\n *\n * if (isPageContent(content)) {\n * return <Page {...content} />;\n * }\n *\n * // Render entry with custom UI\n * return (\n * <article>\n * <h1>{content.entry.title}</h1>\n * <div>{content.entry.content.body}</div>\n * </article>\n * );\n * }\n * ```\n *\n * @example Entry-specific rendering based on content type\n * ```tsx\n * const content = await loadContent({ client, siteId, path });\n *\n * if (content.type === 'entry') {\n * switch (content.entry.type) {\n * case 'blog-post':\n * return <BlogPost entry={content.entry} theme={content.theme} />;\n * case 'product':\n * return <ProductPage entry={content.entry} theme={content.theme} />;\n * default:\n * return <GenericEntry entry={content.entry} />;\n * }\n * }\n *\n * return <Page {...content} />;\n * ```\n *\n * @example Preview mode for draft content\n * ```tsx\n * const content = await loadContent({\n * client,\n * siteId,\n * path,\n * preview: true, // Fetches draft content for both pages and entries\n * });\n * ```\n */\nexport async function loadContent(params: LoadContentParams): Promise<LoadContentResult> {\n const { client, siteId, path, preview = false } = params;\n\n // Fetch site and content in parallel\n const [site, contentResponse] = await Promise.all([\n client.getSite({ id: siteId }),\n client.getPage({ siteId, path, preview }),\n ]);\n\n // Check if response is an entry\n if (isEntryResponse(contentResponse)) {\n const entryData = contentResponse.entry;\n\n const entry: ContentEntryData = {\n id: entryData.id,\n type: entryData.type,\n title: entryData.title,\n slug: entryData.slug,\n path: entryData.path,\n status: entryData.status,\n publishAt: entryData.publishAt,\n // Use draft content in preview mode, otherwise use published content\n content: preview\n ? (entryData.draftContent ?? entryData.content)\n : entryData.content,\n metaTitle: preview\n ? (entryData.draftMetaTitle ?? entryData.metaTitle)\n : entryData.metaTitle,\n metaDescription: preview\n ? (entryData.draftMetaDescription ?? entryData.metaDescription)\n : entryData.metaDescription,\n createdAt: entryData.createdAt,\n updatedAt: entryData.updatedAt,\n };\n\n // Process template if available (uses first template from content type)\n const { templatePage, resolvedData } = await processEntryTemplate(\n contentResponse.templates as Template[] | undefined,\n entry,\n { siteId, preview },\n client\n );\n\n return {\n type: 'entry',\n entry,\n templatePage,\n resolvedData,\n dataContext: { contentEntry: entry.content },\n theme: site.theme,\n siteId,\n site: site.site,\n };\n }\n\n // Handle page response\n const { page: pageData } = contentResponse;\n\n // Convert API response blocks to PageOutline format with validation\n const blocks = pageData.blocks.map((block) => validateAndConvertBlock(block, 'page'));\n\n const pageOutline = {\n name: pageData.name,\n path: pageData.path,\n purpose: pageData.purpose,\n blocks,\n };\n\n // Prefetch block data loaders for pages\n const resolvedData = await prefetchBlockData(\n pageOutline,\n {\n siteId,\n pageId: pageData.id,\n previewStage: preview ? 'preview' : 'published',\n },\n client\n );\n\n return {\n type: 'page',\n page: pageOutline,\n theme: site.theme,\n siteId,\n resolvedData,\n site: site.site,\n };\n}\n\n/**\n * Type guard to check if API response is an entry\n */\nfunction isEntryResponse(response: PageResponse): response is Extract<PageResponse, { type: 'entry' }> {\n return 'type' in response && response.type === 'entry';\n}\n\n/**\n * Validates and converts a raw block from API response to PageOutline block format.\n * Used for both page blocks and template blocks to ensure consistent validation.\n */\nfunction validateAndConvertBlock(\n block: unknown,\n source: 'page' | 'template'\n): { id: string | null; kind: string; purpose: string; content?: Record<string, unknown> } {\n if (!block || typeof block !== 'object') {\n throw new Error(`Invalid block format in ${source} API response`);\n }\n\n const blockRecord = block as Record<string, unknown>;\n\n // Template blocks use 'blockKind', page blocks use 'kind'\n const kindField = source === 'template' ? 'blockKind' : 'kind';\n const kindValue = blockRecord[kindField];\n\n if (typeof blockRecord.id !== 'string' && blockRecord.id !== null) {\n throw new Error(`Invalid block id in ${source}: expected string or null, got ${typeof blockRecord.id}`);\n }\n if (typeof kindValue !== 'string') {\n throw new Error(`Invalid block ${kindField} in ${source}: expected string, got ${typeof kindValue}`);\n }\n\n // Template blocks derive purpose from scope, page blocks have explicit purpose\n if (source === 'page') {\n if (typeof blockRecord.purpose !== 'string') {\n throw new Error(`Invalid block purpose in ${source}: expected string, got ${typeof blockRecord.purpose}`);\n }\n // Include content for page blocks - required for data loader binding resolution\n const content = (blockRecord.content as Record<string, unknown> | null) ?? {};\n return {\n id: blockRecord.id as string | null,\n kind: kindValue,\n purpose: blockRecord.purpose,\n content,\n };\n }\n\n // Template block: derive purpose from scope, include content\n const scope = blockRecord.scope as 'entry' | 'template' | undefined;\n const content = (blockRecord.content as Record<string, unknown> | null) ?? {};\n\n return {\n id: blockRecord.id as string | null,\n kind: kindValue,\n purpose: scope === 'entry' ? 'entry-content' : 'template-layout',\n content,\n };\n}\n\n/** Template block structure from API response */\ntype TemplateBlock = {\n id: string;\n blockKind: string;\n scope: 'entry' | 'template';\n content: Record<string, unknown> | null;\n};\n\n/** Template structure from API response */\ntype Template = {\n id: string;\n name: string;\n templateKey: string;\n blocks: TemplateBlock[];\n};\n\n/**\n * Processes an entry's template into a PageOutline format and prefetches block data.\n * Returns null templatePage if no valid template with blocks is available.\n */\nasync function processEntryTemplate(\n templates: Template[] | undefined,\n entry: ContentEntryData,\n context: { siteId: string; preview: boolean },\n client: RiverbankClient\n): Promise<{ templatePage: PageProps['page'] | null; resolvedData: ResolvedBlockData }> {\n const template = templates?.[0];\n\n // Templates without blocks are treated as \"no template\" - the entry should be\n // rendered with custom UI rather than an empty template page\n if (!template || !template.blocks?.length) {\n return { templatePage: null, resolvedData: {} };\n }\n\n // Convert template blocks to PageOutline format with validation\n const blocks = template.blocks.map((block) => validateAndConvertBlock(block, 'template'));\n\n const templatePage: PageProps['page'] = {\n name: template.name || 'Entry Template',\n path: entry.path || '/',\n purpose: 'entry-template',\n blocks,\n };\n\n // Prefetch block data for template\n const resolvedData = await prefetchBlockData(\n templatePage,\n {\n siteId: context.siteId,\n pageId: template.id,\n previewStage: context.preview ? 'preview' : 'published',\n },\n client\n );\n\n return { templatePage, resolvedData };\n}\n"],"mappings":"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","names":["resolvedData","content"]}
|
|
@@ -1268,6 +1268,15 @@ var ENDPOINT_DEFINITIONS = {
|
|
|
1268
1268
|
auth: "user",
|
|
1269
1269
|
responseKind: "json"
|
|
1270
1270
|
},
|
|
1271
|
+
// Public routable content for SDK SSG
|
|
1272
|
+
getPublicRoutableContent: {
|
|
1273
|
+
path: "/public/sites/{siteId}/routable-content",
|
|
1274
|
+
method: "GET",
|
|
1275
|
+
revalidate: 60,
|
|
1276
|
+
tags: ["site-{siteId}", "routable-content-{siteId}"],
|
|
1277
|
+
auth: "public",
|
|
1278
|
+
responseKind: "json"
|
|
1279
|
+
},
|
|
1271
1280
|
// Generic public content preview (preferred)
|
|
1272
1281
|
getPublishedEntryPreview: {
|
|
1273
1282
|
path: "/public/content/{siteId}/{type}/{slug}/preview",
|
|
@@ -2331,13 +2340,16 @@ var SimpleCache = class {
|
|
|
2331
2340
|
};
|
|
2332
2341
|
|
|
2333
2342
|
// src/version.ts
|
|
2334
|
-
var SDK_VERSION = "0.
|
|
2343
|
+
var SDK_VERSION = "0.8.0";
|
|
2335
2344
|
|
|
2336
2345
|
// src/client/error.ts
|
|
2337
2346
|
var RiverbankApiError = class _RiverbankApiError extends Error {
|
|
2338
2347
|
constructor(apiError) {
|
|
2339
2348
|
super(apiError.message);
|
|
2340
2349
|
this.name = "RiverbankApiError";
|
|
2350
|
+
if ("cause" in apiError && apiError.cause) {
|
|
2351
|
+
this.cause = apiError.cause;
|
|
2352
|
+
}
|
|
2341
2353
|
this.code = apiError.code;
|
|
2342
2354
|
this.requestId = apiError.requestId;
|
|
2343
2355
|
this.status = apiError.status;
|
|
@@ -2424,6 +2436,54 @@ var RiverbankApiError = class _RiverbankApiError extends Error {
|
|
|
2424
2436
|
isServerError() {
|
|
2425
2437
|
return this.code.startsWith("server:");
|
|
2426
2438
|
}
|
|
2439
|
+
/**
|
|
2440
|
+
* Returns a human-readable string representation of the error.
|
|
2441
|
+
* Includes all key details for debugging.
|
|
2442
|
+
*
|
|
2443
|
+
* @example
|
|
2444
|
+
* "RiverbankApiError: Content keys cannot access preview content | Code: auth:forbidden | Status: 401 | RequestId: req-abc123"
|
|
2445
|
+
*/
|
|
2446
|
+
toString() {
|
|
2447
|
+
const parts = [`RiverbankApiError: ${this.message}`];
|
|
2448
|
+
if (this.code) parts.push(`Code: ${this.code}`);
|
|
2449
|
+
if (this.status) parts.push(`Status: ${this.status}`);
|
|
2450
|
+
if (this.requestId) parts.push(`RequestId: ${this.requestId}`);
|
|
2451
|
+
return parts.join(" | ");
|
|
2452
|
+
}
|
|
2453
|
+
/**
|
|
2454
|
+
* Custom Node.js inspect output for better console.log display.
|
|
2455
|
+
* This ensures that console.log(error) shows all relevant details
|
|
2456
|
+
* instead of just "[Object]" for nested properties.
|
|
2457
|
+
*/
|
|
2458
|
+
[Symbol.for("nodejs.util.inspect.custom")]() {
|
|
2459
|
+
return this.toDetailedString();
|
|
2460
|
+
}
|
|
2461
|
+
/**
|
|
2462
|
+
* Returns a detailed multi-line string for debugging.
|
|
2463
|
+
* Used by the Node.js inspect symbol for console output.
|
|
2464
|
+
*/
|
|
2465
|
+
toDetailedString() {
|
|
2466
|
+
const lines = [
|
|
2467
|
+
`RiverbankApiError: ${this.message}`,
|
|
2468
|
+
` Code: ${this.code}`,
|
|
2469
|
+
` Status: ${this.status}`,
|
|
2470
|
+
` RequestId: ${this.requestId}`,
|
|
2471
|
+
` Timestamp: ${this.timestamp}`
|
|
2472
|
+
];
|
|
2473
|
+
if (this.isRetryable) {
|
|
2474
|
+
lines.push(` Retryable: true`);
|
|
2475
|
+
if (this.retryAfterMs) {
|
|
2476
|
+
lines.push(` RetryAfter: ${this.retryAfterMs}ms`);
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
if (this.fieldErrors && this.fieldErrors.length > 0) {
|
|
2480
|
+
lines.push(" FieldErrors:");
|
|
2481
|
+
this.fieldErrors.forEach((fe) => {
|
|
2482
|
+
lines.push(` - ${fe.field}: ${fe.message}`);
|
|
2483
|
+
});
|
|
2484
|
+
}
|
|
2485
|
+
return lines.join("\n");
|
|
2486
|
+
}
|
|
2427
2487
|
};
|
|
2428
2488
|
|
|
2429
2489
|
// src/client/resilience.ts
|
|
@@ -2438,13 +2498,75 @@ var DEFAULT_CIRCUIT_BREAKER_CONFIG = {
|
|
|
2438
2498
|
resetTimeoutMs: 3e4,
|
|
2439
2499
|
halfOpenMaxRequests: 2
|
|
2440
2500
|
};
|
|
2501
|
+
var PERMANENT_NETWORK_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
2502
|
+
"ECONNREFUSED",
|
|
2503
|
+
// Server is not running / port not listening
|
|
2504
|
+
"ENOTFOUND",
|
|
2505
|
+
// DNS lookup failed - hostname doesn't exist
|
|
2506
|
+
"EAI_AGAIN"
|
|
2507
|
+
// DNS lookup timeout (usually permanent for invalid hosts)
|
|
2508
|
+
]);
|
|
2509
|
+
var TRANSIENT_NETWORK_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
2510
|
+
"ECONNRESET",
|
|
2511
|
+
// Connection was reset mid-request (server dropped it)
|
|
2512
|
+
"EPIPE",
|
|
2513
|
+
// Broken pipe (connection closed while writing)
|
|
2514
|
+
"ETIMEDOUT",
|
|
2515
|
+
// Connection timed out (could be temporary congestion)
|
|
2516
|
+
"ESOCKETTIMEDOUT"
|
|
2517
|
+
// Socket timeout
|
|
2518
|
+
]);
|
|
2519
|
+
var NODE_NETWORK_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
2520
|
+
// Permanent
|
|
2521
|
+
"ECONNREFUSED",
|
|
2522
|
+
"ENOTFOUND",
|
|
2523
|
+
"EAI_AGAIN",
|
|
2524
|
+
// Transient
|
|
2525
|
+
"ECONNRESET",
|
|
2526
|
+
"EPIPE",
|
|
2527
|
+
"ETIMEDOUT",
|
|
2528
|
+
"ESOCKETTIMEDOUT"
|
|
2529
|
+
]);
|
|
2530
|
+
function isNodeNetworkErrorCode(code) {
|
|
2531
|
+
return !code.includes(":") && NODE_NETWORK_ERROR_CODES.has(code);
|
|
2532
|
+
}
|
|
2533
|
+
function getErrorCodeFromCause(error) {
|
|
2534
|
+
let current = error;
|
|
2535
|
+
while (current) {
|
|
2536
|
+
const nodeError = current;
|
|
2537
|
+
if (nodeError.code && typeof nodeError.code === "string") {
|
|
2538
|
+
if (isNodeNetworkErrorCode(nodeError.code)) {
|
|
2539
|
+
return nodeError.code;
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
const errorWithCause = current;
|
|
2543
|
+
current = errorWithCause.cause;
|
|
2544
|
+
}
|
|
2545
|
+
return void 0;
|
|
2546
|
+
}
|
|
2441
2547
|
function isTransientError(error) {
|
|
2442
2548
|
if (error instanceof RiverbankApiError) {
|
|
2549
|
+
const errorCode = getErrorCodeFromCause(error);
|
|
2550
|
+
if (errorCode && PERMANENT_NETWORK_ERROR_CODES.has(errorCode)) {
|
|
2551
|
+
return false;
|
|
2552
|
+
}
|
|
2443
2553
|
if (error.status === 0) return true;
|
|
2444
2554
|
if (error.status === 429) return true;
|
|
2445
2555
|
if (error.status >= 500) return true;
|
|
2446
2556
|
return false;
|
|
2447
2557
|
}
|
|
2558
|
+
if (error instanceof TypeError) {
|
|
2559
|
+
const errorCode = getErrorCodeFromCause(error);
|
|
2560
|
+
if (errorCode) {
|
|
2561
|
+
if (PERMANENT_NETWORK_ERROR_CODES.has(errorCode)) {
|
|
2562
|
+
return false;
|
|
2563
|
+
}
|
|
2564
|
+
if (TRANSIENT_NETWORK_ERROR_CODES.has(errorCode)) {
|
|
2565
|
+
return true;
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
return true;
|
|
2569
|
+
}
|
|
2448
2570
|
return true;
|
|
2449
2571
|
}
|
|
2450
2572
|
function calculateBackoff(attempt, config) {
|
|
@@ -2581,7 +2703,19 @@ async function fetchWithTimeoutAndRetry(fetcher, config) {
|
|
|
2581
2703
|
}
|
|
2582
2704
|
throw lastError;
|
|
2583
2705
|
}
|
|
2706
|
+
function isAbortError(error) {
|
|
2707
|
+
if (typeof DOMException !== "undefined" && error instanceof DOMException && error.name === "AbortError") {
|
|
2708
|
+
return true;
|
|
2709
|
+
}
|
|
2710
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
2711
|
+
return true;
|
|
2712
|
+
}
|
|
2713
|
+
return false;
|
|
2714
|
+
}
|
|
2584
2715
|
function shouldRetryError(error, customRetryOn) {
|
|
2716
|
+
if (isAbortError(error)) {
|
|
2717
|
+
return false;
|
|
2718
|
+
}
|
|
2585
2719
|
if (customRetryOn) {
|
|
2586
2720
|
const statusCode = error instanceof RiverbankApiError ? error.status : void 0;
|
|
2587
2721
|
return customRetryOn(error, statusCode);
|
|
@@ -2625,7 +2759,7 @@ var DEFAULT_SERVER_TIMEOUT_MS = 8e3;
|
|
|
2625
2759
|
function generateRequestId2() {
|
|
2626
2760
|
return `req-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
2627
2761
|
}
|
|
2628
|
-
function
|
|
2762
|
+
function isAbortError2(error) {
|
|
2629
2763
|
if (error instanceof DOMException && error.name === "AbortError") {
|
|
2630
2764
|
return true;
|
|
2631
2765
|
}
|
|
@@ -2663,7 +2797,7 @@ function getNetworkErrorCode(error) {
|
|
|
2663
2797
|
return "network:connection_error";
|
|
2664
2798
|
}
|
|
2665
2799
|
function convertToTypedError(error) {
|
|
2666
|
-
if (
|
|
2800
|
+
if (isAbortError2(error)) {
|
|
2667
2801
|
throw error;
|
|
2668
2802
|
}
|
|
2669
2803
|
if (error instanceof ApiEnvelopeError) {
|
|
@@ -2693,12 +2827,23 @@ function convertToTypedError(error) {
|
|
|
2693
2827
|
message: networkError.message || "Network request failed",
|
|
2694
2828
|
requestId: `local-${Date.now()}`,
|
|
2695
2829
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2696
|
-
status: 0
|
|
2830
|
+
status: 0,
|
|
2697
2831
|
// No HTTP response received
|
|
2832
|
+
cause: networkError
|
|
2833
|
+
// Preserve original error for retry/circuit breaker classification
|
|
2698
2834
|
});
|
|
2699
2835
|
}
|
|
2700
2836
|
throw error;
|
|
2701
2837
|
}
|
|
2838
|
+
function detectKeyType(apiKey) {
|
|
2839
|
+
if (apiKey.startsWith("bld_live_sk_") || apiKey.startsWith("bld_test_sk_")) {
|
|
2840
|
+
return "content";
|
|
2841
|
+
}
|
|
2842
|
+
if (apiKey.startsWith("bld_preview_sk_")) {
|
|
2843
|
+
return "preview";
|
|
2844
|
+
}
|
|
2845
|
+
return "unknown";
|
|
2846
|
+
}
|
|
2702
2847
|
function createRiverbankClient(config) {
|
|
2703
2848
|
if (!config.baseUrl) {
|
|
2704
2849
|
throw new Error(
|
|
@@ -2729,6 +2874,7 @@ function createRiverbankClient(config) {
|
|
|
2729
2874
|
resetTimeoutMs: config.resilience?.circuitBreaker?.resetTimeoutMs ?? DEFAULT_CIRCUIT_BREAKER_CONFIG.resetTimeoutMs,
|
|
2730
2875
|
halfOpenMaxRequests: config.resilience?.circuitBreaker?.halfOpenMaxRequests ?? DEFAULT_CIRCUIT_BREAKER_CONFIG.halfOpenMaxRequests
|
|
2731
2876
|
};
|
|
2877
|
+
const keyType = detectKeyType(config.apiKey);
|
|
2732
2878
|
const apiClient = createBearerAPIClient(config.apiKey, config.baseUrl);
|
|
2733
2879
|
const cache = new SimpleCache({
|
|
2734
2880
|
maxSize: cacheMaxSize,
|
|
@@ -3024,9 +3170,26 @@ function createRiverbankClient(config) {
|
|
|
3024
3170
|
});
|
|
3025
3171
|
}, { signal });
|
|
3026
3172
|
},
|
|
3173
|
+
async getAllPublishedRoutes(params) {
|
|
3174
|
+
const { siteId, signal } = params;
|
|
3175
|
+
if (!siteId) {
|
|
3176
|
+
throw new Error("getAllPublishedRoutes() requires siteId");
|
|
3177
|
+
}
|
|
3178
|
+
const cacheKey = `routable-content:${siteId}:published`;
|
|
3179
|
+
return resilientFetch(cacheKey, async (sig) => {
|
|
3180
|
+
return await apiClient({
|
|
3181
|
+
endpoint: "getPublicRoutableContent",
|
|
3182
|
+
params: { siteId, publishedOnly: "true" },
|
|
3183
|
+
options: { signal: sig }
|
|
3184
|
+
});
|
|
3185
|
+
}, { signal });
|
|
3186
|
+
},
|
|
3027
3187
|
clearCache() {
|
|
3028
3188
|
cache.clear();
|
|
3029
3189
|
},
|
|
3190
|
+
getKeyType() {
|
|
3191
|
+
return keyType;
|
|
3192
|
+
},
|
|
3030
3193
|
getLastEmittedStatus() {
|
|
3031
3194
|
return lastStatus;
|
|
3032
3195
|
},
|
|
@@ -3049,4 +3212,4 @@ export {
|
|
|
3049
3212
|
init_loader,
|
|
3050
3213
|
createRiverbankClient
|
|
3051
3214
|
};
|
|
3052
|
-
//# sourceMappingURL=chunk-
|
|
3215
|
+
//# sourceMappingURL=chunk-65A5HAUZ.mjs.map
|