@riverbankcms/sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1892 -0
- package/dist/cli/index.js +327 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/client/analytics.d.mts +103 -0
- package/dist/client/analytics.d.ts +103 -0
- package/dist/client/analytics.js +197 -0
- package/dist/client/analytics.js.map +1 -0
- package/dist/client/analytics.mjs +169 -0
- package/dist/client/analytics.mjs.map +1 -0
- package/dist/client/bookings.d.mts +89 -0
- package/dist/client/bookings.d.ts +89 -0
- package/dist/client/bookings.js +34 -0
- package/dist/client/bookings.js.map +1 -0
- package/dist/client/bookings.mjs +11 -0
- package/dist/client/bookings.mjs.map +1 -0
- package/dist/client/client.d.mts +195 -0
- package/dist/client/client.d.ts +195 -0
- package/dist/client/client.js +606 -0
- package/dist/client/client.js.map +1 -0
- package/dist/client/client.mjs +572 -0
- package/dist/client/client.mjs.map +1 -0
- package/dist/client/hooks.d.mts +71 -0
- package/dist/client/hooks.d.ts +71 -0
- package/dist/client/hooks.js +264 -0
- package/dist/client/hooks.js.map +1 -0
- package/dist/client/hooks.mjs +235 -0
- package/dist/client/hooks.mjs.map +1 -0
- package/dist/client/rendering/client.d.mts +1 -0
- package/dist/client/rendering/client.d.ts +1 -0
- package/dist/client/rendering/client.js +33 -0
- package/dist/client/rendering/client.js.map +1 -0
- package/dist/client/rendering/client.mjs +8 -0
- package/dist/client/rendering/client.mjs.map +1 -0
- package/dist/client/usePage-BvKAa3Zw.d.mts +366 -0
- package/dist/client/usePage-BvKAa3Zw.d.ts +366 -0
- package/dist/server/chunk-2RW5HAQQ.mjs +86 -0
- package/dist/server/chunk-2RW5HAQQ.mjs.map +1 -0
- package/dist/server/chunk-3KKZVGH4.mjs +179 -0
- package/dist/server/chunk-3KKZVGH4.mjs.map +1 -0
- package/dist/server/chunk-4Z3GPTCS.js +179 -0
- package/dist/server/chunk-4Z3GPTCS.js.map +1 -0
- package/dist/server/chunk-4Z5FBFRL.mjs +211 -0
- package/dist/server/chunk-4Z5FBFRL.mjs.map +1 -0
- package/dist/server/chunk-ADREPXFU.js +86 -0
- package/dist/server/chunk-ADREPXFU.js.map +1 -0
- package/dist/server/chunk-F472SMKX.js +140 -0
- package/dist/server/chunk-F472SMKX.js.map +1 -0
- package/dist/server/chunk-GWBMJPLH.mjs +57 -0
- package/dist/server/chunk-GWBMJPLH.mjs.map +1 -0
- package/dist/server/chunk-JB4LIEFS.js +85 -0
- package/dist/server/chunk-JB4LIEFS.js.map +1 -0
- package/dist/server/chunk-PEAXKTDU.mjs +140 -0
- package/dist/server/chunk-PEAXKTDU.mjs.map +1 -0
- package/dist/server/chunk-QQ6U4QX6.js +120 -0
- package/dist/server/chunk-QQ6U4QX6.js.map +1 -0
- package/dist/server/chunk-R5YGLRUG.mjs +122 -0
- package/dist/server/chunk-R5YGLRUG.mjs.map +1 -0
- package/dist/server/chunk-SW7LE4M3.js +211 -0
- package/dist/server/chunk-SW7LE4M3.js.map +1 -0
- package/dist/server/chunk-W3K7LVPS.mjs +120 -0
- package/dist/server/chunk-W3K7LVPS.mjs.map +1 -0
- package/dist/server/chunk-WKG57P2H.mjs +85 -0
- package/dist/server/chunk-WKG57P2H.mjs.map +1 -0
- package/dist/server/chunk-YHEZMVTS.js +122 -0
- package/dist/server/chunk-YHEZMVTS.js.map +1 -0
- package/dist/server/chunk-YXDDFG3N.js +57 -0
- package/dist/server/chunk-YXDDFG3N.js.map +1 -0
- package/dist/server/components.d.mts +49 -0
- package/dist/server/components.d.ts +49 -0
- package/dist/server/components.js +22 -0
- package/dist/server/components.js.map +1 -0
- package/dist/server/components.mjs +22 -0
- package/dist/server/components.mjs.map +1 -0
- package/dist/server/config-validation.d.mts +300 -0
- package/dist/server/config-validation.d.ts +300 -0
- package/dist/server/config-validation.js +50 -0
- package/dist/server/config-validation.js.map +1 -0
- package/dist/server/config-validation.mjs +50 -0
- package/dist/server/config-validation.mjs.map +1 -0
- package/dist/server/config.d.mts +38 -0
- package/dist/server/config.d.ts +38 -0
- package/dist/server/config.js +44 -0
- package/dist/server/config.js.map +1 -0
- package/dist/server/config.mjs +44 -0
- package/dist/server/config.mjs.map +1 -0
- package/dist/server/data.d.mts +108 -0
- package/dist/server/data.d.ts +108 -0
- package/dist/server/data.js +15 -0
- package/dist/server/data.js.map +1 -0
- package/dist/server/data.mjs +15 -0
- package/dist/server/data.mjs.map +1 -0
- package/dist/server/index-B0yI_V6Z.d.mts +18 -0
- package/dist/server/index-C6M0Wfjq.d.ts +18 -0
- package/dist/server/index.d.mts +5 -0
- package/dist/server/index.d.ts +5 -0
- package/dist/server/index.js +12 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/index.mjs +12 -0
- package/dist/server/index.mjs.map +1 -0
- package/dist/server/loadContent-CJcbYF3J.d.ts +152 -0
- package/dist/server/loadContent-zhlL4YSE.d.mts +152 -0
- package/dist/server/loadPage-BYmVMk0V.d.ts +216 -0
- package/dist/server/loadPage-CCf15nt8.d.mts +216 -0
- package/dist/server/loadPage-DVH3DW6E.js +9 -0
- package/dist/server/loadPage-DVH3DW6E.js.map +1 -0
- package/dist/server/loadPage-PHQZ6XQZ.mjs +9 -0
- package/dist/server/loadPage-PHQZ6XQZ.mjs.map +1 -0
- package/dist/server/metadata.d.mts +135 -0
- package/dist/server/metadata.d.ts +135 -0
- package/dist/server/metadata.js +68 -0
- package/dist/server/metadata.js.map +1 -0
- package/dist/server/metadata.mjs +68 -0
- package/dist/server/metadata.mjs.map +1 -0
- package/dist/server/rendering/server.d.mts +83 -0
- package/dist/server/rendering/server.d.ts +83 -0
- package/dist/server/rendering/server.js +14 -0
- package/dist/server/rendering/server.js.map +1 -0
- package/dist/server/rendering/server.mjs +14 -0
- package/dist/server/rendering/server.mjs.map +1 -0
- package/dist/server/rendering.d.mts +12 -0
- package/dist/server/rendering.d.ts +12 -0
- package/dist/server/rendering.js +40 -0
- package/dist/server/rendering.js.map +1 -0
- package/dist/server/rendering.mjs +40 -0
- package/dist/server/rendering.mjs.map +1 -0
- package/dist/server/routing.d.mts +115 -0
- package/dist/server/routing.d.ts +115 -0
- package/dist/server/routing.js +57 -0
- package/dist/server/routing.js.map +1 -0
- package/dist/server/routing.mjs +57 -0
- package/dist/server/routing.mjs.map +1 -0
- package/dist/server/server.d.mts +9 -0
- package/dist/server/server.d.ts +9 -0
- package/dist/server/server.js +21 -0
- package/dist/server/server.js.map +1 -0
- package/dist/server/server.mjs +21 -0
- package/dist/server/server.mjs.map +1 -0
- package/dist/server/theme-bridge.d.mts +232 -0
- package/dist/server/theme-bridge.d.ts +232 -0
- package/dist/server/theme-bridge.js +231 -0
- package/dist/server/theme-bridge.js.map +1 -0
- package/dist/server/theme-bridge.mjs +231 -0
- package/dist/server/theme-bridge.mjs.map +1 -0
- package/dist/server/theme.d.mts +40 -0
- package/dist/server/theme.d.ts +40 -0
- package/dist/server/theme.js +17 -0
- package/dist/server/theme.js.map +1 -0
- package/dist/server/theme.mjs +17 -0
- package/dist/server/theme.mjs.map +1 -0
- package/dist/server/types-BCeqWtI2.d.mts +333 -0
- package/dist/server/types-BCeqWtI2.d.ts +333 -0
- package/dist/server/types-Bbo01M7P.d.mts +76 -0
- package/dist/server/types-Bbo01M7P.d.ts +76 -0
- package/dist/server/types-C6gmRHLe.d.mts +150 -0
- package/dist/server/types-C6gmRHLe.d.ts +150 -0
- package/package.json +147 -0
- package/src/styles/index.css +10 -0
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import { APIEndpoints } from '@riverbankcms/api';
|
|
2
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
|
+
import { PageOutline, Theme, ThemeTokens, RouteMap, OccurrenceContextData, PageRenderer, BlockOverrides } from '@riverbankcms/blocks';
|
|
4
|
+
import { ResolvedBlockData } from '@riverbankcms/blocks/system/data';
|
|
5
|
+
|
|
6
|
+
type SiteResponse = NonNullable<APIEndpoints['getSite']['response']>;
|
|
7
|
+
type PageResponse = NonNullable<APIEndpoints['getContentByPath']['response']>;
|
|
8
|
+
type EntriesResponse = NonNullable<APIEndpoints['listPublishedEntries']['response']>;
|
|
9
|
+
type EntryResponse = NonNullable<APIEndpoints['getPublishedEntryPreview']['response']>;
|
|
10
|
+
type PublicFormResponse = NonNullable<APIEndpoints['getPublicFormById']['response']>;
|
|
11
|
+
type PublicBookingServicesResponse = NonNullable<APIEndpoints['getPublicBookingServices']['response']>;
|
|
12
|
+
type PublicEventsResponse = NonNullable<APIEndpoints['listPublicEvents']['response']>;
|
|
13
|
+
/**
|
|
14
|
+
* Configuration for creating a Riverbank CMS client
|
|
15
|
+
*/
|
|
16
|
+
interface RiverbankClientConfig {
|
|
17
|
+
/**
|
|
18
|
+
* API key for authentication (format: bld_live_... or bld_test_...)
|
|
19
|
+
*/
|
|
20
|
+
apiKey: string;
|
|
21
|
+
/**
|
|
22
|
+
* Base URL for the Riverbank CMS API (required; e.g. https://dashboard.example.com/api)
|
|
23
|
+
*/
|
|
24
|
+
baseUrl: string;
|
|
25
|
+
/**
|
|
26
|
+
* Cache configuration
|
|
27
|
+
*/
|
|
28
|
+
cache?: {
|
|
29
|
+
/**
|
|
30
|
+
* Enable caching (default: true)
|
|
31
|
+
*/
|
|
32
|
+
enabled?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Time-to-live for cached responses in seconds
|
|
35
|
+
* @default 300 (5 minutes)
|
|
36
|
+
*/
|
|
37
|
+
ttl?: number;
|
|
38
|
+
/**
|
|
39
|
+
* Maximum number of cached responses
|
|
40
|
+
* @default 100
|
|
41
|
+
*/
|
|
42
|
+
maxSize?: number;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Riverbank CMS client interface
|
|
47
|
+
*/
|
|
48
|
+
interface RiverbankClient {
|
|
49
|
+
/**
|
|
50
|
+
* Fetch site data by slug, domain, or ID
|
|
51
|
+
*/
|
|
52
|
+
getSite(params: {
|
|
53
|
+
slug?: string;
|
|
54
|
+
domain?: string;
|
|
55
|
+
id?: string;
|
|
56
|
+
}): Promise<SiteResponse>;
|
|
57
|
+
/**
|
|
58
|
+
* Fetch a specific page by path
|
|
59
|
+
*
|
|
60
|
+
* @param params.preview - If true, returns draft content instead of published content (requires API key with site access)
|
|
61
|
+
*/
|
|
62
|
+
getPage(params: {
|
|
63
|
+
siteId: string;
|
|
64
|
+
path: string;
|
|
65
|
+
preview?: boolean;
|
|
66
|
+
}): Promise<PageResponse>;
|
|
67
|
+
/**
|
|
68
|
+
* Fetch published content entries with optional pagination, sorting, and preview mode
|
|
69
|
+
*
|
|
70
|
+
* @param params.siteId - The site ID
|
|
71
|
+
* @param params.contentType - The content type key (e.g., 'blog-post', 'product')
|
|
72
|
+
* @param params.limit - Maximum number of entries to return
|
|
73
|
+
* @param params.order - Sort order: 'newest' (published_at desc) or 'oldest' (published_at asc)
|
|
74
|
+
* @param params.preview - If true, includes draft entries (requires API key with site access)
|
|
75
|
+
* @param params.mode - 'query' for automatic fetching, 'manual' for specific entry IDs
|
|
76
|
+
* @param params.entryIds - Array of entry IDs to fetch (only used when mode is 'manual')
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```ts
|
|
80
|
+
* // Fetch latest 10 blog posts
|
|
81
|
+
* const entries = await client.getEntries({
|
|
82
|
+
* siteId: 'site-id',
|
|
83
|
+
* contentType: 'blog-post',
|
|
84
|
+
* limit: 10,
|
|
85
|
+
* order: 'newest',
|
|
86
|
+
* });
|
|
87
|
+
*
|
|
88
|
+
* // Fetch draft entries for preview
|
|
89
|
+
* const drafts = await client.getEntries({
|
|
90
|
+
* siteId: 'site-id',
|
|
91
|
+
* contentType: 'blog-post',
|
|
92
|
+
* preview: true,
|
|
93
|
+
* });
|
|
94
|
+
*
|
|
95
|
+
* // Fetch specific entries by ID (manual mode)
|
|
96
|
+
* const specific = await client.getEntries({
|
|
97
|
+
* siteId: 'site-id',
|
|
98
|
+
* contentType: 'blog-post',
|
|
99
|
+
* mode: 'manual',
|
|
100
|
+
* entryIds: ['uuid-1', 'uuid-2'],
|
|
101
|
+
* });
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
getEntries(params: {
|
|
105
|
+
siteId: string;
|
|
106
|
+
contentType: string;
|
|
107
|
+
limit?: number;
|
|
108
|
+
/** Sort order for entries. 'order' uses custom ordering (default behavior). */
|
|
109
|
+
order?: 'newest' | 'oldest' | 'title' | 'order';
|
|
110
|
+
preview?: boolean;
|
|
111
|
+
/** Selection mode: 'query' for automatic, 'manual' for specific entry IDs */
|
|
112
|
+
mode?: 'query' | 'manual';
|
|
113
|
+
/** Entry IDs to fetch when mode is 'manual' */
|
|
114
|
+
entryIds?: string[];
|
|
115
|
+
}): Promise<EntriesResponse>;
|
|
116
|
+
/**
|
|
117
|
+
* Fetch a specific content entry by slug
|
|
118
|
+
*/
|
|
119
|
+
getEntry(params: {
|
|
120
|
+
siteId: string;
|
|
121
|
+
contentType: string;
|
|
122
|
+
slug: string;
|
|
123
|
+
}): Promise<EntryResponse>;
|
|
124
|
+
/**
|
|
125
|
+
* Fetch a public form definition by ID
|
|
126
|
+
*/
|
|
127
|
+
getPublicFormById(params: {
|
|
128
|
+
formId: string;
|
|
129
|
+
}): Promise<PublicFormResponse>;
|
|
130
|
+
/**
|
|
131
|
+
* Fetch public booking services for a site
|
|
132
|
+
*/
|
|
133
|
+
getPublicBookingServices(params: {
|
|
134
|
+
siteId: string;
|
|
135
|
+
ids?: string;
|
|
136
|
+
}): Promise<PublicBookingServicesResponse>;
|
|
137
|
+
/**
|
|
138
|
+
* List public events for a site (optionally time-filtered)
|
|
139
|
+
*/
|
|
140
|
+
listPublicEvents(params: {
|
|
141
|
+
siteId: string;
|
|
142
|
+
limit?: number;
|
|
143
|
+
from?: string;
|
|
144
|
+
to?: string;
|
|
145
|
+
stage?: string;
|
|
146
|
+
}): Promise<PublicEventsResponse>;
|
|
147
|
+
/**
|
|
148
|
+
* Force clear the cache
|
|
149
|
+
*/
|
|
150
|
+
clearCache(): void;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Server-side helper to fetch all data needed for <Page> component.
|
|
155
|
+
*
|
|
156
|
+
* Use this in server components, getServerSideProps, or API routes.
|
|
157
|
+
*/
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* SDK config from API response (without siteId which is stripped at storage).
|
|
161
|
+
* This is the runtime representation - for defining configs, use RiverbankSiteConfig.
|
|
162
|
+
*/
|
|
163
|
+
type RuntimeSdkConfig = NonNullable<SiteResponse['sdkConfig']>;
|
|
164
|
+
|
|
165
|
+
type PageProps = {
|
|
166
|
+
page: PageOutline;
|
|
167
|
+
theme: Theme;
|
|
168
|
+
siteId: string;
|
|
169
|
+
themeTokens?: ThemeTokens;
|
|
170
|
+
resolvedData?: ResolvedBlockData;
|
|
171
|
+
routeMap?: RouteMap;
|
|
172
|
+
/**
|
|
173
|
+
* SDK site configuration containing theme palette overrides.
|
|
174
|
+
* When provided, the SDK palette tokens are merged into the theme tokens,
|
|
175
|
+
* allowing blocks to use SDK-defined color tokens for section backgrounds.
|
|
176
|
+
*/
|
|
177
|
+
sdkConfig?: RuntimeSdkConfig | null;
|
|
178
|
+
/**
|
|
179
|
+
* Additional context data for content entry pages.
|
|
180
|
+
* Used to pass occurrence context and content entry data to blocks.
|
|
181
|
+
*/
|
|
182
|
+
dataContext?: {
|
|
183
|
+
/** Occurrence context for event pages (from URL like /events/yoga/2025-01-15) */
|
|
184
|
+
occurrenceContext?: OccurrenceContextData | null;
|
|
185
|
+
/** Content entry data for template pages */
|
|
186
|
+
contentEntry?: Record<string, unknown> | null;
|
|
187
|
+
};
|
|
188
|
+
wrapBlock?: (blockId: string, rendered: React.ReactNode) => React.ReactNode;
|
|
189
|
+
registry?: Parameters<typeof PageRenderer>[0]['registry'];
|
|
190
|
+
usePlaceholders?: boolean;
|
|
191
|
+
/**
|
|
192
|
+
* Custom components to override default block rendering.
|
|
193
|
+
* Keys can be full block kind ("block.hero") or short form ("hero").
|
|
194
|
+
*
|
|
195
|
+
* This is SSR-safe - no context or hooks required.
|
|
196
|
+
*
|
|
197
|
+
* @example
|
|
198
|
+
* ```tsx
|
|
199
|
+
* <Page
|
|
200
|
+
* {...pageData}
|
|
201
|
+
* blockOverrides={{
|
|
202
|
+
* 'hero': MyCustomHero,
|
|
203
|
+
* 'block.testimonials': MyCustomTestimonials,
|
|
204
|
+
* }}
|
|
205
|
+
* />
|
|
206
|
+
* ```
|
|
207
|
+
*/
|
|
208
|
+
blockOverrides?: BlockOverrides;
|
|
209
|
+
};
|
|
210
|
+
/**
|
|
211
|
+
* Pure renderer for Riverbank CMS pages.
|
|
212
|
+
*
|
|
213
|
+
* This component expects all data to be provided via props.
|
|
214
|
+
* For data fetching, use:
|
|
215
|
+
* - Server-side: `await loadPage({ client, siteId, path })`
|
|
216
|
+
* - Client-side: `usePage({ client, siteId, path })`
|
|
217
|
+
*
|
|
218
|
+
* @example Server-side (Next.js App Router)
|
|
219
|
+
* ```tsx
|
|
220
|
+
* import { createRiverbankClient } from '@riverbankcms/sdk';
|
|
221
|
+
* import { loadPage, Page } from '@riverbankcms/sdk/rendering';
|
|
222
|
+
*
|
|
223
|
+
* const client = createRiverbankClient({ apiKey, baseUrl });
|
|
224
|
+
*
|
|
225
|
+
* export default async function PageRoute({ params }) {
|
|
226
|
+
* const pageData = await loadPage({
|
|
227
|
+
* client,
|
|
228
|
+
* siteId: 'site-id',
|
|
229
|
+
* path: `/${params.slug}`,
|
|
230
|
+
* });
|
|
231
|
+
*
|
|
232
|
+
* return <Page {...pageData} />;
|
|
233
|
+
* }
|
|
234
|
+
* ```
|
|
235
|
+
*
|
|
236
|
+
* @example Client-side
|
|
237
|
+
* ```tsx
|
|
238
|
+
* import { createRiverbankClient } from '@riverbankcms/sdk';
|
|
239
|
+
* import { usePage, Page } from '@riverbankcms/sdk/rendering';
|
|
240
|
+
*
|
|
241
|
+
* const client = createRiverbankClient({ apiKey, baseUrl });
|
|
242
|
+
*
|
|
243
|
+
* function MyPage({ path }) {
|
|
244
|
+
* const pageData = usePage({ client, siteId: 'site-id', path });
|
|
245
|
+
*
|
|
246
|
+
* if (pageData.loading) return <LoadingState />;
|
|
247
|
+
* if (pageData.error) return <ErrorState error={pageData.error} />;
|
|
248
|
+
* if (!pageData.page) return <NotFound />;
|
|
249
|
+
*
|
|
250
|
+
* return <Page {...pageData} />;
|
|
251
|
+
* }
|
|
252
|
+
* ```
|
|
253
|
+
*/
|
|
254
|
+
declare function Page({ page, theme, themeTokens: providedTokens, siteId, resolvedData, routeMap, wrapBlock, registry, usePlaceholders, blockOverrides, sdkConfig, dataContext, }: PageProps): react_jsx_runtime.JSX.Element;
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Client-side React hook to fetch page data.
|
|
258
|
+
*
|
|
259
|
+
* Use this in client components for dynamic page loading.
|
|
260
|
+
*/
|
|
261
|
+
|
|
262
|
+
type UsePageParams = {
|
|
263
|
+
client: RiverbankClient;
|
|
264
|
+
siteId: string;
|
|
265
|
+
path: string;
|
|
266
|
+
pageId?: string;
|
|
267
|
+
/**
|
|
268
|
+
* If true, fetches draft/unpublished content instead of published content.
|
|
269
|
+
* This affects both the page structure and block data loaders.
|
|
270
|
+
* Requires API key with site access.
|
|
271
|
+
*
|
|
272
|
+
* @default false
|
|
273
|
+
*/
|
|
274
|
+
preview?: boolean;
|
|
275
|
+
};
|
|
276
|
+
type UsePageResult = {
|
|
277
|
+
loading: true;
|
|
278
|
+
error: null;
|
|
279
|
+
page: null;
|
|
280
|
+
theme: null;
|
|
281
|
+
siteId: string;
|
|
282
|
+
resolvedData: null;
|
|
283
|
+
sdkConfig: null;
|
|
284
|
+
} | {
|
|
285
|
+
loading: false;
|
|
286
|
+
error: Error;
|
|
287
|
+
page: null;
|
|
288
|
+
theme: null;
|
|
289
|
+
siteId: string;
|
|
290
|
+
resolvedData: null;
|
|
291
|
+
sdkConfig: null;
|
|
292
|
+
} | {
|
|
293
|
+
loading: false;
|
|
294
|
+
error: null;
|
|
295
|
+
sdkConfig: RuntimeSdkConfig | null;
|
|
296
|
+
} & Omit<PageProps, 'registry' | 'wrapBlock' | 'usePlaceholders' | 'sdkConfig'>;
|
|
297
|
+
/**
|
|
298
|
+
* Client-side React hook to fetch all data needed for <Page> component.
|
|
299
|
+
*
|
|
300
|
+
* Fetches site data, page data, and prefetches block data loaders.
|
|
301
|
+
* Returns loading and error states for proper UI handling.
|
|
302
|
+
*
|
|
303
|
+
* IMPORTANT: The client object should be stable across renders to avoid
|
|
304
|
+
* unnecessary re-fetches. Create it outside your component or use useMemo:
|
|
305
|
+
*
|
|
306
|
+
* ```tsx
|
|
307
|
+
* // ✅ Good - stable reference
|
|
308
|
+
* const client = useMemo(
|
|
309
|
+
* () => createRiverbankClient({ apiKey, baseUrl }),
|
|
310
|
+
* [apiKey, baseUrl]
|
|
311
|
+
* );
|
|
312
|
+
*
|
|
313
|
+
* // ❌ Bad - new client on every render (causes infinite loops)
|
|
314
|
+
* const client = createRiverbankClient({ apiKey, baseUrl });
|
|
315
|
+
* ```
|
|
316
|
+
*
|
|
317
|
+
* @example Basic usage
|
|
318
|
+
* ```tsx
|
|
319
|
+
* import { createRiverbankClient } from '@riverbankcms/sdk';
|
|
320
|
+
* import { usePage, Page } from '@riverbankcms/sdk/rendering';
|
|
321
|
+
*
|
|
322
|
+
* const client = createRiverbankClient({
|
|
323
|
+
* apiKey: process.env.NEXT_PUBLIC_RIVERBANK_API_KEY!,
|
|
324
|
+
* baseUrl: process.env.NEXT_PUBLIC_DASHBOARD_URL + '/api',
|
|
325
|
+
* });
|
|
326
|
+
*
|
|
327
|
+
* function MyPage({ path }: { path: string }) {
|
|
328
|
+
* const pageData = usePage({ client, siteId: 'site-123', path });
|
|
329
|
+
*
|
|
330
|
+
* if (pageData.loading) {
|
|
331
|
+
* return <div>Loading...</div>;
|
|
332
|
+
* }
|
|
333
|
+
*
|
|
334
|
+
* if (pageData.error) {
|
|
335
|
+
* return <div>Error: {pageData.error.message}</div>;
|
|
336
|
+
* }
|
|
337
|
+
*
|
|
338
|
+
* return <Page {...pageData} />;
|
|
339
|
+
* }
|
|
340
|
+
* ```
|
|
341
|
+
*
|
|
342
|
+
* @example With custom loading/error states
|
|
343
|
+
* ```tsx
|
|
344
|
+
* function MyPage({ path }: { path: string }) {
|
|
345
|
+
* const pageData = usePage({ client, siteId: 'site-123', path });
|
|
346
|
+
*
|
|
347
|
+
* if (pageData.loading) {
|
|
348
|
+
* return <Skeleton />;
|
|
349
|
+
* }
|
|
350
|
+
*
|
|
351
|
+
* if (pageData.error) {
|
|
352
|
+
* return (
|
|
353
|
+
* <ErrorBoundary
|
|
354
|
+
* error={pageData.error}
|
|
355
|
+
* onRetry={() => window.location.reload()}
|
|
356
|
+
* />
|
|
357
|
+
* );
|
|
358
|
+
* }
|
|
359
|
+
*
|
|
360
|
+
* return <Page {...pageData} />;
|
|
361
|
+
* }
|
|
362
|
+
* ```
|
|
363
|
+
*/
|
|
364
|
+
declare function usePage(params: UsePageParams): UsePageResult;
|
|
365
|
+
|
|
366
|
+
export { type PageResponse as P, type RiverbankClient as R, type SiteResponse as S, type UsePageParams as U, type UsePageResult as a, type PageProps as b, type RiverbankClientConfig as c, Page as d, usePage as u };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import {
|
|
2
|
+
executeCodeLoaders,
|
|
3
|
+
mergeLoaderResults
|
|
4
|
+
} from "./chunk-GWBMJPLH.mjs";
|
|
5
|
+
import {
|
|
6
|
+
prefetchBlockData
|
|
7
|
+
} from "./chunk-W3K7LVPS.mjs";
|
|
8
|
+
|
|
9
|
+
// src/rendering/helpers/loadPage.ts
|
|
10
|
+
async function loadPage(params) {
|
|
11
|
+
const { client, siteId, path, pageId, preview = false, dataLoaderOverrides } = params;
|
|
12
|
+
const [site, pageResponse] = await Promise.all([
|
|
13
|
+
client.getSite({ id: siteId }),
|
|
14
|
+
client.getPage({ siteId, path, preview })
|
|
15
|
+
]);
|
|
16
|
+
if ("entry" in pageResponse) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
"This path resolves to a content entry, not a page. Use loadContent() instead, which handles both pages and entries. For entries, loadContent() returns the raw entry data for custom rendering."
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
const { page: pageData } = pageResponse;
|
|
22
|
+
const blocks = pageData.blocks.map((block) => {
|
|
23
|
+
if (!block || typeof block !== "object") {
|
|
24
|
+
throw new Error("Invalid block format in API response");
|
|
25
|
+
}
|
|
26
|
+
if (typeof block.id !== "string" && block.id !== null) {
|
|
27
|
+
throw new Error(`Invalid block id: expected string or null, got ${typeof block.id}`);
|
|
28
|
+
}
|
|
29
|
+
if (typeof block.kind !== "string") {
|
|
30
|
+
throw new Error(`Invalid block kind: expected string, got ${typeof block.kind}`);
|
|
31
|
+
}
|
|
32
|
+
if (typeof block.purpose !== "string") {
|
|
33
|
+
throw new Error(`Invalid block purpose: expected string, got ${typeof block.purpose}`);
|
|
34
|
+
}
|
|
35
|
+
const typedBlock = block;
|
|
36
|
+
return {
|
|
37
|
+
id: typedBlock.id,
|
|
38
|
+
kind: typedBlock.kind,
|
|
39
|
+
purpose: typedBlock.purpose,
|
|
40
|
+
// Include content for PageRenderer's getRenderableContent()
|
|
41
|
+
content: typedBlock.content ?? {},
|
|
42
|
+
// Include draftContent if available (for preview mode)
|
|
43
|
+
draftContent: typedBlock.draftContent?.data ?? null
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
const pageOutline = {
|
|
47
|
+
name: pageData.name,
|
|
48
|
+
path: pageData.path,
|
|
49
|
+
purpose: pageData.purpose,
|
|
50
|
+
blocks
|
|
51
|
+
};
|
|
52
|
+
const prefetchContext = {
|
|
53
|
+
siteId,
|
|
54
|
+
pageId: pageId ?? pageData.id,
|
|
55
|
+
previewStage: preview ? "preview" : "published"
|
|
56
|
+
};
|
|
57
|
+
const configData = await prefetchBlockData(
|
|
58
|
+
pageOutline,
|
|
59
|
+
prefetchContext,
|
|
60
|
+
client,
|
|
61
|
+
{
|
|
62
|
+
// Pass custom blocks so their config-based loaders are discovered
|
|
63
|
+
customBlocks: site.sdkConfig?.customBlocks
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
let resolvedData = configData;
|
|
67
|
+
if (dataLoaderOverrides && Object.keys(dataLoaderOverrides).length > 0) {
|
|
68
|
+
const codeData = await executeCodeLoaders(pageOutline, prefetchContext, dataLoaderOverrides);
|
|
69
|
+
resolvedData = mergeLoaderResults(configData, codeData);
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
page: pageOutline,
|
|
73
|
+
theme: site.theme,
|
|
74
|
+
sdkConfig: site.sdkConfig ?? null,
|
|
75
|
+
siteId,
|
|
76
|
+
resolvedData
|
|
77
|
+
// Note: routeMap is optional and can be built from site data if needed for internal links.
|
|
78
|
+
// Consumers can construct it from site.pages and pass explicitly via Page component props.
|
|
79
|
+
// Example: const routeMap = site.pages.reduce((map, p) => ({ ...map, [p.id]: p.path }), {});
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export {
|
|
84
|
+
loadPage
|
|
85
|
+
};
|
|
86
|
+
//# sourceMappingURL=chunk-2RW5HAQQ.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/rendering/helpers/loadPage.ts"],"sourcesContent":["/**\n * Server-side helper to fetch all data needed for <Page> component.\n *\n * Use this in server components, getServerSideProps, or API routes.\n */\n\nimport type { RiverbankClient, SiteResponse } from '../../client/types';\nimport type { PageProps } from '../components/Page';\nimport { prefetchBlockData } from '../../data/prefetchBlockData';\nimport { executeCodeLoaders, mergeLoaderResults } from '../../data/executeCodeLoaders';\nimport type { DataLoaderOverrides } from '../../data/types';\n\n/**\n * SDK config from API response (without siteId which is stripped at storage).\n * This is the runtime representation - for defining configs, use RiverbankSiteConfig.\n */\nexport type RuntimeSdkConfig = NonNullable<SiteResponse['sdkConfig']>;\n\nexport type LoadPageParams = {\n client: RiverbankClient;\n siteId: string;\n path: string;\n pageId?: string;\n /**\n * If true, fetches draft/unpublished content instead of published content.\n * This affects both the page structure and block data loaders.\n * Requires API key with site access.\n *\n * @default false\n */\n preview?: boolean;\n /**\n * Code-based data loaders for custom blocks.\n *\n * Use this to fetch data from external APIs (not just whitelisted CMS endpoints).\n * Keys are block kinds (e.g., 'custom.featured-products').\n *\n * Config-based loaders (defined in riverbank.config.ts) run first.\n * Code loaders run second and take precedence on key conflicts.\n *\n * @example\n * ```typescript\n * const pageData = await loadPage({\n * client,\n * siteId: 'site-123',\n * path: '/',\n * dataLoaderOverrides: {\n * 'custom.featured-products': {\n * products: async (ctx) => {\n * const res = await fetch(`https://api.shop.com/products?category=${ctx.content.categoryId}`);\n * return res.json();\n * },\n * },\n * },\n * });\n * ```\n */\n dataLoaderOverrides?: DataLoaderOverrides;\n};\n\nexport type LoadPageResult = Omit<PageProps, 'registry' | 'wrapBlock' | 'usePlaceholders' | 'blockOverrides'> & {\n /**\n * SDK site configuration, if available.\n * Contains SDK-defined theme palette, section backgrounds, and style options.\n */\n sdkConfig: RuntimeSdkConfig | null;\n};\n\n/**\n * Server-side helper to fetch all data needed for <Page> component.\n *\n * Fetches site data, page data, and prefetches block data loaders in parallel.\n *\n * @example Next.js App Router (published content)\n * ```tsx\n * import { createRiverbankClient } from '@riverbankcms/sdk';\n * import { loadPage, Page } from '@riverbankcms/sdk/rendering';\n *\n * const client = createRiverbankClient({\n * apiKey: process.env.RIVERBANK_API_KEY!,\n * baseUrl: process.env.NEXT_PUBLIC_DASHBOARD_URL + '/api',\n * });\n *\n * export default async function PageRoute({ params }) {\n * const pageData = await loadPage({\n * client,\n * siteId: 'site-123',\n * path: `/${params.slug || ''}`,\n * });\n *\n * return <Page {...pageData} />;\n * }\n * ```\n *\n * @example Next.js App Router (preview/draft content)\n * ```tsx\n * export default async function PreviewRoute({ params, searchParams }) {\n * const pageData = await loadPage({\n * client,\n * siteId: searchParams.siteId,\n * path: `/${params.slug || ''}`,\n * preview: true, // Fetch draft content\n * });\n *\n * return <Page {...pageData} />;\n * }\n * ```\n *\n * @example Next.js Pages Router (getServerSideProps)\n * ```tsx\n * export async function getServerSideProps({ params }) {\n * const pageData = await loadPage({\n * client,\n * siteId: 'site-123',\n * path: `/${params.slug || ''}`,\n * });\n *\n * return { props: pageData };\n * }\n *\n * export default function PageRoute(props) {\n * return <Page {...props} />;\n * }\n * ```\n */\nexport async function loadPage(params: LoadPageParams): Promise<LoadPageResult> {\n const { client, siteId, path, pageId, preview = false, dataLoaderOverrides } = params;\n\n // Fetch site and page data in parallel\n const [site, pageResponse] = await Promise.all([\n client.getSite({ id: siteId }),\n client.getPage({ siteId, path, preview }),\n ]);\n\n // Extract page data (getContentByPath can return page or entry)\n if ('entry' in pageResponse) {\n throw new Error(\n 'This path resolves to a content entry, not a page. ' +\n 'Use loadContent() instead, which handles both pages and entries. ' +\n 'For entries, loadContent() returns the raw entry data for custom rendering.'\n );\n }\n\n const { page: pageData } = pageResponse;\n\n // Convert API response blocks to PageOutline format with content\n // API returns blocks with full content - PageRenderer needs the content field for rendering\n const blocks = pageData.blocks.map((block) => {\n if (!block || typeof block !== 'object') {\n throw new Error('Invalid block format in API response');\n }\n if (typeof block.id !== 'string' && block.id !== null) {\n throw new Error(`Invalid block id: expected string or null, got ${typeof block.id}`);\n }\n if (typeof block.kind !== 'string') {\n throw new Error(`Invalid block kind: expected string, got ${typeof block.kind}`);\n }\n if (typeof block.purpose !== 'string') {\n throw new Error(`Invalid block purpose: expected string, got ${typeof block.purpose}`);\n }\n\n // Include content for rendering\n // API provides `content` (active content for the requested stage)\n // and optionally `draftContent` for preview mode\n const typedBlock = block as {\n id: string | null;\n kind: string;\n purpose: string;\n content?: Record<string, unknown>;\n draftContent?: { data: Record<string, unknown> } | null;\n };\n\n return {\n id: typedBlock.id,\n kind: typedBlock.kind,\n purpose: typedBlock.purpose,\n // Include content for PageRenderer's getRenderableContent()\n content: typedBlock.content ?? {},\n // Include draftContent if available (for preview mode)\n draftContent: typedBlock.draftContent?.data ?? null,\n };\n });\n\n const pageOutline = {\n name: pageData.name,\n path: pageData.path,\n purpose: pageData.purpose,\n blocks,\n };\n\n // Build prefetch context\n const prefetchContext: { siteId: string; pageId: string; previewStage: 'published' | 'preview' } = {\n siteId,\n pageId: pageId ?? pageData.id,\n previewStage: preview ? 'preview' : 'published',\n };\n\n // Prefetch block data loaders (config-based loaders for CMS endpoints)\n const configData = await prefetchBlockData(\n pageOutline,\n prefetchContext,\n client,\n {\n // Pass custom blocks so their config-based loaders are discovered\n customBlocks: site.sdkConfig?.customBlocks,\n }\n );\n\n // Execute code-based loaders (external APIs) and merge results\n let resolvedData = configData;\n if (dataLoaderOverrides && Object.keys(dataLoaderOverrides).length > 0) {\n const codeData = await executeCodeLoaders(pageOutline, prefetchContext, dataLoaderOverrides);\n resolvedData = mergeLoaderResults(configData, codeData);\n }\n\n return {\n page: pageOutline,\n theme: site.theme,\n sdkConfig: site.sdkConfig ?? null,\n siteId,\n resolvedData,\n // Note: routeMap is optional and can be built from site data if needed for internal links.\n // Consumers can construct it from site.pages and pass explicitly via Page component props.\n // Example: const routeMap = site.pages.reduce((map, p) => ({ ...map, [p.id]: p.path }), {});\n };\n}\n"],"mappings":";;;;;;;;;AA6HA,eAAsB,SAAS,QAAiD;AAC9E,QAAM,EAAE,QAAQ,QAAQ,MAAM,QAAQ,UAAU,OAAO,oBAAoB,IAAI;AAG/E,QAAM,CAAC,MAAM,YAAY,IAAI,MAAM,QAAQ,IAAI;AAAA,IAC7C,OAAO,QAAQ,EAAE,IAAI,OAAO,CAAC;AAAA,IAC7B,OAAO,QAAQ,EAAE,QAAQ,MAAM,QAAQ,CAAC;AAAA,EAC1C,CAAC;AAGD,MAAI,WAAW,cAAc;AAC3B,UAAM,IAAI;AAAA,MACR;AAAA,IAGF;AAAA,EACF;AAEA,QAAM,EAAE,MAAM,SAAS,IAAI;AAI3B,QAAM,SAAS,SAAS,OAAO,IAAI,CAAC,UAAU;AAC5C,QAAI,CAAC,SAAS,OAAO,UAAU,UAAU;AACvC,YAAM,IAAI,MAAM,sCAAsC;AAAA,IACxD;AACA,QAAI,OAAO,MAAM,OAAO,YAAY,MAAM,OAAO,MAAM;AACrD,YAAM,IAAI,MAAM,kDAAkD,OAAO,MAAM,EAAE,EAAE;AAAA,IACrF;AACA,QAAI,OAAO,MAAM,SAAS,UAAU;AAClC,YAAM,IAAI,MAAM,4CAA4C,OAAO,MAAM,IAAI,EAAE;AAAA,IACjF;AACA,QAAI,OAAO,MAAM,YAAY,UAAU;AACrC,YAAM,IAAI,MAAM,+CAA+C,OAAO,MAAM,OAAO,EAAE;AAAA,IACvF;AAKA,UAAM,aAAa;AAQnB,WAAO;AAAA,MACL,IAAI,WAAW;AAAA,MACf,MAAM,WAAW;AAAA,MACjB,SAAS,WAAW;AAAA;AAAA,MAEpB,SAAS,WAAW,WAAW,CAAC;AAAA;AAAA,MAEhC,cAAc,WAAW,cAAc,QAAQ;AAAA,IACjD;AAAA,EACF,CAAC;AAED,QAAM,cAAc;AAAA,IAClB,MAAM,SAAS;AAAA,IACf,MAAM,SAAS;AAAA,IACf,SAAS,SAAS;AAAA,IAClB;AAAA,EACF;AAGA,QAAM,kBAA6F;AAAA,IACjG;AAAA,IACA,QAAQ,UAAU,SAAS;AAAA,IAC3B,cAAc,UAAU,YAAY;AAAA,EACtC;AAGA,QAAM,aAAa,MAAM;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAAA,MAEE,cAAc,KAAK,WAAW;AAAA,IAChC;AAAA,EACF;AAGA,MAAI,eAAe;AACnB,MAAI,uBAAuB,OAAO,KAAK,mBAAmB,EAAE,SAAS,GAAG;AACtE,UAAM,WAAW,MAAM,mBAAmB,aAAa,iBAAiB,mBAAmB;AAC3F,mBAAe,mBAAmB,YAAY,QAAQ;AAAA,EACxD;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO,KAAK;AAAA,IACZ,WAAW,KAAK,aAAa;AAAA,IAC7B;AAAA,IACA;AAAA;AAAA;AAAA;AAAA,EAIF;AACF;","names":[]}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// src/client/index.ts
|
|
2
|
+
import { createBearerAPIClient } from "@riverbankcms/api";
|
|
3
|
+
|
|
4
|
+
// src/client/cache.ts
|
|
5
|
+
var SimpleCache = class {
|
|
6
|
+
constructor(options = {}) {
|
|
7
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
8
|
+
this.maxSize = options.maxSize ?? 100;
|
|
9
|
+
this.ttl = options.ttl ?? 3e5;
|
|
10
|
+
}
|
|
11
|
+
get(key) {
|
|
12
|
+
const entry = this.cache.get(key);
|
|
13
|
+
if (!entry) return void 0;
|
|
14
|
+
if (Date.now() > entry.expires) {
|
|
15
|
+
this.cache.delete(key);
|
|
16
|
+
return void 0;
|
|
17
|
+
}
|
|
18
|
+
return entry.value;
|
|
19
|
+
}
|
|
20
|
+
set(key, value) {
|
|
21
|
+
if (this.cache.size >= this.maxSize) {
|
|
22
|
+
const firstKey = this.cache.keys().next().value;
|
|
23
|
+
if (firstKey) {
|
|
24
|
+
this.cache.delete(firstKey);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
this.cache.set(key, {
|
|
28
|
+
value,
|
|
29
|
+
expires: Date.now() + this.ttl
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
clear() {
|
|
33
|
+
this.cache.clear();
|
|
34
|
+
}
|
|
35
|
+
has(key) {
|
|
36
|
+
return this.get(key) !== void 0;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// src/client/index.ts
|
|
41
|
+
function createRiverbankClient(config) {
|
|
42
|
+
if (!config.baseUrl) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
"baseUrl is required when creating a Builder client. Expected format: https://dashboard.example.com/api (must include /api path)"
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
if (!config.baseUrl.endsWith("/api")) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`baseUrl must end with '/api'. Received: ${config.baseUrl}. Expected format: https://dashboard.example.com/api`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
const cacheEnabled = config.cache?.enabled ?? true;
|
|
53
|
+
const cacheTTL = (config.cache?.ttl ?? 300) * 1e3;
|
|
54
|
+
const cacheMaxSize = config.cache?.maxSize ?? 100;
|
|
55
|
+
const apiClient = createBearerAPIClient(config.apiKey, config.baseUrl);
|
|
56
|
+
const cache = new SimpleCache({
|
|
57
|
+
maxSize: cacheMaxSize,
|
|
58
|
+
ttl: cacheTTL
|
|
59
|
+
});
|
|
60
|
+
async function cachedFetch(cacheKey, fetcher, options) {
|
|
61
|
+
if (cacheEnabled && !options?.force) {
|
|
62
|
+
const cached = cache.get(cacheKey);
|
|
63
|
+
if (cached !== void 0) {
|
|
64
|
+
return cached;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const data = await fetcher();
|
|
68
|
+
if (cacheEnabled) {
|
|
69
|
+
cache.set(cacheKey, data);
|
|
70
|
+
}
|
|
71
|
+
return data;
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
async getSite(params) {
|
|
75
|
+
const { slug, domain, id } = params;
|
|
76
|
+
if (!slug && !domain && !id) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
`getSite() requires at least one identifier: slug, domain, or id. Received: ${JSON.stringify(params)}`
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
const cacheKey = `site:${slug || domain || id}`;
|
|
82
|
+
return cachedFetch(cacheKey, async () => {
|
|
83
|
+
const apiParams = {};
|
|
84
|
+
if (params.slug) apiParams.slug = params.slug;
|
|
85
|
+
if (params.domain) apiParams.domain = params.domain;
|
|
86
|
+
if (params.id) apiParams.id = params.id;
|
|
87
|
+
return await apiClient({ endpoint: "getSite", params: apiParams });
|
|
88
|
+
});
|
|
89
|
+
},
|
|
90
|
+
async getPage(params) {
|
|
91
|
+
const { siteId, path, preview = false } = params;
|
|
92
|
+
const cacheKey = `page:${siteId}:${path}:${preview}`;
|
|
93
|
+
return cachedFetch(cacheKey, async () => {
|
|
94
|
+
return await apiClient({ endpoint: "getContentByPath", params: { siteId }, body: { path, preview } });
|
|
95
|
+
});
|
|
96
|
+
},
|
|
97
|
+
async getEntries(params) {
|
|
98
|
+
const { siteId, contentType, limit, order, preview = false, mode, entryIds } = params;
|
|
99
|
+
const entryIdsCacheKey = mode === "manual" && entryIds?.length ? entryIds.join(",") : "";
|
|
100
|
+
const cacheKey = `entries:${siteId}:${contentType}:${limit ?? ""}:${order ?? ""}:${preview}:${mode ?? ""}:${entryIdsCacheKey}`;
|
|
101
|
+
return cachedFetch(cacheKey, async () => {
|
|
102
|
+
const apiParams = {
|
|
103
|
+
siteId,
|
|
104
|
+
type: contentType
|
|
105
|
+
};
|
|
106
|
+
if (typeof limit === "number") {
|
|
107
|
+
apiParams.limit = String(limit);
|
|
108
|
+
}
|
|
109
|
+
if (order === "newest") {
|
|
110
|
+
apiParams.order = "published_at.desc";
|
|
111
|
+
} else if (order === "oldest") {
|
|
112
|
+
apiParams.order = "published_at.asc";
|
|
113
|
+
} else if (order === "title") {
|
|
114
|
+
apiParams.order = "title.asc";
|
|
115
|
+
}
|
|
116
|
+
if (preview) {
|
|
117
|
+
apiParams.stage = "preview";
|
|
118
|
+
}
|
|
119
|
+
if (mode === "manual" && entryIds?.length) {
|
|
120
|
+
apiParams.mode = "manual";
|
|
121
|
+
apiParams.entryIds = JSON.stringify(entryIds);
|
|
122
|
+
}
|
|
123
|
+
return await apiClient({ endpoint: "listPublishedEntries", params: apiParams });
|
|
124
|
+
});
|
|
125
|
+
},
|
|
126
|
+
async getEntry(params) {
|
|
127
|
+
const { siteId, contentType, slug } = params;
|
|
128
|
+
const cacheKey = `entry:${siteId}:${contentType}:${slug}`;
|
|
129
|
+
return cachedFetch(cacheKey, async () => {
|
|
130
|
+
return await apiClient({ endpoint: "getPublishedEntryPreview", params: { siteId, type: contentType, slug } });
|
|
131
|
+
});
|
|
132
|
+
},
|
|
133
|
+
async getPublicFormById(params) {
|
|
134
|
+
const { formId } = params;
|
|
135
|
+
if (!formId) {
|
|
136
|
+
throw new Error("getPublicFormById() requires formId");
|
|
137
|
+
}
|
|
138
|
+
const cacheKey = `public-form:${formId}`;
|
|
139
|
+
return cachedFetch(cacheKey, async () => {
|
|
140
|
+
return await apiClient({ endpoint: "getPublicFormById", params: { formId } });
|
|
141
|
+
});
|
|
142
|
+
},
|
|
143
|
+
async getPublicBookingServices(params) {
|
|
144
|
+
const { siteId, ids } = params;
|
|
145
|
+
if (!siteId) {
|
|
146
|
+
throw new Error("getPublicBookingServices() requires siteId");
|
|
147
|
+
}
|
|
148
|
+
const cacheKey = `public-booking-services:${siteId}:${ids ?? ""}`;
|
|
149
|
+
return cachedFetch(cacheKey, async () => {
|
|
150
|
+
const apiParams = { siteId };
|
|
151
|
+
if (ids) apiParams.ids = ids;
|
|
152
|
+
return await apiClient({ endpoint: "getPublicBookingServices", params: apiParams });
|
|
153
|
+
});
|
|
154
|
+
},
|
|
155
|
+
async listPublicEvents(params) {
|
|
156
|
+
const { siteId, limit, from, to, stage } = params;
|
|
157
|
+
if (!siteId) {
|
|
158
|
+
throw new Error("listPublicEvents() requires siteId");
|
|
159
|
+
}
|
|
160
|
+
const cacheKey = `public-events:${siteId}:${limit ?? ""}:${from ?? ""}:${to ?? ""}:${stage ?? ""}`;
|
|
161
|
+
return cachedFetch(cacheKey, async () => {
|
|
162
|
+
const apiParams = { siteId };
|
|
163
|
+
if (typeof limit === "number") apiParams.limit = String(limit);
|
|
164
|
+
if (from) apiParams.from = from;
|
|
165
|
+
if (to) apiParams.to = to;
|
|
166
|
+
if (stage) apiParams.stage = stage;
|
|
167
|
+
return await apiClient({ endpoint: "listPublicEvents", params: apiParams });
|
|
168
|
+
});
|
|
169
|
+
},
|
|
170
|
+
clearCache() {
|
|
171
|
+
cache.clear();
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export {
|
|
177
|
+
createRiverbankClient
|
|
178
|
+
};
|
|
179
|
+
//# sourceMappingURL=chunk-3KKZVGH4.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/client/index.ts","../../src/client/cache.ts"],"sourcesContent":["import { createBearerAPIClient } from '@riverbankcms/api';\nimport type {\n RiverbankClient,\n RiverbankClientConfig,\n PublicBookingServicesResponse,\n PublicEventsResponse,\n PublicFormResponse,\n} from './types';\nimport { SimpleCache } from './cache';\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 Builder 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 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 // Create internal API client with Bearer token auth\n const apiClient = createBearerAPIClient(config.apiKey, config.baseUrl);\n\n // Create cache instance\n const cache = new SimpleCache<unknown>({\n maxSize: cacheMaxSize,\n ttl: cacheTTL,\n });\n\n /**\n * Helper to cache API calls\n */\n async function cachedFetch<T>(\n cacheKey: string,\n fetcher: () => Promise<T>,\n options?: { force?: boolean }\n ): Promise<T> {\n // Check cache unless force is true\n if (cacheEnabled && !options?.force) {\n const cached = cache.get(cacheKey) as T | undefined;\n if (cached !== undefined) {\n return cached;\n }\n }\n\n // Fetch fresh data\n const data = await fetcher();\n\n // Store in cache\n if (cacheEnabled) {\n cache.set(cacheKey, data);\n }\n\n return data;\n }\n\n return {\n async getSite(params) {\n const { slug, domain, id } = 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\n return cachedFetch(cacheKey, async () => {\n // Convert params to string record for API client\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 });\n });\n },\n\n async getPage(params) {\n const { siteId, path, preview = false } = params;\n const cacheKey = `page:${siteId}:${path}:${preview}`;\n\n return cachedFetch(cacheKey, async () => {\n return await apiClient({ endpoint: 'getContentByPath', params: { siteId }, body: { path, preview } });\n });\n },\n\n async getEntries(params) {\n const { siteId, contentType, limit, order, preview = false, mode, entryIds } = params;\n\n // Include all params in cache key to ensure different queries are cached separately\n const entryIdsCacheKey = mode === 'manual' && entryIds?.length ? entryIds.join(',') : '';\n const cacheKey = `entries:${siteId}:${contentType}:${limit ?? ''}:${order ?? ''}:${preview}:${mode ?? ''}:${entryIdsCacheKey}`;\n\n return cachedFetch(cacheKey, async () => {\n // Build API params\n const apiParams: Record<string, string> = {\n siteId,\n type: contentType,\n };\n\n // Add optional pagination\n if (typeof limit === 'number') {\n apiParams.limit = String(limit);\n }\n\n // Convert user-friendly order to API format\n // 'order' means custom ordering - we don't pass an order param (API default)\n if (order === 'newest') {\n apiParams.order = 'published_at.desc';\n } else if (order === 'oldest') {\n apiParams.order = 'published_at.asc';\n } else if (order === 'title') {\n apiParams.order = 'title.asc';\n }\n // 'order' or undefined: don't set order param, use API default\n\n // Add preview stage if enabled\n if (preview) {\n apiParams.stage = 'preview';\n }\n\n // Manual mode - pass entry IDs for hydration\n if (mode === 'manual' && entryIds?.length) {\n apiParams.mode = 'manual';\n apiParams.entryIds = JSON.stringify(entryIds);\n }\n\n return await apiClient({ endpoint: 'listPublishedEntries', params: apiParams });\n });\n },\n\n async getEntry(params) {\n const { siteId, contentType, slug } = params;\n const cacheKey = `entry:${siteId}:${contentType}:${slug}`;\n\n return cachedFetch(cacheKey, async () => {\n return await apiClient({ endpoint: 'getPublishedEntryPreview', params: { siteId, type: contentType, slug } });\n });\n },\n\n async getPublicFormById(params) {\n const { formId } = params;\n if (!formId) {\n throw new Error('getPublicFormById() requires formId');\n }\n const cacheKey = `public-form:${formId}`;\n return cachedFetch(cacheKey, async () => {\n return await apiClient({ endpoint: 'getPublicFormById', params: { formId } }) as PublicFormResponse;\n });\n },\n\n async getPublicBookingServices(params) {\n const { siteId, ids } = params;\n if (!siteId) {\n throw new Error('getPublicBookingServices() requires siteId');\n }\n const cacheKey = `public-booking-services:${siteId}:${ids ?? ''}`;\n return cachedFetch(cacheKey, async () => {\n const apiParams: Record<string, string> = { siteId };\n if (ids) apiParams.ids = ids;\n return await apiClient({ endpoint: 'getPublicBookingServices', params: apiParams }) as PublicBookingServicesResponse;\n });\n },\n\n async listPublicEvents(params) {\n const { siteId, limit, from, to, stage } = params;\n if (!siteId) {\n throw new Error('listPublicEvents() requires siteId');\n }\n const cacheKey = `public-events:${siteId}:${limit ?? ''}:${from ?? ''}:${to ?? ''}:${stage ?? ''}`;\n return cachedFetch(cacheKey, async () => {\n const apiParams: Record<string, string> = { siteId };\n if (typeof limit === 'number') apiParams.limit = String(limit);\n if (from) apiParams.from = from;\n if (to) apiParams.to = to;\n if (stage) apiParams.stage = stage;\n return await apiClient({ endpoint: 'listPublicEvents', params: apiParams }) as PublicEventsResponse;\n });\n },\n\n clearCache() {\n cache.clear();\n },\n };\n}\n\n// Re-export types\nexport type { RiverbankClient, RiverbankClientConfig } from './types';\n","/**\n * Simple in-memory cache with TTL support\n */\nexport class SimpleCache<T> {\n private cache = new Map<string, { value: T; expires: number }>();\n private maxSize: number;\n private ttl: number;\n\n constructor(options: { maxSize?: number; ttl?: number } = {}) {\n this.maxSize = options.maxSize ?? 100;\n this.ttl = options.ttl ?? 300000; // 5 minutes in milliseconds\n }\n\n get(key: string): T | undefined {\n const entry = this.cache.get(key);\n if (!entry) return undefined;\n\n // Check if expired\n if (Date.now() > entry.expires) {\n this.cache.delete(key);\n return undefined;\n }\n\n return entry.value;\n }\n\n set(key: string, value: T): void {\n // Enforce max size with simple FIFO eviction\n if (this.cache.size >= this.maxSize) {\n const firstKey = this.cache.keys().next().value;\n if (firstKey) {\n this.cache.delete(firstKey);\n }\n }\n\n this.cache.set(key, {\n value,\n expires: Date.now() + this.ttl,\n });\n }\n\n clear(): void {\n this.cache.clear();\n }\n\n has(key: string): boolean {\n return this.get(key) !== undefined;\n }\n}\n"],"mappings":"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","names":[]}
|