@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.
Files changed (79) hide show
  1. package/README.md +88 -1
  2. package/dist/cli/index.js +3693 -39
  3. package/dist/cli/index.js.map +1 -1
  4. package/dist/client/client.d.mts +2 -2
  5. package/dist/client/client.d.ts +2 -2
  6. package/dist/client/client.js +167 -4
  7. package/dist/client/client.js.map +1 -1
  8. package/dist/client/client.mjs +167 -4
  9. package/dist/client/client.mjs.map +1 -1
  10. package/dist/client/hooks.d.mts +2 -2
  11. package/dist/client/hooks.d.ts +2 -2
  12. package/dist/client/usePage-CdnO2CP5.d.mts +6875 -0
  13. package/dist/client/usePage-_ksKXlUF.d.ts +6875 -0
  14. package/dist/server/{Layout-qWLdVm5-.d.mts → Layout-D4J009eS.d.mts} +1 -1
  15. package/dist/server/{Layout-Yluyb6sK.d.ts → Layout-l2v4Qa6E.d.ts} +1 -1
  16. package/dist/server/{chunk-74XUVNOO.mjs → chunk-4YQJUL5W.mjs} +4 -2
  17. package/dist/server/{chunk-74XUVNOO.mjs.map → chunk-4YQJUL5W.mjs.map} +1 -1
  18. package/dist/server/{chunk-JNU7ZS2V.mjs → chunk-65A5HAUZ.mjs} +168 -5
  19. package/dist/server/chunk-65A5HAUZ.mjs.map +1 -0
  20. package/dist/server/{chunk-JWRNMNWI.js → chunk-EIJ27EZQ.js} +4 -2
  21. package/dist/server/chunk-EIJ27EZQ.js.map +1 -0
  22. package/dist/server/{chunk-6Z4MQG47.js → chunk-WM646WI3.js} +168 -5
  23. package/dist/server/chunk-WM646WI3.js.map +1 -0
  24. package/dist/server/{components-Di5ME6He.d.ts → components-D2uCKCj7.d.ts} +3 -3
  25. package/dist/server/{components-DNHfSCML.d.mts → components-vtYEmmPF.d.mts} +3 -3
  26. package/dist/server/components.d.mts +5 -5
  27. package/dist/server/components.d.ts +5 -5
  28. package/dist/server/config-validation.d.mts +2 -2
  29. package/dist/server/config-validation.d.ts +2 -2
  30. package/dist/server/config.d.mts +3 -3
  31. package/dist/server/config.d.ts +3 -3
  32. package/dist/server/data.d.mts +2 -2
  33. package/dist/server/data.d.ts +2 -2
  34. package/dist/server/{index-Clm3skz_.d.mts → index-2qnY7VH_.d.mts} +1 -1
  35. package/dist/server/{index-DLvNddi-.d.ts → index-BxrAuL9K.d.ts} +1 -1
  36. package/dist/server/{index-C9Ra8dza.d.ts → index-CH_dvF6n.d.ts} +2 -2
  37. package/dist/server/{index--Oyunk_B.d.mts → index-DfWg1Qle.d.mts} +2 -2
  38. package/dist/server/index.d.mts +13 -5
  39. package/dist/server/index.d.ts +13 -5
  40. package/dist/server/index.js +10 -10
  41. package/dist/server/index.mjs +1 -1
  42. package/dist/server/{loadContent-D7LQwI0o.d.ts → loadContent-DECnsp4k.d.ts} +3 -3
  43. package/dist/server/{loadContent-DVfuBLiZ.d.mts → loadContent-Du5kS8UM.d.mts} +3 -3
  44. package/dist/server/{loadPage-BmYJCe_V.d.ts → loadPage-BZohBxxf.d.ts} +2 -2
  45. package/dist/server/{loadPage-BucnLHmE.d.mts → loadPage-VBorKlWv.d.mts} +2 -2
  46. package/dist/server/metadata.d.mts +4 -4
  47. package/dist/server/metadata.d.ts +4 -4
  48. package/dist/server/navigation.d.mts +2 -2
  49. package/dist/server/navigation.d.ts +2 -2
  50. package/dist/server/next.d.mts +38 -7
  51. package/dist/server/next.d.ts +38 -7
  52. package/dist/server/next.js +35 -17
  53. package/dist/server/next.js.map +1 -1
  54. package/dist/server/next.mjs +30 -12
  55. package/dist/server/next.mjs.map +1 -1
  56. package/dist/server/rendering/server.d.mts +4 -4
  57. package/dist/server/rendering/server.d.ts +4 -4
  58. package/dist/server/rendering.d.mts +7 -7
  59. package/dist/server/rendering.d.ts +7 -7
  60. package/dist/server/rendering.js +2 -2
  61. package/dist/server/rendering.mjs +1 -1
  62. package/dist/server/routing.d.mts +4 -4
  63. package/dist/server/routing.d.ts +4 -4
  64. package/dist/server/server.d.mts +5 -5
  65. package/dist/server/server.d.ts +5 -5
  66. package/dist/server/server.js +3 -3
  67. package/dist/server/server.mjs +2 -2
  68. package/dist/server/{types-C-LShyIg.d.mts → types-BRQ_6yOc.d.mts} +43 -1
  69. package/dist/server/{types-BjgZt8xJ.d.mts → types-CJfJwcuL.d.mts} +37 -0
  70. package/dist/server/{types-Dt98DeYa.d.ts → types-CgSO0yxg.d.ts} +8 -0
  71. package/dist/server/{types-BRQyLrQU.d.ts → types-D0rPF8l5.d.ts} +43 -1
  72. package/dist/server/{types-DLBhEPSt.d.ts → types-D8XqwoVd.d.ts} +37 -0
  73. package/dist/server/{types-BSV6Vc-P.d.mts → types-DT30Qy7x.d.mts} +8 -0
  74. package/dist/server/{validation-DU2YE7u5.d.ts → validation-D1LaY1kQ.d.ts} +1 -1
  75. package/dist/server/{validation-BGuRo8P1.d.mts → validation-Pv3Zs6dP.d.mts} +1 -1
  76. package/package.json +2 -1
  77. package/dist/server/chunk-6Z4MQG47.js.map +0 -1
  78. package/dist/server/chunk-JNU7ZS2V.mjs.map +0 -1
  79. 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-C-LShyIg.mjs';
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-BRQyLrQU.js';
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-74XUVNOO.mjs.map
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.7.4";
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 isAbortError(error) {
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 (isAbortError(error)) {
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-JNU7ZS2V.mjs.map
3215
+ //# sourceMappingURL=chunk-65A5HAUZ.mjs.map