@riverbankcms/sdk 0.7.3 → 0.7.5
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/dist/client/client.js +63 -3
- package/dist/client/client.js.map +1 -1
- package/dist/client/client.mjs +63 -3
- package/dist/client/client.mjs.map +1 -1
- package/dist/server/{chunk-NFQLH5IA.mjs → chunk-2NBNOY3C.mjs} +64 -4
- package/dist/server/{chunk-NFQLH5IA.mjs.map → chunk-2NBNOY3C.mjs.map} +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-JWRNMNWI.js → chunk-EIJ27EZQ.js} +4 -2
- package/dist/server/chunk-EIJ27EZQ.js.map +1 -0
- package/dist/server/{chunk-VLXTNB2C.js → chunk-KH3EXBJM.js} +64 -4
- package/dist/server/chunk-KH3EXBJM.js.map +1 -0
- package/dist/server/index.js +10 -10
- package/dist/server/index.mjs +1 -1
- package/dist/server/next.js +8 -8
- package/dist/server/next.mjs +2 -2
- package/dist/server/rendering.js +2 -2
- package/dist/server/rendering.mjs +1 -1
- package/dist/server/server.js +3 -3
- package/dist/server/server.mjs +2 -2
- package/package.json +2 -2
- package/dist/server/chunk-JWRNMNWI.js.map +0 -1
- package/dist/server/chunk-VLXTNB2C.js.map +0 -1
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
|
package/dist/server/server.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var _chunkKH3EXBJMjs = require('./chunk-KH3EXBJM.js');
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
var _chunkP3NNN73Gjs = require('./chunk-P3NNN73G.js');
|
|
@@ -8,7 +8,7 @@ var _chunkP3NNN73Gjs = require('./chunk-P3NNN73G.js');
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
var
|
|
11
|
+
var _chunkEIJ27EZQjs = require('./chunk-EIJ27EZQ.js');
|
|
12
12
|
require('./chunk-Y7347JMZ.js');
|
|
13
13
|
require('./chunk-RVDS7VSP.js');
|
|
14
14
|
require('./chunk-YYO3RIFO.js');
|
|
@@ -19,5 +19,5 @@ require('./chunk-4CV4JOE5.js');
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
exports.createRiverbankClient =
|
|
22
|
+
exports.createRiverbankClient = _chunkKH3EXBJMjs.createRiverbankClient; exports.isEntryContent = _chunkEIJ27EZQjs.isEntryContent; exports.isPageContent = _chunkEIJ27EZQjs.isPageContent; exports.loadContent = _chunkEIJ27EZQjs.loadContent; exports.loadPage = _chunkP3NNN73Gjs.loadPage;
|
|
23
23
|
//# sourceMappingURL=server.js.map
|
package/dist/server/server.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createRiverbankClient
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-2NBNOY3C.mjs";
|
|
4
4
|
import {
|
|
5
5
|
loadPage
|
|
6
6
|
} from "./chunk-C6FIJC7T.mjs";
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
isEntryContent,
|
|
9
9
|
isPageContent,
|
|
10
10
|
loadContent
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-4YQJUL5W.mjs";
|
|
12
12
|
import "./chunk-A2FZMRDW.mjs";
|
|
13
13
|
import "./chunk-AEFWG657.mjs";
|
|
14
14
|
import "./chunk-BYBJA6SP.mjs";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@riverbankcms/sdk",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.5",
|
|
4
4
|
"description": "Riverbank CMS SDK for headless content consumption",
|
|
5
5
|
"main": "./dist/server/index.js",
|
|
6
6
|
"module": "./dist/server/index.mjs",
|
|
@@ -145,7 +145,6 @@
|
|
|
145
145
|
"verify": "./verify.sh"
|
|
146
146
|
},
|
|
147
147
|
"dependencies": {
|
|
148
|
-
"@riverbankcms/altcha": "workspace:*",
|
|
149
148
|
"commander": "^12.1.0",
|
|
150
149
|
"dotenv": "^16.4.5",
|
|
151
150
|
"fast-deep-equal": "^3.1.3",
|
|
@@ -164,6 +163,7 @@
|
|
|
164
163
|
}
|
|
165
164
|
},
|
|
166
165
|
"devDependencies": {
|
|
166
|
+
"@riverbankcms/altcha": "workspace:*",
|
|
167
167
|
"@riverbankcms/api": "workspace:*",
|
|
168
168
|
"@riverbankcms/blocks": "workspace:*",
|
|
169
169
|
"@riverbankcms/site-renderer": "workspace:*",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/will/Projects/Business/cms/builder/packages/sdk/dist/server/chunk-JWRNMNWI.js","../../src/rendering/helpers/loadContent.ts"],"names":["resolvedData"],"mappings":"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","file":"/Users/will/Projects/Business/cms/builder/packages/sdk/dist/server/chunk-JWRNMNWI.js","sourcesContent":[null,"/**\n * Server-side helper to fetch content (page or entry) by path.\n *\n * Use this for dynamic routing where a path could resolve to either\n * a page or a content entry.\n */\n\nimport type { Theme } from '@riverbankcms/blocks';\nimport type { RiverbankClient, PageResponse, SiteResponse } from '../../client/types';\nimport type { PageProps } from '../components/Page';\nimport { prefetchBlockData } from '../../data/prefetchBlockData';\nimport type { ResolvedBlockData } from '../../data/prefetchBlockData';\n\n/**\n * Site data included in content results for metadata generation.\n */\nexport type SiteData = SiteResponse['site'];\n\nexport type LoadContentParams = {\n client: RiverbankClient;\n siteId: string;\n path: string;\n /**\n * If true, fetches draft/unpublished content instead of published content.\n * This affects both pages and entries.\n * Requires API key with site access.\n *\n * @default false\n */\n preview?: boolean;\n};\n\n/**\n * Content entry data returned when a path resolves to an entry\n */\nexport type ContentEntryData = {\n id: string;\n /** Content type key (e.g., 'blog-post', 'product') */\n type: string | null;\n title: string;\n slug: string | null;\n path: string | null;\n status: string;\n publishAt: string | null;\n /** The raw content fields - use these to render your own UI */\n content: Record<string, unknown>;\n metaTitle: string | null;\n metaDescription: string | null;\n createdAt: string;\n updatedAt: string;\n};\n\n/**\n * Result when path resolves to a page\n */\nexport type PageContentResult = {\n type: 'page';\n /** Page outline ready for rendering with <Page> component */\n page: PageProps['page'];\n /** Site theme for styling */\n theme: Theme;\n /** Site ID */\n siteId: string;\n /** Pre-fetched block data for data loaders */\n resolvedData: ResolvedBlockData;\n /** Site data for metadata generation */\n site: SiteData;\n};\n\n/**\n * Result when path resolves to a content entry\n */\nexport type EntryContentResult = {\n type: 'entry';\n /** Raw entry data - render this however you want */\n entry: ContentEntryData;\n /** Template page for rendering the entry (if available) */\n templatePage: PageProps['page'] | null;\n /** Pre-fetched block data for template page data loaders */\n resolvedData: ResolvedBlockData;\n /** Data context for template blocks (includes entry content for bindings) */\n dataContext: { contentEntry: Record<string, unknown> };\n /** Site theme for styling (useful if rendering with SDK components) */\n theme: Theme;\n /** Site ID */\n siteId: string;\n /** Site data for metadata generation */\n site: SiteData;\n};\n\n/**\n * Discriminated union result from loadContent\n */\nexport type LoadContentResult = PageContentResult | EntryContentResult;\n\n/**\n * Type guard to check if result is a page\n */\nexport function isPageContent(result: LoadContentResult): result is PageContentResult {\n return result.type === 'page';\n}\n\n/**\n * Type guard to check if result is an entry\n */\nexport function isEntryContent(result: LoadContentResult): result is EntryContentResult {\n return result.type === 'entry';\n}\n\n/**\n * Server-side helper to fetch content by path.\n *\n * Returns a discriminated union - either page data (ready for `<Page>` component)\n * or raw entry data (for custom rendering).\n *\n * @example Dynamic routing with both pages and entries\n * ```tsx\n * import { loadContent, Page, isPageContent } from '@riverbankcms/sdk';\n *\n * export default async function DynamicRoute({ params }) {\n * const content = await loadContent({\n * client,\n * siteId: 'site-123',\n * path: `/${params.slug?.join('/') || ''}`,\n * });\n *\n * if (isPageContent(content)) {\n * return <Page {...content} />;\n * }\n *\n * // Render entry with custom UI\n * return (\n * <article>\n * <h1>{content.entry.title}</h1>\n * <div>{content.entry.content.body}</div>\n * </article>\n * );\n * }\n * ```\n *\n * @example Entry-specific rendering based on content type\n * ```tsx\n * const content = await loadContent({ client, siteId, path });\n *\n * if (content.type === 'entry') {\n * switch (content.entry.type) {\n * case 'blog-post':\n * return <BlogPost entry={content.entry} theme={content.theme} />;\n * case 'product':\n * return <ProductPage entry={content.entry} theme={content.theme} />;\n * default:\n * return <GenericEntry entry={content.entry} />;\n * }\n * }\n *\n * return <Page {...content} />;\n * ```\n *\n * @example Preview mode for draft content\n * ```tsx\n * const content = await loadContent({\n * client,\n * siteId,\n * path,\n * preview: true, // Fetches draft content for both pages and entries\n * });\n * ```\n */\nexport async function loadContent(params: LoadContentParams): Promise<LoadContentResult> {\n const { client, siteId, path, preview = false } = params;\n\n // Fetch site and content in parallel\n const [site, contentResponse] = await Promise.all([\n client.getSite({ id: siteId }),\n client.getPage({ siteId, path, preview }),\n ]);\n\n // Check if response is an entry\n if (isEntryResponse(contentResponse)) {\n const entryData = contentResponse.entry;\n\n const entry: ContentEntryData = {\n id: entryData.id,\n type: entryData.type,\n title: entryData.title,\n slug: entryData.slug,\n path: entryData.path,\n status: entryData.status,\n publishAt: entryData.publishAt,\n // Use draft content in preview mode, otherwise use published content\n content: preview\n ? (entryData.draftContent ?? entryData.content)\n : entryData.content,\n metaTitle: preview\n ? (entryData.draftMetaTitle ?? entryData.metaTitle)\n : entryData.metaTitle,\n metaDescription: preview\n ? (entryData.draftMetaDescription ?? entryData.metaDescription)\n : entryData.metaDescription,\n createdAt: entryData.createdAt,\n updatedAt: entryData.updatedAt,\n };\n\n // Process template if available (uses first template from content type)\n const { templatePage, resolvedData } = await processEntryTemplate(\n contentResponse.templates as Template[] | undefined,\n entry,\n { siteId, preview },\n client\n );\n\n return {\n type: 'entry',\n entry,\n templatePage,\n resolvedData,\n dataContext: { contentEntry: entry.content },\n theme: site.theme,\n siteId,\n site: site.site,\n };\n }\n\n // Handle page response\n const { page: pageData } = contentResponse;\n\n // Convert API response blocks to PageOutline format with validation\n const blocks = pageData.blocks.map((block) => validateAndConvertBlock(block, 'page'));\n\n const pageOutline = {\n name: pageData.name,\n path: pageData.path,\n purpose: pageData.purpose,\n blocks,\n };\n\n // Prefetch block data loaders for pages\n const resolvedData = await prefetchBlockData(\n pageOutline,\n {\n siteId,\n pageId: pageData.id,\n previewStage: preview ? 'preview' : 'published',\n },\n client\n );\n\n return {\n type: 'page',\n page: pageOutline,\n theme: site.theme,\n siteId,\n resolvedData,\n site: site.site,\n };\n}\n\n/**\n * Type guard to check if API response is an entry\n */\nfunction isEntryResponse(response: PageResponse): response is Extract<PageResponse, { type: 'entry' }> {\n return 'type' in response && response.type === 'entry';\n}\n\n/**\n * Validates and converts a raw block from API response to PageOutline block format.\n * Used for both page blocks and template blocks to ensure consistent validation.\n */\nfunction validateAndConvertBlock(\n block: unknown,\n source: 'page' | 'template'\n): { id: string | null; kind: string; purpose: string; content?: Record<string, unknown> } {\n if (!block || typeof block !== 'object') {\n throw new Error(`Invalid block format in ${source} API response`);\n }\n\n const blockRecord = block as Record<string, unknown>;\n\n // Template blocks use 'blockKind', page blocks use 'kind'\n const kindField = source === 'template' ? 'blockKind' : 'kind';\n const kindValue = blockRecord[kindField];\n\n if (typeof blockRecord.id !== 'string' && blockRecord.id !== null) {\n throw new Error(`Invalid block id in ${source}: expected string or null, got ${typeof blockRecord.id}`);\n }\n if (typeof kindValue !== 'string') {\n throw new Error(`Invalid block ${kindField} in ${source}: expected string, got ${typeof kindValue}`);\n }\n\n // Template blocks derive purpose from scope, page blocks have explicit purpose\n if (source === 'page') {\n if (typeof blockRecord.purpose !== 'string') {\n throw new Error(`Invalid block purpose in ${source}: expected string, got ${typeof blockRecord.purpose}`);\n }\n return {\n id: blockRecord.id as string | null,\n kind: kindValue,\n purpose: blockRecord.purpose,\n };\n }\n\n // Template block: derive purpose from scope, include content\n const scope = blockRecord.scope as 'entry' | 'template' | undefined;\n const content = (blockRecord.content as Record<string, unknown> | null) ?? {};\n\n return {\n id: blockRecord.id as string | null,\n kind: kindValue,\n purpose: scope === 'entry' ? 'entry-content' : 'template-layout',\n content,\n };\n}\n\n/** Template block structure from API response */\ntype TemplateBlock = {\n id: string;\n blockKind: string;\n scope: 'entry' | 'template';\n content: Record<string, unknown> | null;\n};\n\n/** Template structure from API response */\ntype Template = {\n id: string;\n name: string;\n templateKey: string;\n blocks: TemplateBlock[];\n};\n\n/**\n * Processes an entry's template into a PageOutline format and prefetches block data.\n * Returns null templatePage if no valid template with blocks is available.\n */\nasync function processEntryTemplate(\n templates: Template[] | undefined,\n entry: ContentEntryData,\n context: { siteId: string; preview: boolean },\n client: RiverbankClient\n): Promise<{ templatePage: PageProps['page'] | null; resolvedData: ResolvedBlockData }> {\n const template = templates?.[0];\n\n // Templates without blocks are treated as \"no template\" - the entry should be\n // rendered with custom UI rather than an empty template page\n if (!template || !template.blocks?.length) {\n return { templatePage: null, resolvedData: {} };\n }\n\n // Convert template blocks to PageOutline format with validation\n const blocks = template.blocks.map((block) => validateAndConvertBlock(block, 'template'));\n\n const templatePage: PageProps['page'] = {\n name: template.name || 'Entry Template',\n path: entry.path || '/',\n purpose: 'entry-template',\n blocks,\n };\n\n // Prefetch block data for template\n const resolvedData = await prefetchBlockData(\n templatePage,\n {\n siteId: context.siteId,\n pageId: template.id,\n previewStage: context.preview ? 'preview' : 'published',\n },\n client\n );\n\n return { templatePage, resolvedData };\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/will/Projects/Business/cms/builder/packages/sdk/dist/server/chunk-VLXTNB2C.js","../../src/constants.ts","../../src/prebuild/loader.ts","../../../api/src/endpoints.ts","../../../api/src/url.ts","../../../api/src/request.ts","../../../api/src/common/envelope.ts","../../src/client/cache.ts","../../src/version.ts","../../src/client/error.ts","../../src/client/resilience.ts","../../src/client/index.ts"],"names":["errorInfo"],"mappings":"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","file":"/Users/will/Projects/Business/cms/builder/packages/sdk/dist/server/chunk-VLXTNB2C.js","sourcesContent":[null,"/**\n * SDK constants\n *\n * Shared constants used across the SDK.\n */\n\n/**\n * Page size used when prebuilding entry caches.\n * API max limit is 50, so we use that to minimize requests.\n */\nexport const PREBUILD_PAGE_SIZE = 50;\n\n/**\n * Default ISR revalidation interval in seconds.\n * 5 minutes provides a good balance between freshness and performance.\n */\nexport const ISR_REVALIDATE_SECONDS = 300;\n\n/**\n * Default cache TTL in seconds.\n * Matches ISR_REVALIDATE_SECONDS for consistency.\n */\nexport const CACHE_TTL_SECONDS = 300;\n\n/**\n * Default maximum age for prebuild cache in seconds.\n * 24 hours - older prebuilds are rejected.\n */\nexport const DEFAULT_MAX_PREBUILD_AGE_SEC = 86400;\n\n/**\n * Default prebuild output directory.\n */\nexport const DEFAULT_PREBUILD_DIR = '.riverbank-cache';\n","/**\n * Prebuild cache loader\n *\n * Loads content from the prebuild cache at runtime as a last-resort fallback.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type {\n PrebuildManifest,\n SiteCacheFile,\n PageCacheFile,\n EntriesCacheFile,\n NavigationCacheFile,\n} from './types';\nimport type {\n SiteResponse,\n PageResponse,\n EntriesResponse,\n EntriesResponseWithMeta,\n GetEntriesBaseParams,\n} from '../client/types';\nimport { DEFAULT_MAX_PREBUILD_AGE_SEC, DEFAULT_PREBUILD_DIR } from '../constants';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface PrebuildLoaderConfig {\n /** Path to prebuild cache directory */\n prebuildDir: string;\n /** Maximum age in seconds for prebuild cache */\n maxPrebuildAgeSec?: number;\n}\n\nexport interface LoadResult<T> {\n /** Loaded data */\n data: T;\n /** Age of prebuild in seconds */\n prebuildAgeSec: number;\n}\n\n// ============================================================================\n// Cache Loading Helpers\n// ============================================================================\n\nlet cachedManifest: { manifest: PrebuildManifest; dir: string } | null = null;\n\n/**\n * Load and cache the manifest file.\n */\nfunction loadManifest(prebuildDir: string): PrebuildManifest | null {\n // Return cached manifest if same directory\n if (cachedManifest?.dir === prebuildDir) {\n return cachedManifest.manifest;\n }\n\n const manifestPath = path.join(prebuildDir, 'manifest.json');\n\n if (!fs.existsSync(manifestPath)) {\n return null;\n }\n\n try {\n const content = fs.readFileSync(manifestPath, 'utf-8');\n const manifest = JSON.parse(content) as PrebuildManifest;\n cachedManifest = { manifest, dir: prebuildDir };\n return manifest;\n } catch {\n return null;\n }\n}\n\n/**\n * Load a JSON file from the prebuild cache.\n */\nfunction loadJsonFile<T>(prebuildDir: string, relativePath: string): T | null {\n const filePath = path.join(prebuildDir, relativePath);\n\n if (!fs.existsSync(filePath)) {\n return null;\n }\n\n try {\n const content = fs.readFileSync(filePath, 'utf-8');\n return JSON.parse(content) as T;\n } catch {\n return null;\n }\n}\n\n/**\n * Check if prebuild is too old.\n */\nfunction isPrebuildExpired(\n manifest: PrebuildManifest,\n maxAgeSec: number\n): boolean {\n const ageMs = Date.now() - new Date(manifest.generatedAt).getTime();\n return ageMs > maxAgeSec * 1000;\n}\n\n/**\n * Calculate prebuild age in seconds.\n */\nfunction getPrebuildAgeSec(manifest: PrebuildManifest): number {\n const ageMs = Date.now() - new Date(manifest.generatedAt).getTime();\n return Math.floor(ageMs / 1000);\n}\n\n// ============================================================================\n// Runtime Environment Check\n// ============================================================================\n\n/**\n * Check if we're running in a Node.js environment with filesystem access.\n * Edge runtimes (Vercel Edge, Cloudflare Workers) don't have fs access.\n */\nexport function canUsePrebuild(): boolean {\n // Check for Node.js environment\n if (typeof process === 'undefined' || !process.versions?.node) {\n return false;\n }\n\n // Check that fs module is available (some bundlers stub it)\n try {\n return typeof fs.existsSync === 'function';\n } catch {\n return false;\n }\n}\n\n// ============================================================================\n// Prebuild Loader Class\n// ============================================================================\n\n/**\n * Loader for reading data from the prebuild cache.\n *\n * Used as a last-resort fallback when the CMS is unavailable and\n * there's no cached data.\n */\nexport class PrebuildLoader {\n private readonly prebuildDir: string;\n private readonly maxPrebuildAgeSec: number;\n\n constructor(config: PrebuildLoaderConfig) {\n this.prebuildDir = config.prebuildDir ?? DEFAULT_PREBUILD_DIR;\n this.maxPrebuildAgeSec = config.maxPrebuildAgeSec ?? DEFAULT_MAX_PREBUILD_AGE_SEC;\n }\n\n /**\n * Check if prebuild is available and not expired.\n */\n isAvailable(): boolean {\n if (!canUsePrebuild()) {\n return false;\n }\n\n const manifest = loadManifest(this.prebuildDir);\n if (!manifest) {\n return false;\n }\n\n return !isPrebuildExpired(manifest, this.maxPrebuildAgeSec);\n }\n\n /**\n * Load site data from prebuild cache.\n */\n loadSite(siteId: string): LoadResult<SiteResponse> | null {\n const manifest = loadManifest(this.prebuildDir);\n if (!manifest || isPrebuildExpired(manifest, this.maxPrebuildAgeSec)) {\n return null;\n }\n\n const cacheFile = loadJsonFile<SiteCacheFile>(this.prebuildDir, 'site.json');\n if (!cacheFile || cacheFile.data.site.id !== siteId) {\n return null;\n }\n\n return {\n data: cacheFile.data,\n prebuildAgeSec: getPrebuildAgeSec(manifest),\n };\n }\n\n /**\n * Load page data from prebuild cache.\n */\n loadPage(siteId: string, pagePath: string): LoadResult<PageResponse> | null {\n const manifest = loadManifest(this.prebuildDir);\n if (!manifest || isPrebuildExpired(manifest, this.maxPrebuildAgeSec)) {\n return null;\n }\n\n // Check if this page is in the manifest\n const cacheKey = `page:${siteId}:${pagePath}:false`;\n const relativePath = manifest.keyToFile[cacheKey];\n\n if (!relativePath) {\n return null;\n }\n\n const cacheFile = loadJsonFile<PageCacheFile>(this.prebuildDir, relativePath);\n if (!cacheFile) {\n return null;\n }\n\n return {\n data: cacheFile.data,\n prebuildAgeSec: getPrebuildAgeSec(manifest),\n };\n }\n\n /**\n * Load entries from prebuild cache with runtime filtering.\n *\n * The prebuild stores ALL entries for each content type.\n * Filtering, ordering, and pagination are applied at runtime.\n */\n loadEntries(\n siteId: string,\n params: GetEntriesBaseParams & { includeMeta?: boolean }\n ): LoadResult<EntriesResponse | EntriesResponseWithMeta> | null {\n // Prebuild only supports published content (not preview)\n if (params.preview) {\n return null;\n }\n\n // Prebuild doesn't support manual mode with specific entry IDs\n if (params.mode === 'manual' && params.entryIds?.length) {\n return null;\n }\n\n const manifest = loadManifest(this.prebuildDir);\n if (!manifest || isPrebuildExpired(manifest, this.maxPrebuildAgeSec)) {\n return null;\n }\n\n // Load all entries for this content type\n const cacheKey = `entries-all:${siteId}:${params.contentType}`;\n const relativePath = manifest.keyToFile[cacheKey];\n\n if (!relativePath) {\n return null;\n }\n\n const cacheFile = loadJsonFile<EntriesCacheFile>(this.prebuildDir, relativePath);\n if (!cacheFile) {\n return null;\n }\n\n // Apply runtime filtering\n let entries = [...cacheFile.entries];\n\n // Apply ordering\n if (params.order === 'newest') {\n entries.sort((a, b) => {\n const aTime = a.publishedAt ? new Date(a.publishedAt).getTime() : 0;\n const bTime = b.publishedAt ? new Date(b.publishedAt).getTime() : 0;\n return bTime - aTime;\n });\n } else if (params.order === 'oldest') {\n entries.sort((a, b) => {\n const aTime = a.publishedAt ? new Date(a.publishedAt).getTime() : 0;\n const bTime = b.publishedAt ? new Date(b.publishedAt).getTime() : 0;\n return aTime - bTime;\n });\n } else if (params.order === 'title') {\n entries.sort((a, b) => (a.title || '').localeCompare(b.title || ''));\n }\n // 'order' mode uses custom ordering - entries should already be in order\n\n // Store total before pagination\n const total = entries.length;\n const offset = params.offset ?? 0;\n const limit = params.limit ?? 10;\n\n // Apply pagination\n if (offset > 0) {\n entries = entries.slice(offset);\n }\n entries = entries.slice(0, limit);\n\n const prebuildAgeSec = getPrebuildAgeSec(manifest);\n\n // Return with or without metadata\n if (params.includeMeta) {\n return {\n data: {\n entries,\n total,\n hasMore: offset + entries.length < total,\n limit,\n offset,\n totalPages: Math.ceil(total / limit),\n currentPage: Math.floor(offset / limit) + 1,\n } as EntriesResponseWithMeta,\n prebuildAgeSec,\n };\n }\n\n return {\n data: { entries } as EntriesResponse,\n prebuildAgeSec,\n };\n }\n\n /**\n * Load navigation data from prebuild cache.\n */\n loadNavigation(): LoadResult<NavigationCacheFile['menus']> | null {\n const manifest = loadManifest(this.prebuildDir);\n if (!manifest || isPrebuildExpired(manifest, this.maxPrebuildAgeSec)) {\n return null;\n }\n\n const cacheFile = loadJsonFile<NavigationCacheFile>(\n this.prebuildDir,\n 'navigation/menus.json'\n );\n if (!cacheFile) {\n return null;\n }\n\n return {\n data: cacheFile.menus,\n prebuildAgeSec: getPrebuildAgeSec(manifest),\n };\n }\n\n /**\n * Get the manifest for inspection.\n */\n getManifest(): PrebuildManifest | null {\n return loadManifest(this.prebuildDir);\n }\n\n /**\n * Clear the cached manifest (for testing).\n */\n clearCache(): void {\n cachedManifest = null;\n }\n}\n\n/**\n * Create a prebuild loader instance.\n */\nexport function createPrebuildLoader(config: PrebuildLoaderConfig): PrebuildLoader {\n return new PrebuildLoader(config);\n}\n","import type { APIEndpoints } from \"./types\";\n\nconst ENDPOINT_DEFINITIONS = {\n // AI endpoints - no cache due to dynamic nature\n aiContentUpdateChat: {\n path: \"/ai/content-update-chat\",\n method: \"POST\",\n auth: 'user',\n responseKind: 'stream',\n },\n aiChat: {\n path: \"/ai/chat\",\n method: \"POST\",\n auth: 'user',\n responseKind: 'stream',\n },\n applySeoChanges: {\n path: \"/seo/apply\",\n method: \"POST\",\n auth: 'user',\n responseKind: 'json',\n },\n aiCreateBriefChat: {\n path: \"/ai/chat/create-brief\",\n method: \"POST\",\n auth: 'user',\n responseKind: 'json',\n },\n aiPrototypeChat: {\n path: \"/ai/chat/create-prototype\",\n method: \"POST\",\n revalidate: 30, // Short cache for AI responses to avoid duplicate calls\n tags: [\"ai-prototype\"],\n auth: 'user',\n responseKind: 'json',\n },\n aiPatchDryRun: {\n path: \"/ai/patch/dry-run\",\n method: \"POST\",\n auth: 'user',\n responseKind: 'json',\n },\n aiPatchApply: {\n path: \"/ai/patch/apply\",\n method: \"POST\",\n auth: 'user',\n responseKind: 'json',\n },\n aiPlaygroundPropose: {\n path: \"/sites/{siteId}/ai/playground/propose\",\n method: \"POST\",\n auth: 'user',\n responseKind: 'json',\n },\n // Admin SEO\n listGscPropertiesAdmin: {\n path: \"/admin/seo/gsc/properties\",\n method: \"GET\",\n auth: 'admin',\n responseKind: 'json',\n },\n adminSetGscPersist: {\n path: \"/admin/seo/gsc/meta/persist\",\n method: \"POST\",\n auth: 'admin',\n responseKind: 'json',\n },\n adminStartGscVerification: {\n path: \"/admin/seo/gsc/properties/verify/start\",\n method: \"POST\",\n auth: 'admin',\n responseKind: 'json',\n },\n adminConfirmGscVerification: {\n path: \"/admin/seo/gsc/properties/verify/confirm\",\n method: \"POST\",\n auth: 'admin',\n responseKind: 'json',\n },\n adminRunGscIngest: {\n path: \"/admin/seo/ingest/run\",\n method: \"POST\",\n auth: 'admin',\n responseKind: 'json',\n },\n getSeoVerificationMeta: {\n path: \"/public/seo/verification/meta\",\n method: \"GET\",\n auth: 'public',\n responseKind: 'json',\n },\n checkRedirect: {\n path: \"/api/public/content/redirect\",\n method: \"GET\",\n revalidate: 86400, // 24 hours - redirects rarely change\n tags: ['redirect'],\n auth: 'public',\n responseKind: 'json',\n },\n listRedirectRules: {\n path: \"/sites/{siteId}/redirects\",\n method: \"GET\",\n auth: 'user',\n responseKind: 'json',\n },\n createRedirectRule: {\n path: \"/sites/{siteId}/redirects\",\n method: \"POST\",\n auth: 'user',\n responseKind: 'json',\n },\n deleteRedirectRule: {\n path: \"/sites/{siteId}/redirects/{ruleId}\",\n method: \"DELETE\",\n auth: 'user',\n responseKind: 'json',\n },\n // API Keys (Account-level - DEPRECATED, use site-scoped endpoints)\n listApiKeys: {\n path: \"/account/api-keys\",\n method: \"GET\",\n auth: 'user',\n responseKind: 'json',\n },\n createApiKey: {\n path: \"/account/api-keys\",\n method: \"POST\",\n auth: 'user',\n responseKind: 'json',\n },\n revokeApiKey: {\n path: \"/account/api-keys/{keyId}\",\n method: \"DELETE\",\n auth: 'user',\n responseKind: 'json',\n },\n // API Keys (Site-scoped - preferred)\n listSiteApiKeys: {\n path: \"/sites/{siteId}/api-keys\",\n method: \"GET\",\n auth: 'user',\n tags: ['site-{siteId}', 'api-keys'],\n responseKind: 'json',\n },\n createSiteApiKey: {\n path: \"/sites/{siteId}/api-keys\",\n method: \"POST\",\n auth: 'user',\n tags: ['site-{siteId}', 'api-keys'],\n responseKind: 'json',\n },\n revokeSiteApiKey: {\n path: \"/sites/{siteId}/api-keys/{keyId}\",\n method: \"DELETE\",\n auth: 'user',\n tags: ['site-{siteId}', 'api-keys'],\n responseKind: 'json',\n },\n getSitePreviewKey: {\n path: \"/sites/{siteId}/api-keys/preview\",\n method: \"GET\",\n auth: 'user',\n tags: ['site-{siteId}', 'api-keys', 'preview-key'],\n responseKind: 'json',\n },\n regenerateSitePreviewKey: {\n path: \"/sites/{siteId}/api-keys/preview/regenerate\",\n method: \"POST\",\n auth: 'user',\n tags: ['site-{siteId}', 'api-keys', 'preview-key'],\n responseKind: 'json',\n },\n getSiteApiKeyAccessLogs: {\n path: \"/sites/{siteId}/api-keys/access-logs\",\n method: \"GET\",\n auth: 'user',\n tags: ['site-{siteId}', 'api-keys', 'access-logs'],\n responseKind: 'json',\n },\n // Management API Keys (SDK write operations)\n listManagementKeys: {\n path: \"/sites/{siteId}/api-keys/management\",\n method: \"GET\",\n auth: 'user',\n tags: ['site-{siteId}', 'api-keys', 'management-keys'],\n responseKind: 'json',\n },\n createManagementKey: {\n path: \"/sites/{siteId}/api-keys/management\",\n method: \"POST\",\n auth: 'user',\n tags: ['site-{siteId}', 'api-keys', 'management-keys'],\n responseKind: 'json',\n },\n revokeManagementKey: {\n path: \"/sites/{siteId}/api-keys/management\",\n method: \"DELETE\",\n auth: 'user',\n tags: ['site-{siteId}', 'api-keys', 'management-keys'],\n responseKind: 'json',\n },\n getBookingSettings: {\n path: \"/sites/{siteId}/bookings/settings\",\n method: \"GET\",\n auth: 'user',\n responseKind: 'json',\n },\n updateBookingSettings: {\n path: \"/sites/{siteId}/bookings/settings\",\n method: \"PUT\",\n auth: 'user',\n responseKind: 'json',\n },\n listAppointmentResources: {\n path: \"/sites/{siteId}/bookings/resources\",\n method: \"GET\",\n auth: 'user',\n responseKind: 'json',\n },\n createAppointmentResource: {\n path: \"/sites/{siteId}/bookings/resources\",\n method: \"POST\",\n auth: 'user',\n responseKind: 'json',\n },\n getAppointmentResource: {\n path: \"/sites/{siteId}/bookings/resources/{resourceId}\",\n method: \"GET\",\n auth: 'user',\n responseKind: 'json',\n },\n updateAppointmentResource: {\n path: \"/sites/{siteId}/bookings/resources/{resourceId}\",\n method: \"PUT\",\n auth: 'user',\n responseKind: 'json',\n },\n deleteAppointmentResource: {\n path: \"/sites/{siteId}/bookings/resources/{resourceId}\",\n method: \"DELETE\",\n auth: 'user',\n responseKind: 'json',\n },\n listAppointmentServices: {\n path: \"/sites/{siteId}/bookings/services\",\n method: \"GET\",\n auth: 'user',\n responseKind: 'json',\n },\n createAppointmentService: {\n path: \"/sites/{siteId}/bookings/services\",\n method: \"POST\",\n auth: 'user',\n responseKind: 'json',\n },\n getAppointmentService: {\n path: \"/sites/{siteId}/bookings/services/{serviceId}\",\n method: \"GET\",\n auth: 'user',\n responseKind: 'json',\n },\n updateAppointmentService: {\n path: \"/sites/{siteId}/bookings/services/{serviceId}\",\n method: \"PUT\",\n auth: 'user',\n responseKind: 'json',\n },\n deleteAppointmentService: {\n path: \"/sites/{siteId}/bookings/services/{serviceId}\",\n method: \"DELETE\",\n auth: 'user',\n responseKind: 'json',\n },\n getAppointmentServicesReference: {\n path: \"/sites/{siteId}/bookings/services/reference\",\n method: \"GET\",\n auth: 'user',\n responseKind: 'json',\n },\n getAppointmentResourcesReference: {\n path: \"/sites/{siteId}/bookings/resources/reference\",\n method: \"GET\",\n auth: 'user',\n responseKind: 'json',\n },\n\n // Service-Resource linking\n getResourceServices: {\n path: \"/sites/{siteId}/bookings/resources/{resourceId}/services\",\n method: \"GET\",\n auth: 'user',\n responseKind: 'json',\n },\n updateResourceServices: {\n path: \"/sites/{siteId}/bookings/resources/{resourceId}/services\",\n method: \"PUT\",\n auth: 'user',\n responseKind: 'json',\n },\n getServiceResources: {\n path: \"/sites/{siteId}/bookings/services/{serviceId}/resources\",\n method: \"GET\",\n auth: 'user',\n responseKind: 'json',\n },\n updateServiceResources: {\n path: \"/sites/{siteId}/bookings/services/{serviceId}/resources\",\n method: \"PUT\",\n auth: 'user',\n responseKind: 'json',\n },\n\n // Availability management\n listAvailabilityRules: {\n path: \"/sites/{siteId}/bookings/resources/{resourceId}/availability\",\n method: \"GET\",\n auth: 'user',\n responseKind: 'json',\n },\n upsertAvailabilityRule: {\n path: \"/sites/{siteId}/bookings/resources/{resourceId}/availability\",\n method: \"POST\",\n auth: 'user',\n responseKind: 'json',\n },\n deleteAvailabilityRule: {\n path: \"/sites/{siteId}/bookings/resources/{resourceId}/availability/{ruleId}\",\n method: \"DELETE\",\n auth: 'user',\n responseKind: 'json',\n },\n listBlackouts: {\n path: \"/sites/{siteId}/bookings/resources/{resourceId}/blackouts\",\n method: \"GET\",\n auth: 'user',\n responseKind: 'json',\n },\n createBlackout: {\n path: \"/sites/{siteId}/bookings/resources/{resourceId}/blackouts\",\n method: \"POST\",\n auth: 'user',\n responseKind: 'json',\n },\n deleteBlackout: {\n path: \"/sites/{siteId}/bookings/resources/{resourceId}/blackouts/{blackoutId}\",\n method: \"DELETE\",\n auth: 'user',\n responseKind: 'json',\n },\n getAvailableSlots: {\n path: \"/sites/{siteId}/bookings/availability/slots\",\n method: \"GET\",\n auth: 'user',\n responseKind: 'json',\n },\n createAppointment: {\n path: \"/sites/{siteId}/bookings/appointments\",\n method: \"POST\",\n auth: 'user',\n responseKind: 'json',\n },\n\n // Data retrieval endpoints - good candidates for caching\n getBrief: {\n path: \"/briefs\",\n method: \"GET\",\n revalidate: 120, // 2 minutes\n tags: [\"brief\"],\n auth: 'user',\n responseKind: 'json',\n },\n // Unified site data endpoint - use this for all site lookups\n getSite: {\n path: \"/sites\",\n method: \"GET\",\n revalidate: 900, // 15 minutes - site data changes less frequently\n tags: [\"site\"],\n auth: 'user',\n responseKind: 'json',\n },\n // DEPRECATED: Use getSite with ?slug={slug} instead\n getSiteBySlug: {\n path: \"/sites/by-slug/{slug}\",\n method: \"GET\",\n revalidate: 900, // 15 minutes - site data changes less frequently\n tags: [\"site\", \"site-{slug}\"],\n auth: 'user',\n responseKind: 'json',\n },\n // DEPRECATED: Use getSite with ?domain={domain} instead\n getSiteByDomain: {\n path: \"/sites/by-domain/{domain}\",\n method: \"GET\",\n revalidate: 900, // 15 minutes - site data changes less frequently\n tags: [\"site\", \"site-domain-{domain}\"],\n auth: 'user',\n responseKind: 'json',\n },\n createBriefTurn: {\n path: \"/brief-turns\",\n method: \"POST\",\n tags: [\"brief\"], // Tags for invalidation after mutation\n auth: 'user',\n responseKind: 'json',\n },\n upsertBrief: {\n path: \"/briefs\",\n method: \"PUT\",\n tags: [\"brief\"],\n auth: 'user',\n responseKind: 'json',\n },\n aiBriefToSpec: {\n path: \"/ai/actions/brief-to-spec\",\n method: \"POST\",\n tags: [\"brief\", \"spec\"],\n auth: 'user',\n responseKind: 'json',\n },\n createSite: {\n path: \"/sites\",\n method: \"POST\",\n tags: [\"site\"],\n auth: 'user',\n responseKind: 'json',\n },\n createSiteManual: {\n path: \"/sites/manual\",\n method: \"POST\",\n tags: [\"site\"],\n auth: 'user',\n responseKind: 'json',\n },\n updateSite: {\n path: '/sites',\n method: 'PUT',\n tags: [\"site\"],\n auth: 'user',\n responseKind: 'json',\n },\n deleteSite: {\n path: '/sites/{siteId}',\n method: 'DELETE',\n tags: ['site', 'site-{siteId}'],\n auth: 'user',\n responseKind: 'json',\n },\n listSiteMembers: {\n path: '/sites/{siteId}/members',\n method: 'GET',\n tags: ['site-{siteId}', 'site-members-{siteId}'],\n auth: 'user',\n responseKind: 'json',\n },\n inviteSiteMember: {\n path: '/sites/{siteId}/members',\n method: 'POST',\n tags: ['site-{siteId}', 'site-members-{siteId}'],\n auth: 'user',\n responseKind: 'json',\n },\n updateSiteMemberRole: {\n path: '/sites/{siteId}/members/{memberId}',\n method: 'PATCH',\n tags: ['site-{siteId}', 'site-members-{siteId}', 'site-member-{memberId}'],\n auth: 'user',\n responseKind: 'json',\n },\n authLogin: {\n path: '/auth/login/submit',\n method: 'POST',\n tags: ['auth'],\n auth: 'public',\n responseKind: 'json',\n },\n authForgotPassword: {\n path: '/auth/forgot/submit',\n method: 'POST',\n tags: ['auth'],\n auth: 'public',\n responseKind: 'json',\n },\n authRegister: {\n path: '/auth/register/submit',\n method: 'POST',\n tags: ['auth'],\n auth: 'public',\n responseKind: 'json',\n },\n authReauthenticate: {\n path: '/auth/reauth/submit',\n method: 'POST',\n tags: ['auth'],\n auth: 'public',\n responseKind: 'json',\n },\n lookupSiteDomains: {\n path: '/sites/{siteId}/domains/lookup',\n method: 'POST',\n tags: ['site-{siteId}', 'site-domains'],\n auth: 'user',\n responseKind: 'json',\n },\n registerSiteDomain: {\n path: '/sites/{siteId}/domains/register',\n method: 'POST',\n tags: ['site-{siteId}', 'site-domains'],\n auth: 'user',\n responseKind: 'json',\n },\n addCustomDomain: {\n path: '/sites/{siteId}/domains/custom',\n method: 'POST',\n tags: ['site-{siteId}', 'site-domains'],\n auth: 'user',\n responseKind: 'json',\n },\n removeCustomDomain: {\n path: '/sites/{siteId}/domains/custom',\n method: 'DELETE',\n tags: ['site-{siteId}', 'site-domains'],\n auth: 'user',\n responseKind: 'json',\n },\n syncCustomDomainToEdgeConfig: {\n path: '/sites/{siteId}/domains/custom/sync',\n method: 'POST',\n tags: ['site-{siteId}', 'site-domains'],\n auth: 'user',\n responseKind: 'json',\n },\n removeSiteMember: {\n path: '/sites/{siteId}/members/{memberId}',\n method: 'DELETE',\n tags: ['site-{siteId}', 'site-members-{siteId}', 'site-member-{memberId}'],\n auth: 'user',\n responseKind: 'json',\n },\n revokeSiteInvitation: {\n path: '/sites/{siteId}/members/invitations/{invitationId}',\n method: 'DELETE',\n tags: ['site-{siteId}', 'site-members-{siteId}', 'site-invite-{invitationId}'],\n auth: 'user',\n responseKind: 'json',\n },\n transferSiteOwnership: {\n path: '/sites/{siteId}/members/transfer-ownership',\n method: 'POST',\n tags: ['site-{siteId}', 'site-members-{siteId}'],\n auth: 'user',\n responseKind: 'json',\n },\n listContentEntries: {\n path: '/sites/{siteId}/content/{type}',\n method: 'GET',\n revalidate: 60,\n tags: ['site-{siteId}', 'content-{siteId}-{type}'],\n auth: 'user',\n responseKind: 'json',\n },\n getContentTemplate: {\n path: '/sites/{siteId}/content/types/{type}/template',\n method: 'GET',\n revalidate: 60,\n tags: ['site-{siteId}', 'content-template-{siteId}-{type}'],\n auth: 'user',\n responseKind: 'json',\n },\n updateContentTemplateBlock: {\n path: '/sites/{siteId}/content/types/{type}/template/blocks/{blockId}',\n method: 'PATCH',\n tags: ['site-{siteId}', 'content-template-{siteId}-{type}', 'content-template-block-{blockId}'],\n auth: 'user',\n responseKind: 'json',\n },\n applyContentTemplateAddon: {\n path: '/sites/{siteId}/content/types/{type}/template/addons',\n method: 'POST',\n tags: ['site-{siteId}', 'content-template-{siteId}-{type}'],\n auth: 'user',\n responseKind: 'json',\n },\n updateContentTemplateBlockBindings: {\n path: '/sites/{siteId}/content/types/{type}/template/blocks/{blockId}/bindings',\n method: 'PATCH',\n tags: ['site-{siteId}', 'content-template-{siteId}-{type}', 'content-template-block-{blockId}'],\n auth: 'user',\n responseKind: 'json',\n },\n createTemplateBlock: {\n path: '/sites/{siteId}/content/types/{type}/template/blocks',\n method: 'POST',\n tags: ['site-{siteId}', 'content-template-{siteId}-{type}'],\n auth: 'user',\n responseKind: 'json',\n },\n deleteTemplateBlock: {\n path: '/sites/{siteId}/content/types/{type}/template/blocks/{blockId}',\n method: 'DELETE',\n tags: ['site-{siteId}', 'content-template-{siteId}-{type}', 'content-template-block-{blockId}'],\n auth: 'user',\n responseKind: 'json',\n },\n reorderTemplateBlocks: {\n path: '/sites/{siteId}/content/types/{type}/template/blocks/reorder',\n method: 'POST',\n tags: ['site-{siteId}', 'content-template-{siteId}-{type}'],\n auth: 'user',\n responseKind: 'json',\n },\n getTransforms: {\n path: '/transforms',\n method: 'GET',\n revalidate: 3600,\n tags: ['transforms'],\n auth: 'public',\n responseKind: 'json',\n },\n createContentEntry: {\n path: '/sites/{siteId}/content/{type}',\n method: 'POST',\n tags: ['site-{siteId}', 'content-{siteId}-{type}'],\n auth: 'user',\n responseKind: 'json',\n },\n getContentEntry: {\n path: '/sites/{siteId}/content/{type}/{entryId}',\n method: 'GET',\n tags: ['site-{siteId}', 'content-{siteId}-{type}', 'content-entry-{entryId}'],\n auth: 'user',\n responseKind: 'json',\n },\n updateContentEntry: {\n path: '/sites/{siteId}/content/{type}/{entryId}',\n method: 'PUT',\n tags: ['site-{siteId}', 'content-{siteId}-{type}', 'content-entry-{entryId}'],\n auth: 'user',\n responseKind: 'json',\n },\n updateContentEntryContent: {\n path: '/sites/{siteId}/content/{type}/{entryId}/content',\n method: 'PUT',\n tags: ['site-{siteId}', 'content-{siteId}-{type}', 'content-entry-{entryId}'],\n auth: 'user',\n responseKind: 'json',\n },\n updateRouteMetadata: {\n path: '/sites/{siteId}/routes/{routeId}/metadata',\n method: 'PATCH',\n tags: ['site-{siteId}', 'route-{routeId}'],\n auth: 'user',\n responseKind: 'json',\n },\n publishContentEntry: {\n path: '/sites/{siteId}/content/{type}/{entryId}/publish',\n method: 'POST',\n tags: ['site-{siteId}', 'content-{siteId}-{type}', 'content-entry-{entryId}'],\n auth: 'user',\n responseKind: 'json',\n },\n discardContentEntry: {\n path: '/sites/{siteId}/content/{type}/{entryId}/discard',\n method: 'POST',\n tags: ['site-{siteId}', 'content-{siteId}-{type}', 'content-entry-{entryId}'],\n auth: 'user',\n responseKind: 'json',\n },\n unpublishContentEntry: {\n path: '/sites/{siteId}/content/{type}/{entryId}/unpublish',\n method: 'POST',\n tags: ['site-{siteId}', 'content-{siteId}-{type}', 'content-entry-{entryId}'],\n auth: 'user',\n responseKind: 'json',\n },\n // Content types: enable + setup\n listContentTypes: {\n path: '/sites/{siteId}/content-types',\n method: 'GET',\n revalidate: 120, // 2 minutes\n tags: ['site-{siteId}', 'content-types'],\n auth: 'user',\n responseKind: 'json',\n },\n enableContentType: {\n path: '/sites/{siteId}/content-types/{key}/enable',\n method: 'POST',\n tags: ['site-{siteId}', 'content-types', 'content-type-{key}'],\n auth: 'user',\n responseKind: 'json',\n },\n setupContentType: {\n path: '/sites/{siteId}/content-types/{key}/setup',\n method: 'POST',\n tags: ['site-{siteId}', 'content-types', 'content-type-{key}'],\n auth: 'user',\n responseKind: 'json',\n },\n deleteContentEntry: {\n path: '/sites/{siteId}/content/{type}/{entryId}',\n method: 'DELETE',\n tags: ['site-{siteId}', 'content-{siteId}-{type}', 'content-entry-{entryId}'],\n auth: 'user',\n responseKind: 'json',\n },\n updateSiteGeneralSettings: {\n path: '/sites/{siteId}/settings/general',\n method: 'POST',\n tags: ['site-{siteId}', 'site-settings-{siteId}'],\n auth: 'user',\n responseKind: 'json',\n },\n getSiteLayoutSettings: {\n path: '/sites/{siteId}/settings/layout',\n method: 'GET',\n revalidate: 120,\n tags: ['site-{siteId}', 'site-settings-{siteId}'],\n auth: 'user',\n responseKind: 'json',\n },\n updateSiteLayoutSettings: {\n path: '/sites/{siteId}/settings/layout',\n method: 'POST',\n tags: ['site-{siteId}', 'site-settings-{siteId}'],\n auth: 'user',\n responseKind: 'json',\n },\n getMaintenanceSettings: {\n path: '/sites/{siteId}/settings/maintenance',\n method: 'GET',\n revalidate: 120,\n tags: ['site-{siteId}', 'site-settings-{siteId}'],\n auth: 'user',\n responseKind: 'json',\n },\n updateMaintenanceSettings: {\n path: '/sites/{siteId}/settings/maintenance',\n method: 'POST',\n tags: ['site-{siteId}', 'site-settings-{siteId}'],\n auth: 'user',\n responseKind: 'json',\n },\n setHomepage: {\n path: '/sites/{siteId}/settings/homepage',\n method: 'POST',\n tags: ['site-{siteId}', 'site-homepage'],\n auth: 'user',\n responseKind: 'json',\n },\n listAccountDomains: {\n path: '/domains',\n method: 'GET',\n tags: ['domains'],\n auth: 'user',\n responseKind: 'json',\n },\n searchDomains: {\n path: '/domains/search',\n method: 'POST',\n tags: ['domains'],\n auth: 'user',\n responseKind: 'json',\n },\n registerDomain: {\n path: '/domains/register',\n method: 'POST',\n tags: ['domains'],\n auth: 'user',\n responseKind: 'json',\n },\n assignDomain: {\n path: '/domains/{domainId}/assign',\n method: 'POST',\n tags: ['domains', 'domain-{domainId}'],\n auth: 'user',\n responseKind: 'json',\n },\n adminStartImpersonation: {\n path: '/admin/impersonation/start',\n method: 'POST',\n tags: ['admin', 'impersonation'],\n auth: 'user',\n responseKind: 'json',\n },\n adminStopImpersonation: {\n path: '/admin/impersonation/stop',\n method: 'POST',\n tags: ['admin', 'impersonation'],\n auth: 'user',\n responseKind: 'json',\n },\n adminAssignRole: {\n path: '/admin/roles/assign',\n method: 'POST',\n tags: ['admin', 'roles'],\n auth: 'user',\n responseKind: 'json',\n },\n adminRevokeRole: {\n path: '/admin/roles/revoke',\n method: 'POST',\n tags: ['admin', 'roles'],\n auth: 'user',\n responseKind: 'json',\n },\n adminChangePlan: {\n path: '/admin/billing/plan',\n method: 'POST',\n tags: ['admin', 'billing'],\n auth: 'user',\n responseKind: 'json',\n },\n changePlan: {\n path: '/billing/plan/change',\n method: 'POST',\n tags: ['billing'],\n auth: 'user',\n responseKind: 'json',\n },\n mfaTotpEnroll: {\n path: '/auth/mfa/totp/enroll',\n method: 'POST',\n tags: ['mfa'],\n auth: 'user',\n responseKind: 'json',\n },\n mfaTotpVerify: {\n path: '/auth/mfa/totp/verify',\n method: 'POST',\n tags: ['mfa'],\n auth: 'user',\n responseKind: 'json',\n },\n mfaTotpActivate: {\n path: '/auth/mfa/totp/activate',\n method: 'POST',\n tags: ['mfa'],\n auth: 'user',\n responseKind: 'json',\n },\n mfaDeleteFactor: {\n path: '/auth/mfa/factors/{factorId}',\n method: 'DELETE',\n tags: ['mfa'],\n auth: 'user',\n responseKind: 'json',\n },\n mfaBackupCodesGet: {\n path: '/auth/mfa/backup-codes',\n method: 'GET',\n tags: ['mfa'],\n auth: 'user',\n responseKind: 'json',\n },\n mfaBackupCodesRotate: {\n path: '/auth/mfa/backup-codes/rotate',\n method: 'POST',\n tags: ['mfa'],\n auth: 'user',\n responseKind: 'json',\n },\n mfaPhoneEnroll: {\n path: '/auth/mfa/phone/enroll',\n method: 'POST',\n tags: ['mfa'],\n auth: 'user',\n responseKind: 'json',\n },\n mfaPhoneChallenge: {\n path: '/auth/mfa/phone/challenge',\n method: 'POST',\n tags: ['mfa'],\n auth: 'user',\n responseKind: 'json',\n },\n mfaPhoneVerify: {\n path: '/auth/mfa/phone/verify',\n method: 'POST',\n tags: ['mfa'],\n auth: 'user',\n responseKind: 'json',\n },\n accountUpdatePassword: {\n path: '/account/password/update',\n method: 'POST',\n tags: ['account'],\n auth: 'user',\n responseKind: 'json',\n },\n accountRevokeSessions: {\n path: '/account/sessions/revoke',\n method: 'POST',\n tags: ['account'],\n auth: 'user',\n responseKind: 'json',\n },\n adminListInvites: {\n path: '/admin/invites',\n method: 'GET',\n tags: ['admin', 'invites'],\n auth: 'user',\n responseKind: 'json',\n },\n adminCreateInvite: {\n path: '/admin/invites',\n method: 'POST',\n tags: ['admin', 'invites'],\n auth: 'user',\n responseKind: 'json',\n },\n adminRevokeInvite: {\n path: '/admin/invites/{inviteId}',\n method: 'DELETE',\n tags: ['admin', 'invites'],\n auth: 'user',\n responseKind: 'json',\n },\n adminCreateUser: {\n path: '/admin/users',\n method: 'POST',\n tags: ['admin', 'users'],\n auth: 'admin',\n responseKind: 'json',\n },\n adminListAllowedDomains: {\n path: '/admin/allowed-domains',\n method: 'GET',\n tags: ['admin', 'allowed-domains'],\n auth: 'user',\n responseKind: 'json',\n },\n adminAddAllowedDomain: {\n path: '/admin/allowed-domains',\n method: 'POST',\n tags: ['admin', 'allowed-domains'],\n auth: 'user',\n responseKind: 'json',\n },\n adminDeleteAllowedDomain: {\n path: '/admin/allowed-domains/{domainId}',\n method: 'DELETE',\n tags: ['admin', 'allowed-domains'],\n auth: 'user',\n responseKind: 'json',\n },\n authAcceptInvite: {\n path: '/auth/invite/accept',\n method: 'POST',\n tags: ['auth', 'invite'],\n auth: 'user',\n responseKind: 'json',\n },\n acceptSiteInvitation: {\n path: '/sites/invitations/accept',\n method: 'POST',\n tags: ['site-invitations'],\n auth: 'user',\n responseKind: 'json',\n },\n getNavigationMenus: {\n path: '/sites/{siteId}/navigation/menus',\n method: 'GET',\n tags: ['site-{siteId}', 'navigation'],\n auth: 'user',\n responseKind: 'json',\n },\n createNavigationMenu: {\n path: '/sites/{siteId}/navigation/menus',\n method: 'POST',\n tags: ['site-{siteId}', 'navigation'],\n auth: 'user',\n responseKind: 'json',\n },\n updateNavigationMenu: {\n path: '/sites/{siteId}/navigation/menus/{menuId}',\n method: 'PATCH',\n tags: ['site-{siteId}', 'navigation', 'navigation-menu-{menuId}'],\n auth: 'user',\n responseKind: 'json',\n },\n deleteNavigationMenu: {\n path: '/sites/{siteId}/navigation/menus/{menuId}',\n method: 'DELETE',\n tags: ['site-{siteId}', 'navigation', 'navigation-menu-{menuId}'],\n auth: 'user',\n responseKind: 'json',\n },\n createNavigationItem: {\n path: '/sites/{siteId}/navigation/menus/{menuId}/items',\n method: 'POST',\n tags: ['site-{siteId}', 'navigation', 'navigation-menu-{menuId}'],\n auth: 'user',\n responseKind: 'json',\n },\n updateNavigationItem: {\n path: '/sites/{siteId}/navigation/menus/{menuId}/items/{itemId}',\n method: 'PATCH',\n tags: ['site-{siteId}', 'navigation', 'navigation-menu-{menuId}', 'navigation-item-{itemId}'],\n auth: 'user',\n responseKind: 'json',\n },\n deleteNavigationItem: {\n path: '/sites/{siteId}/navigation/menus/{menuId}/items/{itemId}',\n method: 'DELETE',\n tags: ['site-{siteId}', 'navigation', 'navigation-menu-{menuId}', 'navigation-item-{itemId}'],\n auth: 'user',\n responseKind: 'json',\n },\n reorderNavigationItems: {\n path: '/sites/{siteId}/navigation/menus/{menuId}/items/reorder',\n method: 'POST',\n tags: ['site-{siteId}', 'navigation', 'navigation-menu-{menuId}'],\n auth: 'user',\n responseKind: 'json',\n },\n getRoutableContent: {\n path: '/sites/{siteId}/routable-content',\n method: 'GET',\n revalidate: 60,\n tags: ['site-{siteId}', 'routable-content-{siteId}'],\n auth: 'user',\n responseKind: 'json',\n },\n // Generic public content preview (preferred)\n getPublishedEntryPreview: {\n path: '/public/content/{siteId}/{type}/{slug}/preview',\n method: 'GET',\n revalidate: 60,\n tags: ['content-{siteId}-{type}-{slug}'],\n auth: 'public',\n responseKind: 'json',\n },\n listPublishedEntries: {\n path: '/public/content/{siteId}/{type}/entries',\n method: 'GET',\n revalidate: 60,\n tags: ['content-{siteId}-{type}'],\n auth: 'public',\n responseKind: 'json',\n },\n getPublishedPostPreview: {\n path: '/public/posts/{siteId}/{slug}/preview',\n method: 'GET',\n revalidate: 60,\n tags: ['blog-post:{siteId}:{slug}'],\n auth: 'public',\n responseKind: 'json',\n },\n proposalsSelect: {\n path: '/proposals/select',\n method: 'POST',\n tags: [\"proposal\", \"site\"],\n auth: 'user',\n responseKind: 'json',\n },\n createTheme: {\n path: '/ai/chat/create-theme',\n method: 'POST',\n revalidate: 60, // 1 minute cache for theme creation to avoid duplicate requests\n tags: [\"theme\", \"ai-theme\"],\n auth: 'user',\n responseKind: 'json',\n },\n generateThemes: {\n path: '/theme/generate',\n method: 'POST',\n tags: [\"theme\"],\n auth: 'user',\n responseKind: 'json',\n },\n extractThemeFromInspiration: {\n path: '/theme/extract-from-inspiration',\n method: 'POST',\n tags: [\"theme\", \"preferences\"],\n auth: 'user',\n responseKind: 'json',\n },\n saveSiteTheme: {\n path: '/sites/{siteId}/theme/save',\n method: 'POST',\n tags: ['site-{siteId}', 'theme'],\n auth: 'user',\n responseKind: 'json',\n },\n uploadSiteLogo: {\n path: '/storage/upload-site-logo',\n method: 'POST',\n tags: [\"site\", \"logo\"],\n auth: 'user',\n responseKind: 'json',\n },\n upsertThemePreferences: {\n path: '/theme-preferences/upsert',\n method: 'POST',\n tags: [\"theme\", \"preferences\"],\n auth: 'user',\n responseKind: 'json',\n },\n finalizeSite: {\n path: '/sites/finalize',\n method: 'POST',\n tags: [\"site\"],\n auth: 'user',\n responseKind: 'json',\n },\n instagramUploadZip: {\n path: '/api/instagram/upload-zip',\n method: 'POST',\n tags: ['instagram-import'],\n auth: 'user',\n responseKind: 'json',\n },\n getAnalyticsReport: {\n path: '/sites/{siteId}/analytics/report',\n method: 'GET',\n revalidate: 300,\n tags: ['site-{siteId}', 'analytics-{siteId}'],\n auth: 'user',\n responseKind: 'json',\n },\n getSeoOverview: {\n path: '/sites/{siteId}/seo/overview',\n method: 'GET',\n revalidate: 300,\n tags: ['site-{siteId}', 'seo-overview-{siteId}'],\n auth: 'user',\n responseKind: 'json',\n },\n getSeoPages: {\n path: '/sites/{siteId}/seo/pages',\n method: 'GET',\n revalidate: 300,\n tags: ['site-{siteId}', 'seo-pages-{siteId}'],\n auth: 'user',\n responseKind: 'json',\n },\n getSeoQueries: {\n path: '/sites/{siteId}/seo/queries',\n method: 'GET',\n revalidate: 300,\n tags: ['site-{siteId}', 'seo-queries-{siteId}'],\n auth: 'user',\n responseKind: 'json',\n },\n getPerformanceOverview: {\n path: '/sites/{siteId}/performance/overview',\n method: 'GET',\n revalidate: 300,\n tags: ['site-{siteId}', 'performance-overview-{siteId}'],\n auth: 'user',\n responseKind: 'json',\n },\n createMediaAsset: {\n path: '/media',\n method: 'POST',\n tags: ['media'],\n auth: 'user',\n responseKind: 'json',\n },\n mediaList: {\n path: '/media',\n method: 'GET',\n tags: ['media'],\n auth: 'user',\n responseKind: 'json',\n },\n mediaGet: {\n path: '/media/{assetId}',\n method: 'GET',\n tags: ['media', 'media-{assetId}'],\n auth: 'user',\n responseKind: 'json',\n },\n mediaUpdate: {\n path: '/media/{assetId}',\n method: 'PATCH',\n tags: ['media', 'media-{assetId}'],\n auth: 'user',\n responseKind: 'json',\n },\n mediaDelete: {\n path: '/media/{assetId}',\n method: 'DELETE',\n tags: ['media', 'media-{assetId}'],\n auth: 'user',\n responseKind: 'void',\n },\n mediaBulkDelete: {\n path: '/media/bulk-delete',\n method: 'POST',\n tags: ['media'],\n auth: 'user',\n responseKind: 'json',\n },\n mediaGetSignedUrl: {\n path: '/media/{assetId}/signed-url',\n method: 'GET',\n tags: ['media', 'media-{assetId}'],\n auth: 'user',\n responseKind: 'json',\n },\n mediaUpload: {\n path: '/media/upload',\n method: 'POST',\n tags: ['media'],\n auth: 'user',\n responseKind: 'json',\n },\n getMediaLabels: {\n path: '/media/labels',\n method: 'GET',\n tags: ['media', 'media-labels'],\n auth: 'user',\n responseKind: 'json',\n },\n getMediaSettings: {\n path: '/media/settings',\n method: 'GET',\n tags: ['media-settings'],\n auth: 'user',\n responseKind: 'json',\n },\n updateMediaSettings: {\n path: '/media/settings',\n method: 'POST',\n tags: ['media-settings'],\n auth: 'user',\n responseKind: 'json',\n },\n // Media endpoints\n mediaSearch: {\n path: '/media/search',\n method: 'POST',\n tags: ['media-search'],\n auth: 'user',\n responseKind: 'json',\n },\n classifyMediaAsset: {\n path: '/media/{assetId}/classify',\n method: 'POST',\n tags: ['media', 'media-{assetId}'],\n auth: 'user',\n responseKind: 'json',\n },\n classifyMediaBatch: {\n path: '/media/classify',\n method: 'POST',\n tags: ['media'],\n auth: 'user',\n responseKind: 'json',\n },\n mediaJobsStatus: {\n path: '/media/jobs/status',\n method: 'GET',\n tags: ['media'],\n auth: 'user',\n responseKind: 'json',\n },\n enqueueMediaClassifyJob: {\n path: '/media/jobs',\n method: 'POST',\n tags: ['media'],\n auth: 'user',\n responseKind: 'json',\n },\n runMediaClassifyJob: {\n path: '/media/jobs/run',\n method: 'POST',\n tags: ['media'],\n auth: 'user',\n responseKind: 'json',\n },\n runAllMediaClassifyJobs: {\n path: '/media/jobs/run-all',\n method: 'POST',\n tags: ['media'],\n auth: 'user',\n responseKind: 'json',\n },\n initSiteContent: {\n path: '/sites/{siteId}/content/init',\n method: 'POST',\n tags: [\"site\", \"content\"],\n auth: 'user',\n responseKind: 'json',\n },\n updateBlockContent: {\n path: '/blocks/{blockId}/content',\n method: 'POST',\n tags: ['block', 'block-{blockId}', 'content'],\n auth: 'user',\n responseKind: 'json',\n },\n saveBlockContent: {\n path: '/blocks/{blockId}/content',\n method: 'POST',\n tags: ['block', 'block-{blockId}', 'content'],\n auth: 'user',\n responseKind: 'json',\n },\n createBlock: {\n path: '/sites/{siteId}/pages/{pageId}/blocks',\n method: 'POST',\n tags: ['site-{siteId}', 'page-{pageId}', 'blocks'],\n auth: 'user',\n responseKind: 'json',\n },\n reorderBlocks: {\n path: '/sites/{siteId}/pages/{pageId}/blocks/reorder',\n method: 'POST',\n tags: ['site-{siteId}', 'page-{pageId}', 'blocks'],\n auth: 'user',\n responseKind: 'json',\n },\n deleteBlock: {\n path: '/sites/{siteId}/pages/{pageId}/blocks/{blockId}',\n method: 'DELETE',\n tags: ['site-{siteId}', 'page-{pageId}', 'blocks'],\n auth: 'user',\n responseKind: 'json',\n },\n listBlocks: {\n path: '/sites/{siteId}/pages/{pageId}/blocks',\n method: 'GET',\n tags: ['site-{siteId}', 'page-{pageId}', 'blocks'],\n auth: 'user',\n responseKind: 'json',\n },\n getBlockContent: {\n path: '/blocks/{blockId}/content',\n method: 'GET',\n tags: ['block-{blockId}', 'content'],\n auth: 'user',\n responseKind: 'json',\n },\n // DEPRECATED: Use getSite with ?id={id} instead\n getSiteById: {\n path: '/sites/by-id/{id}',\n method: 'GET',\n revalidate: 900, // 15 minutes\n tags: ['site-{id}'],\n auth: 'user',\n responseKind: 'json',\n },\n // Page data endpoints\n getPageById: {\n path: '/sites/{siteId}/pages/{pageId}',\n method: 'GET',\n revalidate: 900, // 15 minutes\n tags: ['site-{siteId}', 'page-{pageId}'],\n auth: 'user',\n responseKind: 'json',\n },\n getContentByPath: {\n path: '/sites/{siteId}/pages',\n method: 'GET',\n tags: ['site-{siteId}', 'routable-content-{siteId}'],\n auth: 'public',\n responseKind: 'json',\n },\n getPageByPath: {\n path: '/sites/{siteId}/pages',\n method: 'GET',\n tags: ['site-{siteId}'],\n auth: 'user',\n responseKind: 'json',\n },\n // Forms CRUD\n listForms: {\n path: '/sites/{siteId}/forms',\n method: 'GET',\n tags: ['site-{siteId}', 'forms'],\n auth: 'user',\n responseKind: 'json',\n },\n listBookingForms: {\n path: '/sites/{siteId}/bookings/forms',\n method: 'GET',\n tags: ['site-{siteId}', 'forms'],\n auth: 'user',\n responseKind: 'json',\n },\n \n createForm: {\n path: '/sites/{siteId}/forms',\n method: 'POST',\n tags: ['site-{siteId}', 'forms'],\n auth: 'user',\n responseKind: 'json',\n },\n updateForm: {\n path: '/sites/{siteId}/forms/{slug}',\n method: 'PATCH',\n tags: ['site-{siteId}', 'forms', 'form-{slug}'],\n auth: 'user',\n responseKind: 'json',\n },\n deleteForm: {\n path: '/sites/{siteId}/forms/{slug}',\n method: 'DELETE',\n tags: ['site-{siteId}', 'forms', 'form-{slug}'],\n auth: 'user',\n responseKind: 'json',\n },\n listFormSubmissions: {\n path: '/sites/{siteId}/forms/{slug}/submissions',\n method: 'GET',\n revalidate: 30,\n tags: ['site-{siteId}', 'form-{slug}', 'form-submissions-{slug}'],\n auth: 'user',\n responseKind: 'json',\n },\n // Public submit\n submitForm: {\n path: '/forms/submit',\n method: 'POST',\n tags: ['forms-submit'],\n auth: 'public',\n responseKind: 'json',\n },\n // Public forms\n getPublicFormById: {\n path: '/public/forms/{formId}',\n method: 'GET',\n revalidate: 60,\n tags: ['form-{formId}'],\n auth: 'public',\n responseKind: 'json',\n },\n // Public booking services\n getPublicBookingServices: {\n path: '/public/bookings/services',\n method: 'GET',\n revalidate: 60,\n tags: ['site-{siteId}'],\n auth: 'public',\n responseKind: 'json',\n },\n devToolsImpersonateAdmin: {\n path: '/dev-tools/impersonate/admin',\n method: 'POST',\n auth: 'admin',\n responseKind: 'json',\n },\n devToolsImpersonateRandom: {\n path: '/dev-tools/impersonate/random',\n method: 'POST',\n auth: 'admin',\n responseKind: 'json',\n },\n devToolsSeedDemoData: {\n path: '/dev-tools/seed',\n method: 'POST',\n auth: 'admin',\n responseKind: 'json',\n },\n // Public analytics collection endpoint\n analyticsCollect: {\n path: '/api/analytics/collect',\n method: 'POST',\n auth: 'public',\n responseKind: 'json',\n },\n // Public events for event calendar block\n listPublicEvents: {\n path: '/public/sites/{siteId}/events',\n method: 'GET',\n revalidate: 60,\n tags: ['public-events-{siteId}'],\n auth: 'public',\n responseKind: 'json',\n },\n // Resolve event occurrence by URL segment (date or UUID)\n resolveEventOccurrence: {\n path: '/public/sites/{siteId}/events/occurrences/resolve',\n method: 'GET',\n revalidate: 60,\n tags: ['public-events-{siteId}', 'event-occurrence'],\n auth: 'public',\n responseKind: 'json',\n },\n // Public event registration\n registerForEvent: {\n path: '/public/sites/{siteId}/events/register',\n method: 'POST',\n tags: ['public-events-{siteId}', 'event-registration'],\n auth: 'public',\n responseKind: 'json',\n },\n // Content Types CRUD\n listSiteContentTypes: {\n path: '/sites/{siteId}/content-types',\n method: 'GET',\n revalidate: 60,\n tags: ['site-{siteId}', 'content-types-{siteId}'],\n auth: 'user',\n responseKind: 'json',\n },\n getContentType: {\n path: '/sites/{siteId}/content-types/by-id/{typeId}',\n method: 'GET',\n revalidate: 60,\n tags: ['site-{siteId}', 'content-type-{typeId}'],\n auth: 'user',\n responseKind: 'json',\n },\n createContentType: {\n path: '/sites/{siteId}/content-types',\n method: 'POST',\n tags: ['site-{siteId}', 'content-types-{siteId}'],\n auth: 'user',\n responseKind: 'json',\n },\n updateContentType: {\n path: '/sites/{siteId}/content-types/by-id/{typeId}',\n method: 'PATCH',\n tags: ['site-{siteId}', 'content-type-{typeId}', 'content-types-{siteId}'],\n auth: 'user',\n responseKind: 'json',\n },\n deleteContentType: {\n path: '/sites/{siteId}/content-types/by-id/{typeId}',\n method: 'DELETE',\n tags: ['site-{siteId}', 'content-type-{typeId}', 'content-types-{sideId}'],\n auth: 'user',\n responseKind: 'json',\n },\n duplicateContentType: {\n path: '/sites/{siteId}/content-types/by-id/{typeId}/duplicate',\n method: 'POST',\n tags: ['site-{siteId}', 'content-type-{typeId}', 'content-types-{siteId}'],\n auth: 'user',\n responseKind: 'json',\n },\n // Admin site creation\n adminCreateSite: {\n path: '/admin/sites',\n method: 'POST',\n tags: ['admin', 'sites'],\n auth: 'admin',\n responseKind: 'json',\n },\n // SDK Config\n refreshSdkConfig: {\n path: '/sites/{siteId}/refresh-sdk-config',\n method: 'POST',\n tags: ['site-{siteId}', 'sdk-config'],\n auth: 'user',\n responseKind: 'json',\n },\n // Stripe Connect - Site billing\n stripeConnectAuthorize: {\n path: '/sites/{siteId}/billing/connect/authorize',\n method: 'POST',\n tags: ['site-{siteId}', 'stripe-connect'],\n auth: 'user',\n responseKind: 'json',\n },\n stripeConnectStatus: {\n path: '/sites/{siteId}/billing/connect/status',\n method: 'GET',\n tags: ['site-{siteId}', 'stripe-connect'],\n auth: 'user',\n responseKind: 'json',\n },\n stripeConnectDisconnect: {\n path: '/sites/{siteId}/billing/connect/disconnect',\n method: 'DELETE',\n tags: ['site-{siteId}', 'stripe-connect'],\n auth: 'user',\n responseKind: 'json',\n },\n // Backup\n importSiteBackup: {\n path: '/sites/{siteId}/backup/import',\n method: 'POST',\n tags: ['site-{siteId}', 'backup'],\n auth: 'user',\n responseKind: 'json',\n },\n previewBackup: {\n path: '/backup/preview',\n method: 'POST',\n tags: ['backup'],\n auth: 'user',\n responseKind: 'json',\n },\n importBackupAsNewSite: {\n path: '/sites/backup/import-as-new',\n method: 'POST',\n tags: ['site', 'backup'],\n auth: 'user',\n responseKind: 'json',\n },\n // Admin billing price overrides\n adminGetPriceOverride: {\n path: '/admin/billing/price-override',\n method: 'GET',\n tags: ['admin', 'billing', 'price-override'],\n auth: 'admin',\n responseKind: 'json',\n },\n adminUpsertPriceOverride: {\n path: '/admin/billing/price-override',\n method: 'POST',\n tags: ['admin', 'billing', 'price-override'],\n auth: 'admin',\n responseKind: 'json',\n },\n adminDeletePriceOverride: {\n path: '/admin/billing/price-override',\n method: 'DELETE',\n tags: ['admin', 'billing', 'price-override'],\n auth: 'admin',\n responseKind: 'json',\n },\n} as const satisfies APIEndpoints;\n\nexport const API_ENDPOINTS = ENDPOINT_DEFINITIONS;\n\ntype IsUnknown<T> = unknown extends T ? ([T] extends [unknown] ? true : false) : false;\n\ntype MissingResponseTypes = {\n [K in keyof APIEndpoints]: IsUnknown<NonNullable<APIEndpoints[K]['response']>> extends true ? K : never;\n}[keyof APIEndpoints];\n\ntype MissingResponseKindMetadata = {\n [K in keyof typeof ENDPOINT_DEFINITIONS]: typeof ENDPOINT_DEFINITIONS[K] extends { responseKind: infer RK }\n ? RK extends import('./types').ApiResponseKind\n ? never\n : K\n : K;\n}[keyof typeof ENDPOINT_DEFINITIONS];\n\ntype MissingAuthMetadata = {\n [K in keyof typeof ENDPOINT_DEFINITIONS]: typeof ENDPOINT_DEFINITIONS[K] extends { auth: infer A }\n ? A extends import('./types').ApiAuthRequirement\n ? never\n : K\n : K;\n}[keyof typeof ENDPOINT_DEFINITIONS];\n\ntype AssertTrue<T extends true> = T;\n\ntype _EnsureResponseTypes = AssertTrue<MissingResponseTypes extends never ? true : false>;\ntype _EnsureResponseKindMetadata = AssertTrue<MissingResponseKindMetadata extends never ? true : false>;\ntype _EnsureAuthMetadata = AssertTrue<MissingAuthMetadata extends never ? true : false>;\n\n// Helper function to build full URL\nexport function buildEndpointURL(baseURL: string, endpoint: keyof APIEndpoints): string {\n return baseURL + API_ENDPOINTS[endpoint].path;\n}\n","/**\n * URL construction helpers for dashboard and API URLs.\n *\n * These functions ensure consistent URL construction across the codebase\n * and derive the API URL from the dashboard URL to avoid duplicate env vars.\n */\n\n/**\n * Get the base dashboard URL from environment\n * @returns Dashboard base URL (e.g., 'http://dashboard.local:4000')\n * @throws Error if neither NEXT_PUBLIC_DASHBOARD_URL nor NEXT_PUBLIC_CMS_API_URL is configured\n */\nexport function getDashboardBaseUrl(): string {\n const internalUrl = process.env.CMS_API_URL;\n if (internalUrl) {\n return internalUrl.replace(/\\/api\\/?$/, '').replace(/\\/$/, '');\n }\n\n const dashboardUrl = process.env.NEXT_PUBLIC_DASHBOARD_URL;\n\n if (dashboardUrl) {\n // Remove trailing slash for consistency\n return dashboardUrl.replace(/\\/$/, '');\n }\n\n // Legacy fallback: derive from NEXT_PUBLIC_CMS_API_URL\n const legacyApiUrl = process.env.NEXT_PUBLIC_CMS_API_URL;\n if (legacyApiUrl) {\n // Strip /api suffix if present\n return legacyApiUrl.replace(/\\/$/, '').replace(/\\/api$/, '');\n }\n\n throw new Error(\n 'NEXT_PUBLIC_DASHBOARD_URL is not configured. ' +\n 'Set it to your dashboard URL (e.g., http://dashboard.local:4000)'\n );\n}\n\n/**\n * Get the CMS API URL by appending /api to the dashboard URL\n * @returns API base URL (e.g., 'http://dashboard.local:4000/api' or '/api' in browser)\n * @throws Error if neither NEXT_PUBLIC_DASHBOARD_URL nor NEXT_PUBLIC_CMS_API_URL is configured (server-side only)\n */\nexport function getCmsApiUrl(): string {\n // In browser, always use relative URL to avoid CORS issues\n // (e.g., www.example.com calling example.com/api would be cross-origin)\n if (typeof window !== 'undefined') {\n return '/api';\n }\n\n // Server-side: use explicit URLs for SSR/API routes\n const internalUrl = process.env.CMS_API_URL;\n if (internalUrl) {\n return internalUrl.replace(/\\/$/, '');\n }\n\n // Check for NEXT_PUBLIC_DASHBOARD_URL first (new standard)\n const dashboardUrl = process.env.NEXT_PUBLIC_DASHBOARD_URL;\n if (dashboardUrl) {\n const base = dashboardUrl.replace(/\\/$/, '');\n return `${base}/api`;\n }\n\n // Legacy: Use NEXT_PUBLIC_CMS_API_URL directly if available\n const legacyApiUrl = process.env.NEXT_PUBLIC_CMS_API_URL;\n if (legacyApiUrl) {\n return legacyApiUrl.replace(/\\/$/, '');\n }\n\n throw new Error(\n 'NEXT_PUBLIC_DASHBOARD_URL is not configured. ' +\n 'Set it to your dashboard URL (e.g., http://dashboard.local:4000)'\n );\n}\n\n/**\n * Legacy: Get API URL, supporting old NEXT_PUBLIC_CMS_API_URL env var\n * @deprecated Use getCmsApiUrl() instead - this function is maintained for backwards compatibility\n */\nexport function resolveApiBaseUrl(): string {\n return getCmsApiUrl();\n}\n","import SupabaseClient from \"@supabase/supabase-js/dist/module/SupabaseClient\";\nimport { API_ENDPOINTS } from \"./endpoints\";\nimport { AuthHeaders, getAuthHeaders } from \"./token\";\nimport { APICallParams, APIEndpoints, ApiAuthRequirement, ApiResponseKind, HTTPMethod } from \"./types\";\nimport { resolveApiBaseUrl } from \"./url\";\nimport { normalizeCmsApiKey } from \"./env\";\nimport type { ApiErrorCode, ApiResult, ApiResponse, ApiError } from \"./common/envelope\";\n\n// Optional Next.js imports - only available when Next.js is present\n// Next.js 16 requires a cacheLife profile as the second argument\ntype RevalidateTagFn = (tag: string, cacheLifeProfile: string) => void;\nlet revalidateTag: RevalidateTagFn | null = null;\n\n// Dynamically load Next.js cache helpers on the server\n// We use Function constructor to create a require call that esbuild won't transform\n// to __require shim, which causes \"dynamic usage of require is not supported\" in Turbopack.\n// This is necessary because @riverbankcms/api is bundled into the SDK, and direct\n// require() calls get wrapped even when 'next' is marked as external.\nif (typeof window === 'undefined') {\n try {\n const dynamicRequire = new Function('modulePath', 'return require(modulePath)') as (path: string) => unknown;\n const nextCache = dynamicRequire('next/cache') as { revalidateTag?: RevalidateTagFn };\n revalidateTag = nextCache.revalidateTag ?? null;\n } catch {\n // Next.js not available - revalidation functions will be no-ops\n }\n}\n\n// SDK version tracking - set by the SDK to include in request headers\nlet sdkVersion: string | undefined;\n\n/**\n * Generate a UUID, with fallback for environments where crypto.randomUUID isn't available.\n */\nfunction generateRequestId(): string {\n // crypto.randomUUID is available in modern browsers and Node 19+\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n // Fallback: generate a pseudo-random UUID v4\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n\n/**\n * Set the SDK version to be included in API request headers\n * Called by the SDK during initialization\n */\nexport function setSdkVersion(version: string): void {\n sdkVersion = version;\n}\n\n/**\n * Get the currently set SDK version\n */\nexport function getSdkVersion(): string | undefined {\n return sdkVersion;\n}\n\ntype RawApiClient = <E extends keyof APIEndpoints>(params: APICallParams<E>) => Promise<Response>;\ntype EndpointResponse<E extends keyof APIEndpoints> = APIEndpoints[E]['response'];\ntype ApiResponseFor<E extends keyof APIEndpoints> =\n EndpointResponse<E> extends undefined ? void : NonNullable<EndpointResponse<E>>;\n\n/**\n * API client that returns typed envelope responses.\n * Consumers must use isApiSuccess/isApiError type guards or unwrapResponse helper.\n */\nexport type ApiClient = <E extends keyof APIEndpoints>(\n params: APICallParams<E>\n) => Promise<ApiResult<ApiResponseFor<E>>>;\n\nexport interface ApiRequestErrorOptions {\n endpoint: keyof APIEndpoints;\n status: number;\n method: HTTPMethod;\n auth?: ApiAuthRequirement;\n requestId?: string;\n body?: unknown;\n cause?: unknown;\n /** Typed error code from envelope error responses */\n errorCode?: ApiErrorCode;\n /** Retry delay from Retry-After header, in milliseconds */\n retryAfterMs?: number;\n}\n\nexport class ApiRequestError extends Error {\n readonly endpoint: keyof APIEndpoints;\n readonly status: number;\n readonly method: HTTPMethod;\n readonly auth?: ApiAuthRequirement;\n readonly requestId?: string;\n readonly body?: unknown;\n readonly cause?: unknown;\n /** Typed error code from envelope error responses (e.g., 'auth:forbidden', 'validation:invalid_input') */\n readonly errorCode?: ApiErrorCode;\n /** Retry delay from Retry-After header, in milliseconds */\n readonly retryAfterMs?: number;\n\n constructor(message: string, options: ApiRequestErrorOptions) {\n super(message);\n this.name = 'ApiRequestError';\n this.endpoint = options.endpoint;\n this.status = options.status;\n this.method = options.method;\n this.auth = options.auth;\n this.requestId = options.requestId;\n this.body = options.body;\n this.cause = options.cause;\n this.errorCode = options.errorCode;\n this.retryAfterMs = options.retryAfterMs;\n }\n}\n\n/**\n * Parse the Retry-After header value to milliseconds.\n * The header can be either:\n * - A number of seconds (e.g., \"120\")\n * - An HTTP-date (e.g., \"Wed, 21 Oct 2025 07:28:00 GMT\")\n *\n * @returns milliseconds to wait, or undefined if header is missing/invalid.\n * Returns 0 for \"Retry-After: 0\" (meaning retry immediately).\n */\nexport function parseRetryAfterHeader(headerValue: string | null): number | undefined {\n if (!headerValue) return undefined;\n\n // Try parsing as seconds first (most common)\n // Use strict regex to ensure entire string is numeric (not \"120abc\")\n if (/^\\d+$/.test(headerValue)) {\n const seconds = parseInt(headerValue, 10);\n return seconds * 1000;\n }\n\n // Try parsing as HTTP-date\n const date = new Date(headerValue);\n if (!isNaN(date.getTime())) {\n const delayMs = date.getTime() - Date.now();\n // Only return if the date is in the future\n return delayMs > 0 ? delayMs : undefined;\n }\n\n return undefined;\n}\n\n// Helper function to build full URL\nfunction buildEndpointURL(baseURL: string, endpoint: keyof APIEndpoints): string {\n return baseURL + API_ENDPOINTS[endpoint].path;\n}\n\nexport const getApiUrl = (endpoint: keyof APIEndpoints) => buildEndpointURL(resolveApiBaseUrl(), endpoint);\n\n// Simple cache invalidation helper\nfunction invalidateCacheTags(tags?: string[], params?: Record<string, string | number>): void {\n // Only run on the server, and only when Next.js provides a revalidateTag function\n if (typeof window !== 'undefined' || !revalidateTag || !tags) return;\n\n tags.forEach(tag => {\n // Replace parameter placeholders in tags\n let processedTag = tag;\n if (params) {\n Object.entries(params).forEach(([key, value]) => {\n processedTag = processedTag.replace(`{${key}}`, String(value));\n });\n }\n\n try {\n revalidateTag(processedTag, 'max');\n } catch {\n // If static generation store is not available in this context, skip\n }\n });\n}\n\nexport async function parseErrorBody(response: Response): Promise<unknown> {\n const clone = response.clone();\n const contentType = clone.headers.get('content-type') ?? '';\n if (contentType.includes('application/json')) {\n try {\n return await clone.json();\n } catch {\n // fall through to attempt plain text parsing\n }\n }\n\n try {\n const text = await clone.text();\n return text.length ? text : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Build a success envelope response.\n */\nfunction buildSuccessEnvelope<T>(data: T, requestId: string | undefined): ApiResponse<T> {\n return {\n success: true,\n data,\n meta: {\n requestId: requestId ?? generateRequestId(),\n timestamp: new Date().toISOString(),\n apiVersion: '2025-01-01',\n },\n };\n}\n\n/**\n * Build an error envelope response.\n */\nfunction buildErrorEnvelope(\n code: ApiErrorCode,\n message: string,\n status: number,\n requestId: string | undefined,\n): ApiError {\n return {\n success: false,\n error: {\n code,\n message,\n requestId: requestId ?? generateRequestId(),\n timestamp: new Date().toISOString(),\n status,\n },\n };\n}\n\n/**\n * Parse a successful HTTP response and return an ApiResult envelope.\n *\n * This function ALWAYS returns an ApiResult - it never throws for API-level errors.\n * HTTP-level errors (network failures, JSON parse errors) still throw ApiRequestError.\n */\nexport async function parseSuccessResponse<E extends keyof APIEndpoints>(\n endpoint: E,\n response: Response,\n config: APIEndpoints[E],\n): Promise<ApiResult<ApiResponseFor<E>>> {\n const responseKind: ApiResponseKind = config.responseKind ?? 'json';\n const auth = config.auth ?? 'user';\n const requestId = response.headers.get('x-request-id') ?? undefined;\n\n switch (responseKind) {\n case 'json': {\n // Empty responses\n if (response.status === 204 || response.status === 205 || response.status === 304) {\n return buildSuccessEnvelope(undefined as ApiResponseFor<E>, requestId);\n }\n\n const raw = await response.text();\n if (!raw.trim()) {\n return buildSuccessEnvelope(undefined as ApiResponseFor<E>, requestId);\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (cause) {\n // JSON parse error is a fatal error - throw, don't return envelope\n throw new ApiRequestError(\n `Failed to parse JSON response for endpoint ${String(endpoint)}`,\n {\n endpoint,\n status: response.status,\n method: config.method,\n auth,\n requestId,\n body: raw,\n cause,\n },\n );\n }\n\n // Check if response is already in envelope format\n if (\n parsed &&\n typeof parsed === 'object' &&\n 'success' in parsed &&\n typeof (parsed as { success: unknown }).success === 'boolean'\n ) {\n // Already an envelope - return as-is (properly typed)\n return parsed as ApiResult<ApiResponseFor<E>>;\n }\n\n // Legacy format - wrap in success envelope\n return buildSuccessEnvelope(parsed as ApiResponseFor<E>, requestId);\n }\n case 'text': {\n const text = await response.text();\n return buildSuccessEnvelope(text as ApiResponseFor<E>, requestId);\n }\n case 'stream': {\n const body = response.body;\n if (!body) {\n return buildErrorEnvelope(\n 'server:internal_error',\n `Expected a streamed body for endpoint ${String(endpoint)}`,\n response.status,\n requestId,\n );\n }\n const stream = body as ReadableStream<Uint8Array>;\n return buildSuccessEnvelope(stream as unknown as ApiResponseFor<E>, requestId);\n }\n case 'void': {\n return buildSuccessEnvelope(undefined as ApiResponseFor<E>, requestId);\n }\n default: {\n return buildSuccessEnvelope(undefined as ApiResponseFor<E>, requestId);\n }\n }\n}\n\nexport async function createAuthenticatedApiClient(supabase: SupabaseClient): Promise<ApiClient> {\n const authHeaders = await getAuthHeaders(supabase)\n\n if (!authHeaders) {\n throw new Error('No authentication token available')\n }\n \n return createCMSClient(authHeaders);\n}\n\nfunction createRawCMSClient(headers: AuthHeaders | {} = {}, baseUrl?: string): RawApiClient {\n return <E extends keyof APIEndpoints>({\n endpoint,\n body,\n params,\n options = {}\n }: APICallParams<E>): Promise<Response> => {\n const resolvedBaseUrl = baseUrl ?? resolveApiBaseUrl();\n let url = buildEndpointURL(resolvedBaseUrl, endpoint);\n const originalPath = API_ENDPOINTS[endpoint].path;\n\n // Handle URL path parameters and collect unused params for query string\n const unusedParams: Record<string, string> = {};\n if (params) {\n Object.entries(params).forEach(([key, value]) => {\n const placeholder = `{${key}}`;\n if (originalPath.includes(placeholder)) {\n // This param is used in the path - substitute it\n url = url.replace(placeholder, value);\n } else {\n // This param is not used in the path - save for query string\n unusedParams[key] = value;\n }\n });\n }\n\n const endpointConfig = API_ENDPOINTS[endpoint];\n const method = endpointConfig.method as HTTPMethod;\n const isGetOrHead = method === 'GET' || method === 'HEAD';\n\n // For GET/HEAD requests, convert body and unused params to query parameters\n if (isGetOrHead) {\n const queryParams = new URLSearchParams();\n\n // Add unused params (not used in path) to query string\n Object.entries(unusedParams).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n queryParams.append(key, String(value));\n }\n });\n\n // Add body params to query string\n if (body) {\n Object.entries(body as Record<string, any>).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n queryParams.append(key, String(value));\n }\n });\n }\n\n const queryString = queryParams.toString();\n if (queryString) {\n url += (url.includes('?') ? '&' : '?') + queryString;\n }\n }\n // Detect if body is FormData (for file uploads)\n const isFormData = typeof FormData !== 'undefined' && body instanceof FormData;\n \n // Build Next.js cache options for read operations\n const nextOptions: { revalidate?: number; tags?: string[] } = {};\n if (\n isGetOrHead &&\n 'revalidate' in endpointConfig &&\n typeof endpointConfig.revalidate === 'number'\n ) {\n nextOptions.revalidate = endpointConfig.revalidate;\n }\n if ('tags' in endpointConfig && Array.isArray(endpointConfig.tags)) {\n // Process tags with parameter replacement\n nextOptions.tags = endpointConfig.tags.map<string>((tag) => {\n let processedTag = tag as string;\n if (params) {\n Object.entries(params).forEach(([key, value]) => {\n processedTag = processedTag.replace(`{${key}}`, String(value));\n });\n }\n return processedTag;\n });\n }\n \n const requestInit: RequestInit & { next?: { revalidate?: number; tags?: string[] } } = {\n method,\n ...options,\n // Include credentials for same-origin requests (sends cookies for auth)\n credentials: 'same-origin',\n // Don't include body for GET/HEAD requests\n body: isGetOrHead ? undefined : (isFormData ? (body as unknown as BodyInit) : (body ? JSON.stringify(body) : undefined)),\n headers: {\n ...options.headers,\n ...headers,\n // Include SDK version if set\n ...(sdkVersion && { 'x-sdk-version': sdkVersion }),\n // Don't set Content-Type for GET/HEAD requests without body\n ...(isGetOrHead ? {} : (isFormData ? {} : { 'Content-Type': 'application/json' })),\n },\n // Add Next.js caching options\n next: Object.keys(nextOptions).length > 0 ? nextOptions : undefined,\n };\n\n // Make the fetch request\n const fetchPromise = fetch(url, requestInit);\n \n // For mutation requests, invalidate cache after successful response\n if (!isGetOrHead && 'tags' in endpointConfig && Array.isArray(endpointConfig.tags)) {\n const tags = endpointConfig.tags.map((tag) => tag as string);\n return fetchPromise.then(response => {\n if (response.ok) {\n invalidateCacheTags(tags, params);\n }\n return response;\n });\n }\n \n return fetchPromise;\n }\n}\n\nfunction createParsedClient(rawClient: RawApiClient): ApiClient {\n return async <E extends keyof APIEndpoints>(params: APICallParams<E>) => {\n const response = await rawClient(params);\n const endpoint = params.endpoint;\n const config = API_ENDPOINTS[endpoint];\n const auth = config.auth ?? 'user';\n\n if (!response.ok) {\n const body = await parseErrorBody(response);\n const requestId = response.headers.get('x-request-id') ?? undefined;\n const retryAfterMs = parseRetryAfterHeader(response.headers.get('retry-after'));\n throw new ApiRequestError(\n `Request to ${String(endpoint)} failed with status ${response.status}`,\n {\n endpoint,\n status: response.status,\n method: config.method,\n auth,\n requestId,\n body,\n retryAfterMs,\n },\n );\n }\n\n return parseSuccessResponse(endpoint, response, config);\n };\n}\n\nexport function createCMSClient(headers: AuthHeaders | {} = {}, baseUrl?: string): ApiClient {\n return createParsedClient(createRawCMSClient(headers, baseUrl));\n}\n\nexport const createServerAPIClient = (apiKey: string, baseUrl?: string): ApiClient => {\n const normalizedApiKey = normalizeCmsApiKey(apiKey);\n\n if (!normalizedApiKey) {\n throw new Error('No API key provided for server API client')\n }\n const authHeaders = {\n Authorization: `Bearer ${normalizedApiKey}`,\n }\n return createCMSClient(authHeaders, baseUrl);\n}\n\nexport function createInternalAPIClient(baseUrl?: string): ApiClient {\n return createCMSClient({}, baseUrl);\n}\n\n// Bearer-token client for preview-auth flows\nexport function createBearerAPIClient(token: string, baseUrl?: string): ApiClient {\n const authHeaders = {\n Authorization: `Bearer ${token}`,\n };\n return createCMSClient(authHeaders, baseUrl);\n}\n","/**\n * API Response Envelope Types\n *\n * Provides standardized response format for all API endpoints:\n * - Success: { success: true, data, meta }\n * - Error: { success: false, error: { code, message, requestId, ... } }\n */\n\n// API version - update when breaking changes are made\nexport type ApiVersion = '2025-01-01';\nexport const CURRENT_API_VERSION: ApiVersion = '2025-01-01';\n\n// Response metadata\nexport interface ResponseMeta {\n requestId: string;\n timestamp: string;\n apiVersion: ApiVersion;\n deprecations?: DeprecationWarning[];\n}\n\nexport interface DeprecationWarning {\n feature: string;\n message: string;\n sunsetDate: string;\n alternative?: string;\n}\n\n// Success envelope\nexport interface ApiResponse<TData> {\n success: true;\n data: TData;\n meta: ResponseMeta;\n}\n\n// Error codes - namespaced by domain\nexport type ApiErrorCode =\n // Auth (401, 403)\n | 'auth:unauthenticated'\n | 'auth:token_expired'\n | 'auth:token_invalid'\n | 'auth:forbidden'\n | 'auth:mfa_required'\n | 'auth:sudo_required'\n | 'auth:insufficient_permissions'\n | 'auth:impersonation_restricted'\n // Validation (400, 413)\n | 'validation:invalid_input'\n | 'validation:missing_field'\n | 'validation:invalid_format'\n | 'validation:payload_too_large'\n // Resources (404, 409, 410)\n | 'resource:not_found'\n | 'resource:already_exists'\n | 'resource:conflict'\n | 'resource:gone'\n // Rate limiting (429)\n | 'rate_limit:exceeded'\n // Billing (402)\n | 'billing:payment_required'\n | 'billing:plan_limit_exceeded'\n // Server (500+)\n | 'server:internal_error'\n | 'server:unavailable'\n // External service errors (502)\n | 'external:service_error'\n // Network errors (no HTTP response - status 0)\n | 'network:connection_error'\n | 'network:timeout'\n | 'network:dns_error';\n\n// Field-level validation error\nexport interface FieldError {\n field: string;\n code: string;\n message: string;\n}\n\n// Error response\nexport interface ApiError {\n success: false;\n error: {\n code: ApiErrorCode;\n message: string;\n requestId: string;\n timestamp: string;\n status: number;\n fieldErrors?: FieldError[];\n };\n}\n\n// Union type for any response\nexport type ApiResult<TData> = ApiResponse<TData> | ApiError;\n\n// Type guards\nexport function isApiError(result: ApiResult<unknown>): result is ApiError {\n return result.success === false;\n}\n\nexport function isApiSuccess<T>(result: ApiResult<T>): result is ApiResponse<T> {\n return result.success === true;\n}\n\n/**\n * Error class for API failures that can be thrown from unwrapResponse.\n * Preserves all error metadata from the envelope.\n */\nexport class ApiEnvelopeError extends Error {\n readonly code: ApiErrorCode;\n readonly requestId: string;\n readonly timestamp: string;\n readonly status: number;\n readonly fieldErrors?: FieldError[];\n\n constructor(error: ApiError['error']) {\n super(error.message);\n this.name = 'ApiEnvelopeError';\n this.code = error.code;\n this.requestId = error.requestId;\n this.timestamp = error.timestamp;\n this.status = error.status;\n this.fieldErrors = error.fieldErrors;\n }\n}\n\n/**\n * Unwrap a successful API response or throw on error.\n *\n * @example\n * ```typescript\n * const response = await apiClient({ endpoint: 'getUser', params: { id } });\n * const user = unwrapResponse(response); // Throws if not success\n * ```\n */\nexport function unwrapResponse<T>(result: ApiResult<T>): T {\n if (isApiSuccess(result)) {\n return result.data;\n }\n throw new ApiEnvelopeError(result.error);\n}\n\n/**\n * Unwrap a successful API response or return a default value on error.\n *\n * @example\n * ```typescript\n * const response = await apiClient({ endpoint: 'getUser', params: { id } });\n * const user = unwrapResponseOr(response, null);\n * ```\n */\nexport function unwrapResponseOr<T>(result: ApiResult<T>, defaultValue: T): T {\n return isApiSuccess(result) ? result.data : defaultValue;\n}\n","/**\n * Simple in-memory cache with TTL and stale support\n *\n * Supports:\n * - Fresh entries: Within TTL, returned immediately\n * - Stale entries: Past TTL but within staleTtl, returned as fallback\n * - Automatic eviction: Stale entries evicted first, then oldest fresh\n */\n\n/**\n * Cache entry with freshness and stale windows\n */\ninterface CacheEntry<T> {\n value: T;\n /** Timestamp when entry was created */\n createdAt: number;\n /** Timestamp until which entry is fresh */\n freshUntil: number;\n /** Timestamp until which entry can be served as stale */\n staleUntil: number;\n}\n\n/**\n * Result from getStale() with age information\n */\nexport interface StaleResult<T> {\n value: T;\n /** Seconds since the entry's TTL expired (0 if still fresh) */\n staleAgeSec: number;\n}\n\n/**\n * Options for cache.set()\n */\nexport interface CacheSetOptions {\n /** Time-to-live in milliseconds (overrides default) */\n ttl?: number;\n /** Additional time after TTL during which stale data can be served (overrides default) */\n staleTtl?: number;\n}\n\nexport class SimpleCache<T> {\n private cache = new Map<string, CacheEntry<T>>();\n private maxSize: number;\n private ttl: number;\n private staleTtl: number;\n\n constructor(options: { maxSize?: number; ttl?: number; staleTtl?: number } = {}) {\n this.maxSize = options.maxSize ?? 100;\n this.ttl = options.ttl ?? 300000; // 5 minutes in milliseconds\n this.staleTtl = options.staleTtl ?? 300000; // 5 minutes additional stale window\n }\n\n /**\n * Get a fresh value (within TTL)\n * @returns The value if fresh, null otherwise\n */\n getFresh(key: string): T | null {\n const entry = this.cache.get(key);\n if (!entry) return null;\n\n const now = Date.now();\n if (now <= entry.freshUntil) {\n return entry.value;\n }\n\n return null;\n }\n\n /**\n * Get a value that may be stale (past TTL but within staleTtl)\n * @returns Object with value and stale age, or null if expired\n */\n getStale(key: string): StaleResult<T> | null {\n const entry = this.cache.get(key);\n if (!entry) return null;\n\n const now = Date.now();\n\n // Completely expired\n if (now > entry.staleUntil) {\n this.cache.delete(key);\n return null;\n }\n\n // Calculate stale age (0 if still fresh)\n const staleAgeSec = now <= entry.freshUntil\n ? 0\n : Math.floor((now - entry.freshUntil) / 1000);\n\n return {\n value: entry.value,\n staleAgeSec,\n };\n }\n\n /**\n * Store a value with TTL and stale window\n */\n set(key: string, value: T, options?: CacheSetOptions): void {\n const ttl = options?.ttl ?? this.ttl;\n const staleTtl = options?.staleTtl ?? this.staleTtl;\n const now = Date.now();\n\n // Enforce max size with smart eviction.\n // Use while loop because evictOne() may not reduce size if the cache\n // contains only non-expired entries and evictOne finds nothing to remove\n // (defensive edge case - shouldn't happen in normal operation).\n while (this.cache.size >= this.maxSize && !this.cache.has(key)) {\n this.evictOne(now);\n }\n\n this.cache.set(key, {\n value,\n createdAt: now,\n freshUntil: now + ttl,\n staleUntil: now + ttl + staleTtl,\n });\n }\n\n /**\n * Evict one entry to make room for a new one\n * Priority: oldest stale entry, then oldest fresh entry\n */\n private evictOne(now: number): void {\n let oldestStaleKey: string | null = null;\n let oldestStaleTime = Infinity;\n let oldestFreshKey: string | null = null;\n let oldestFreshTime = Infinity;\n\n for (const [key, entry] of this.cache) {\n if (now > entry.freshUntil) {\n // Stale entry\n if (entry.createdAt < oldestStaleTime) {\n oldestStaleTime = entry.createdAt;\n oldestStaleKey = key;\n }\n } else {\n // Fresh entry\n if (entry.createdAt < oldestFreshTime) {\n oldestFreshTime = entry.createdAt;\n oldestFreshKey = key;\n }\n }\n }\n\n // Evict oldest stale first, then oldest fresh\n const keyToEvict = oldestStaleKey ?? oldestFreshKey;\n if (keyToEvict) {\n this.cache.delete(keyToEvict);\n }\n }\n\n /**\n * Remove all fully expired entries (past staleUntil)\n */\n prune(): void {\n const now = Date.now();\n for (const [key, entry] of this.cache) {\n if (now > entry.staleUntil) {\n this.cache.delete(key);\n }\n }\n }\n\n /**\n * Clear all entries\n */\n clear(): void {\n this.cache.clear();\n }\n\n /**\n * Check if a key exists and is not fully expired\n */\n has(key: string): boolean {\n const entry = this.cache.get(key);\n if (!entry) return false;\n\n const now = Date.now();\n if (now > entry.staleUntil) {\n this.cache.delete(key);\n return false;\n }\n\n return now <= entry.freshUntil;\n }\n}\n","/**\n * SDK version - MUST match version in package.json\n *\n * This version is sent to the API in the x-sdk-version header\n * to help with debugging and compatibility tracking.\n *\n * IMPORTANT: When releasing a new SDK version, update BOTH:\n * 1. This constant\n * 2. The \"version\" field in package.json\n */\nexport const SDK_VERSION = '0.7.3';\n","/**\n * SDK API Error Class\n *\n * Thrown when the API returns an error response with the new envelope format.\n * Provides typed access to error details including code, message, request ID, and field errors.\n */\n\nimport type { ApiErrorCode, FieldError, ApiError } from '@riverbankcms/api/public';\n\n/**\n * Error options for creating RiverbankApiError\n */\nexport interface RiverbankApiErrorOptions {\n code: ApiErrorCode;\n message: string;\n requestId: string;\n status: number;\n timestamp: string;\n fieldErrors?: FieldError[];\n /** Retry delay from Retry-After header, in milliseconds */\n retryAfterMs?: number;\n}\n\n/**\n * Error thrown by SDK when API returns an error response\n *\n * @example\n * ```ts\n * try {\n * await client.getSite({ slug: 'my-site' });\n * } catch (error) {\n * if (error instanceof RiverbankApiError) {\n * console.log('Error code:', error.code);\n * console.log('Request ID:', error.requestId);\n *\n * if (error.isAuthError()) {\n * // Handle authentication error\n * }\n *\n * if (error.isValidationError() && error.fieldErrors) {\n * // Handle field-level validation errors\n * error.fieldErrors.forEach(fe => {\n * console.log(`${fe.field}: ${fe.message}`);\n * });\n * }\n *\n * // Check if error can be retried\n * if (error.isRetryable) {\n * const delay = error.retryAfterMs ?? 1000;\n * console.log(`Retrying after ${delay}ms...`);\n * }\n * }\n * }\n * ```\n */\nexport class RiverbankApiError extends Error {\n /** Namespaced error code (e.g., 'auth:unauthenticated', 'validation:invalid_input') */\n readonly code: ApiErrorCode;\n\n /** Unique request ID for debugging/support */\n readonly requestId: string;\n\n /** HTTP status code */\n readonly status: number;\n\n /** Field-level validation errors (only present for validation errors) */\n readonly fieldErrors?: FieldError[];\n\n /** ISO timestamp when the error occurred */\n readonly timestamp: string;\n\n /**\n * Whether this error is safe to retry.\n * True for: 429 rate limit, 5xx server errors\n * False for: 4xx client errors (except 429)\n */\n readonly isRetryable: boolean;\n\n /**\n * Suggested retry delay from Retry-After header, in milliseconds.\n * Only present when the server provides a Retry-After header.\n */\n readonly retryAfterMs?: number;\n\n constructor(apiError: ApiError['error'] | RiverbankApiErrorOptions) {\n super(apiError.message);\n this.name = 'RiverbankApiError';\n this.code = apiError.code;\n this.requestId = apiError.requestId;\n this.status = apiError.status;\n this.fieldErrors = apiError.fieldErrors;\n this.timestamp = apiError.timestamp;\n this.retryAfterMs = 'retryAfterMs' in apiError ? apiError.retryAfterMs : undefined;\n this.isRetryable = this.computeRetryable();\n\n // Maintain proper prototype chain for instanceof checks\n Object.setPrototypeOf(this, RiverbankApiError.prototype);\n }\n\n /**\n * Compute whether this error is retryable based on HTTP status code.\n * - 0 (network errors - no HTTP response): retryable\n * - 429 (rate limit): retryable\n * - 5xx (server errors): retryable\n * - 4xx (client errors, except 429): NOT retryable\n */\n private computeRetryable(): boolean {\n // Network errors (no HTTP response) are retryable\n if (this.status === 0) return true;\n // Rate limiting is retryable\n if (this.status === 429) return true;\n // Server errors are retryable\n if (this.status >= 500) return true;\n // Client errors (4xx except 429) are not retryable\n return false;\n }\n\n /**\n * Check if this is a network error (no HTTP response received)\n *\n * Matches: network:connection_error, network:timeout, network:dns_error\n */\n isNetworkError(): boolean {\n return this.code.startsWith('network:');\n }\n\n /**\n * Check if this error matches a specific error code\n *\n * @example\n * ```ts\n * if (error.is('auth:unauthenticated')) {\n * // Redirect to login\n * }\n * ```\n */\n is(code: ApiErrorCode): boolean {\n return this.code === code;\n }\n\n /**\n * Check if this is an authentication or authorization error\n *\n * Matches: auth:unauthenticated, auth:token_expired, auth:token_invalid,\n * auth:forbidden, auth:mfa_required, auth:insufficient_permissions\n */\n isAuthError(): boolean {\n return this.code.startsWith('auth:');\n }\n\n /**\n * Check if this is a validation error\n *\n * Matches: validation:invalid_input, validation:missing_field, validation:invalid_format\n */\n isValidationError(): boolean {\n return this.code.startsWith('validation:');\n }\n\n /**\n * Check if this is a resource error (not found, conflict, etc.)\n *\n * Matches: resource:not_found, resource:already_exists, resource:conflict, resource:gone\n */\n isResourceError(): boolean {\n return this.code.startsWith('resource:');\n }\n\n /**\n * Check if this is a rate limiting error\n */\n isRateLimitError(): boolean {\n return this.code.startsWith('rate_limit:');\n }\n\n /**\n * Check if this is a billing/payment error\n */\n isBillingError(): boolean {\n return this.code.startsWith('billing:');\n }\n\n /**\n * Check if this is a server error\n */\n isServerError(): boolean {\n return this.code.startsWith('server:');\n }\n}\n","/**\n * Resilience utilities for the SDK client\n *\n * Provides:\n * - Circuit breaker pattern for fail-fast behavior\n * - Retry with exponential backoff and jitter\n * - Timeout handling\n */\n\nimport type { RetryConfig, CircuitBreakerConfig, CircuitState } from './types';\nimport { RiverbankApiError } from './error';\n\n// ============================================================================\n// Default Configurations\n// ============================================================================\n\nexport const DEFAULT_RETRY_CONFIG: Required<Omit<RetryConfig, 'retryOn'>> = {\n maxAttempts: 3,\n baseDelayMs: 200,\n maxDelayMs: 2000,\n jitter: 'full',\n};\n\nexport const DEFAULT_CIRCUIT_BREAKER_CONFIG: Required<CircuitBreakerConfig> = {\n failureThreshold: 5,\n resetTimeoutMs: 30000,\n halfOpenMaxRequests: 2,\n};\n\n// ============================================================================\n// Error Classification\n// ============================================================================\n\n/**\n * Determine if an error is transient and should trigger circuit breaker / retry\n *\n * Transient (opens circuit, triggers retry):\n * - Network errors (status 0)\n * - 5xx server errors\n * - 429 rate limit\n *\n * Permanent (does NOT open circuit, no retry):\n * - 4xx client errors (400, 401, 403, 404)\n * - Validation errors\n */\nexport function isTransientError(error: Error): boolean {\n if (error instanceof RiverbankApiError) {\n // Network errors are transient\n if (error.status === 0) return true;\n\n // 429 rate limit is transient\n if (error.status === 429) return true;\n\n // 5xx server errors are transient\n if (error.status >= 500) return true;\n\n // 4xx client errors are permanent\n return false;\n }\n\n // Generic errors (TypeError, etc.) are treated as transient (likely network issues)\n return true;\n}\n\n// ============================================================================\n// Backoff Calculation\n// ============================================================================\n\n/**\n * Calculate backoff delay for retry attempt\n *\n * Uses exponential backoff with optional full jitter.\n * Formula: min(maxDelay, baseDelay * 2^(attempt-1)) * random()\n */\nexport function calculateBackoff(\n attempt: number,\n config: Partial<Pick<RetryConfig, 'baseDelayMs' | 'maxDelayMs' | 'jitter'>>\n): number {\n const baseDelayMs = config.baseDelayMs ?? DEFAULT_RETRY_CONFIG.baseDelayMs;\n const maxDelayMs = config.maxDelayMs ?? DEFAULT_RETRY_CONFIG.maxDelayMs;\n const jitter = config.jitter ?? DEFAULT_RETRY_CONFIG.jitter;\n\n // Exponential: baseDelay * 2^(attempt-1)\n const exponential = baseDelayMs * Math.pow(2, attempt - 1);\n\n // Cap at maxDelay\n const capped = Math.min(exponential, maxDelayMs);\n\n // Apply jitter\n if (jitter === 'full') {\n return Math.random() * capped;\n }\n\n return capped;\n}\n\n// ============================================================================\n// Circuit Breaker\n// ============================================================================\n\n/**\n * Circuit breaker for fail-fast behavior\n *\n * States:\n * - closed: Normal operation, requests allowed\n * - open: Too many failures, requests blocked\n * - half-open: Testing if service recovered, limited requests allowed\n */\nexport class CircuitBreaker {\n private state: 'closed' | 'open' | 'half-open' = 'closed';\n private failureCount = 0;\n private successCount = 0;\n private openUntil = 0;\n private halfOpenRequests = 0;\n private config: Required<CircuitBreakerConfig>;\n\n constructor(config?: CircuitBreakerConfig) {\n this.config = {\n failureThreshold: config?.failureThreshold ?? DEFAULT_CIRCUIT_BREAKER_CONFIG.failureThreshold,\n resetTimeoutMs: config?.resetTimeoutMs ?? DEFAULT_CIRCUIT_BREAKER_CONFIG.resetTimeoutMs,\n halfOpenMaxRequests: config?.halfOpenMaxRequests ?? DEFAULT_CIRCUIT_BREAKER_CONFIG.halfOpenMaxRequests,\n };\n }\n\n /**\n * Check if circuit is open (requests should be blocked)\n * Also handles automatic transition from open to half-open after timeout\n */\n isOpen(): boolean {\n if (this.state === 'open' && Date.now() >= this.openUntil) {\n this.transitionTo('half-open');\n }\n return this.state === 'open';\n }\n\n /**\n * Check if a request can be attempted\n * - closed: always yes\n * - open: always no\n * - half-open: limited number of probes\n */\n canAttempt(): boolean {\n if (this.state === 'closed') return true;\n if (this.state === 'open') return false;\n // half-open: allow limited probes\n return this.halfOpenRequests < this.config.halfOpenMaxRequests;\n }\n\n /**\n * Increment half-open request counter (call before making request in half-open)\n */\n incrementHalfOpenRequests(): void {\n if (this.state === 'half-open') {\n this.halfOpenRequests++;\n }\n }\n\n /**\n * Record a successful request\n */\n recordSuccess(): void {\n if (this.state === 'half-open') {\n this.successCount++;\n if (this.successCount >= this.config.halfOpenMaxRequests) {\n this.transitionTo('closed');\n }\n } else {\n // Reset consecutive failures on any success\n this.failureCount = 0;\n }\n }\n\n /**\n * Record a failed request\n * Only counts transient failures toward circuit breaker threshold\n */\n recordFailure(error: Error): void {\n // Only count transient failures\n if (!isTransientError(error)) return;\n\n this.failureCount++;\n\n if (this.state === 'half-open') {\n this.transitionTo('open');\n } else if (this.failureCount >= this.config.failureThreshold) {\n this.transitionTo('open');\n }\n }\n\n /**\n * Get current circuit state\n */\n getState(): CircuitState {\n return {\n state: this.state,\n failureCount: this.failureCount,\n openUntil: this.state === 'open' ? this.openUntil : undefined,\n };\n }\n\n /**\n * Transition to a new state\n */\n private transitionTo(newState: 'closed' | 'open' | 'half-open'): void {\n this.state = newState;\n\n if (newState === 'open') {\n this.openUntil = Date.now() + this.config.resetTimeoutMs;\n } else if (newState === 'half-open') {\n this.halfOpenRequests = 0;\n this.successCount = 0;\n } else if (newState === 'closed') {\n this.failureCount = 0;\n this.successCount = 0;\n this.halfOpenRequests = 0;\n }\n }\n}\n\n// ============================================================================\n// Retry with Timeout\n// ============================================================================\n\n/**\n * Configuration for fetchWithTimeoutAndRetry\n */\nexport interface FetchWithRetryConfig {\n maxAttempts?: number;\n baseDelayMs?: number;\n maxDelayMs?: number;\n jitter?: 'full' | 'none';\n requestTimeoutMs?: number;\n retryOn?: (error: Error, statusCode?: number) => boolean;\n}\n\n/**\n * Fetch with timeout and retry logic\n *\n * @param fetcher - Function that performs the fetch, receives AbortSignal for timeout\n * @param config - Retry configuration\n * @returns Result from successful fetch\n * @throws Last error after all retries exhausted\n */\nexport async function fetchWithTimeoutAndRetry<T>(\n fetcher: (signal: AbortSignal) => Promise<T>,\n config: FetchWithRetryConfig\n): Promise<T> {\n const maxAttempts = config.maxAttempts ?? DEFAULT_RETRY_CONFIG.maxAttempts;\n const requestTimeoutMs = config.requestTimeoutMs ?? 8000;\n\n let lastError: Error | undefined;\n\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), requestTimeoutMs);\n\n try {\n const result = await fetcher(controller.signal);\n return result;\n } finally {\n clearTimeout(timeoutId);\n }\n } catch (error) {\n lastError = error as Error;\n\n // Check if we should retry\n const shouldRetry = shouldRetryError(error as Error, config.retryOn);\n\n if (!shouldRetry) {\n throw error;\n }\n\n // Don't delay after last attempt\n if (attempt < maxAttempts) {\n const delay = getRetryDelay(error as Error, attempt, config);\n await sleep(delay);\n }\n }\n }\n\n throw lastError;\n}\n\n/**\n * Check if an error should be retried\n */\nfunction shouldRetryError(\n error: Error,\n customRetryOn?: (error: Error, statusCode?: number) => boolean\n): boolean {\n // Custom retry logic takes precedence\n if (customRetryOn) {\n const statusCode = error instanceof RiverbankApiError ? error.status : undefined;\n return customRetryOn(error, statusCode);\n }\n\n // Default: retry transient errors\n return isTransientError(error);\n}\n\n/**\n * Get delay before retrying, respecting Retry-After if present\n */\nfunction getRetryDelay(error: Error, attempt: number, config: FetchWithRetryConfig): number {\n // Check for Retry-After from rate limiting\n if (error instanceof RiverbankApiError && error.retryAfterMs) {\n return error.retryAfterMs;\n }\n\n // Use exponential backoff\n return calculateBackoff(attempt, config);\n}\n\n/**\n * Sleep for specified milliseconds\n */\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n// ============================================================================\n// Circuit Open Error\n// ============================================================================\n\n/**\n * Error thrown when circuit breaker is open\n */\nexport class CircuitOpenError extends Error {\n readonly circuitState: CircuitState;\n\n constructor(state: CircuitState) {\n super('Circuit breaker is open');\n this.name = 'CircuitOpenError';\n this.circuitState = state;\n }\n}\n","import { createBearerAPIClient, setSdkVersion, ApiRequestError, isApiError, unwrapResponse, ApiEnvelopeError } from '@riverbankcms/api/public';\nimport type { ApiResult, ApiError } from '@riverbankcms/api/public';\nimport type {\n RiverbankClient,\n RiverbankClientConfig,\n GetEntriesBaseParams,\n ResilienceStatus,\n ResilienceSource,\n CircuitState,\n} from './types';\nimport { SimpleCache } from './cache';\nimport { SDK_VERSION } from '../version';\nimport { RiverbankApiError } from './error';\nimport {\n CircuitBreaker,\n CircuitOpenError,\n fetchWithTimeoutAndRetry,\n DEFAULT_RETRY_CONFIG,\n DEFAULT_CIRCUIT_BREAKER_CONFIG,\n} from './resilience';\n\n// Lazy-loaded prebuild module to avoid bundling Node.js 'fs' module in client builds.\n// The prebuild loader is only used server-side for fallback content.\nlet prebuildModule: typeof import('../prebuild/loader') | null = null;\n\nfunction getPrebuildModule(): typeof import('../prebuild/loader') | null {\n if (prebuildModule !== null) return prebuildModule;\n\n // Only attempt to load in Node.js environments\n if (typeof process === 'undefined' || !process.versions?.node) {\n return null;\n }\n\n try {\n // Use require for synchronous loading in Node.js (avoids static bundling)\n prebuildModule = require('../prebuild/loader');\n return prebuildModule;\n } catch {\n return null;\n }\n}\n\n// Set SDK version for all API requests from this SDK\nsetSdkVersion(SDK_VERSION);\n\n// Default timeout values - shorter for browsers (user is waiting), longer for servers\nconst DEFAULT_BROWSER_TIMEOUT_MS = 5000;\nconst DEFAULT_SERVER_TIMEOUT_MS = 8000;\n\n/**\n * Generate a unique request ID for tracing\n */\nfunction generateRequestId(): string {\n return `req-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n}\n\n/**\n * Check if an error is an AbortError (from AbortController.abort())\n */\nfunction isAbortError(error: unknown): boolean {\n // DOMException with name 'AbortError' (browsers)\n if (error instanceof DOMException && error.name === 'AbortError') {\n return true;\n }\n // Node.js AbortError\n if (error instanceof Error && error.name === 'AbortError') {\n return true;\n }\n return false;\n}\n\n/**\n * Determine the network error code based on error type, code, and message.\n * Uses multiple detection strategies for cross-runtime compatibility:\n * 1. Error name (TimeoutError, AbortError) - reliable in browsers\n * 2. Node.js error code (ETIMEDOUT, ENOTFOUND, etc.) - most reliable in Node\n * 3. Message matching - fallback for edge cases\n */\nfunction getNetworkErrorCode(error: Error): 'network:connection_error' | 'network:timeout' | 'network:dns_error' {\n // Check error name first (reliable for browser and some Node errors)\n if (error.name === 'TimeoutError' || error.name === 'AbortError') {\n return 'network:timeout';\n }\n\n // Check Node.js error code (most reliable for Node.js)\n const nodeError = error as NodeJS.ErrnoException;\n if (nodeError.code) {\n switch (nodeError.code) {\n case 'ETIMEDOUT':\n case 'ESOCKETTIMEDOUT':\n return 'network:timeout';\n case 'ENOTFOUND':\n case 'EAI_AGAIN':\n return 'network:dns_error';\n case 'ECONNREFUSED':\n case 'ECONNRESET':\n case 'EPIPE':\n return 'network:connection_error';\n }\n }\n\n // Fallback to message matching for browsers and edge cases\n const message = error.message.toLowerCase();\n if (message.includes('timeout') || message.includes('timed out')) {\n return 'network:timeout';\n }\n if (message.includes('dns') || message.includes('getaddrinfo') || message.includes('enotfound')) {\n return 'network:dns_error';\n }\n\n return 'network:connection_error';\n}\n\n/**\n * Convert API errors to RiverbankApiError for consistent SDK error handling.\n * Handles:\n * - ApiEnvelopeError (from unwrapResponse)\n * - ApiRequestError (from fetch with HTTP error)\n * - Network errors (TypeError, etc. - wrapped with status 0)\n * - AbortError (let propagate raw for consumer detection)\n */\nfunction convertToTypedError(error: unknown): never {\n // Let AbortError propagate raw - it's intentional cancellation, not an error to retry\n if (isAbortError(error)) {\n throw error;\n }\n\n // Handle ApiEnvelopeError from unwrapResponse (error envelope in successful HTTP response)\n if (error instanceof ApiEnvelopeError) {\n throw new RiverbankApiError({\n code: error.code,\n message: error.message,\n requestId: error.requestId,\n timestamp: error.timestamp,\n status: error.status,\n fieldErrors: error.fieldErrors,\n });\n }\n\n // Handle ApiRequestError with envelope body (HTTP error with envelope format)\n if (error instanceof ApiRequestError && error.body && typeof error.body === 'object') {\n const body = error.body as ApiResult<unknown>;\n if (isApiError(body)) {\n // Merge envelope error with retryAfterMs from ApiRequestError\n const envelopeError = (body as ApiError).error;\n throw new RiverbankApiError({\n ...envelopeError,\n retryAfterMs: error.retryAfterMs,\n });\n }\n }\n\n // Wrap network errors (TypeError: Failed to fetch, etc.) as RiverbankApiError\n // These are transient errors that should be retryable\n if (error instanceof TypeError || (error instanceof Error && !('status' in error))) {\n const networkError = error as Error;\n throw new RiverbankApiError({\n code: getNetworkErrorCode(networkError),\n message: networkError.message || 'Network request failed',\n requestId: `local-${Date.now()}`,\n timestamp: new Date().toISOString(),\n status: 0, // No HTTP response received\n });\n }\n\n // Re-throw original error if not in envelope format\n throw error;\n}\n\n\n/**\n * Create a Riverbank CMS client for fetching content\n *\n * @example\n * ```ts\n * const client = createRiverbankClient({\n * apiKey: 'bld_live_sk_...',\n * baseUrl: 'https://dashboard.example.com/api',\n * });\n *\n * const site = await client.getSite({ slug: 'my-site' });\n * ```\n */\nexport function createRiverbankClient(config: RiverbankClientConfig): RiverbankClient {\n if (!config.baseUrl) {\n throw new Error(\n 'baseUrl is required when creating a Riverbank client. ' +\n 'Expected format: https://dashboard.example.com/api (must include /api path)'\n );\n }\n\n // Validate baseUrl format\n if (!config.baseUrl.endsWith('/api')) {\n throw new Error(\n `baseUrl must end with '/api'. Received: ${config.baseUrl}. ` +\n 'Expected format: https://dashboard.example.com/api'\n );\n }\n\n // ============================================================================\n // Configuration\n // ============================================================================\n\n const cacheEnabled = config.cache?.enabled ?? true;\n const cacheTTL = (config.cache?.ttl ?? 300) * 1000; // Convert seconds to milliseconds\n const cacheMaxSize = config.cache?.maxSize ?? 100;\n\n // Resilience config with defaults\n const resilienceEnabled = config.resilience?.enabled ?? true;\n const staleIfError = config.resilience?.staleIfError ?? true;\n const staleTtlMs = (config.resilience?.staleTtlSec ?? 300) * 1000;\n const requestTimeoutMs = config.resilience?.requestTimeoutMs ??\n (typeof window !== 'undefined' ? DEFAULT_BROWSER_TIMEOUT_MS : DEFAULT_SERVER_TIMEOUT_MS);\n\n const retryConfig = {\n maxAttempts: config.resilience?.retry?.maxAttempts ?? DEFAULT_RETRY_CONFIG.maxAttempts,\n baseDelayMs: config.resilience?.retry?.baseDelayMs ?? DEFAULT_RETRY_CONFIG.baseDelayMs,\n maxDelayMs: config.resilience?.retry?.maxDelayMs ?? DEFAULT_RETRY_CONFIG.maxDelayMs,\n jitter: config.resilience?.retry?.jitter ?? DEFAULT_RETRY_CONFIG.jitter,\n retryOn: config.resilience?.retry?.retryOn,\n };\n\n const circuitBreakerConfig = {\n failureThreshold: config.resilience?.circuitBreaker?.failureThreshold ?? DEFAULT_CIRCUIT_BREAKER_CONFIG.failureThreshold,\n resetTimeoutMs: config.resilience?.circuitBreaker?.resetTimeoutMs ?? DEFAULT_CIRCUIT_BREAKER_CONFIG.resetTimeoutMs,\n halfOpenMaxRequests: config.resilience?.circuitBreaker?.halfOpenMaxRequests ?? DEFAULT_CIRCUIT_BREAKER_CONFIG.halfOpenMaxRequests,\n };\n\n // ============================================================================\n // State\n // ============================================================================\n\n // Create internal API client with Bearer token auth\n const apiClient = createBearerAPIClient(config.apiKey, config.baseUrl);\n\n // Create cache instance with stale support\n const cache = new SimpleCache<unknown>({\n maxSize: cacheMaxSize,\n ttl: cacheTTL,\n staleTtl: staleTtlMs,\n });\n\n // Create circuit breaker\n const circuitBreaker = new CircuitBreaker(circuitBreakerConfig);\n\n // Create prebuild loader if configured and available (lazy-load to avoid fs in browser)\n const prebuildDir = config.resilience?.prebuildDir;\n const prebuildMod = prebuildDir ? getPrebuildModule() : null;\n const prebuildLoader = prebuildMod?.canUsePrebuild() && prebuildDir\n ? new prebuildMod.PrebuildLoader({\n prebuildDir,\n maxPrebuildAgeSec: config.resilience?.maxPrebuildAgeSec,\n })\n : null;\n\n // Last request status (for getResilienceStatus())\n let lastStatus: ResilienceStatus | null = null;\n\n // Track degraded mode for onDegradedMode callback\n let isDegraded = false;\n\n // ============================================================================\n // Status Emission\n // ============================================================================\n\n /**\n * Emit a status update and trigger callbacks\n */\n function emitStatus<T>(\n source: ResilienceSource,\n data: T | null,\n details: {\n requestId: string;\n cacheKey: string;\n isPreview: boolean;\n durationMs: number;\n error?: { code?: string; message: string };\n staleAgeSec?: number;\n prebuildAgeSec?: number;\n }\n ): T {\n const status: ResilienceStatus = {\n source,\n isPreview: details.isPreview,\n cacheKey: details.cacheKey,\n error: details.error,\n staleAgeSec: details.staleAgeSec,\n prebuildAgeSec: details.prebuildAgeSec,\n circuit: circuitBreaker.getState(),\n requestId: details.requestId,\n durationMs: details.durationMs,\n };\n\n lastStatus = status;\n\n // Fire onStatusChange callback\n config.resilience?.onStatusChange?.(status);\n\n // Fire onDegradedMode callback on transitions\n const nowDegraded = source === 'stale' || source === 'error';\n if (nowDegraded !== isDegraded) {\n isDegraded = nowDegraded;\n config.resilience?.onDegradedMode?.(nowDegraded, status);\n }\n\n return data as T;\n }\n\n // ============================================================================\n // Resilient Fetch\n // ============================================================================\n\n /**\n * Optional prebuild fallback callback type.\n * Returns the data from prebuild cache along with its age, or null if not available.\n */\n type PrebuildFallback<T> = () => { data: T; prebuildAgeSec: number } | null;\n\n /**\n * Fetch with resilience features: cache, stale fallback, retry, circuit breaker, prebuild\n */\n async function resilientFetch<T>(\n cacheKey: string,\n fetcher: (signal: AbortSignal) => Promise<ApiResult<T>>,\n options: {\n preview?: boolean;\n force?: boolean;\n signal?: AbortSignal;\n /** Optional prebuild fallback - called when live + stale both fail */\n prebuildFallback?: PrebuildFallback<T>;\n }\n ): Promise<T> {\n const requestId = generateRequestId();\n const startTime = Date.now();\n const isPreview = options.preview ?? false;\n\n // Helper to create status details\n const statusDetails = (extra: Partial<Parameters<typeof emitStatus>[2]> = {}) => ({\n requestId,\n cacheKey,\n isPreview,\n durationMs: Date.now() - startTime,\n ...extra,\n });\n\n // -------------------------------------------------------------------------\n // 1. Check fresh cache (unless force refresh)\n // -------------------------------------------------------------------------\n if (cacheEnabled && !options.force) {\n const fresh = cache.getFresh(cacheKey) as T | null;\n if (fresh !== null) {\n return emitStatus('cache', fresh, statusDetails());\n }\n }\n\n // -------------------------------------------------------------------------\n // 2. Circuit breaker check (always check, regardless of force)\n // force=true means skip cache, not skip circuit breaker protection\n // -------------------------------------------------------------------------\n if (resilienceEnabled && circuitBreaker.isOpen()) {\n // Try stale fallback (non-preview only)\n if (!isPreview && staleIfError) {\n const stale = cache.getStale(cacheKey);\n if (stale) {\n return emitStatus('stale', stale.value as T, statusDetails({\n staleAgeSec: stale.staleAgeSec,\n error: { code: 'circuit_open', message: 'Circuit breaker is open' },\n }));\n }\n }\n\n // Try prebuild fallback (non-preview only)\n if (!isPreview && options.prebuildFallback) {\n const prebuildResult = options.prebuildFallback();\n if (prebuildResult) {\n return emitStatus('prebuild', prebuildResult.data, statusDetails({\n prebuildAgeSec: prebuildResult.prebuildAgeSec,\n error: { code: 'circuit_open', message: 'Circuit breaker is open' },\n }));\n }\n }\n\n // No fallback available - circuit is protecting the system\n const circuitState = circuitBreaker.getState();\n emitStatus('error', null, statusDetails({\n error: { code: 'circuit_open', message: 'Circuit breaker is open' },\n }));\n throw new CircuitOpenError(circuitState);\n }\n\n // -------------------------------------------------------------------------\n // 3. Attempt live fetch with retries\n // -------------------------------------------------------------------------\n try {\n let data: T;\n\n if (resilienceEnabled) {\n // Track half-open requests\n if (circuitBreaker.getState().state === 'half-open') {\n circuitBreaker.incrementHalfOpenRequests();\n }\n\n // Fetch with timeout and retry\n data = await fetchWithTimeoutAndRetry(\n async (timeoutSignal) => {\n // Combine timeout signal with user-provided signal\n const combinedSignal = options.signal\n ? combineAbortSignals(timeoutSignal, options.signal)\n : timeoutSignal;\n\n try {\n const response = await fetcher(combinedSignal);\n return unwrapResponse(response);\n } catch (error) {\n convertToTypedError(error);\n }\n },\n {\n ...retryConfig,\n requestTimeoutMs,\n }\n );\n\n circuitBreaker.recordSuccess();\n } else {\n // Non-resilient path (resilience disabled)\n try {\n const response = await fetcher(options.signal ?? new AbortController().signal);\n data = unwrapResponse(response);\n } catch (error) {\n convertToTypedError(error);\n }\n }\n\n // Store in cache\n if (cacheEnabled) {\n cache.set(cacheKey, data);\n }\n\n return emitStatus('live', data, statusDetails());\n } catch (error) {\n // Record failure for circuit breaker\n if (resilienceEnabled && error instanceof Error) {\n circuitBreaker.recordFailure(error);\n }\n\n // -----------------------------------------------------------------------\n // 4. Stale fallback (published only, not preview)\n // -----------------------------------------------------------------------\n if (!isPreview && staleIfError && cacheEnabled) {\n const stale = cache.getStale(cacheKey);\n if (stale) {\n const errorInfo = error instanceof RiverbankApiError\n ? { code: error.code, message: error.message }\n : { message: (error as Error).message };\n\n return emitStatus('stale', stale.value as T, statusDetails({\n staleAgeSec: stale.staleAgeSec,\n error: errorInfo,\n }));\n }\n }\n\n // -----------------------------------------------------------------------\n // 5. Prebuild fallback (published only, not preview)\n // -----------------------------------------------------------------------\n if (!isPreview && options.prebuildFallback) {\n const prebuildResult = options.prebuildFallback();\n if (prebuildResult) {\n const errorInfo = error instanceof RiverbankApiError\n ? { code: error.code, message: error.message }\n : { message: (error as Error).message };\n\n return emitStatus('prebuild', prebuildResult.data, statusDetails({\n prebuildAgeSec: prebuildResult.prebuildAgeSec,\n error: errorInfo,\n }));\n }\n }\n\n // -----------------------------------------------------------------------\n // 6. No fallback available - propagate error\n // -----------------------------------------------------------------------\n const errorInfo = error instanceof RiverbankApiError\n ? { code: error.code, message: error.message }\n : { message: (error as Error).message };\n\n emitStatus('error', null, statusDetails({ error: errorInfo }));\n throw error;\n }\n }\n\n // ============================================================================\n // Helper: Combine AbortSignals\n // ============================================================================\n\n /**\n * Combine multiple AbortSignals into one that aborts when any input aborts\n */\n function combineAbortSignals(...signals: AbortSignal[]): AbortSignal {\n const controller = new AbortController();\n\n for (const signal of signals) {\n if (signal.aborted) {\n controller.abort(signal.reason);\n break;\n }\n signal.addEventListener('abort', () => controller.abort(signal.reason), { once: true });\n }\n\n return controller.signal;\n }\n\n // ============================================================================\n // Client Methods\n // ============================================================================\n\n return {\n async getSite(params) {\n const { slug, domain, id, signal } = params;\n\n if (!slug && !domain && !id) {\n throw new Error(\n 'getSite() requires at least one identifier: slug, domain, or id. ' +\n `Received: ${JSON.stringify(params)}`\n );\n }\n\n const cacheKey = `site:${slug || domain || id}`;\n const siteId = id || slug || domain;\n\n return resilientFetch(cacheKey, async (sig) => {\n const apiParams: Record<string, string> = {};\n if (params.slug) apiParams.slug = params.slug;\n if (params.domain) apiParams.domain = params.domain;\n if (params.id) apiParams.id = params.id;\n return await apiClient({ endpoint: 'getSite', params: apiParams, options: { signal: sig } });\n }, {\n signal,\n prebuildFallback: prebuildLoader && siteId\n ? () => prebuildLoader.loadSite(siteId)\n : undefined,\n });\n },\n\n async getPage(params) {\n const { siteId, path, preview = false, signal } = params;\n const cacheKey = `page:${siteId}:${path}:${preview}`;\n\n return resilientFetch(cacheKey, async (sig) => {\n return await apiClient({ endpoint: 'getContentByPath', params: { siteId }, body: { path, preview }, options: { signal: sig } });\n }, {\n preview,\n signal,\n // Prebuild fallback only for published pages (not preview)\n prebuildFallback: prebuildLoader && !preview\n ? () => prebuildLoader.loadPage(siteId, path)\n : undefined,\n });\n },\n\n async getEntries(params: GetEntriesBaseParams & { includeMeta?: boolean }) {\n const { siteId, contentType, limit, offset, order, preview = false, mode, entryIds, includeMeta, signal } = params;\n\n const entryIdsCacheKey = mode === 'manual' && entryIds?.length ? entryIds.join(',') : '';\n const cacheKey = `entries:${siteId}:${contentType}:${limit ?? ''}:${offset ?? ''}:${order ?? ''}:${preview}:${mode ?? ''}:${entryIdsCacheKey}:${includeMeta ?? ''}`;\n\n return resilientFetch(cacheKey, async (sig) => {\n let orderParam: string | undefined;\n if (order === 'newest') {\n orderParam = 'published_at.desc';\n } else if (order === 'oldest') {\n orderParam = 'published_at.asc';\n } else if (order === 'title') {\n orderParam = 'title.asc';\n }\n\n const apiParams = {\n siteId,\n type: contentType,\n ...(typeof limit === 'number' && { limit: String(limit) }),\n ...(typeof offset === 'number' && { offset: String(offset) }),\n ...(includeMeta && { meta: 'true' }),\n ...(orderParam && { order: orderParam }),\n ...(preview && { stage: 'preview' as const }),\n ...(mode === 'manual' && entryIds?.length && {\n mode: 'manual' as const,\n entryIds: JSON.stringify(entryIds),\n }),\n };\n\n return await apiClient({ endpoint: 'listPublishedEntries', params: apiParams, options: { signal: sig } });\n }, {\n preview,\n signal,\n // Prebuild fallback only for published entries (not preview, not manual mode)\n prebuildFallback: prebuildLoader && !preview\n ? () => prebuildLoader.loadEntries(siteId, params)\n : undefined,\n });\n },\n\n async getEntry(params) {\n const { siteId, contentType, slug, signal } = params;\n const cacheKey = `entry:${siteId}:${contentType}:${slug}`;\n\n return resilientFetch(cacheKey, async (sig) => {\n return await apiClient({ endpoint: 'getPublishedEntryPreview', params: { siteId, type: contentType, slug }, options: { signal: sig } });\n }, { signal });\n },\n\n async getPublicFormById(params) {\n const { formId, signal } = params;\n if (!formId) {\n throw new Error('getPublicFormById() requires formId');\n }\n const cacheKey = `public-form:${formId}`;\n return resilientFetch(cacheKey, async (sig) => {\n return await apiClient({ endpoint: 'getPublicFormById', params: { formId }, options: { signal: sig } });\n }, { signal });\n },\n\n async getPublicBookingServices(params) {\n const { siteId, ids, signal } = params;\n if (!siteId) {\n throw new Error('getPublicBookingServices() requires siteId');\n }\n const cacheKey = `public-booking-services:${siteId}:${ids ?? ''}`;\n return resilientFetch(cacheKey, async (sig) => {\n const apiParams = {\n siteId,\n ...(ids && { ids }),\n };\n return await apiClient({ endpoint: 'getPublicBookingServices', params: apiParams, options: { signal: sig } });\n }, { signal });\n },\n\n async listPublicEvents(params) {\n const { siteId, limit, from, to, stage, signal } = params;\n if (!siteId) {\n throw new Error('listPublicEvents() requires siteId');\n }\n const cacheKey = `public-events:${siteId}:${limit ?? ''}:${from ?? ''}:${to ?? ''}:${stage ?? ''}`;\n return resilientFetch(cacheKey, async (sig) => {\n const apiParams = {\n siteId,\n ...(typeof limit === 'number' && { limit: String(limit) }),\n ...(from && { from }),\n ...(to && { to }),\n ...(stage && { stage }),\n };\n return await apiClient({ endpoint: 'listPublicEvents', params: apiParams, options: { signal: sig } });\n }, { signal });\n },\n\n async resolveEventOccurrence(params) {\n const { siteId, entryId, segment, signal } = params;\n if (!siteId || !entryId || !segment) {\n throw new Error('resolveEventOccurrence() requires siteId, entryId, and segment');\n }\n const cacheKey = `event-occurrence:${siteId}:${entryId}:${segment}`;\n return resilientFetch(cacheKey, async (sig) => {\n return await apiClient({\n endpoint: 'resolveEventOccurrence',\n params: { siteId, entryId, segment },\n options: { signal: sig },\n });\n }, { signal });\n },\n\n async checkRedirect(params) {\n const { siteId, path, signal } = params;\n if (!siteId || !path) {\n throw new Error('checkRedirect() requires siteId and path');\n }\n const cacheKey = `redirect:${siteId}:${path}`;\n return resilientFetch(cacheKey, async (sig) => {\n return await apiClient({\n endpoint: 'checkRedirect',\n params: { site: siteId, path },\n options: { signal: sig },\n });\n }, { signal });\n },\n\n clearCache() {\n cache.clear();\n },\n\n getLastEmittedStatus(): ResilienceStatus | null {\n return lastStatus;\n },\n\n getCircuitState(): CircuitState {\n return circuitBreaker.getState();\n },\n } as RiverbankClient;\n}\n\n// Re-export types\nexport type { RiverbankClient, RiverbankClientConfig, ResilienceConfig, ResilienceStatus } from './types';\n\n// Re-export error class\nexport { RiverbankApiError } from './error';\n\n// Re-export CircuitOpenError for consumer detection\nexport { CircuitOpenError } from './resilience';\n"]}
|