@botlr/next 0.0.0-stage → 0.2.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.
@@ -0,0 +1,383 @@
1
+ import { BotlrClient, BotlrClientConfig, PublisherScope, PageResponse, LayoutNode } from '@botlr/sdk';
2
+ export * from '@botlr/sdk';
3
+ export { BotlrConflictError, BotlrError, createAssetUrl, createBotlrClient, getAssetFilename, getAssetId, getAssetUrl, isAssetData } from '@botlr/sdk';
4
+ import { Metadata } from 'next';
5
+ import * as react$1 from 'react';
6
+ import { ReactNode, JSX } from 'react';
7
+ import { ComponentRegistry } from '@botlr/react';
8
+ export * from '@botlr/react';
9
+
10
+ type GetBotlrParams = {
11
+ /** The app instance ID. Falls back to `BOTLR_APP_ID`. */
12
+ appId?: string;
13
+ /** Locale override; defaults to the request's `Accept-Language`, then `en`. */
14
+ locale?: string;
15
+ preview?: boolean;
16
+ /** Site token; falls back to `BOTLR_API_KEY`. Omit in dev mode. */
17
+ apiKey?: string;
18
+ /** Preview JWT, forwarded to `/api/resolve` in preview mode. */
19
+ previewToken?: string;
20
+ /** API origin; falls back to `BOTLR_API_URL`. */
21
+ baseUrl?: string;
22
+ fetch?: typeof fetch;
23
+ };
24
+ /**
25
+ * A Botlr client for the current request, for Server Components, route
26
+ * handlers and server actions. Reads the locale from `Accept-Language`
27
+ * unless given.
28
+ *
29
+ * ```ts
30
+ * const botlr = await getBotlrForRoute({ appId: scope.appInstanceId, locale: scope.locale, preview });
31
+ * const pages = await botlr.getPageList({ tags: ["news"] });
32
+ * ```
33
+ */
34
+ declare function getBotlrForRoute(params?: GetBotlrParams): Promise<BotlrClient>;
35
+ /**
36
+ * `getBotlrForRoute` memoised per request with React `cache`, so several
37
+ * components in one render share a client.
38
+ */
39
+ declare const getCachedBotlr: (params?: GetBotlrParams) => Promise<BotlrClient>;
40
+ /**
41
+ * A client from the environment alone, with no request context. For scripts,
42
+ * `generateStaticParams`, instrumentation and anywhere `headers()` is not
43
+ * available.
44
+ */
45
+ declare function createBotlrFromEnv(overrides?: Partial<GetBotlrParams>): BotlrClient;
46
+
47
+ /** Environment variables the helpers read. All server-side: the API URL is never exposed to the browser. */
48
+ declare const BOTLR_ENV: {
49
+ readonly apiUrl: "BOTLR_API_URL";
50
+ readonly apiKey: "BOTLR_API_KEY";
51
+ readonly appId: "BOTLR_APP_ID";
52
+ readonly pat: "BOTLR_PAT";
53
+ readonly orgSlug: "BOTLR_ORG_SLUG";
54
+ readonly projectSlug: "BOTLR_PROJECT_SLUG";
55
+ readonly appInstanceSlug: "BOTLR_APP_INSTANCE_SLUG";
56
+ readonly environment: "BOTLR_ENVIRONMENT";
57
+ };
58
+ declare const DEFAULT_API_URL = "http://localhost:8888";
59
+ /**
60
+ * Client config from the environment, with `overrides` winning. Throws when
61
+ * no app id is known, since every Botlr request needs one.
62
+ */
63
+ declare function configFromEnv(overrides?: Partial<BotlrClientConfig>): BotlrClientConfig;
64
+ /**
65
+ * The deployment environment used by visibility rules: `BOTLR_ENVIRONMENT`,
66
+ * then `ENVIRONMENT`, then `NODE_ENV`, then `fallback`.
67
+ */
68
+ declare function environmentFromEnv(fallback?: string): string;
69
+ /**
70
+ * The `PublisherScope` for a locale from `BOTLR_ORG_SLUG`, `BOTLR_PROJECT_SLUG`,
71
+ * `BOTLR_APP_INSTANCE_SLUG` and `BOTLR_APP_ID`, with `overrides` winning.
72
+ * Missing slugs become empty strings rather than errors, since only asset
73
+ * URLs need them.
74
+ */
75
+ declare function scopeFromEnv(overrides: Partial<PublisherScope> & Pick<PublisherScope, "locale">): PublisherScope;
76
+
77
+ /** The first language of an `Accept-Language` value, or `fallback`. */
78
+ declare function localeFromAcceptLanguage(header: string | null | undefined, fallback?: string): string;
79
+
80
+ type ResolvedPage = {
81
+ kind: "page";
82
+ pageData: PageResponse;
83
+ } | {
84
+ kind: "redirect";
85
+ destination: string;
86
+ } | {
87
+ kind: "404";
88
+ } | {
89
+ kind: "error";
90
+ message: string;
91
+ };
92
+ interface ResolvePageOptions {
93
+ /**
94
+ * Non-page content types rendered through a section: the resolved content
95
+ * is prepended to the `main` region as an inline section of the mapped
96
+ * type. Defaults to `{ article: "article" }`.
97
+ */
98
+ contentSections?: Record<string, string>;
99
+ }
100
+ /**
101
+ * Resolves a slug to a page through `/api/resolve`, folding redirects, 404s
102
+ * and errors into one result and injecting article content into the layout.
103
+ * Not cached; see `createPageResolver`.
104
+ */
105
+ declare function resolvePage(client: BotlrClient, slug: string, locale?: string, options?: ResolvePageOptions): Promise<ResolvedPage>;
106
+ /**
107
+ * `resolvePage` bound to a client and memoised per request with React
108
+ * `cache`, so `generateMetadata` and the page share one fetch.
109
+ *
110
+ * ```ts
111
+ * const resolvePage = createPageResolver(botlr);
112
+ * const resolved = await resolvePage("about/team", "en");
113
+ * ```
114
+ */
115
+ declare function createPageResolver(client: BotlrClient, options?: ResolvePageOptions): (slug: string, locale?: string) => Promise<ResolvedPage>;
116
+
117
+ interface PageMetadataOptions {
118
+ /**
119
+ * The site origin, for example `https://www.example.com`. Needed for the
120
+ * canonical URL and hreflang alternates; without it only an explicit
121
+ * `seo_meta.canonical` is emitted.
122
+ */
123
+ siteUrl?: string;
124
+ /** URL prefix of the page's locale, for example `/en`. Empty for the root locale. */
125
+ folder?: string;
126
+ }
127
+ /**
128
+ * Next.js `Metadata` from a page's `seo_meta`: title, description, keywords,
129
+ * Open Graph, Twitter, robots, canonical and hreflang alternates.
130
+ */
131
+ declare function buildPageMetadata(pageData: PageResponse, options?: PageMetadataOptions): Metadata;
132
+
133
+ type Layout = Record<string, LayoutNode[]>;
134
+ interface RenderCmsPageOptions {
135
+ pageData: PageResponse;
136
+ scope: PublisherScope;
137
+ /** Section type to component map used for every region. */
138
+ componentRegistry: ComponentRegistry;
139
+ /** Defaults to `"published"`; `"draft"` also renders draft sections. */
140
+ mode?: "draft" | "published";
141
+ /** Defaults to `scope.locale`. */
142
+ locale?: string;
143
+ /** Environment for visibility rules; defaults to `environmentFromEnv()`. */
144
+ environment?: string;
145
+ /** Used to fetch shared sections unless `fetchSharedSection` is given. */
146
+ client?: BotlrClient;
147
+ /** Fetch a shared section's page by id. Defaults to `client.getPageBySlug(id, locale)`. */
148
+ fetchSharedSection?: (id: string) => Promise<{
149
+ layout?: Partial<Layout>;
150
+ } | null>;
151
+ /** Show a fixed "draft/published preview" badge; for preview routes only. */
152
+ showModeBadge?: boolean;
153
+ /** Rendered when the template cannot be parsed. */
154
+ renderError?: (error: unknown) => ReactNode;
155
+ }
156
+ /** Ids of every shared section in the layout, grid columns included. */
157
+ declare function collectSharedSectionIds(nodes: LayoutNode[]): string[];
158
+ /**
159
+ * The template's `{{...}}` placeholders resolved: title, lang, theme css,
160
+ * `{{{REGION}}}` markers turned into text markers for the parser, and the
161
+ * body extracted.
162
+ */
163
+ declare function prepareTemplate(templateHtml: string, pageData: PageResponse, templateCss: string, locale: string): string;
164
+ /** Template sections first, then the page's own, per region. */
165
+ declare function mergeTemplateLayout(pageData: PageResponse): Layout;
166
+ /**
167
+ * Renders a resolved page: picks the page template (page, then theme, then a
168
+ * built-in default), merges template and page layouts, applies visibility
169
+ * rules, fetches shared sections and mounts a `SectionRenderer` at every
170
+ * `{{{REGION}}}` marker.
171
+ */
172
+ declare function renderCmsPage({ pageData, scope, componentRegistry, mode, locale, environment, client, fetchSharedSection, showModeBadge, renderError, }: RenderCmsPageOptions): Promise<JSX.Element>;
173
+
174
+ interface ThemeColorSet {
175
+ primary?: string;
176
+ primaryForeground?: string;
177
+ secondary?: string;
178
+ secondaryForeground?: string;
179
+ accent?: string;
180
+ accentForeground?: string;
181
+ background?: string;
182
+ foreground?: string;
183
+ muted?: string;
184
+ mutedForeground?: string;
185
+ border?: string;
186
+ input?: string;
187
+ ring?: string;
188
+ destructive?: string;
189
+ destructiveForeground?: string;
190
+ success?: string;
191
+ successForeground?: string;
192
+ warning?: string;
193
+ warningForeground?: string;
194
+ info?: string;
195
+ infoForeground?: string;
196
+ }
197
+ interface ThemeSettings {
198
+ colors?: {
199
+ light?: ThemeColorSet;
200
+ dark?: ThemeColorSet;
201
+ };
202
+ typography?: {
203
+ fontFamily?: {
204
+ heading?: string;
205
+ body?: string;
206
+ ui?: string;
207
+ code?: string;
208
+ sans?: string;
209
+ serif?: string;
210
+ mono?: string;
211
+ };
212
+ fontSize?: {
213
+ h1?: string;
214
+ h2?: string;
215
+ h3?: string;
216
+ h4?: string;
217
+ h5?: string;
218
+ h6?: string;
219
+ xs?: string;
220
+ sm?: string;
221
+ base?: string;
222
+ lg?: string;
223
+ xl?: string;
224
+ "2xl"?: string;
225
+ "3xl"?: string;
226
+ "4xl"?: string;
227
+ };
228
+ fontWeight?: {
229
+ normal?: string;
230
+ medium?: string;
231
+ semibold?: string;
232
+ bold?: string;
233
+ };
234
+ lineHeight?: {
235
+ tight?: string;
236
+ normal?: string;
237
+ relaxed?: string;
238
+ };
239
+ };
240
+ spacing?: {
241
+ xs?: string;
242
+ sm?: string;
243
+ md?: string;
244
+ lg?: string;
245
+ xl?: string;
246
+ "2xl"?: string;
247
+ };
248
+ borderRadius?: {
249
+ none?: string;
250
+ sm?: string;
251
+ md?: string;
252
+ lg?: string;
253
+ xl?: string;
254
+ full?: string;
255
+ };
256
+ shadows?: {
257
+ sm?: string;
258
+ md?: string;
259
+ lg?: string;
260
+ xl?: string;
261
+ };
262
+ layout?: {
263
+ containerMaxWidth?: string;
264
+ sidebarWidth?: string;
265
+ headerHeight?: string;
266
+ footerHeight?: string;
267
+ };
268
+ }
269
+ /**
270
+ * Theme settings as CSS custom properties: common variables and light colors
271
+ * on `:root`, dark colors under `prefers-color-scheme: dark` and `.dark`.
272
+ */
273
+ declare function themeSettingsToCss(settings?: ThemeSettings | Record<string, any>): string;
274
+
275
+ interface StaticTextFileOptions {
276
+ /** Defaults to the client's locale, then `en`. */
277
+ locale?: string;
278
+ /** Environment for visibility rules; defaults to `environmentFromEnv()`. */
279
+ environment?: string;
280
+ /** The section type whose content is served; defaults to `text`. */
281
+ sectionType?: string;
282
+ /** Section props tried in order for the text; defaults to `content`, `body`, `text`. */
283
+ propKeys?: string[];
284
+ }
285
+ /**
286
+ * A CMS-managed plain-text file such as `robots.txt`, `humans.txt` or
287
+ * `llms.txt`: the page at `slug`, its visible text sections concatenated.
288
+ * Serves `defaultContent` when the page has none or cannot be fetched.
289
+ *
290
+ * ```ts
291
+ * // app/robots.txt/route.ts
292
+ * export const GET = () => serveStaticTextFile(botlr, "robots.txt", defaultRobotsContent());
293
+ * ```
294
+ */
295
+ declare function serveStaticTextFile(client: BotlrClient, slug: string, defaultContent: string, options?: StaticTextFileOptions): Promise<Response>;
296
+ /** Allow everything in production, disallow everything elsewhere. */
297
+ declare function defaultRobotsContent(environment?: string): string;
298
+
299
+ interface LocalePageConfig {
300
+ /** CMS locale code, for example `da` or `en`. */
301
+ locale: string;
302
+ /** URL prefix for this locale, for example `/en`. Empty for the root locale. */
303
+ folder?: string;
304
+ /** The client the pages are fetched with; usually one built with `createBotlrFromEnv`. */
305
+ client: BotlrClient;
306
+ componentRegistry: ComponentRegistry;
307
+ /** Overrides on the scope built from the environment. */
308
+ scope?: Partial<PublisherScope>;
309
+ /** The site origin, for canonical and hreflang metadata. */
310
+ siteUrl?: string;
311
+ /** Environment for visibility rules; defaults to `environmentFromEnv()`. */
312
+ environment?: string;
313
+ resolveOptions?: ResolvePageOptions;
314
+ /** Redirect kind for CMS redirects; defaults to `permanent` (308). */
315
+ redirect?: "permanent" | "temporary";
316
+ /** Page slugs fetched for `generateStaticParams`; defaults to 1000. */
317
+ staticParamsLimit?: number;
318
+ /** Rendered when resolving fails. */
319
+ renderError?: (message: string) => ReactNode;
320
+ }
321
+ interface LocalePageProps {
322
+ params: Promise<{
323
+ slug?: string[];
324
+ }>;
325
+ searchParams?: Promise<Record<string, string | string[] | undefined>>;
326
+ }
327
+ /** The page slug for a catch-all route: the segments joined, or `index`. */
328
+ declare function pageSlugFromParams(params: LocalePageProps["params"]): Promise<string>;
329
+ /**
330
+ * Everything a static per-locale `[[...slug]]` route needs. The route file
331
+ * keeps only its segment config, which Next must read statically:
332
+ *
333
+ * ```ts
334
+ * // app/(root)/[[...slug]]/page.tsx
335
+ * export const revalidate = false;
336
+ * export const dynamicParams = false;
337
+ * const page = createLocalePage({ locale: "da", folder: "", client: botlr, componentRegistry });
338
+ * export const generateStaticParams = page.generateStaticParams;
339
+ * export const generateMetadata = page.generateMetadata;
340
+ * export default page.Page;
341
+ * ```
342
+ */
343
+ declare function createLocalePage(config: LocalePageConfig): {
344
+ generateStaticParams: () => Promise<{
345
+ slug?: string[];
346
+ }[]>;
347
+ generateMetadata: ({ params }: LocalePageProps) => Promise<Metadata>;
348
+ Page: ({ params }: LocalePageProps) => Promise<react$1.JSX.Element>;
349
+ resolvePage: (slug: string, locale?: string) => Promise<ResolvedPage>;
350
+ scope: PublisherScope;
351
+ };
352
+ /**
353
+ * A locale folder that only aliases another, for example `/se/*` to `/sv/*`.
354
+ * Permanently redirects, keeping the rest of the path.
355
+ */
356
+ declare function createLocaleAliasPage(targetFolder: string): ({ params }: LocalePageProps) => Promise<never>;
357
+
358
+ interface RateLimitConfig {
359
+ /** Max requests allowed in the window */
360
+ limit: number;
361
+ /** Window size in seconds */
362
+ window: number;
363
+ }
364
+ interface RateLimitResult {
365
+ allowed: boolean;
366
+ remaining: number;
367
+ /** Epoch ms when the window resets */
368
+ resetAt: number;
369
+ }
370
+ /**
371
+ * A fixed-window in-memory rate limit, per server instance. Enough to slow
372
+ * down form and forum spam; not shared across instances or restarts.
373
+ *
374
+ * ```ts
375
+ * const rl = rateLimit(`form:${getClientIp(request)}`, { limit: 5, window: 60 });
376
+ * if (!rl.allowed) return new Response("Too many requests", { status: 429 });
377
+ * ```
378
+ */
379
+ declare function rateLimit(key: string, config: RateLimitConfig): RateLimitResult;
380
+ /** The client IP from `x-forwarded-for` or `x-real-ip`, or `"unknown"`. */
381
+ declare function getClientIp(request: Request): string;
382
+
383
+ export { BOTLR_ENV, DEFAULT_API_URL, type GetBotlrParams, type LocalePageConfig, type LocalePageProps, type PageMetadataOptions, type RateLimitConfig, type RateLimitResult, type RenderCmsPageOptions, type ResolvePageOptions, type ResolvedPage, type StaticTextFileOptions, type ThemeColorSet, type ThemeSettings, buildPageMetadata, collectSharedSectionIds, configFromEnv, createBotlrFromEnv, createLocaleAliasPage, createLocalePage, createPageResolver, defaultRobotsContent, environmentFromEnv, getBotlrForRoute, getCachedBotlr, getClientIp, localeFromAcceptLanguage, mergeTemplateLayout, pageSlugFromParams, prepareTemplate, rateLimit, renderCmsPage, resolvePage, scopeFromEnv, serveStaticTextFile, themeSettingsToCss };