@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
package/dist/server/next.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/will/Projects/Business/cms/builder/packages/sdk/dist/server/next.js","../../src/next/catch-all.tsx","../../src/rendering/hooks/usePage.ts","../../src/rendering/hooks/useContent.ts","../../src/next/static-params.ts","../../src/next/index.ts"],"names":["isPreviewMode","Page"],"mappings":"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","file":"/Users/will/Projects/Business/cms/builder/packages/sdk/dist/server/next.js","sourcesContent":[null,"/**\n * Next.js catch-all page factory for Riverbank CMS.\n *\n * Provides a simple, opinionated way to render CMS content in Next.js catch-all routes.\n * Reduces typical page.tsx boilerplate from ~160 lines to ~10 lines.\n *\n * @example\n * ```tsx\n * // src/app/[[...slug]]/page.tsx\n * import { createCatchAllPage } from '@riverbankcms/sdk/next';\n * import { getRiverbankClient } from '@/lib/builder-client';\n * import config from '@/riverbank.config';\n *\n * const { Page, generateMetadata } = createCatchAllPage({\n * getClient: getRiverbankClient,\n * config,\n * blockOverrides: { hero: MyCustomHero },\n * });\n *\n * export default Page;\n * export { generateMetadata };\n * ```\n */\n\n// Note: next is a peerDependency - this module only works in Next.js projects.\nimport { notFound } from 'next/navigation';\n\nimport {\n loadContent,\n isPageContent,\n isEntryContent,\n} from '../rendering/helpers/loadContent';\nimport type { LoadContentResult } from '../rendering/helpers/loadContent';\nimport { Page as PageComponent } from '../rendering/components/Page';\nimport {\n generatePageMetadata,\n generatePreviewMetadata,\n type Metadata,\n} from '../metadata/generatePageMetadata';\nimport type {\n CreateCatchAllPageOptions,\n CatchAllPageProps,\n CreateCatchAllPageResult,\n} from './types';\n\n/**\n * Detect preview mode from environment variable.\n *\n * Sites can enable preview mode by setting:\n * ```\n * RIVERBANK_PREVIEW_MODE=true\n * ```\n *\n * This is typically set in preview/staging environments to fetch\n * draft content instead of published content.\n */\nfunction isPreviewMode(): boolean {\n return process.env.RIVERBANK_PREVIEW_MODE === 'true';\n}\n\n/**\n * Convert URL slug segments to a path string.\n *\n * @example\n * ```\n * slugToPath(undefined) // '/'\n * slugToPath([]) // '/'\n * slugToPath(['about']) // '/about'\n * slugToPath(['blog', 'post']) // '/blog/post'\n * ```\n */\nfunction slugToPath(slug?: string[]): string {\n if (!slug || slug.length === 0) return '/';\n return '/' + slug.join('/');\n}\n\n/**\n * Factory function to create a Next.js catch-all page component and metadata generator.\n *\n * This provides a simple, opinionated setup for rendering Riverbank CMS content\n * in external SDK sites. It handles:\n *\n * - Page and content entry routing\n * - Preview mode detection (via `RIVERBANK_PREVIEW_MODE` env var)\n * - SEO metadata generation\n * - Block overrides for custom components\n * - 404 handling for missing content\n *\n * ## Escape Hatches\n *\n * For customization beyond the defaults, use these options:\n *\n * - `beforeRender`: Intercept before rendering (maintenance mode, access control)\n * - `customMetadata`: Full control over SEO metadata\n * - `wrapper`: Wrap all content (analytics, error boundaries)\n *\n * For maximum control, use the lower-level helpers directly:\n * - `loadContent()` from `@riverbankcms/sdk/rendering`\n * - `Page` component from `@riverbankcms/sdk/rendering`\n * - `generatePageMetadata()` from `@riverbankcms/sdk/metadata`\n *\n * @param options - Configuration options\n * @returns Object with `Page` component and `generateMetadata` function\n *\n * @example Basic usage\n * ```tsx\n * const { Page, generateMetadata } = createCatchAllPage({\n * getClient: getRiverbankClient,\n * config,\n * });\n *\n * export default Page;\n * export { generateMetadata };\n * ```\n *\n * @example With maintenance mode\n * ```tsx\n * const { Page, generateMetadata } = createCatchAllPage({\n * getClient,\n * config,\n * beforeRender: async () => {\n * if (process.env.MAINTENANCE_MODE === 'true') {\n * return <MaintenancePage />;\n * }\n * return null;\n * },\n * });\n * ```\n */\nexport function createCatchAllPage(\n options: CreateCatchAllPageOptions\n): CreateCatchAllPageResult {\n const {\n getClient,\n config,\n blockOverrides,\n siteUrl,\n beforeRender,\n customMetadata,\n wrapper: Wrapper,\n } = options;\n\n /**\n * Main page component that loads and renders CMS content.\n */\n async function Page({ params, searchParams: searchParamsPromise }: CatchAllPageProps): Promise<React.ReactNode> {\n const [{ slug }, searchParams] = await Promise.all([\n params,\n searchParamsPromise ?? Promise.resolve({}),\n ]);\n const path = slugToPath(slug);\n const preview = isPreviewMode();\n const client = getClient();\n\n // Load content (page or entry)\n let content: LoadContentResult;\n try {\n content = await loadContent({\n client,\n siteId: config.siteId,\n path,\n preview,\n });\n } catch (error) {\n // Log the error for debugging - could be a network error, auth failure, etc.\n // We treat all errors as \"not found\" since the page can't be rendered\n console.debug('[createCatchAllPage] Failed to load content for path:', path, error);\n return notFound();\n }\n\n // ESCAPE HATCH: beforeRender for maintenance mode, access control, etc.\n if (beforeRender) {\n const intercepted = await beforeRender({ content, path, preview, searchParams });\n if (intercepted !== null) {\n return Wrapper ? <Wrapper>{intercepted}</Wrapper> : intercepted;\n }\n }\n\n let rendered: React.ReactNode;\n\n // Render page content\n if (isPageContent(content)) {\n rendered = (\n <PageComponent\n page={content.page}\n theme={content.theme}\n siteId={content.siteId}\n resolvedData={content.resolvedData}\n blockOverrides={blockOverrides}\n />\n );\n }\n // Render entry content with template\n else if (isEntryContent(content)) {\n // Entries without template pages should 404\n if (!content.templatePage) {\n return notFound();\n }\n\n rendered = (\n <PageComponent\n page={content.templatePage}\n theme={content.theme}\n siteId={content.siteId}\n resolvedData={content.resolvedData}\n blockOverrides={blockOverrides}\n dataContext={{\n contentEntry: content.dataContext.contentEntry,\n }}\n />\n );\n }\n // Unexpected content type - should never happen\n else {\n return notFound();\n }\n\n return Wrapper ? <Wrapper>{rendered}</Wrapper> : rendered;\n }\n\n /**\n * Generate SEO metadata for the page.\n */\n async function generateMetadataFn({ params, searchParams: searchParamsPromise }: CatchAllPageProps): Promise<Metadata> {\n const [{ slug }, searchParams] = await Promise.all([\n params,\n searchParamsPromise ?? Promise.resolve({}),\n ]);\n const path = slugToPath(slug);\n const preview = isPreviewMode();\n\n try {\n const content = await loadContent({\n client: getClient(),\n siteId: config.siteId,\n path,\n preview,\n });\n\n // ESCAPE HATCH: Custom metadata generation\n if (customMetadata) {\n return customMetadata({ content, path, preview, searchParams });\n }\n\n // Resolve site URL (use config URLs as fallback)\n const resolvedUrl =\n siteUrl ?? (preview ? config.previewUrl : config.liveUrl) ?? '';\n\n // Warn if no site URL is configured - OG/Twitter tags require absolute URLs\n if (!resolvedUrl) {\n console.warn(\n '[createCatchAllPage] No siteUrl configured. OpenGraph and Twitter tags will have relative URLs which may cause social sharing previews to fail. ' +\n 'Set siteUrl option, or config.liveUrl/config.previewUrl in your riverbank.config.'\n );\n }\n\n // Build page object for metadata\n // For entries, we use metaTitle/metaDescription; purpose is used as description fallback\n const page = isPageContent(content)\n ? content.page\n : isEntryContent(content)\n ? {\n name: content.entry.metaTitle ?? content.entry.title,\n purpose: content.entry.metaDescription ?? undefined,\n }\n : { name: 'Page' };\n\n // Use preview metadata (noindex) in preview mode\n const metadataGenerator = preview\n ? generatePreviewMetadata\n : generatePageMetadata;\n\n // Use site data from loadContent result (no duplicate API call needed)\n return metadataGenerator({\n page,\n site: content.site,\n path,\n siteUrl: resolvedUrl,\n });\n } catch (error) {\n // Log the error for debugging purposes\n console.debug('[createCatchAllPage] Failed to generate metadata for path:', path, error);\n // Return minimal metadata on error - page will handle the actual 404\n return {\n title: 'Page Not Found',\n robots: { index: false, follow: false },\n };\n }\n }\n\n return {\n Page,\n generateMetadata: generateMetadataFn,\n };\n}\n","/**\n * Client-side React hook to fetch page data.\n *\n * Use this in client components for dynamic page loading.\n */\n\nimport { useState, useEffect } from 'react';\nimport type { RiverbankClient } from '../../client/types';\nimport type { PageProps } from '../components/Page';\nimport type { RuntimeSdkConfig } from '../helpers/loadPage';\nimport { prefetchBlockData } from '../../data/prefetchBlockData';\n\nexport type UsePageParams = {\n client: RiverbankClient;\n siteId: string;\n path: string;\n pageId?: string;\n /**\n * If true, fetches draft/unpublished content instead of published content.\n * This affects both the page structure and block data loaders.\n * Requires API key with site access.\n *\n * @default false\n */\n preview?: boolean;\n};\n\nexport type UsePageResult =\n | { loading: true; error: null; page: null; theme: null; siteId: string; resolvedData: null; sdkConfig: null }\n | { loading: false; error: Error; page: null; theme: null; siteId: string; resolvedData: null; sdkConfig: null }\n | { loading: false; error: null; sdkConfig: RuntimeSdkConfig | null } & Omit<PageProps, 'registry' | 'wrapBlock' | 'usePlaceholders' | 'sdkConfig'>;\n\n/**\n * Client-side React hook to fetch all data needed for <Page> component.\n *\n * Fetches site data, page data, and prefetches block data loaders.\n * Returns loading and error states for proper UI handling.\n *\n * IMPORTANT: The client object should be stable across renders to avoid\n * unnecessary re-fetches. Create it outside your component or use useMemo:\n *\n * ```tsx\n * // ✅ Good - stable reference\n * const client = useMemo(\n * () => createRiverbankClient({ apiKey, baseUrl }),\n * [apiKey, baseUrl]\n * );\n *\n * // ❌ Bad - new client on every render (causes infinite loops)\n * const client = createRiverbankClient({ apiKey, baseUrl });\n * ```\n *\n * @example Basic usage\n * ```tsx\n * import { createRiverbankClient } from '@riverbankcms/sdk';\n * import { usePage, Page } from '@riverbankcms/sdk/rendering';\n *\n * const client = createRiverbankClient({\n * apiKey: process.env.NEXT_PUBLIC_RIVERBANK_API_KEY!,\n * baseUrl: process.env.NEXT_PUBLIC_DASHBOARD_URL + '/api',\n * });\n *\n * function MyPage({ path }: { path: string }) {\n * const pageData = usePage({ client, siteId: 'site-123', path });\n *\n * if (pageData.loading) {\n * return <div>Loading...</div>;\n * }\n *\n * if (pageData.error) {\n * return <div>Error: {pageData.error.message}</div>;\n * }\n *\n * return <Page {...pageData} />;\n * }\n * ```\n *\n * @example With custom loading/error states\n * ```tsx\n * function MyPage({ path }: { path: string }) {\n * const pageData = usePage({ client, siteId: 'site-123', path });\n *\n * if (pageData.loading) {\n * return <Skeleton />;\n * }\n *\n * if (pageData.error) {\n * return (\n * <ErrorBoundary\n * error={pageData.error}\n * onRetry={() => window.location.reload()}\n * />\n * );\n * }\n *\n * return <Page {...pageData} />;\n * }\n * ```\n */\nexport function usePage(params: UsePageParams): UsePageResult {\n const { client, siteId, path, pageId, preview = false } = params;\n\n const [result, setResult] = useState<UsePageResult>({\n loading: true,\n error: null,\n page: null,\n theme: null,\n siteId,\n resolvedData: null,\n sdkConfig: null,\n });\n\n useEffect(() => {\n let cancelled = false;\n\n async function fetchPage() {\n try {\n // Fetch site and page data in parallel\n const [site, pageResponse] = await Promise.all([\n client.getSite({ id: siteId }),\n client.getPage({ siteId, path, preview }),\n ]);\n\n // If component unmounted, don't update state\n if (cancelled) return;\n\n // Extract page data (getContentByPath can return page or entry)\n if ('entry' in pageResponse) {\n throw new Error(\n 'This path resolves to a content entry, not a page. ' +\n 'Use useContent() instead, which handles both pages and entries. ' +\n 'For entries, useContent() returns the raw entry data for custom rendering.'\n );\n }\n\n const { page: pageData } = pageResponse;\n\n // Convert API response blocks to PageOutline format\n // API returns blocks with full content, but PageOutline only needs structure\n const blocks = pageData.blocks.map((block) => {\n if (!block || typeof block !== 'object') {\n throw new Error('Invalid block format in API response');\n }\n if (typeof block.id !== 'string' && block.id !== null) {\n throw new Error(`Invalid block id: expected string or null, got ${typeof block.id}`);\n }\n if (typeof block.kind !== 'string') {\n throw new Error(`Invalid block kind: expected string, got ${typeof block.kind}`);\n }\n if (typeof block.purpose !== 'string') {\n throw new Error(`Invalid block purpose: expected string, got ${typeof block.purpose}`);\n }\n return {\n id: block.id,\n kind: block.kind,\n purpose: block.purpose,\n };\n });\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\n const resolvedData = await prefetchBlockData(\n pageOutline,\n {\n siteId,\n pageId: pageId ?? pageData.id,\n previewStage: preview ? 'preview' : 'published',\n },\n client\n );\n\n // If component unmounted, don't update state\n if (cancelled) return;\n\n setResult({\n loading: false,\n error: null,\n page: pageOutline,\n theme: site.theme,\n siteId,\n resolvedData,\n sdkConfig: site.sdkConfig ?? null,\n });\n } catch (error) {\n if (cancelled) return;\n\n setResult({\n loading: false,\n error: error instanceof Error ? error : new Error(String(error)),\n page: null,\n theme: null,\n siteId,\n resolvedData: null,\n sdkConfig: null,\n });\n }\n }\n\n fetchPage();\n\n return () => {\n cancelled = true;\n };\n }, [client, siteId, path, pageId, preview]);\n\n return result;\n}\n","/**\n * Client-side React hook to fetch content (page or entry) by path.\n *\n * Use this in client components for dynamic routing where a path\n * could resolve to either a page or content entry.\n */\n\nimport { useState, useEffect } from 'react';\nimport type { Theme } from '@riverbankcms/blocks';\nimport type { RiverbankClient, PageResponse } from '../../client/types';\nimport type { PageProps } from '../components/Page';\nimport { prefetchBlockData } from '../../data/prefetchBlockData';\nimport type { ResolvedBlockData } from '../../data/prefetchBlockData';\nimport type { ContentEntryData } from '../helpers/loadContent';\n\nexport type UseContentParams = {\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 * Loading state\n */\ntype LoadingState = {\n loading: true;\n error: null;\n type: null;\n page: null;\n entry: null;\n theme: null;\n siteId: string;\n resolvedData: null;\n};\n\n/**\n * Error state\n */\ntype ErrorState = {\n loading: false;\n error: Error;\n type: null;\n page: null;\n entry: null;\n theme: null;\n siteId: string;\n resolvedData: null;\n};\n\n/**\n * Success state for page content\n */\ntype PageSuccessState = {\n loading: false;\n error: null;\n type: 'page';\n page: PageProps['page'];\n entry: null;\n theme: Theme;\n siteId: string;\n resolvedData: ResolvedBlockData;\n};\n\n/**\n * Success state for entry content\n */\ntype EntrySuccessState = {\n loading: false;\n error: null;\n type: 'entry';\n page: null;\n entry: ContentEntryData;\n theme: Theme;\n siteId: string;\n resolvedData: null;\n};\n\nexport type UseContentResult = LoadingState | ErrorState | PageSuccessState | EntrySuccessState;\n\n/**\n * Type guard to check if result is loading\n */\nexport function isContentLoading(result: UseContentResult): result is LoadingState {\n return result.loading === true;\n}\n\n/**\n * Type guard to check if result has an error\n */\nexport function isContentError(result: UseContentResult): result is ErrorState {\n return result.loading === false && result.error !== null;\n}\n\n/**\n * Type guard to check if result is a page\n */\nexport function isPageContentResult(result: UseContentResult): result is PageSuccessState {\n return result.loading === false && result.error === null && result.type === 'page';\n}\n\n/**\n * Type guard to check if result is an entry\n */\nexport function isEntryContentResult(result: UseContentResult): result is EntrySuccessState {\n return result.loading === false && result.error === null && result.type === 'entry';\n}\n\n/**\n * Client-side React hook to fetch content by path.\n *\n * Returns a discriminated union with loading/error states, and either\n * page data (ready for `<Page>` component) or raw entry data (for custom rendering).\n *\n * IMPORTANT: The client object should be stable across renders to avoid\n * unnecessary re-fetches. Create it outside your component or use useMemo.\n *\n * @example Dynamic routing with both pages and entries\n * ```tsx\n * \"use client\";\n *\n * import { useContent, Page, isPageContentResult } from '@riverbankcms/sdk/client';\n *\n * function DynamicPage({ path }: { path: string }) {\n * const content = useContent({ client, siteId: 'site-123', path });\n *\n * if (content.loading) return <div>Loading...</div>;\n * if (content.error) return <div>Error: {content.error.message}</div>;\n *\n * if (isPageContentResult(content)) {\n * return <Page page={content.page} theme={content.theme} siteId={content.siteId} resolvedData={content.resolvedData} />;\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 = useContent({ client, siteId, path });\n *\n * if (content.loading) return <Spinner />;\n * if (content.error) return <Error error={content.error} />;\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 * }\n * }\n *\n * return <Page {...content} />;\n * ```\n */\nexport function useContent(params: UseContentParams): UseContentResult {\n const { client, siteId, path, preview = false } = params;\n\n const [result, setResult] = useState<UseContentResult>({\n loading: true,\n error: null,\n type: null,\n page: null,\n entry: null,\n theme: null,\n siteId,\n resolvedData: null,\n });\n\n useEffect(() => {\n let cancelled = false;\n\n async function fetchContent() {\n try {\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 // If component unmounted, don't update state\n if (cancelled) return;\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 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 setResult({\n loading: false,\n error: null,\n type: 'entry',\n page: null,\n entry,\n theme: site.theme,\n siteId,\n resolvedData: null,\n });\n return;\n }\n\n // Handle page response\n const { page: pageData } = contentResponse;\n\n // Convert API response blocks to PageOutline format\n const blocks = pageData.blocks.map((block) => {\n if (!block || typeof block !== 'object') {\n throw new Error('Invalid block format in API response');\n }\n if (typeof block.id !== 'string' && block.id !== null) {\n throw new Error(`Invalid block id: expected string or null, got ${typeof block.id}`);\n }\n if (typeof block.kind !== 'string') {\n throw new Error(`Invalid block kind: expected string, got ${typeof block.kind}`);\n }\n if (typeof block.purpose !== 'string') {\n throw new Error(`Invalid block purpose: expected string, got ${typeof block.purpose}`);\n }\n return {\n id: block.id,\n kind: block.kind,\n purpose: block.purpose,\n };\n });\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 // If component unmounted, don't update state\n if (cancelled) return;\n\n setResult({\n loading: false,\n error: null,\n type: 'page',\n page: pageOutline,\n entry: null,\n theme: site.theme,\n siteId,\n resolvedData,\n });\n } catch (error) {\n if (cancelled) return;\n\n setResult({\n loading: false,\n error: error instanceof Error ? error : new Error(String(error)),\n type: null,\n page: null,\n entry: null,\n theme: null,\n siteId,\n resolvedData: null,\n });\n }\n }\n\n fetchContent();\n\n return () => {\n cancelled = true;\n };\n }, [client, siteId, path, preview]);\n\n return result;\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 * Static params generation for Next.js SSG\n *\n * Provides helpers for generating static params from published CMS routes.\n *\n * @example\n * ```tsx\n * // app/[[...slug]]/page.tsx\n * import { generateAllStaticParams } from '@riverbankcms/sdk/next';\n *\n * export { generateAllStaticParams as generateStaticParams };\n * ```\n */\n\nimport { createRiverbankClient } from '../client';\n\n/**\n * Environment variable validation result\n */\nexport type StaticParamsEnvResult =\n | { valid: true; config: { apiKey: string; siteId: string; baseUrl: string } }\n | { valid: false; missingVars: string[] };\n\n/**\n * Validate that all required environment variables are set.\n *\n * Required env vars:\n * - RIVERBANK_API_KEY: API key for published content\n * - RIVERBANK_SITE_ID: Site ID\n * - NEXT_PUBLIC_DASHBOARD_URL: Dashboard API URL\n *\n * @returns Validation result with config or missing vars\n */\nexport function validateStaticParamsEnv(): StaticParamsEnvResult {\n const apiKey = process.env.RIVERBANK_API_KEY;\n const siteId = process.env.RIVERBANK_SITE_ID;\n const baseUrl = process.env.NEXT_PUBLIC_DASHBOARD_URL;\n\n const missingVars: string[] = [];\n\n if (!apiKey) missingVars.push('RIVERBANK_API_KEY');\n if (!siteId) missingVars.push('RIVERBANK_SITE_ID');\n if (!baseUrl) missingVars.push('NEXT_PUBLIC_DASHBOARD_URL');\n\n if (missingVars.length > 0) {\n return { valid: false, missingVars };\n }\n\n // TypeScript can't narrow through the checks above, so assert non-null\n return {\n valid: true,\n config: { apiKey: apiKey!, siteId: siteId!, baseUrl: baseUrl! },\n };\n}\n\n/**\n * Convert a route path to a Next.js slug array.\n *\n * @param path - The route path (e.g., '/about', '/blog/post')\n * @returns The slug array for Next.js catch-all route\n *\n * @example\n * ```ts\n * pathToSlugArray('/') // []\n * pathToSlugArray('/about') // ['about']\n * pathToSlugArray('/blog/post') // ['blog', 'post']\n * ```\n */\nexport function pathToSlugArray(path: string): string[] {\n if (path === '/') return [];\n return path.slice(1).split('/');\n}\n\n/**\n * Generate static params for all published routes.\n *\n * This function fetches all published routes from the CMS and converts them\n * to the static params format expected by Next.js catch-all routes.\n *\n * Requires environment variables:\n * - RIVERBANK_API_KEY: API key for published content\n * - RIVERBANK_SITE_ID: Site ID\n * - NEXT_PUBLIC_DASHBOARD_URL: Dashboard API URL\n *\n * @throws Error if required env vars are missing (prevents silent empty SSG in CI)\n * @returns Array of static params objects for Next.js\n *\n * @example\n * ```tsx\n * // app/[[...slug]]/page.tsx\n * import { generateAllStaticParams } from '@riverbankcms/sdk/next';\n *\n * export { generateAllStaticParams as generateStaticParams };\n *\n * // Or with custom logic:\n * export async function generateStaticParams() {\n * const params = await generateAllStaticParams();\n * // Filter or modify params as needed\n * return params.filter(p => !p.slug.includes('private'));\n * }\n * ```\n */\nexport async function generateAllStaticParams(): Promise<{ slug: string[] }[]> {\n const envResult = validateStaticParamsEnv();\n\n if (!envResult.valid) {\n throw new Error(\n `[Riverbank] generateAllStaticParams requires env vars: ${envResult.missingVars.join(', ')}. ` +\n `This error prevents accidentally deploying with zero static pages.`\n );\n }\n\n const { apiKey, siteId: _siteId, baseUrl } = envResult.config;\n\n // Create client (unused until getAllPublishedRoutes is implemented)\n const _client = createRiverbankClient({\n apiKey,\n baseUrl,\n // Disable caching for build-time fetches\n cache: { enabled: false },\n // Disable resilience for build-time fetches (want fast failure)\n resilience: { enabled: false },\n });\n\n // TODO: This requires adding getAllPublishedRoutes to the client\n // which requires the CMS endpoint to be updated to accept API key auth\n // and support the publishedOnly query param.\n //\n // For now, we'll use getPage to fetch the site's homepage which contains\n // the navigation data, and extract routes from there.\n //\n // In the future, implement:\n // const routes = await client.getAllPublishedRoutes({ siteId });\n // return routes.map(path => ({ slug: pathToSlugArray(path) }));\n\n // Temporary: Return empty array with warning\n // This allows the helper to be used but won't pre-render any pages\n console.warn(\n '[Riverbank] generateAllStaticParams: getAllPublishedRoutes API not yet implemented. ' +\n 'Pages will be generated on-demand with ISR.'\n );\n\n // Return at least the homepage\n return [{ slug: [] }];\n}\n","/**\n * Next.js integration helpers for Riverbank CMS SDK.\n *\n * Provides opinionated factories for common Next.js patterns, reducing\n * boilerplate while maintaining full customizability through escape hatches.\n *\n * @example Basic catch-all page\n * ```tsx\n * // src/app/[[...slug]]/page.tsx\n * import { createCatchAllPage } from '@riverbankcms/sdk/next';\n * import { getRiverbankClient } from '@/lib/builder-client';\n * import config from '@/riverbank.config';\n *\n * const { Page, generateMetadata } = createCatchAllPage({\n * getClient: getRiverbankClient,\n * config,\n * });\n *\n * export default Page;\n * export { generateMetadata };\n * ```\n *\n * @example With customization\n * ```tsx\n * const { Page, generateMetadata } = createCatchAllPage({\n * getClient,\n * config,\n * blockOverrides: { hero: MyCustomHero },\n * beforeRender: async () => {\n * if (process.env.MAINTENANCE_MODE === 'true') {\n * return <MaintenancePage />;\n * }\n * return null;\n * },\n * });\n * ```\n *\n * @packageDocumentation\n */\n\nimport { isPreviewMode } from '../env';\n\n// Catch-all page factory\nexport { createCatchAllPage } from './catch-all';\nexport type {\n CreateCatchAllPageOptions,\n CreateCatchAllPageResult,\n CatchAllPageProps,\n CatchAllContext,\n} from './types';\n\n// Static params utilities\nexport {\n generateAllStaticParams,\n validateStaticParamsEnv,\n pathToSlugArray,\n type StaticParamsEnvResult,\n} from './static-params';\n\n/**\n * ISR revalidation duration in seconds for production mode.\n * 5 minutes provides a good balance between freshness and performance.\n */\nexport const ISR_REVALIDATE_SECONDS = 300;\n\nexport interface ISRConfig {\n /**\n * Revalidation interval in seconds.\n * - 0: Dynamic rendering (no caching) - used in preview mode\n * - 300: 5 minute ISR - used in production mode\n */\n revalidate: number | false;\n\n /**\n * Whether the current environment is in preview mode.\n * Useful for conditional rendering or data fetching behavior.\n */\n isPreview: boolean;\n}\n\n/**\n * Get ISR configuration based on the current environment.\n *\n * @example\n * ```tsx\n * // app/[[...slug]]/page.tsx\n * import { getISRConfig } from '@riverbankcms/sdk/next';\n *\n * export const revalidate = getISRConfig().revalidate;\n * ```\n *\n * @returns ISR configuration with revalidate interval and preview flag\n */\nexport function getISRConfig(): ISRConfig {\n const preview = isPreviewMode();\n return {\n revalidate: preview ? 0 : ISR_REVALIDATE_SECONDS,\n isPreview: preview,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["/Users/will/Projects/Business/cms/builder/packages/sdk/dist/server/next.js","../../src/next/catch-all.tsx","../../src/rendering/hooks/usePage.ts","../../src/rendering/hooks/useContent.ts","../../src/next/static-params.ts","../../src/next/index.ts"],"names":["Page"],"mappings":"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","file":"/Users/will/Projects/Business/cms/builder/packages/sdk/dist/server/next.js","sourcesContent":[null,"/**\n * Next.js catch-all page factory for Riverbank CMS.\n *\n * Provides a simple, opinionated way to render CMS content in Next.js catch-all routes.\n * Reduces typical page.tsx boilerplate from ~160 lines to ~10 lines.\n *\n * @example\n * ```tsx\n * // src/app/[[...slug]]/page.tsx\n * import { createCatchAllPage } from '@riverbankcms/sdk/next';\n * import { getRiverbankClient } from '@/lib/builder-client';\n * import config from '@/riverbank.config';\n *\n * const { Page, generateMetadata } = createCatchAllPage({\n * getClient: getRiverbankClient,\n * config,\n * blockOverrides: { hero: MyCustomHero },\n * });\n *\n * export default Page;\n * export { generateMetadata };\n * ```\n */\n\n// Note: next is a peerDependency - this module only works in Next.js projects.\nimport { notFound } from 'next/navigation';\n\nimport {\n loadContent,\n isPageContent,\n isEntryContent,\n} from '../rendering/helpers/loadContent';\nimport type { LoadContentResult } from '../rendering/helpers/loadContent';\nimport { Page as PageComponent } from '../rendering/components/Page';\nimport {\n generatePageMetadata,\n generatePreviewMetadata,\n type Metadata,\n} from '../metadata/generatePageMetadata';\nimport type {\n CreateCatchAllPageOptions,\n CatchAllPageProps,\n CreateCatchAllPageResult,\n} from './types';\nimport type { ApiKeyType } from '../client/types';\n\n/**\n * Detect preview mode from client's API key type.\n *\n * The client automatically detects its key type from the prefix:\n * - bld_live_sk_* / bld_test_sk_* → content key → preview: false\n * - bld_preview_sk_* → preview key → preview: true\n *\n * This eliminates the need for separate env vars and ensures\n * the preview mode always matches the API key type.\n */\nfunction detectPreviewFromClient(getClient: () => { getKeyType: () => ApiKeyType }): boolean {\n const client = getClient();\n const keyType = client.getKeyType();\n\n if (keyType === 'preview') {\n return true;\n }\n\n if (keyType === 'unknown') {\n console.warn(\n '[createCatchAllPage] Could not detect API key type from prefix. ' +\n 'Expected bld_live_sk_*, bld_test_sk_*, or bld_preview_sk_*. ' +\n 'Defaulting to published content (preview: false).'\n );\n }\n\n return false;\n}\n\n/**\n * Convert URL slug segments to a path string.\n *\n * @example\n * ```\n * slugToPath(undefined) // '/'\n * slugToPath([]) // '/'\n * slugToPath(['about']) // '/about'\n * slugToPath(['blog', 'post']) // '/blog/post'\n * ```\n */\nfunction slugToPath(slug?: string[]): string {\n if (!slug || slug.length === 0) return '/';\n return '/' + slug.join('/');\n}\n\n/**\n * Factory function to create a Next.js catch-all page component and metadata generator.\n *\n * This provides a simple, opinionated setup for rendering Riverbank CMS content\n * in external SDK sites. It handles:\n *\n * - Page and content entry routing\n * - Preview mode detection (automatically from API key type)\n * - SEO metadata generation\n * - Block overrides for custom components\n * - 404 handling for missing content\n *\n * ## Escape Hatches\n *\n * For customization beyond the defaults, use these options:\n *\n * - `beforeRender`: Intercept before rendering (maintenance mode, access control)\n * - `customMetadata`: Full control over SEO metadata\n * - `wrapper`: Wrap all content (analytics, error boundaries)\n *\n * For maximum control, use the lower-level helpers directly:\n * - `loadContent()` from `@riverbankcms/sdk/rendering`\n * - `Page` component from `@riverbankcms/sdk/rendering`\n * - `generatePageMetadata()` from `@riverbankcms/sdk/metadata`\n *\n * @param options - Configuration options\n * @returns Object with `Page` component and `generateMetadata` function\n *\n * @example Basic usage\n * ```tsx\n * const { Page, generateMetadata } = createCatchAllPage({\n * getClient: getRiverbankClient,\n * config,\n * });\n *\n * export default Page;\n * export { generateMetadata };\n * ```\n *\n * @example With maintenance mode\n * ```tsx\n * const { Page, generateMetadata } = createCatchAllPage({\n * getClient,\n * config,\n * beforeRender: async () => {\n * if (process.env.MAINTENANCE_MODE === 'true') {\n * return <MaintenancePage />;\n * }\n * return null;\n * },\n * });\n * ```\n */\nexport function createCatchAllPage(\n options: CreateCatchAllPageOptions\n): CreateCatchAllPageResult {\n const {\n getClient,\n config,\n preview: previewOption,\n blockOverrides,\n siteUrl,\n beforeRender,\n customMetadata,\n wrapper: Wrapper,\n } = options;\n\n // Resolve preview mode once: use explicit option if provided, otherwise detect from client key type\n const preview = previewOption ?? detectPreviewFromClient(getClient);\n\n /**\n * Main page component that loads and renders CMS content.\n */\n async function Page({ params, searchParams: searchParamsPromise }: CatchAllPageProps): Promise<React.ReactNode> {\n const [{ slug }, searchParams] = await Promise.all([\n params,\n searchParamsPromise ?? Promise.resolve({}),\n ]);\n const path = slugToPath(slug);\n const client = getClient();\n\n // Load content (page or entry)\n let content: LoadContentResult;\n try {\n content = await loadContent({\n client,\n siteId: config.siteId,\n path,\n preview,\n });\n } catch (error) {\n // Log the error for debugging - could be a network error, auth failure, etc.\n // We treat all errors as \"not found\" since the page can't be rendered\n console.debug('[createCatchAllPage] Failed to load content for path:', path, error);\n return notFound();\n }\n\n // ESCAPE HATCH: beforeRender for maintenance mode, access control, etc.\n if (beforeRender) {\n const intercepted = await beforeRender({ content, path, preview, searchParams });\n if (intercepted !== null) {\n return Wrapper ? <Wrapper>{intercepted}</Wrapper> : intercepted;\n }\n }\n\n let rendered: React.ReactNode;\n\n // Render page content\n if (isPageContent(content)) {\n rendered = (\n <PageComponent\n page={content.page}\n theme={content.theme}\n siteId={content.siteId}\n resolvedData={content.resolvedData}\n blockOverrides={blockOverrides}\n />\n );\n }\n // Render entry content with template\n else if (isEntryContent(content)) {\n // Entries without template pages should 404\n if (!content.templatePage) {\n return notFound();\n }\n\n rendered = (\n <PageComponent\n page={content.templatePage}\n theme={content.theme}\n siteId={content.siteId}\n resolvedData={content.resolvedData}\n blockOverrides={blockOverrides}\n dataContext={{\n contentEntry: content.dataContext.contentEntry,\n }}\n />\n );\n }\n // Unexpected content type - should never happen\n else {\n return notFound();\n }\n\n return Wrapper ? <Wrapper>{rendered}</Wrapper> : rendered;\n }\n\n /**\n * Generate SEO metadata for the page.\n */\n async function generateMetadataFn({ params, searchParams: searchParamsPromise }: CatchAllPageProps): Promise<Metadata> {\n const [{ slug }, searchParams] = await Promise.all([\n params,\n searchParamsPromise ?? Promise.resolve({}),\n ]);\n const path = slugToPath(slug);\n // Note: uses `preview` from outer scope (resolved once at factory creation)\n\n try {\n const content = await loadContent({\n client: getClient(),\n siteId: config.siteId,\n path,\n preview,\n });\n\n // ESCAPE HATCH: Custom metadata generation\n if (customMetadata) {\n return customMetadata({ content, path, preview, searchParams });\n }\n\n // Resolve site URL (use config URLs as fallback)\n const resolvedUrl =\n siteUrl ?? (preview ? config.previewUrl : config.liveUrl) ?? '';\n\n // Warn if no site URL is configured - OG/Twitter tags require absolute URLs\n if (!resolvedUrl) {\n console.warn(\n '[createCatchAllPage] No siteUrl configured. OpenGraph and Twitter tags will have relative URLs which may cause social sharing previews to fail. ' +\n 'Set siteUrl option, or config.liveUrl/config.previewUrl in your riverbank.config.'\n );\n }\n\n // Build page object for metadata\n // For entries, we use metaTitle/metaDescription; purpose is used as description fallback\n const page = isPageContent(content)\n ? content.page\n : isEntryContent(content)\n ? {\n name: content.entry.metaTitle ?? content.entry.title,\n purpose: content.entry.metaDescription ?? undefined,\n }\n : { name: 'Page' };\n\n // Use preview metadata (noindex) in preview mode\n const metadataGenerator = preview\n ? generatePreviewMetadata\n : generatePageMetadata;\n\n // Use site data from loadContent result (no duplicate API call needed)\n return metadataGenerator({\n page,\n site: content.site,\n path,\n siteUrl: resolvedUrl,\n });\n } catch (error) {\n // Log the error for debugging purposes\n console.debug('[createCatchAllPage] Failed to generate metadata for path:', path, error);\n // Return minimal metadata on error - page will handle the actual 404\n return {\n title: 'Page Not Found',\n robots: { index: false, follow: false },\n };\n }\n }\n\n return {\n Page,\n generateMetadata: generateMetadataFn,\n };\n}\n","/**\n * Client-side React hook to fetch page data.\n *\n * Use this in client components for dynamic page loading.\n */\n\nimport { useState, useEffect } from 'react';\nimport type { RiverbankClient } from '../../client/types';\nimport type { PageProps } from '../components/Page';\nimport type { RuntimeSdkConfig } from '../helpers/loadPage';\nimport { prefetchBlockData } from '../../data/prefetchBlockData';\n\nexport type UsePageParams = {\n client: RiverbankClient;\n siteId: string;\n path: string;\n pageId?: string;\n /**\n * If true, fetches draft/unpublished content instead of published content.\n * This affects both the page structure and block data loaders.\n * Requires API key with site access.\n *\n * @default false\n */\n preview?: boolean;\n};\n\nexport type UsePageResult =\n | { loading: true; error: null; page: null; theme: null; siteId: string; resolvedData: null; sdkConfig: null }\n | { loading: false; error: Error; page: null; theme: null; siteId: string; resolvedData: null; sdkConfig: null }\n | { loading: false; error: null; sdkConfig: RuntimeSdkConfig | null } & Omit<PageProps, 'registry' | 'wrapBlock' | 'usePlaceholders' | 'sdkConfig'>;\n\n/**\n * Client-side React hook to fetch all data needed for <Page> component.\n *\n * Fetches site data, page data, and prefetches block data loaders.\n * Returns loading and error states for proper UI handling.\n *\n * IMPORTANT: The client object should be stable across renders to avoid\n * unnecessary re-fetches. Create it outside your component or use useMemo:\n *\n * ```tsx\n * // ✅ Good - stable reference\n * const client = useMemo(\n * () => createRiverbankClient({ apiKey, baseUrl }),\n * [apiKey, baseUrl]\n * );\n *\n * // ❌ Bad - new client on every render (causes infinite loops)\n * const client = createRiverbankClient({ apiKey, baseUrl });\n * ```\n *\n * @example Basic usage\n * ```tsx\n * import { createRiverbankClient } from '@riverbankcms/sdk';\n * import { usePage, Page } from '@riverbankcms/sdk/rendering';\n *\n * const client = createRiverbankClient({\n * apiKey: process.env.NEXT_PUBLIC_RIVERBANK_API_KEY!,\n * baseUrl: process.env.NEXT_PUBLIC_DASHBOARD_URL + '/api',\n * });\n *\n * function MyPage({ path }: { path: string }) {\n * const pageData = usePage({ client, siteId: 'site-123', path });\n *\n * if (pageData.loading) {\n * return <div>Loading...</div>;\n * }\n *\n * if (pageData.error) {\n * return <div>Error: {pageData.error.message}</div>;\n * }\n *\n * return <Page {...pageData} />;\n * }\n * ```\n *\n * @example With custom loading/error states\n * ```tsx\n * function MyPage({ path }: { path: string }) {\n * const pageData = usePage({ client, siteId: 'site-123', path });\n *\n * if (pageData.loading) {\n * return <Skeleton />;\n * }\n *\n * if (pageData.error) {\n * return (\n * <ErrorBoundary\n * error={pageData.error}\n * onRetry={() => window.location.reload()}\n * />\n * );\n * }\n *\n * return <Page {...pageData} />;\n * }\n * ```\n */\nexport function usePage(params: UsePageParams): UsePageResult {\n const { client, siteId, path, pageId, preview = false } = params;\n\n const [result, setResult] = useState<UsePageResult>({\n loading: true,\n error: null,\n page: null,\n theme: null,\n siteId,\n resolvedData: null,\n sdkConfig: null,\n });\n\n useEffect(() => {\n let cancelled = false;\n\n async function fetchPage() {\n try {\n // Fetch site and page data in parallel\n const [site, pageResponse] = await Promise.all([\n client.getSite({ id: siteId }),\n client.getPage({ siteId, path, preview }),\n ]);\n\n // If component unmounted, don't update state\n if (cancelled) return;\n\n // Extract page data (getContentByPath can return page or entry)\n if ('entry' in pageResponse) {\n throw new Error(\n 'This path resolves to a content entry, not a page. ' +\n 'Use useContent() instead, which handles both pages and entries. ' +\n 'For entries, useContent() returns the raw entry data for custom rendering.'\n );\n }\n\n const { page: pageData } = pageResponse;\n\n // Convert API response blocks to PageOutline format\n // API returns blocks with full content, but PageOutline only needs structure\n const blocks = pageData.blocks.map((block) => {\n if (!block || typeof block !== 'object') {\n throw new Error('Invalid block format in API response');\n }\n if (typeof block.id !== 'string' && block.id !== null) {\n throw new Error(`Invalid block id: expected string or null, got ${typeof block.id}`);\n }\n if (typeof block.kind !== 'string') {\n throw new Error(`Invalid block kind: expected string, got ${typeof block.kind}`);\n }\n if (typeof block.purpose !== 'string') {\n throw new Error(`Invalid block purpose: expected string, got ${typeof block.purpose}`);\n }\n return {\n id: block.id,\n kind: block.kind,\n purpose: block.purpose,\n };\n });\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\n const resolvedData = await prefetchBlockData(\n pageOutline,\n {\n siteId,\n pageId: pageId ?? pageData.id,\n previewStage: preview ? 'preview' : 'published',\n },\n client\n );\n\n // If component unmounted, don't update state\n if (cancelled) return;\n\n setResult({\n loading: false,\n error: null,\n page: pageOutline,\n theme: site.theme,\n siteId,\n resolvedData,\n sdkConfig: site.sdkConfig ?? null,\n });\n } catch (error) {\n if (cancelled) return;\n\n setResult({\n loading: false,\n error: error instanceof Error ? error : new Error(String(error)),\n page: null,\n theme: null,\n siteId,\n resolvedData: null,\n sdkConfig: null,\n });\n }\n }\n\n fetchPage();\n\n return () => {\n cancelled = true;\n };\n }, [client, siteId, path, pageId, preview]);\n\n return result;\n}\n","/**\n * Client-side React hook to fetch content (page or entry) by path.\n *\n * Use this in client components for dynamic routing where a path\n * could resolve to either a page or content entry.\n */\n\nimport { useState, useEffect } from 'react';\nimport type { Theme } from '@riverbankcms/blocks';\nimport type { RiverbankClient, PageResponse } from '../../client/types';\nimport type { PageProps } from '../components/Page';\nimport { prefetchBlockData } from '../../data/prefetchBlockData';\nimport type { ResolvedBlockData } from '../../data/prefetchBlockData';\nimport type { ContentEntryData } from '../helpers/loadContent';\n\nexport type UseContentParams = {\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 * Loading state\n */\ntype LoadingState = {\n loading: true;\n error: null;\n type: null;\n page: null;\n entry: null;\n theme: null;\n siteId: string;\n resolvedData: null;\n};\n\n/**\n * Error state\n */\ntype ErrorState = {\n loading: false;\n error: Error;\n type: null;\n page: null;\n entry: null;\n theme: null;\n siteId: string;\n resolvedData: null;\n};\n\n/**\n * Success state for page content\n */\ntype PageSuccessState = {\n loading: false;\n error: null;\n type: 'page';\n page: PageProps['page'];\n entry: null;\n theme: Theme;\n siteId: string;\n resolvedData: ResolvedBlockData;\n};\n\n/**\n * Success state for entry content\n */\ntype EntrySuccessState = {\n loading: false;\n error: null;\n type: 'entry';\n page: null;\n entry: ContentEntryData;\n theme: Theme;\n siteId: string;\n resolvedData: null;\n};\n\nexport type UseContentResult = LoadingState | ErrorState | PageSuccessState | EntrySuccessState;\n\n/**\n * Type guard to check if result is loading\n */\nexport function isContentLoading(result: UseContentResult): result is LoadingState {\n return result.loading === true;\n}\n\n/**\n * Type guard to check if result has an error\n */\nexport function isContentError(result: UseContentResult): result is ErrorState {\n return result.loading === false && result.error !== null;\n}\n\n/**\n * Type guard to check if result is a page\n */\nexport function isPageContentResult(result: UseContentResult): result is PageSuccessState {\n return result.loading === false && result.error === null && result.type === 'page';\n}\n\n/**\n * Type guard to check if result is an entry\n */\nexport function isEntryContentResult(result: UseContentResult): result is EntrySuccessState {\n return result.loading === false && result.error === null && result.type === 'entry';\n}\n\n/**\n * Client-side React hook to fetch content by path.\n *\n * Returns a discriminated union with loading/error states, and either\n * page data (ready for `<Page>` component) or raw entry data (for custom rendering).\n *\n * IMPORTANT: The client object should be stable across renders to avoid\n * unnecessary re-fetches. Create it outside your component or use useMemo.\n *\n * @example Dynamic routing with both pages and entries\n * ```tsx\n * \"use client\";\n *\n * import { useContent, Page, isPageContentResult } from '@riverbankcms/sdk/client';\n *\n * function DynamicPage({ path }: { path: string }) {\n * const content = useContent({ client, siteId: 'site-123', path });\n *\n * if (content.loading) return <div>Loading...</div>;\n * if (content.error) return <div>Error: {content.error.message}</div>;\n *\n * if (isPageContentResult(content)) {\n * return <Page page={content.page} theme={content.theme} siteId={content.siteId} resolvedData={content.resolvedData} />;\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 = useContent({ client, siteId, path });\n *\n * if (content.loading) return <Spinner />;\n * if (content.error) return <Error error={content.error} />;\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 * }\n * }\n *\n * return <Page {...content} />;\n * ```\n */\nexport function useContent(params: UseContentParams): UseContentResult {\n const { client, siteId, path, preview = false } = params;\n\n const [result, setResult] = useState<UseContentResult>({\n loading: true,\n error: null,\n type: null,\n page: null,\n entry: null,\n theme: null,\n siteId,\n resolvedData: null,\n });\n\n useEffect(() => {\n let cancelled = false;\n\n async function fetchContent() {\n try {\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 // If component unmounted, don't update state\n if (cancelled) return;\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 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 setResult({\n loading: false,\n error: null,\n type: 'entry',\n page: null,\n entry,\n theme: site.theme,\n siteId,\n resolvedData: null,\n });\n return;\n }\n\n // Handle page response\n const { page: pageData } = contentResponse;\n\n // Convert API response blocks to PageOutline format\n const blocks = pageData.blocks.map((block) => {\n if (!block || typeof block !== 'object') {\n throw new Error('Invalid block format in API response');\n }\n if (typeof block.id !== 'string' && block.id !== null) {\n throw new Error(`Invalid block id: expected string or null, got ${typeof block.id}`);\n }\n if (typeof block.kind !== 'string') {\n throw new Error(`Invalid block kind: expected string, got ${typeof block.kind}`);\n }\n if (typeof block.purpose !== 'string') {\n throw new Error(`Invalid block purpose: expected string, got ${typeof block.purpose}`);\n }\n return {\n id: block.id,\n kind: block.kind,\n purpose: block.purpose,\n };\n });\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 // If component unmounted, don't update state\n if (cancelled) return;\n\n setResult({\n loading: false,\n error: null,\n type: 'page',\n page: pageOutline,\n entry: null,\n theme: site.theme,\n siteId,\n resolvedData,\n });\n } catch (error) {\n if (cancelled) return;\n\n setResult({\n loading: false,\n error: error instanceof Error ? error : new Error(String(error)),\n type: null,\n page: null,\n entry: null,\n theme: null,\n siteId,\n resolvedData: null,\n });\n }\n }\n\n fetchContent();\n\n return () => {\n cancelled = true;\n };\n }, [client, siteId, path, preview]);\n\n return result;\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 * Static params generation for Next.js SSG\n *\n * Provides helpers for generating static params from published CMS routes.\n *\n * @example\n * ```tsx\n * // app/[[...slug]]/page.tsx\n * import { generateAllStaticParams } from '@riverbankcms/sdk/next';\n *\n * export { generateAllStaticParams as generateStaticParams };\n * ```\n */\n\nimport { createRiverbankClient } from '../client';\n\n/**\n * Environment variable validation result\n */\nexport type StaticParamsEnvResult =\n | { valid: true; config: { apiKey: string; siteId: string; baseUrl: string } }\n | { valid: false; missingVars: string[] };\n\n/**\n * Validate that all required environment variables are set.\n *\n * Required env vars:\n * - RIVERBANK_API_KEY: API key for published content\n * - RIVERBANK_SITE_ID: Site ID\n * - NEXT_PUBLIC_DASHBOARD_URL: Dashboard API URL\n *\n * @returns Validation result with config or missing vars\n */\nexport function validateStaticParamsEnv(): StaticParamsEnvResult {\n const apiKey = process.env.RIVERBANK_API_KEY;\n const siteId = process.env.RIVERBANK_SITE_ID;\n const baseUrl = process.env.NEXT_PUBLIC_DASHBOARD_URL;\n\n const missingVars: string[] = [];\n\n if (!apiKey) missingVars.push('RIVERBANK_API_KEY');\n if (!siteId) missingVars.push('RIVERBANK_SITE_ID');\n if (!baseUrl) missingVars.push('NEXT_PUBLIC_DASHBOARD_URL');\n\n if (missingVars.length > 0) {\n return { valid: false, missingVars };\n }\n\n // TypeScript can't narrow through the checks above, so assert non-null\n return {\n valid: true,\n config: { apiKey: apiKey!, siteId: siteId!, baseUrl: baseUrl! },\n };\n}\n\n/**\n * Convert a route path to a Next.js slug array.\n *\n * @param path - The route path (e.g., '/about', '/blog/post')\n * @returns The slug array for Next.js catch-all route\n *\n * @example\n * ```ts\n * pathToSlugArray('/') // []\n * pathToSlugArray('/about') // ['about']\n * pathToSlugArray('/blog/post') // ['blog', 'post']\n * ```\n */\nexport function pathToSlugArray(path: string): string[] {\n if (path === '/') return [];\n return path.slice(1).split('/');\n}\n\n/**\n * Generate static params for all published routes.\n *\n * This function fetches all published routes from the CMS and converts them\n * to the static params format expected by Next.js catch-all routes.\n *\n * Requires environment variables:\n * - RIVERBANK_API_KEY: API key for published content\n * - RIVERBANK_SITE_ID: Site ID\n * - NEXT_PUBLIC_DASHBOARD_URL: Dashboard API URL\n *\n * @throws Error if required env vars are missing (prevents silent empty SSG in CI)\n * @returns Array of static params objects for Next.js\n *\n * @example\n * ```tsx\n * // app/[[...slug]]/page.tsx\n * import { generateAllStaticParams } from '@riverbankcms/sdk/next';\n *\n * export { generateAllStaticParams as generateStaticParams };\n *\n * // Or with custom logic:\n * export async function generateStaticParams() {\n * const params = await generateAllStaticParams();\n * // Filter or modify params as needed\n * return params.filter(p => !p.slug.includes('private'));\n * }\n * ```\n */\nexport async function generateAllStaticParams(): Promise<{ slug: string[] }[]> {\n const envResult = validateStaticParamsEnv();\n\n if (!envResult.valid) {\n throw new Error(\n `[Riverbank] generateAllStaticParams requires env vars: ${envResult.missingVars.join(', ')}. ` +\n `This error prevents accidentally deploying with zero static pages.`\n );\n }\n\n const { apiKey, siteId, baseUrl } = envResult.config;\n\n const client = createRiverbankClient({\n apiKey,\n baseUrl,\n // Disable caching for build-time fetches\n cache: { enabled: false },\n // Disable resilience for build-time fetches (want fast failure)\n resilience: { enabled: false },\n });\n\n try {\n const routes = await client.getAllPublishedRoutes({ siteId });\n return routes.items.map((route) => ({\n slug: pathToSlugArray(route.path),\n }));\n } catch (error) {\n // Log error but don't fail the build - allow ISR fallback\n console.error(\n '[Riverbank] generateAllStaticParams failed to fetch routes:',\n error instanceof Error ? error.message : error\n );\n // Return at least the homepage so the build doesn't fail\n return [{ slug: [] }];\n }\n}\n","/**\n * Next.js integration helpers for Riverbank CMS SDK.\n *\n * Provides opinionated factories for common Next.js patterns, reducing\n * boilerplate while maintaining full customizability through escape hatches.\n *\n * @example Basic catch-all page\n * ```tsx\n * // src/app/[[...slug]]/page.tsx\n * import { createCatchAllPage } from '@riverbankcms/sdk/next';\n * import { getRiverbankClient } from '@/lib/builder-client';\n * import config from '@/riverbank.config';\n *\n * const { Page, generateMetadata } = createCatchAllPage({\n * getClient: getRiverbankClient,\n * config,\n * });\n *\n * export default Page;\n * export { generateMetadata };\n * ```\n *\n * @example With customization\n * ```tsx\n * const { Page, generateMetadata } = createCatchAllPage({\n * getClient,\n * config,\n * blockOverrides: { hero: MyCustomHero },\n * beforeRender: async () => {\n * if (process.env.MAINTENANCE_MODE === 'true') {\n * return <MaintenancePage />;\n * }\n * return null;\n * },\n * });\n * ```\n *\n * @packageDocumentation\n */\n\nimport { isPreviewMode } from '../env';\n\n// Catch-all page factory\nexport { createCatchAllPage } from './catch-all';\nexport type {\n CreateCatchAllPageOptions,\n CreateCatchAllPageResult,\n CatchAllPageProps,\n CatchAllContext,\n} from './types';\n\n// Static params utilities\nexport {\n generateAllStaticParams,\n validateStaticParamsEnv,\n pathToSlugArray,\n type StaticParamsEnvResult,\n} from './static-params';\n\n/**\n * ISR revalidation duration in seconds for production mode.\n * 5 minutes provides a good balance between freshness and performance.\n */\nexport const ISR_REVALIDATE_SECONDS = 300;\n\nexport interface ISRConfig {\n /**\n * Revalidation interval in seconds.\n * - 0: Dynamic rendering (no caching) - used in preview mode\n * - 300: 5 minute ISR - used in production mode\n */\n revalidate: number | false;\n\n /**\n * Whether the current environment is in preview mode.\n * Useful for conditional rendering or data fetching behavior.\n */\n isPreview: boolean;\n}\n\n/**\n * Get ISR configuration based on the current environment.\n *\n * @example\n * ```tsx\n * // app/[[...slug]]/page.tsx\n * import { getISRConfig } from '@riverbankcms/sdk/next';\n *\n * export const revalidate = getISRConfig().revalidate;\n * ```\n *\n * @returns ISR configuration with revalidate interval and preview flag\n */\nexport function getISRConfig(): ISRConfig {\n const preview = isPreviewMode();\n return {\n revalidate: preview ? 0 : ISR_REVALIDATE_SECONDS,\n isPreview: preview,\n };\n}\n"]}
|
package/dist/server/next.mjs
CHANGED
|
@@ -7,12 +7,12 @@ import {
|
|
|
7
7
|
} from "./chunk-PPHZV6YD.mjs";
|
|
8
8
|
import {
|
|
9
9
|
createRiverbankClient
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-65A5HAUZ.mjs";
|
|
11
11
|
import {
|
|
12
12
|
isEntryContent,
|
|
13
13
|
isPageContent,
|
|
14
14
|
loadContent
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-4YQJUL5W.mjs";
|
|
16
16
|
import {
|
|
17
17
|
Page
|
|
18
18
|
} from "./chunk-ARNCLSQT.mjs";
|
|
@@ -24,8 +24,18 @@ import "./chunk-NFEGQTCC.mjs";
|
|
|
24
24
|
// src/next/catch-all.tsx
|
|
25
25
|
import { notFound } from "next/navigation";
|
|
26
26
|
import { jsx } from "react/jsx-runtime";
|
|
27
|
-
function
|
|
28
|
-
|
|
27
|
+
function detectPreviewFromClient(getClient) {
|
|
28
|
+
const client = getClient();
|
|
29
|
+
const keyType = client.getKeyType();
|
|
30
|
+
if (keyType === "preview") {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
if (keyType === "unknown") {
|
|
34
|
+
console.warn(
|
|
35
|
+
"[createCatchAllPage] Could not detect API key type from prefix. Expected bld_live_sk_*, bld_test_sk_*, or bld_preview_sk_*. Defaulting to published content (preview: false)."
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
return false;
|
|
29
39
|
}
|
|
30
40
|
function slugToPath(slug) {
|
|
31
41
|
if (!slug || slug.length === 0) return "/";
|
|
@@ -35,19 +45,20 @@ function createCatchAllPage(options) {
|
|
|
35
45
|
const {
|
|
36
46
|
getClient,
|
|
37
47
|
config,
|
|
48
|
+
preview: previewOption,
|
|
38
49
|
blockOverrides,
|
|
39
50
|
siteUrl,
|
|
40
51
|
beforeRender,
|
|
41
52
|
customMetadata,
|
|
42
53
|
wrapper: Wrapper
|
|
43
54
|
} = options;
|
|
55
|
+
const preview = previewOption ?? detectPreviewFromClient(getClient);
|
|
44
56
|
async function Page2({ params, searchParams: searchParamsPromise }) {
|
|
45
57
|
const [{ slug }, searchParams] = await Promise.all([
|
|
46
58
|
params,
|
|
47
59
|
searchParamsPromise ?? Promise.resolve({})
|
|
48
60
|
]);
|
|
49
61
|
const path = slugToPath(slug);
|
|
50
|
-
const preview = isPreviewMode2();
|
|
51
62
|
const client = getClient();
|
|
52
63
|
let content;
|
|
53
64
|
try {
|
|
@@ -107,7 +118,6 @@ function createCatchAllPage(options) {
|
|
|
107
118
|
searchParamsPromise ?? Promise.resolve({})
|
|
108
119
|
]);
|
|
109
120
|
const path = slugToPath(slug);
|
|
110
|
-
const preview = isPreviewMode2();
|
|
111
121
|
try {
|
|
112
122
|
const content = await loadContent({
|
|
113
123
|
client: getClient(),
|
|
@@ -183,8 +193,8 @@ async function generateAllStaticParams() {
|
|
|
183
193
|
`[Riverbank] generateAllStaticParams requires env vars: ${envResult.missingVars.join(", ")}. This error prevents accidentally deploying with zero static pages.`
|
|
184
194
|
);
|
|
185
195
|
}
|
|
186
|
-
const { apiKey, siteId
|
|
187
|
-
const
|
|
196
|
+
const { apiKey, siteId, baseUrl } = envResult.config;
|
|
197
|
+
const client = createRiverbankClient({
|
|
188
198
|
apiKey,
|
|
189
199
|
baseUrl,
|
|
190
200
|
// Disable caching for build-time fetches
|
|
@@ -192,10 +202,18 @@ async function generateAllStaticParams() {
|
|
|
192
202
|
// Disable resilience for build-time fetches (want fast failure)
|
|
193
203
|
resilience: { enabled: false }
|
|
194
204
|
});
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
205
|
+
try {
|
|
206
|
+
const routes = await client.getAllPublishedRoutes({ siteId });
|
|
207
|
+
return routes.items.map((route) => ({
|
|
208
|
+
slug: pathToSlugArray(route.path)
|
|
209
|
+
}));
|
|
210
|
+
} catch (error) {
|
|
211
|
+
console.error(
|
|
212
|
+
"[Riverbank] generateAllStaticParams failed to fetch routes:",
|
|
213
|
+
error instanceof Error ? error.message : error
|
|
214
|
+
);
|
|
215
|
+
return [{ slug: [] }];
|
|
216
|
+
}
|
|
199
217
|
}
|
|
200
218
|
|
|
201
219
|
// src/next/index.ts
|
package/dist/server/next.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/next/catch-all.tsx","../../src/rendering/hooks/usePage.ts","../../src/rendering/hooks/useContent.ts","../../src/next/static-params.ts","../../src/next/index.ts"],"sourcesContent":["/**\n * Next.js catch-all page factory for Riverbank CMS.\n *\n * Provides a simple, opinionated way to render CMS content in Next.js catch-all routes.\n * Reduces typical page.tsx boilerplate from ~160 lines to ~10 lines.\n *\n * @example\n * ```tsx\n * // src/app/[[...slug]]/page.tsx\n * import { createCatchAllPage } from '@riverbankcms/sdk/next';\n * import { getRiverbankClient } from '@/lib/builder-client';\n * import config from '@/riverbank.config';\n *\n * const { Page, generateMetadata } = createCatchAllPage({\n * getClient: getRiverbankClient,\n * config,\n * blockOverrides: { hero: MyCustomHero },\n * });\n *\n * export default Page;\n * export { generateMetadata };\n * ```\n */\n\n// Note: next is a peerDependency - this module only works in Next.js projects.\nimport { notFound } from 'next/navigation';\n\nimport {\n loadContent,\n isPageContent,\n isEntryContent,\n} from '../rendering/helpers/loadContent';\nimport type { LoadContentResult } from '../rendering/helpers/loadContent';\nimport { Page as PageComponent } from '../rendering/components/Page';\nimport {\n generatePageMetadata,\n generatePreviewMetadata,\n type Metadata,\n} from '../metadata/generatePageMetadata';\nimport type {\n CreateCatchAllPageOptions,\n CatchAllPageProps,\n CreateCatchAllPageResult,\n} from './types';\n\n/**\n * Detect preview mode from environment variable.\n *\n * Sites can enable preview mode by setting:\n * ```\n * RIVERBANK_PREVIEW_MODE=true\n * ```\n *\n * This is typically set in preview/staging environments to fetch\n * draft content instead of published content.\n */\nfunction isPreviewMode(): boolean {\n return process.env.RIVERBANK_PREVIEW_MODE === 'true';\n}\n\n/**\n * Convert URL slug segments to a path string.\n *\n * @example\n * ```\n * slugToPath(undefined) // '/'\n * slugToPath([]) // '/'\n * slugToPath(['about']) // '/about'\n * slugToPath(['blog', 'post']) // '/blog/post'\n * ```\n */\nfunction slugToPath(slug?: string[]): string {\n if (!slug || slug.length === 0) return '/';\n return '/' + slug.join('/');\n}\n\n/**\n * Factory function to create a Next.js catch-all page component and metadata generator.\n *\n * This provides a simple, opinionated setup for rendering Riverbank CMS content\n * in external SDK sites. It handles:\n *\n * - Page and content entry routing\n * - Preview mode detection (via `RIVERBANK_PREVIEW_MODE` env var)\n * - SEO metadata generation\n * - Block overrides for custom components\n * - 404 handling for missing content\n *\n * ## Escape Hatches\n *\n * For customization beyond the defaults, use these options:\n *\n * - `beforeRender`: Intercept before rendering (maintenance mode, access control)\n * - `customMetadata`: Full control over SEO metadata\n * - `wrapper`: Wrap all content (analytics, error boundaries)\n *\n * For maximum control, use the lower-level helpers directly:\n * - `loadContent()` from `@riverbankcms/sdk/rendering`\n * - `Page` component from `@riverbankcms/sdk/rendering`\n * - `generatePageMetadata()` from `@riverbankcms/sdk/metadata`\n *\n * @param options - Configuration options\n * @returns Object with `Page` component and `generateMetadata` function\n *\n * @example Basic usage\n * ```tsx\n * const { Page, generateMetadata } = createCatchAllPage({\n * getClient: getRiverbankClient,\n * config,\n * });\n *\n * export default Page;\n * export { generateMetadata };\n * ```\n *\n * @example With maintenance mode\n * ```tsx\n * const { Page, generateMetadata } = createCatchAllPage({\n * getClient,\n * config,\n * beforeRender: async () => {\n * if (process.env.MAINTENANCE_MODE === 'true') {\n * return <MaintenancePage />;\n * }\n * return null;\n * },\n * });\n * ```\n */\nexport function createCatchAllPage(\n options: CreateCatchAllPageOptions\n): CreateCatchAllPageResult {\n const {\n getClient,\n config,\n blockOverrides,\n siteUrl,\n beforeRender,\n customMetadata,\n wrapper: Wrapper,\n } = options;\n\n /**\n * Main page component that loads and renders CMS content.\n */\n async function Page({ params, searchParams: searchParamsPromise }: CatchAllPageProps): Promise<React.ReactNode> {\n const [{ slug }, searchParams] = await Promise.all([\n params,\n searchParamsPromise ?? Promise.resolve({}),\n ]);\n const path = slugToPath(slug);\n const preview = isPreviewMode();\n const client = getClient();\n\n // Load content (page or entry)\n let content: LoadContentResult;\n try {\n content = await loadContent({\n client,\n siteId: config.siteId,\n path,\n preview,\n });\n } catch (error) {\n // Log the error for debugging - could be a network error, auth failure, etc.\n // We treat all errors as \"not found\" since the page can't be rendered\n console.debug('[createCatchAllPage] Failed to load content for path:', path, error);\n return notFound();\n }\n\n // ESCAPE HATCH: beforeRender for maintenance mode, access control, etc.\n if (beforeRender) {\n const intercepted = await beforeRender({ content, path, preview, searchParams });\n if (intercepted !== null) {\n return Wrapper ? <Wrapper>{intercepted}</Wrapper> : intercepted;\n }\n }\n\n let rendered: React.ReactNode;\n\n // Render page content\n if (isPageContent(content)) {\n rendered = (\n <PageComponent\n page={content.page}\n theme={content.theme}\n siteId={content.siteId}\n resolvedData={content.resolvedData}\n blockOverrides={blockOverrides}\n />\n );\n }\n // Render entry content with template\n else if (isEntryContent(content)) {\n // Entries without template pages should 404\n if (!content.templatePage) {\n return notFound();\n }\n\n rendered = (\n <PageComponent\n page={content.templatePage}\n theme={content.theme}\n siteId={content.siteId}\n resolvedData={content.resolvedData}\n blockOverrides={blockOverrides}\n dataContext={{\n contentEntry: content.dataContext.contentEntry,\n }}\n />\n );\n }\n // Unexpected content type - should never happen\n else {\n return notFound();\n }\n\n return Wrapper ? <Wrapper>{rendered}</Wrapper> : rendered;\n }\n\n /**\n * Generate SEO metadata for the page.\n */\n async function generateMetadataFn({ params, searchParams: searchParamsPromise }: CatchAllPageProps): Promise<Metadata> {\n const [{ slug }, searchParams] = await Promise.all([\n params,\n searchParamsPromise ?? Promise.resolve({}),\n ]);\n const path = slugToPath(slug);\n const preview = isPreviewMode();\n\n try {\n const content = await loadContent({\n client: getClient(),\n siteId: config.siteId,\n path,\n preview,\n });\n\n // ESCAPE HATCH: Custom metadata generation\n if (customMetadata) {\n return customMetadata({ content, path, preview, searchParams });\n }\n\n // Resolve site URL (use config URLs as fallback)\n const resolvedUrl =\n siteUrl ?? (preview ? config.previewUrl : config.liveUrl) ?? '';\n\n // Warn if no site URL is configured - OG/Twitter tags require absolute URLs\n if (!resolvedUrl) {\n console.warn(\n '[createCatchAllPage] No siteUrl configured. OpenGraph and Twitter tags will have relative URLs which may cause social sharing previews to fail. ' +\n 'Set siteUrl option, or config.liveUrl/config.previewUrl in your riverbank.config.'\n );\n }\n\n // Build page object for metadata\n // For entries, we use metaTitle/metaDescription; purpose is used as description fallback\n const page = isPageContent(content)\n ? content.page\n : isEntryContent(content)\n ? {\n name: content.entry.metaTitle ?? content.entry.title,\n purpose: content.entry.metaDescription ?? undefined,\n }\n : { name: 'Page' };\n\n // Use preview metadata (noindex) in preview mode\n const metadataGenerator = preview\n ? generatePreviewMetadata\n : generatePageMetadata;\n\n // Use site data from loadContent result (no duplicate API call needed)\n return metadataGenerator({\n page,\n site: content.site,\n path,\n siteUrl: resolvedUrl,\n });\n } catch (error) {\n // Log the error for debugging purposes\n console.debug('[createCatchAllPage] Failed to generate metadata for path:', path, error);\n // Return minimal metadata on error - page will handle the actual 404\n return {\n title: 'Page Not Found',\n robots: { index: false, follow: false },\n };\n }\n }\n\n return {\n Page,\n generateMetadata: generateMetadataFn,\n };\n}\n","/**\n * Client-side React hook to fetch page data.\n *\n * Use this in client components for dynamic page loading.\n */\n\nimport { useState, useEffect } from 'react';\nimport type { RiverbankClient } from '../../client/types';\nimport type { PageProps } from '../components/Page';\nimport type { RuntimeSdkConfig } from '../helpers/loadPage';\nimport { prefetchBlockData } from '../../data/prefetchBlockData';\n\nexport type UsePageParams = {\n client: RiverbankClient;\n siteId: string;\n path: string;\n pageId?: string;\n /**\n * If true, fetches draft/unpublished content instead of published content.\n * This affects both the page structure and block data loaders.\n * Requires API key with site access.\n *\n * @default false\n */\n preview?: boolean;\n};\n\nexport type UsePageResult =\n | { loading: true; error: null; page: null; theme: null; siteId: string; resolvedData: null; sdkConfig: null }\n | { loading: false; error: Error; page: null; theme: null; siteId: string; resolvedData: null; sdkConfig: null }\n | { loading: false; error: null; sdkConfig: RuntimeSdkConfig | null } & Omit<PageProps, 'registry' | 'wrapBlock' | 'usePlaceholders' | 'sdkConfig'>;\n\n/**\n * Client-side React hook to fetch all data needed for <Page> component.\n *\n * Fetches site data, page data, and prefetches block data loaders.\n * Returns loading and error states for proper UI handling.\n *\n * IMPORTANT: The client object should be stable across renders to avoid\n * unnecessary re-fetches. Create it outside your component or use useMemo:\n *\n * ```tsx\n * // ✅ Good - stable reference\n * const client = useMemo(\n * () => createRiverbankClient({ apiKey, baseUrl }),\n * [apiKey, baseUrl]\n * );\n *\n * // ❌ Bad - new client on every render (causes infinite loops)\n * const client = createRiverbankClient({ apiKey, baseUrl });\n * ```\n *\n * @example Basic usage\n * ```tsx\n * import { createRiverbankClient } from '@riverbankcms/sdk';\n * import { usePage, Page } from '@riverbankcms/sdk/rendering';\n *\n * const client = createRiverbankClient({\n * apiKey: process.env.NEXT_PUBLIC_RIVERBANK_API_KEY!,\n * baseUrl: process.env.NEXT_PUBLIC_DASHBOARD_URL + '/api',\n * });\n *\n * function MyPage({ path }: { path: string }) {\n * const pageData = usePage({ client, siteId: 'site-123', path });\n *\n * if (pageData.loading) {\n * return <div>Loading...</div>;\n * }\n *\n * if (pageData.error) {\n * return <div>Error: {pageData.error.message}</div>;\n * }\n *\n * return <Page {...pageData} />;\n * }\n * ```\n *\n * @example With custom loading/error states\n * ```tsx\n * function MyPage({ path }: { path: string }) {\n * const pageData = usePage({ client, siteId: 'site-123', path });\n *\n * if (pageData.loading) {\n * return <Skeleton />;\n * }\n *\n * if (pageData.error) {\n * return (\n * <ErrorBoundary\n * error={pageData.error}\n * onRetry={() => window.location.reload()}\n * />\n * );\n * }\n *\n * return <Page {...pageData} />;\n * }\n * ```\n */\nexport function usePage(params: UsePageParams): UsePageResult {\n const { client, siteId, path, pageId, preview = false } = params;\n\n const [result, setResult] = useState<UsePageResult>({\n loading: true,\n error: null,\n page: null,\n theme: null,\n siteId,\n resolvedData: null,\n sdkConfig: null,\n });\n\n useEffect(() => {\n let cancelled = false;\n\n async function fetchPage() {\n try {\n // Fetch site and page data in parallel\n const [site, pageResponse] = await Promise.all([\n client.getSite({ id: siteId }),\n client.getPage({ siteId, path, preview }),\n ]);\n\n // If component unmounted, don't update state\n if (cancelled) return;\n\n // Extract page data (getContentByPath can return page or entry)\n if ('entry' in pageResponse) {\n throw new Error(\n 'This path resolves to a content entry, not a page. ' +\n 'Use useContent() instead, which handles both pages and entries. ' +\n 'For entries, useContent() returns the raw entry data for custom rendering.'\n );\n }\n\n const { page: pageData } = pageResponse;\n\n // Convert API response blocks to PageOutline format\n // API returns blocks with full content, but PageOutline only needs structure\n const blocks = pageData.blocks.map((block) => {\n if (!block || typeof block !== 'object') {\n throw new Error('Invalid block format in API response');\n }\n if (typeof block.id !== 'string' && block.id !== null) {\n throw new Error(`Invalid block id: expected string or null, got ${typeof block.id}`);\n }\n if (typeof block.kind !== 'string') {\n throw new Error(`Invalid block kind: expected string, got ${typeof block.kind}`);\n }\n if (typeof block.purpose !== 'string') {\n throw new Error(`Invalid block purpose: expected string, got ${typeof block.purpose}`);\n }\n return {\n id: block.id,\n kind: block.kind,\n purpose: block.purpose,\n };\n });\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\n const resolvedData = await prefetchBlockData(\n pageOutline,\n {\n siteId,\n pageId: pageId ?? pageData.id,\n previewStage: preview ? 'preview' : 'published',\n },\n client\n );\n\n // If component unmounted, don't update state\n if (cancelled) return;\n\n setResult({\n loading: false,\n error: null,\n page: pageOutline,\n theme: site.theme,\n siteId,\n resolvedData,\n sdkConfig: site.sdkConfig ?? null,\n });\n } catch (error) {\n if (cancelled) return;\n\n setResult({\n loading: false,\n error: error instanceof Error ? error : new Error(String(error)),\n page: null,\n theme: null,\n siteId,\n resolvedData: null,\n sdkConfig: null,\n });\n }\n }\n\n fetchPage();\n\n return () => {\n cancelled = true;\n };\n }, [client, siteId, path, pageId, preview]);\n\n return result;\n}\n","/**\n * Client-side React hook to fetch content (page or entry) by path.\n *\n * Use this in client components for dynamic routing where a path\n * could resolve to either a page or content entry.\n */\n\nimport { useState, useEffect } from 'react';\nimport type { Theme } from '@riverbankcms/blocks';\nimport type { RiverbankClient, PageResponse } from '../../client/types';\nimport type { PageProps } from '../components/Page';\nimport { prefetchBlockData } from '../../data/prefetchBlockData';\nimport type { ResolvedBlockData } from '../../data/prefetchBlockData';\nimport type { ContentEntryData } from '../helpers/loadContent';\n\nexport type UseContentParams = {\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 * Loading state\n */\ntype LoadingState = {\n loading: true;\n error: null;\n type: null;\n page: null;\n entry: null;\n theme: null;\n siteId: string;\n resolvedData: null;\n};\n\n/**\n * Error state\n */\ntype ErrorState = {\n loading: false;\n error: Error;\n type: null;\n page: null;\n entry: null;\n theme: null;\n siteId: string;\n resolvedData: null;\n};\n\n/**\n * Success state for page content\n */\ntype PageSuccessState = {\n loading: false;\n error: null;\n type: 'page';\n page: PageProps['page'];\n entry: null;\n theme: Theme;\n siteId: string;\n resolvedData: ResolvedBlockData;\n};\n\n/**\n * Success state for entry content\n */\ntype EntrySuccessState = {\n loading: false;\n error: null;\n type: 'entry';\n page: null;\n entry: ContentEntryData;\n theme: Theme;\n siteId: string;\n resolvedData: null;\n};\n\nexport type UseContentResult = LoadingState | ErrorState | PageSuccessState | EntrySuccessState;\n\n/**\n * Type guard to check if result is loading\n */\nexport function isContentLoading(result: UseContentResult): result is LoadingState {\n return result.loading === true;\n}\n\n/**\n * Type guard to check if result has an error\n */\nexport function isContentError(result: UseContentResult): result is ErrorState {\n return result.loading === false && result.error !== null;\n}\n\n/**\n * Type guard to check if result is a page\n */\nexport function isPageContentResult(result: UseContentResult): result is PageSuccessState {\n return result.loading === false && result.error === null && result.type === 'page';\n}\n\n/**\n * Type guard to check if result is an entry\n */\nexport function isEntryContentResult(result: UseContentResult): result is EntrySuccessState {\n return result.loading === false && result.error === null && result.type === 'entry';\n}\n\n/**\n * Client-side React hook to fetch content by path.\n *\n * Returns a discriminated union with loading/error states, and either\n * page data (ready for `<Page>` component) or raw entry data (for custom rendering).\n *\n * IMPORTANT: The client object should be stable across renders to avoid\n * unnecessary re-fetches. Create it outside your component or use useMemo.\n *\n * @example Dynamic routing with both pages and entries\n * ```tsx\n * \"use client\";\n *\n * import { useContent, Page, isPageContentResult } from '@riverbankcms/sdk/client';\n *\n * function DynamicPage({ path }: { path: string }) {\n * const content = useContent({ client, siteId: 'site-123', path });\n *\n * if (content.loading) return <div>Loading...</div>;\n * if (content.error) return <div>Error: {content.error.message}</div>;\n *\n * if (isPageContentResult(content)) {\n * return <Page page={content.page} theme={content.theme} siteId={content.siteId} resolvedData={content.resolvedData} />;\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 = useContent({ client, siteId, path });\n *\n * if (content.loading) return <Spinner />;\n * if (content.error) return <Error error={content.error} />;\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 * }\n * }\n *\n * return <Page {...content} />;\n * ```\n */\nexport function useContent(params: UseContentParams): UseContentResult {\n const { client, siteId, path, preview = false } = params;\n\n const [result, setResult] = useState<UseContentResult>({\n loading: true,\n error: null,\n type: null,\n page: null,\n entry: null,\n theme: null,\n siteId,\n resolvedData: null,\n });\n\n useEffect(() => {\n let cancelled = false;\n\n async function fetchContent() {\n try {\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 // If component unmounted, don't update state\n if (cancelled) return;\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 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 setResult({\n loading: false,\n error: null,\n type: 'entry',\n page: null,\n entry,\n theme: site.theme,\n siteId,\n resolvedData: null,\n });\n return;\n }\n\n // Handle page response\n const { page: pageData } = contentResponse;\n\n // Convert API response blocks to PageOutline format\n const blocks = pageData.blocks.map((block) => {\n if (!block || typeof block !== 'object') {\n throw new Error('Invalid block format in API response');\n }\n if (typeof block.id !== 'string' && block.id !== null) {\n throw new Error(`Invalid block id: expected string or null, got ${typeof block.id}`);\n }\n if (typeof block.kind !== 'string') {\n throw new Error(`Invalid block kind: expected string, got ${typeof block.kind}`);\n }\n if (typeof block.purpose !== 'string') {\n throw new Error(`Invalid block purpose: expected string, got ${typeof block.purpose}`);\n }\n return {\n id: block.id,\n kind: block.kind,\n purpose: block.purpose,\n };\n });\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 // If component unmounted, don't update state\n if (cancelled) return;\n\n setResult({\n loading: false,\n error: null,\n type: 'page',\n page: pageOutline,\n entry: null,\n theme: site.theme,\n siteId,\n resolvedData,\n });\n } catch (error) {\n if (cancelled) return;\n\n setResult({\n loading: false,\n error: error instanceof Error ? error : new Error(String(error)),\n type: null,\n page: null,\n entry: null,\n theme: null,\n siteId,\n resolvedData: null,\n });\n }\n }\n\n fetchContent();\n\n return () => {\n cancelled = true;\n };\n }, [client, siteId, path, preview]);\n\n return result;\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 * Static params generation for Next.js SSG\n *\n * Provides helpers for generating static params from published CMS routes.\n *\n * @example\n * ```tsx\n * // app/[[...slug]]/page.tsx\n * import { generateAllStaticParams } from '@riverbankcms/sdk/next';\n *\n * export { generateAllStaticParams as generateStaticParams };\n * ```\n */\n\nimport { createRiverbankClient } from '../client';\n\n/**\n * Environment variable validation result\n */\nexport type StaticParamsEnvResult =\n | { valid: true; config: { apiKey: string; siteId: string; baseUrl: string } }\n | { valid: false; missingVars: string[] };\n\n/**\n * Validate that all required environment variables are set.\n *\n * Required env vars:\n * - RIVERBANK_API_KEY: API key for published content\n * - RIVERBANK_SITE_ID: Site ID\n * - NEXT_PUBLIC_DASHBOARD_URL: Dashboard API URL\n *\n * @returns Validation result with config or missing vars\n */\nexport function validateStaticParamsEnv(): StaticParamsEnvResult {\n const apiKey = process.env.RIVERBANK_API_KEY;\n const siteId = process.env.RIVERBANK_SITE_ID;\n const baseUrl = process.env.NEXT_PUBLIC_DASHBOARD_URL;\n\n const missingVars: string[] = [];\n\n if (!apiKey) missingVars.push('RIVERBANK_API_KEY');\n if (!siteId) missingVars.push('RIVERBANK_SITE_ID');\n if (!baseUrl) missingVars.push('NEXT_PUBLIC_DASHBOARD_URL');\n\n if (missingVars.length > 0) {\n return { valid: false, missingVars };\n }\n\n // TypeScript can't narrow through the checks above, so assert non-null\n return {\n valid: true,\n config: { apiKey: apiKey!, siteId: siteId!, baseUrl: baseUrl! },\n };\n}\n\n/**\n * Convert a route path to a Next.js slug array.\n *\n * @param path - The route path (e.g., '/about', '/blog/post')\n * @returns The slug array for Next.js catch-all route\n *\n * @example\n * ```ts\n * pathToSlugArray('/') // []\n * pathToSlugArray('/about') // ['about']\n * pathToSlugArray('/blog/post') // ['blog', 'post']\n * ```\n */\nexport function pathToSlugArray(path: string): string[] {\n if (path === '/') return [];\n return path.slice(1).split('/');\n}\n\n/**\n * Generate static params for all published routes.\n *\n * This function fetches all published routes from the CMS and converts them\n * to the static params format expected by Next.js catch-all routes.\n *\n * Requires environment variables:\n * - RIVERBANK_API_KEY: API key for published content\n * - RIVERBANK_SITE_ID: Site ID\n * - NEXT_PUBLIC_DASHBOARD_URL: Dashboard API URL\n *\n * @throws Error if required env vars are missing (prevents silent empty SSG in CI)\n * @returns Array of static params objects for Next.js\n *\n * @example\n * ```tsx\n * // app/[[...slug]]/page.tsx\n * import { generateAllStaticParams } from '@riverbankcms/sdk/next';\n *\n * export { generateAllStaticParams as generateStaticParams };\n *\n * // Or with custom logic:\n * export async function generateStaticParams() {\n * const params = await generateAllStaticParams();\n * // Filter or modify params as needed\n * return params.filter(p => !p.slug.includes('private'));\n * }\n * ```\n */\nexport async function generateAllStaticParams(): Promise<{ slug: string[] }[]> {\n const envResult = validateStaticParamsEnv();\n\n if (!envResult.valid) {\n throw new Error(\n `[Riverbank] generateAllStaticParams requires env vars: ${envResult.missingVars.join(', ')}. ` +\n `This error prevents accidentally deploying with zero static pages.`\n );\n }\n\n const { apiKey, siteId: _siteId, baseUrl } = envResult.config;\n\n // Create client (unused until getAllPublishedRoutes is implemented)\n const _client = createRiverbankClient({\n apiKey,\n baseUrl,\n // Disable caching for build-time fetches\n cache: { enabled: false },\n // Disable resilience for build-time fetches (want fast failure)\n resilience: { enabled: false },\n });\n\n // TODO: This requires adding getAllPublishedRoutes to the client\n // which requires the CMS endpoint to be updated to accept API key auth\n // and support the publishedOnly query param.\n //\n // For now, we'll use getPage to fetch the site's homepage which contains\n // the navigation data, and extract routes from there.\n //\n // In the future, implement:\n // const routes = await client.getAllPublishedRoutes({ siteId });\n // return routes.map(path => ({ slug: pathToSlugArray(path) }));\n\n // Temporary: Return empty array with warning\n // This allows the helper to be used but won't pre-render any pages\n console.warn(\n '[Riverbank] generateAllStaticParams: getAllPublishedRoutes API not yet implemented. ' +\n 'Pages will be generated on-demand with ISR.'\n );\n\n // Return at least the homepage\n return [{ slug: [] }];\n}\n","/**\n * Next.js integration helpers for Riverbank CMS SDK.\n *\n * Provides opinionated factories for common Next.js patterns, reducing\n * boilerplate while maintaining full customizability through escape hatches.\n *\n * @example Basic catch-all page\n * ```tsx\n * // src/app/[[...slug]]/page.tsx\n * import { createCatchAllPage } from '@riverbankcms/sdk/next';\n * import { getRiverbankClient } from '@/lib/builder-client';\n * import config from '@/riverbank.config';\n *\n * const { Page, generateMetadata } = createCatchAllPage({\n * getClient: getRiverbankClient,\n * config,\n * });\n *\n * export default Page;\n * export { generateMetadata };\n * ```\n *\n * @example With customization\n * ```tsx\n * const { Page, generateMetadata } = createCatchAllPage({\n * getClient,\n * config,\n * blockOverrides: { hero: MyCustomHero },\n * beforeRender: async () => {\n * if (process.env.MAINTENANCE_MODE === 'true') {\n * return <MaintenancePage />;\n * }\n * return null;\n * },\n * });\n * ```\n *\n * @packageDocumentation\n */\n\nimport { isPreviewMode } from '../env';\n\n// Catch-all page factory\nexport { createCatchAllPage } from './catch-all';\nexport type {\n CreateCatchAllPageOptions,\n CreateCatchAllPageResult,\n CatchAllPageProps,\n CatchAllContext,\n} from './types';\n\n// Static params utilities\nexport {\n generateAllStaticParams,\n validateStaticParamsEnv,\n pathToSlugArray,\n type StaticParamsEnvResult,\n} from './static-params';\n\n/**\n * ISR revalidation duration in seconds for production mode.\n * 5 minutes provides a good balance between freshness and performance.\n */\nexport const ISR_REVALIDATE_SECONDS = 300;\n\nexport interface ISRConfig {\n /**\n * Revalidation interval in seconds.\n * - 0: Dynamic rendering (no caching) - used in preview mode\n * - 300: 5 minute ISR - used in production mode\n */\n revalidate: number | false;\n\n /**\n * Whether the current environment is in preview mode.\n * Useful for conditional rendering or data fetching behavior.\n */\n isPreview: boolean;\n}\n\n/**\n * Get ISR configuration based on the current environment.\n *\n * @example\n * ```tsx\n * // app/[[...slug]]/page.tsx\n * import { getISRConfig } from '@riverbankcms/sdk/next';\n *\n * export const revalidate = getISRConfig().revalidate;\n * ```\n *\n * @returns ISR configuration with revalidate interval and preview flag\n */\nexport function getISRConfig(): ISRConfig {\n const preview = isPreviewMode();\n return {\n revalidate: preview ? 0 : ISR_REVALIDATE_SECONDS,\n isPreview: preview,\n };\n}\n"],"mappings":"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","names":["isPreviewMode","Page","useState","useEffect"]}
|
|
1
|
+
{"version":3,"sources":["../../src/next/catch-all.tsx","../../src/rendering/hooks/usePage.ts","../../src/rendering/hooks/useContent.ts","../../src/next/static-params.ts","../../src/next/index.ts"],"sourcesContent":["/**\n * Next.js catch-all page factory for Riverbank CMS.\n *\n * Provides a simple, opinionated way to render CMS content in Next.js catch-all routes.\n * Reduces typical page.tsx boilerplate from ~160 lines to ~10 lines.\n *\n * @example\n * ```tsx\n * // src/app/[[...slug]]/page.tsx\n * import { createCatchAllPage } from '@riverbankcms/sdk/next';\n * import { getRiverbankClient } from '@/lib/builder-client';\n * import config from '@/riverbank.config';\n *\n * const { Page, generateMetadata } = createCatchAllPage({\n * getClient: getRiverbankClient,\n * config,\n * blockOverrides: { hero: MyCustomHero },\n * });\n *\n * export default Page;\n * export { generateMetadata };\n * ```\n */\n\n// Note: next is a peerDependency - this module only works in Next.js projects.\nimport { notFound } from 'next/navigation';\n\nimport {\n loadContent,\n isPageContent,\n isEntryContent,\n} from '../rendering/helpers/loadContent';\nimport type { LoadContentResult } from '../rendering/helpers/loadContent';\nimport { Page as PageComponent } from '../rendering/components/Page';\nimport {\n generatePageMetadata,\n generatePreviewMetadata,\n type Metadata,\n} from '../metadata/generatePageMetadata';\nimport type {\n CreateCatchAllPageOptions,\n CatchAllPageProps,\n CreateCatchAllPageResult,\n} from './types';\nimport type { ApiKeyType } from '../client/types';\n\n/**\n * Detect preview mode from client's API key type.\n *\n * The client automatically detects its key type from the prefix:\n * - bld_live_sk_* / bld_test_sk_* → content key → preview: false\n * - bld_preview_sk_* → preview key → preview: true\n *\n * This eliminates the need for separate env vars and ensures\n * the preview mode always matches the API key type.\n */\nfunction detectPreviewFromClient(getClient: () => { getKeyType: () => ApiKeyType }): boolean {\n const client = getClient();\n const keyType = client.getKeyType();\n\n if (keyType === 'preview') {\n return true;\n }\n\n if (keyType === 'unknown') {\n console.warn(\n '[createCatchAllPage] Could not detect API key type from prefix. ' +\n 'Expected bld_live_sk_*, bld_test_sk_*, or bld_preview_sk_*. ' +\n 'Defaulting to published content (preview: false).'\n );\n }\n\n return false;\n}\n\n/**\n * Convert URL slug segments to a path string.\n *\n * @example\n * ```\n * slugToPath(undefined) // '/'\n * slugToPath([]) // '/'\n * slugToPath(['about']) // '/about'\n * slugToPath(['blog', 'post']) // '/blog/post'\n * ```\n */\nfunction slugToPath(slug?: string[]): string {\n if (!slug || slug.length === 0) return '/';\n return '/' + slug.join('/');\n}\n\n/**\n * Factory function to create a Next.js catch-all page component and metadata generator.\n *\n * This provides a simple, opinionated setup for rendering Riverbank CMS content\n * in external SDK sites. It handles:\n *\n * - Page and content entry routing\n * - Preview mode detection (automatically from API key type)\n * - SEO metadata generation\n * - Block overrides for custom components\n * - 404 handling for missing content\n *\n * ## Escape Hatches\n *\n * For customization beyond the defaults, use these options:\n *\n * - `beforeRender`: Intercept before rendering (maintenance mode, access control)\n * - `customMetadata`: Full control over SEO metadata\n * - `wrapper`: Wrap all content (analytics, error boundaries)\n *\n * For maximum control, use the lower-level helpers directly:\n * - `loadContent()` from `@riverbankcms/sdk/rendering`\n * - `Page` component from `@riverbankcms/sdk/rendering`\n * - `generatePageMetadata()` from `@riverbankcms/sdk/metadata`\n *\n * @param options - Configuration options\n * @returns Object with `Page` component and `generateMetadata` function\n *\n * @example Basic usage\n * ```tsx\n * const { Page, generateMetadata } = createCatchAllPage({\n * getClient: getRiverbankClient,\n * config,\n * });\n *\n * export default Page;\n * export { generateMetadata };\n * ```\n *\n * @example With maintenance mode\n * ```tsx\n * const { Page, generateMetadata } = createCatchAllPage({\n * getClient,\n * config,\n * beforeRender: async () => {\n * if (process.env.MAINTENANCE_MODE === 'true') {\n * return <MaintenancePage />;\n * }\n * return null;\n * },\n * });\n * ```\n */\nexport function createCatchAllPage(\n options: CreateCatchAllPageOptions\n): CreateCatchAllPageResult {\n const {\n getClient,\n config,\n preview: previewOption,\n blockOverrides,\n siteUrl,\n beforeRender,\n customMetadata,\n wrapper: Wrapper,\n } = options;\n\n // Resolve preview mode once: use explicit option if provided, otherwise detect from client key type\n const preview = previewOption ?? detectPreviewFromClient(getClient);\n\n /**\n * Main page component that loads and renders CMS content.\n */\n async function Page({ params, searchParams: searchParamsPromise }: CatchAllPageProps): Promise<React.ReactNode> {\n const [{ slug }, searchParams] = await Promise.all([\n params,\n searchParamsPromise ?? Promise.resolve({}),\n ]);\n const path = slugToPath(slug);\n const client = getClient();\n\n // Load content (page or entry)\n let content: LoadContentResult;\n try {\n content = await loadContent({\n client,\n siteId: config.siteId,\n path,\n preview,\n });\n } catch (error) {\n // Log the error for debugging - could be a network error, auth failure, etc.\n // We treat all errors as \"not found\" since the page can't be rendered\n console.debug('[createCatchAllPage] Failed to load content for path:', path, error);\n return notFound();\n }\n\n // ESCAPE HATCH: beforeRender for maintenance mode, access control, etc.\n if (beforeRender) {\n const intercepted = await beforeRender({ content, path, preview, searchParams });\n if (intercepted !== null) {\n return Wrapper ? <Wrapper>{intercepted}</Wrapper> : intercepted;\n }\n }\n\n let rendered: React.ReactNode;\n\n // Render page content\n if (isPageContent(content)) {\n rendered = (\n <PageComponent\n page={content.page}\n theme={content.theme}\n siteId={content.siteId}\n resolvedData={content.resolvedData}\n blockOverrides={blockOverrides}\n />\n );\n }\n // Render entry content with template\n else if (isEntryContent(content)) {\n // Entries without template pages should 404\n if (!content.templatePage) {\n return notFound();\n }\n\n rendered = (\n <PageComponent\n page={content.templatePage}\n theme={content.theme}\n siteId={content.siteId}\n resolvedData={content.resolvedData}\n blockOverrides={blockOverrides}\n dataContext={{\n contentEntry: content.dataContext.contentEntry,\n }}\n />\n );\n }\n // Unexpected content type - should never happen\n else {\n return notFound();\n }\n\n return Wrapper ? <Wrapper>{rendered}</Wrapper> : rendered;\n }\n\n /**\n * Generate SEO metadata for the page.\n */\n async function generateMetadataFn({ params, searchParams: searchParamsPromise }: CatchAllPageProps): Promise<Metadata> {\n const [{ slug }, searchParams] = await Promise.all([\n params,\n searchParamsPromise ?? Promise.resolve({}),\n ]);\n const path = slugToPath(slug);\n // Note: uses `preview` from outer scope (resolved once at factory creation)\n\n try {\n const content = await loadContent({\n client: getClient(),\n siteId: config.siteId,\n path,\n preview,\n });\n\n // ESCAPE HATCH: Custom metadata generation\n if (customMetadata) {\n return customMetadata({ content, path, preview, searchParams });\n }\n\n // Resolve site URL (use config URLs as fallback)\n const resolvedUrl =\n siteUrl ?? (preview ? config.previewUrl : config.liveUrl) ?? '';\n\n // Warn if no site URL is configured - OG/Twitter tags require absolute URLs\n if (!resolvedUrl) {\n console.warn(\n '[createCatchAllPage] No siteUrl configured. OpenGraph and Twitter tags will have relative URLs which may cause social sharing previews to fail. ' +\n 'Set siteUrl option, or config.liveUrl/config.previewUrl in your riverbank.config.'\n );\n }\n\n // Build page object for metadata\n // For entries, we use metaTitle/metaDescription; purpose is used as description fallback\n const page = isPageContent(content)\n ? content.page\n : isEntryContent(content)\n ? {\n name: content.entry.metaTitle ?? content.entry.title,\n purpose: content.entry.metaDescription ?? undefined,\n }\n : { name: 'Page' };\n\n // Use preview metadata (noindex) in preview mode\n const metadataGenerator = preview\n ? generatePreviewMetadata\n : generatePageMetadata;\n\n // Use site data from loadContent result (no duplicate API call needed)\n return metadataGenerator({\n page,\n site: content.site,\n path,\n siteUrl: resolvedUrl,\n });\n } catch (error) {\n // Log the error for debugging purposes\n console.debug('[createCatchAllPage] Failed to generate metadata for path:', path, error);\n // Return minimal metadata on error - page will handle the actual 404\n return {\n title: 'Page Not Found',\n robots: { index: false, follow: false },\n };\n }\n }\n\n return {\n Page,\n generateMetadata: generateMetadataFn,\n };\n}\n","/**\n * Client-side React hook to fetch page data.\n *\n * Use this in client components for dynamic page loading.\n */\n\nimport { useState, useEffect } from 'react';\nimport type { RiverbankClient } from '../../client/types';\nimport type { PageProps } from '../components/Page';\nimport type { RuntimeSdkConfig } from '../helpers/loadPage';\nimport { prefetchBlockData } from '../../data/prefetchBlockData';\n\nexport type UsePageParams = {\n client: RiverbankClient;\n siteId: string;\n path: string;\n pageId?: string;\n /**\n * If true, fetches draft/unpublished content instead of published content.\n * This affects both the page structure and block data loaders.\n * Requires API key with site access.\n *\n * @default false\n */\n preview?: boolean;\n};\n\nexport type UsePageResult =\n | { loading: true; error: null; page: null; theme: null; siteId: string; resolvedData: null; sdkConfig: null }\n | { loading: false; error: Error; page: null; theme: null; siteId: string; resolvedData: null; sdkConfig: null }\n | { loading: false; error: null; sdkConfig: RuntimeSdkConfig | null } & Omit<PageProps, 'registry' | 'wrapBlock' | 'usePlaceholders' | 'sdkConfig'>;\n\n/**\n * Client-side React hook to fetch all data needed for <Page> component.\n *\n * Fetches site data, page data, and prefetches block data loaders.\n * Returns loading and error states for proper UI handling.\n *\n * IMPORTANT: The client object should be stable across renders to avoid\n * unnecessary re-fetches. Create it outside your component or use useMemo:\n *\n * ```tsx\n * // ✅ Good - stable reference\n * const client = useMemo(\n * () => createRiverbankClient({ apiKey, baseUrl }),\n * [apiKey, baseUrl]\n * );\n *\n * // ❌ Bad - new client on every render (causes infinite loops)\n * const client = createRiverbankClient({ apiKey, baseUrl });\n * ```\n *\n * @example Basic usage\n * ```tsx\n * import { createRiverbankClient } from '@riverbankcms/sdk';\n * import { usePage, Page } from '@riverbankcms/sdk/rendering';\n *\n * const client = createRiverbankClient({\n * apiKey: process.env.NEXT_PUBLIC_RIVERBANK_API_KEY!,\n * baseUrl: process.env.NEXT_PUBLIC_DASHBOARD_URL + '/api',\n * });\n *\n * function MyPage({ path }: { path: string }) {\n * const pageData = usePage({ client, siteId: 'site-123', path });\n *\n * if (pageData.loading) {\n * return <div>Loading...</div>;\n * }\n *\n * if (pageData.error) {\n * return <div>Error: {pageData.error.message}</div>;\n * }\n *\n * return <Page {...pageData} />;\n * }\n * ```\n *\n * @example With custom loading/error states\n * ```tsx\n * function MyPage({ path }: { path: string }) {\n * const pageData = usePage({ client, siteId: 'site-123', path });\n *\n * if (pageData.loading) {\n * return <Skeleton />;\n * }\n *\n * if (pageData.error) {\n * return (\n * <ErrorBoundary\n * error={pageData.error}\n * onRetry={() => window.location.reload()}\n * />\n * );\n * }\n *\n * return <Page {...pageData} />;\n * }\n * ```\n */\nexport function usePage(params: UsePageParams): UsePageResult {\n const { client, siteId, path, pageId, preview = false } = params;\n\n const [result, setResult] = useState<UsePageResult>({\n loading: true,\n error: null,\n page: null,\n theme: null,\n siteId,\n resolvedData: null,\n sdkConfig: null,\n });\n\n useEffect(() => {\n let cancelled = false;\n\n async function fetchPage() {\n try {\n // Fetch site and page data in parallel\n const [site, pageResponse] = await Promise.all([\n client.getSite({ id: siteId }),\n client.getPage({ siteId, path, preview }),\n ]);\n\n // If component unmounted, don't update state\n if (cancelled) return;\n\n // Extract page data (getContentByPath can return page or entry)\n if ('entry' in pageResponse) {\n throw new Error(\n 'This path resolves to a content entry, not a page. ' +\n 'Use useContent() instead, which handles both pages and entries. ' +\n 'For entries, useContent() returns the raw entry data for custom rendering.'\n );\n }\n\n const { page: pageData } = pageResponse;\n\n // Convert API response blocks to PageOutline format\n // API returns blocks with full content, but PageOutline only needs structure\n const blocks = pageData.blocks.map((block) => {\n if (!block || typeof block !== 'object') {\n throw new Error('Invalid block format in API response');\n }\n if (typeof block.id !== 'string' && block.id !== null) {\n throw new Error(`Invalid block id: expected string or null, got ${typeof block.id}`);\n }\n if (typeof block.kind !== 'string') {\n throw new Error(`Invalid block kind: expected string, got ${typeof block.kind}`);\n }\n if (typeof block.purpose !== 'string') {\n throw new Error(`Invalid block purpose: expected string, got ${typeof block.purpose}`);\n }\n return {\n id: block.id,\n kind: block.kind,\n purpose: block.purpose,\n };\n });\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\n const resolvedData = await prefetchBlockData(\n pageOutline,\n {\n siteId,\n pageId: pageId ?? pageData.id,\n previewStage: preview ? 'preview' : 'published',\n },\n client\n );\n\n // If component unmounted, don't update state\n if (cancelled) return;\n\n setResult({\n loading: false,\n error: null,\n page: pageOutline,\n theme: site.theme,\n siteId,\n resolvedData,\n sdkConfig: site.sdkConfig ?? null,\n });\n } catch (error) {\n if (cancelled) return;\n\n setResult({\n loading: false,\n error: error instanceof Error ? error : new Error(String(error)),\n page: null,\n theme: null,\n siteId,\n resolvedData: null,\n sdkConfig: null,\n });\n }\n }\n\n fetchPage();\n\n return () => {\n cancelled = true;\n };\n }, [client, siteId, path, pageId, preview]);\n\n return result;\n}\n","/**\n * Client-side React hook to fetch content (page or entry) by path.\n *\n * Use this in client components for dynamic routing where a path\n * could resolve to either a page or content entry.\n */\n\nimport { useState, useEffect } from 'react';\nimport type { Theme } from '@riverbankcms/blocks';\nimport type { RiverbankClient, PageResponse } from '../../client/types';\nimport type { PageProps } from '../components/Page';\nimport { prefetchBlockData } from '../../data/prefetchBlockData';\nimport type { ResolvedBlockData } from '../../data/prefetchBlockData';\nimport type { ContentEntryData } from '../helpers/loadContent';\n\nexport type UseContentParams = {\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 * Loading state\n */\ntype LoadingState = {\n loading: true;\n error: null;\n type: null;\n page: null;\n entry: null;\n theme: null;\n siteId: string;\n resolvedData: null;\n};\n\n/**\n * Error state\n */\ntype ErrorState = {\n loading: false;\n error: Error;\n type: null;\n page: null;\n entry: null;\n theme: null;\n siteId: string;\n resolvedData: null;\n};\n\n/**\n * Success state for page content\n */\ntype PageSuccessState = {\n loading: false;\n error: null;\n type: 'page';\n page: PageProps['page'];\n entry: null;\n theme: Theme;\n siteId: string;\n resolvedData: ResolvedBlockData;\n};\n\n/**\n * Success state for entry content\n */\ntype EntrySuccessState = {\n loading: false;\n error: null;\n type: 'entry';\n page: null;\n entry: ContentEntryData;\n theme: Theme;\n siteId: string;\n resolvedData: null;\n};\n\nexport type UseContentResult = LoadingState | ErrorState | PageSuccessState | EntrySuccessState;\n\n/**\n * Type guard to check if result is loading\n */\nexport function isContentLoading(result: UseContentResult): result is LoadingState {\n return result.loading === true;\n}\n\n/**\n * Type guard to check if result has an error\n */\nexport function isContentError(result: UseContentResult): result is ErrorState {\n return result.loading === false && result.error !== null;\n}\n\n/**\n * Type guard to check if result is a page\n */\nexport function isPageContentResult(result: UseContentResult): result is PageSuccessState {\n return result.loading === false && result.error === null && result.type === 'page';\n}\n\n/**\n * Type guard to check if result is an entry\n */\nexport function isEntryContentResult(result: UseContentResult): result is EntrySuccessState {\n return result.loading === false && result.error === null && result.type === 'entry';\n}\n\n/**\n * Client-side React hook to fetch content by path.\n *\n * Returns a discriminated union with loading/error states, and either\n * page data (ready for `<Page>` component) or raw entry data (for custom rendering).\n *\n * IMPORTANT: The client object should be stable across renders to avoid\n * unnecessary re-fetches. Create it outside your component or use useMemo.\n *\n * @example Dynamic routing with both pages and entries\n * ```tsx\n * \"use client\";\n *\n * import { useContent, Page, isPageContentResult } from '@riverbankcms/sdk/client';\n *\n * function DynamicPage({ path }: { path: string }) {\n * const content = useContent({ client, siteId: 'site-123', path });\n *\n * if (content.loading) return <div>Loading...</div>;\n * if (content.error) return <div>Error: {content.error.message}</div>;\n *\n * if (isPageContentResult(content)) {\n * return <Page page={content.page} theme={content.theme} siteId={content.siteId} resolvedData={content.resolvedData} />;\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 = useContent({ client, siteId, path });\n *\n * if (content.loading) return <Spinner />;\n * if (content.error) return <Error error={content.error} />;\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 * }\n * }\n *\n * return <Page {...content} />;\n * ```\n */\nexport function useContent(params: UseContentParams): UseContentResult {\n const { client, siteId, path, preview = false } = params;\n\n const [result, setResult] = useState<UseContentResult>({\n loading: true,\n error: null,\n type: null,\n page: null,\n entry: null,\n theme: null,\n siteId,\n resolvedData: null,\n });\n\n useEffect(() => {\n let cancelled = false;\n\n async function fetchContent() {\n try {\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 // If component unmounted, don't update state\n if (cancelled) return;\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 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 setResult({\n loading: false,\n error: null,\n type: 'entry',\n page: null,\n entry,\n theme: site.theme,\n siteId,\n resolvedData: null,\n });\n return;\n }\n\n // Handle page response\n const { page: pageData } = contentResponse;\n\n // Convert API response blocks to PageOutline format\n const blocks = pageData.blocks.map((block) => {\n if (!block || typeof block !== 'object') {\n throw new Error('Invalid block format in API response');\n }\n if (typeof block.id !== 'string' && block.id !== null) {\n throw new Error(`Invalid block id: expected string or null, got ${typeof block.id}`);\n }\n if (typeof block.kind !== 'string') {\n throw new Error(`Invalid block kind: expected string, got ${typeof block.kind}`);\n }\n if (typeof block.purpose !== 'string') {\n throw new Error(`Invalid block purpose: expected string, got ${typeof block.purpose}`);\n }\n return {\n id: block.id,\n kind: block.kind,\n purpose: block.purpose,\n };\n });\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 // If component unmounted, don't update state\n if (cancelled) return;\n\n setResult({\n loading: false,\n error: null,\n type: 'page',\n page: pageOutline,\n entry: null,\n theme: site.theme,\n siteId,\n resolvedData,\n });\n } catch (error) {\n if (cancelled) return;\n\n setResult({\n loading: false,\n error: error instanceof Error ? error : new Error(String(error)),\n type: null,\n page: null,\n entry: null,\n theme: null,\n siteId,\n resolvedData: null,\n });\n }\n }\n\n fetchContent();\n\n return () => {\n cancelled = true;\n };\n }, [client, siteId, path, preview]);\n\n return result;\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 * Static params generation for Next.js SSG\n *\n * Provides helpers for generating static params from published CMS routes.\n *\n * @example\n * ```tsx\n * // app/[[...slug]]/page.tsx\n * import { generateAllStaticParams } from '@riverbankcms/sdk/next';\n *\n * export { generateAllStaticParams as generateStaticParams };\n * ```\n */\n\nimport { createRiverbankClient } from '../client';\n\n/**\n * Environment variable validation result\n */\nexport type StaticParamsEnvResult =\n | { valid: true; config: { apiKey: string; siteId: string; baseUrl: string } }\n | { valid: false; missingVars: string[] };\n\n/**\n * Validate that all required environment variables are set.\n *\n * Required env vars:\n * - RIVERBANK_API_KEY: API key for published content\n * - RIVERBANK_SITE_ID: Site ID\n * - NEXT_PUBLIC_DASHBOARD_URL: Dashboard API URL\n *\n * @returns Validation result with config or missing vars\n */\nexport function validateStaticParamsEnv(): StaticParamsEnvResult {\n const apiKey = process.env.RIVERBANK_API_KEY;\n const siteId = process.env.RIVERBANK_SITE_ID;\n const baseUrl = process.env.NEXT_PUBLIC_DASHBOARD_URL;\n\n const missingVars: string[] = [];\n\n if (!apiKey) missingVars.push('RIVERBANK_API_KEY');\n if (!siteId) missingVars.push('RIVERBANK_SITE_ID');\n if (!baseUrl) missingVars.push('NEXT_PUBLIC_DASHBOARD_URL');\n\n if (missingVars.length > 0) {\n return { valid: false, missingVars };\n }\n\n // TypeScript can't narrow through the checks above, so assert non-null\n return {\n valid: true,\n config: { apiKey: apiKey!, siteId: siteId!, baseUrl: baseUrl! },\n };\n}\n\n/**\n * Convert a route path to a Next.js slug array.\n *\n * @param path - The route path (e.g., '/about', '/blog/post')\n * @returns The slug array for Next.js catch-all route\n *\n * @example\n * ```ts\n * pathToSlugArray('/') // []\n * pathToSlugArray('/about') // ['about']\n * pathToSlugArray('/blog/post') // ['blog', 'post']\n * ```\n */\nexport function pathToSlugArray(path: string): string[] {\n if (path === '/') return [];\n return path.slice(1).split('/');\n}\n\n/**\n * Generate static params for all published routes.\n *\n * This function fetches all published routes from the CMS and converts them\n * to the static params format expected by Next.js catch-all routes.\n *\n * Requires environment variables:\n * - RIVERBANK_API_KEY: API key for published content\n * - RIVERBANK_SITE_ID: Site ID\n * - NEXT_PUBLIC_DASHBOARD_URL: Dashboard API URL\n *\n * @throws Error if required env vars are missing (prevents silent empty SSG in CI)\n * @returns Array of static params objects for Next.js\n *\n * @example\n * ```tsx\n * // app/[[...slug]]/page.tsx\n * import { generateAllStaticParams } from '@riverbankcms/sdk/next';\n *\n * export { generateAllStaticParams as generateStaticParams };\n *\n * // Or with custom logic:\n * export async function generateStaticParams() {\n * const params = await generateAllStaticParams();\n * // Filter or modify params as needed\n * return params.filter(p => !p.slug.includes('private'));\n * }\n * ```\n */\nexport async function generateAllStaticParams(): Promise<{ slug: string[] }[]> {\n const envResult = validateStaticParamsEnv();\n\n if (!envResult.valid) {\n throw new Error(\n `[Riverbank] generateAllStaticParams requires env vars: ${envResult.missingVars.join(', ')}. ` +\n `This error prevents accidentally deploying with zero static pages.`\n );\n }\n\n const { apiKey, siteId, baseUrl } = envResult.config;\n\n const client = createRiverbankClient({\n apiKey,\n baseUrl,\n // Disable caching for build-time fetches\n cache: { enabled: false },\n // Disable resilience for build-time fetches (want fast failure)\n resilience: { enabled: false },\n });\n\n try {\n const routes = await client.getAllPublishedRoutes({ siteId });\n return routes.items.map((route) => ({\n slug: pathToSlugArray(route.path),\n }));\n } catch (error) {\n // Log error but don't fail the build - allow ISR fallback\n console.error(\n '[Riverbank] generateAllStaticParams failed to fetch routes:',\n error instanceof Error ? error.message : error\n );\n // Return at least the homepage so the build doesn't fail\n return [{ slug: [] }];\n }\n}\n","/**\n * Next.js integration helpers for Riverbank CMS SDK.\n *\n * Provides opinionated factories for common Next.js patterns, reducing\n * boilerplate while maintaining full customizability through escape hatches.\n *\n * @example Basic catch-all page\n * ```tsx\n * // src/app/[[...slug]]/page.tsx\n * import { createCatchAllPage } from '@riverbankcms/sdk/next';\n * import { getRiverbankClient } from '@/lib/builder-client';\n * import config from '@/riverbank.config';\n *\n * const { Page, generateMetadata } = createCatchAllPage({\n * getClient: getRiverbankClient,\n * config,\n * });\n *\n * export default Page;\n * export { generateMetadata };\n * ```\n *\n * @example With customization\n * ```tsx\n * const { Page, generateMetadata } = createCatchAllPage({\n * getClient,\n * config,\n * blockOverrides: { hero: MyCustomHero },\n * beforeRender: async () => {\n * if (process.env.MAINTENANCE_MODE === 'true') {\n * return <MaintenancePage />;\n * }\n * return null;\n * },\n * });\n * ```\n *\n * @packageDocumentation\n */\n\nimport { isPreviewMode } from '../env';\n\n// Catch-all page factory\nexport { createCatchAllPage } from './catch-all';\nexport type {\n CreateCatchAllPageOptions,\n CreateCatchAllPageResult,\n CatchAllPageProps,\n CatchAllContext,\n} from './types';\n\n// Static params utilities\nexport {\n generateAllStaticParams,\n validateStaticParamsEnv,\n pathToSlugArray,\n type StaticParamsEnvResult,\n} from './static-params';\n\n/**\n * ISR revalidation duration in seconds for production mode.\n * 5 minutes provides a good balance between freshness and performance.\n */\nexport const ISR_REVALIDATE_SECONDS = 300;\n\nexport interface ISRConfig {\n /**\n * Revalidation interval in seconds.\n * - 0: Dynamic rendering (no caching) - used in preview mode\n * - 300: 5 minute ISR - used in production mode\n */\n revalidate: number | false;\n\n /**\n * Whether the current environment is in preview mode.\n * Useful for conditional rendering or data fetching behavior.\n */\n isPreview: boolean;\n}\n\n/**\n * Get ISR configuration based on the current environment.\n *\n * @example\n * ```tsx\n * // app/[[...slug]]/page.tsx\n * import { getISRConfig } from '@riverbankcms/sdk/next';\n *\n * export const revalidate = getISRConfig().revalidate;\n * ```\n *\n * @returns ISR configuration with revalidate interval and preview flag\n */\nexport function getISRConfig(): ISRConfig {\n const preview = isPreviewMode();\n return {\n revalidate: preview ? 0 : ISR_REVALIDATE_SECONDS,\n isPreview: preview,\n };\n}\n"],"mappings":"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","names":["Page","useState","useEffect"]}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
export { b as Page, P as PageProps } from '../loadPage-
|
|
2
|
-
export { H as HeaderData, L as Layout, a as LayoutProps } from '../Layout-
|
|
1
|
+
export { b as Page, P as PageProps } from '../loadPage-VBorKlWv.mjs';
|
|
2
|
+
export { H as HeaderData, L as Layout, a as LayoutProps } from '../Layout-D4J009eS.mjs';
|
|
3
3
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
4
4
|
import * as React from 'react';
|
|
5
|
-
import { T as Theme } from '../types-
|
|
5
|
+
import { T as Theme } from '../types-DT30Qy7x.mjs';
|
|
6
6
|
import '../schema-Z6-afHJG.mjs';
|
|
7
|
-
import { R as RiverbankClient } from '../types-
|
|
7
|
+
import { R as RiverbankClient } from '../types-BRQ_6yOc.mjs';
|
|
8
8
|
import { B as BlockKind } from '../blockKinds-B6MWzNWp.mjs';
|
|
9
9
|
import '../core-DsNWrl3o.mjs';
|
|
10
10
|
import 'zod';
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
export { b as Page, P as PageProps } from '../loadPage-
|
|
2
|
-
export { H as HeaderData, L as Layout, a as LayoutProps } from '../Layout-
|
|
1
|
+
export { b as Page, P as PageProps } from '../loadPage-BZohBxxf.js';
|
|
2
|
+
export { H as HeaderData, L as Layout, a as LayoutProps } from '../Layout-l2v4Qa6E.js';
|
|
3
3
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
4
4
|
import * as React from 'react';
|
|
5
|
-
import { T as Theme } from '../types-
|
|
5
|
+
import { T as Theme } from '../types-CgSO0yxg.js';
|
|
6
6
|
import '../schema-Z6-afHJG.js';
|
|
7
|
-
import { R as RiverbankClient } from '../types-
|
|
7
|
+
import { R as RiverbankClient } from '../types-D0rPF8l5.js';
|
|
8
8
|
import { B as BlockKind } from '../blockKinds-B6MWzNWp.js';
|
|
9
9
|
import '../core-DsNWrl3o.js';
|
|
10
10
|
import 'zod';
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
export { d as BlockOverrideComponent, e as BlockOverrideProps, B as BlockOverrides, L as LoadPageParams, a as LoadPageResult, b as Page, P as PageProps, c as PageRenderer, l as loadPage } from './loadPage-
|
|
2
|
-
export { H as HeaderData, L as Layout, a as LayoutProps } from './Layout-
|
|
1
|
+
export { d as BlockOverrideComponent, e as BlockOverrideProps, B as BlockOverrides, L as LoadPageParams, a as LoadPageResult, b as Page, P as PageProps, c as PageRenderer, l as loadPage } from './loadPage-VBorKlWv.mjs';
|
|
2
|
+
export { H as HeaderData, L as Layout, a as LayoutProps } from './Layout-D4J009eS.mjs';
|
|
3
3
|
export { Block, BlockProps } from './rendering/server.mjs';
|
|
4
|
-
export { C as ContentEntryData, L as LoadContentParams, b as LoadContentResult, S as SiteData, a as isEntryContent, i as isPageContent, l as loadContent } from './loadContent-
|
|
5
|
-
export { c as SiteFooterContent, S as SiteHeaderContent, T as Theme } from './types-
|
|
4
|
+
export { C as ContentEntryData, L as LoadContentParams, b as LoadContentResult, S as SiteData, a as isEntryContent, i as isPageContent, l as loadContent } from './loadContent-Du5kS8UM.mjs';
|
|
5
|
+
export { c as SiteFooterContent, S as SiteHeaderContent, T as Theme } from './types-DT30Qy7x.mjs';
|
|
6
6
|
export { P as PageOutline } from './schema-Z6-afHJG.mjs';
|
|
7
|
-
import { G as GradientConfig } from './components-
|
|
8
|
-
export { f as BackgroundInput, B as BodyTextContent, H as HeroContent, a as HeroMedia, g as ResolvedBackground, R as RichText, c as RichTextPrimitiveProps, e as SectionBackground, h as SectionBackgroundProps, S as SystemBlockComponentProps, b as buildThemeRuntime, d as resolveBackground, r as resolveImageUrl } from './components-
|
|
7
|
+
import { G as GradientConfig } from './components-vtYEmmPF.mjs';
|
|
8
|
+
export { f as BackgroundInput, B as BodyTextContent, H as HeroContent, a as HeroMedia, g as ResolvedBackground, R as RichText, c as RichTextPrimitiveProps, e as SectionBackground, h as SectionBackgroundProps, S as SystemBlockComponentProps, b as buildThemeRuntime, d as resolveBackground, r as resolveImageUrl } from './components-vtYEmmPF.mjs';
|
|
9
9
|
export { R as RouteMap, a as ThemeTokens } from './core-DsNWrl3o.mjs';
|
|
10
10
|
import { M as Media } from './richTextSchema-DURiozvD.mjs';
|
|
11
11
|
export { T as TipTapNode } from './richTextSchema-DURiozvD.mjs';
|
|
12
12
|
export { C as CustomLinkValue, E as ExternalLinkValue, I as InternalLinkValue, L as LinkValue } from './link-DjxLyC82.mjs';
|
|
13
|
-
import './types-
|
|
13
|
+
import './types-BRQ_6yOc.mjs';
|
|
14
14
|
import 'react/jsx-runtime';
|
|
15
15
|
import 'react';
|
|
16
16
|
import './types-CbagRQ_7.mjs';
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
export { d as BlockOverrideComponent, e as BlockOverrideProps, B as BlockOverrides, L as LoadPageParams, a as LoadPageResult, b as Page, P as PageProps, c as PageRenderer, l as loadPage } from './loadPage-
|
|
2
|
-
export { H as HeaderData, L as Layout, a as LayoutProps } from './Layout-
|
|
1
|
+
export { d as BlockOverrideComponent, e as BlockOverrideProps, B as BlockOverrides, L as LoadPageParams, a as LoadPageResult, b as Page, P as PageProps, c as PageRenderer, l as loadPage } from './loadPage-BZohBxxf.js';
|
|
2
|
+
export { H as HeaderData, L as Layout, a as LayoutProps } from './Layout-l2v4Qa6E.js';
|
|
3
3
|
export { Block, BlockProps } from './rendering/server.js';
|
|
4
|
-
export { C as ContentEntryData, L as LoadContentParams, b as LoadContentResult, S as SiteData, a as isEntryContent, i as isPageContent, l as loadContent } from './loadContent-
|
|
5
|
-
export { c as SiteFooterContent, S as SiteHeaderContent, T as Theme } from './types-
|
|
4
|
+
export { C as ContentEntryData, L as LoadContentParams, b as LoadContentResult, S as SiteData, a as isEntryContent, i as isPageContent, l as loadContent } from './loadContent-DECnsp4k.js';
|
|
5
|
+
export { c as SiteFooterContent, S as SiteHeaderContent, T as Theme } from './types-CgSO0yxg.js';
|
|
6
6
|
export { P as PageOutline } from './schema-Z6-afHJG.js';
|
|
7
|
-
import { G as GradientConfig } from './components-
|
|
8
|
-
export { f as BackgroundInput, B as BodyTextContent, H as HeroContent, a as HeroMedia, g as ResolvedBackground, R as RichText, c as RichTextPrimitiveProps, e as SectionBackground, h as SectionBackgroundProps, S as SystemBlockComponentProps, b as buildThemeRuntime, d as resolveBackground, r as resolveImageUrl } from './components-
|
|
7
|
+
import { G as GradientConfig } from './components-D2uCKCj7.js';
|
|
8
|
+
export { f as BackgroundInput, B as BodyTextContent, H as HeroContent, a as HeroMedia, g as ResolvedBackground, R as RichText, c as RichTextPrimitiveProps, e as SectionBackground, h as SectionBackgroundProps, S as SystemBlockComponentProps, b as buildThemeRuntime, d as resolveBackground, r as resolveImageUrl } from './components-D2uCKCj7.js';
|
|
9
9
|
export { R as RouteMap, a as ThemeTokens } from './core-DsNWrl3o.js';
|
|
10
10
|
import { M as Media } from './richTextSchema-DURiozvD.js';
|
|
11
11
|
export { T as TipTapNode } from './richTextSchema-DURiozvD.js';
|
|
12
12
|
export { C as CustomLinkValue, E as ExternalLinkValue, I as InternalLinkValue, L as LinkValue } from './link-DjxLyC82.js';
|
|
13
|
-
import './types-
|
|
13
|
+
import './types-D0rPF8l5.js';
|
|
14
14
|
import 'react/jsx-runtime';
|
|
15
15
|
import 'react';
|
|
16
16
|
import './types-DuQCNVV0.js';
|
package/dist/server/rendering.js
CHANGED
|
@@ -5,7 +5,7 @@ var _chunkP3NNN73Gjs = require('./chunk-P3NNN73G.js');
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
var
|
|
8
|
+
var _chunkEIJ27EZQjs = require('./chunk-EIJ27EZQ.js');
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
var _chunkTT5JWA4Xjs = require('./chunk-TT5JWA4X.js');
|
|
@@ -42,5 +42,5 @@ require('./chunk-4CV4JOE5.js');
|
|
|
42
42
|
|
|
43
43
|
|
|
44
44
|
|
|
45
|
-
exports.Block = _chunkTT5JWA4Xjs.Block; exports.Layout = _chunk7BVRA5MYjs.Layout; exports.Page = _chunkT26N3P26js.Page; exports.PageRenderer = _chunk7UPVCT3Kjs.PageRenderer; exports.RichText = _chunk7UPVCT3Kjs.RichText; exports.SectionBackground = _chunk7UPVCT3Kjs.SectionBackground; exports.buildThemeRuntime = _chunk7UPVCT3Kjs.buildThemeRuntime; exports.isEntryContent =
|
|
45
|
+
exports.Block = _chunkTT5JWA4Xjs.Block; exports.Layout = _chunk7BVRA5MYjs.Layout; exports.Page = _chunkT26N3P26js.Page; exports.PageRenderer = _chunk7UPVCT3Kjs.PageRenderer; exports.RichText = _chunk7UPVCT3Kjs.RichText; exports.SectionBackground = _chunk7UPVCT3Kjs.SectionBackground; exports.buildThemeRuntime = _chunk7UPVCT3Kjs.buildThemeRuntime; exports.isEntryContent = _chunkEIJ27EZQjs.isEntryContent; exports.isPageContent = _chunkEIJ27EZQjs.isPageContent; exports.loadContent = _chunkEIJ27EZQjs.loadContent; exports.loadPage = _chunkP3NNN73Gjs.loadPage; exports.resolveBackground = _chunk7UPVCT3Kjs.resolveBackground; exports.resolveImageUrl = _chunk7UPVCT3Kjs.resolveImageUrl;
|
|
46
46
|
//# sourceMappingURL=rendering.js.map
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { R as RiverbankClient, c as ResolveEventOccurrenceResponse } from './types-
|
|
2
|
-
import { a as LoadPageResult } from './loadPage-
|
|
3
|
-
import { R as RiverbankSiteConfig } from './types-
|
|
1
|
+
import { R as RiverbankClient, c as ResolveEventOccurrenceResponse } from './types-BRQ_6yOc.mjs';
|
|
2
|
+
import { a as LoadPageResult } from './loadPage-VBorKlWv.mjs';
|
|
3
|
+
import { R as RiverbankSiteConfig } from './types-CJfJwcuL.mjs';
|
|
4
4
|
import './schema-Z6-afHJG.mjs';
|
|
5
|
-
import './types-
|
|
5
|
+
import './types-DT30Qy7x.mjs';
|
|
6
6
|
import '@riverbankcms/ai';
|
|
7
7
|
import 'zod';
|
|
8
8
|
import './link-DjxLyC82.mjs';
|