@botlr/sdk 0.5.0 → 0.6.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/CHANGELOG.md +10 -0
- package/dist/index.cjs +9 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +44 -19
- package/dist/index.d.ts +44 -19
- package/dist/index.js +9 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @botlr/sdk
|
|
2
2
|
|
|
3
|
+
## 0.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- `botlr schema push` follows the server's merge behaviour for FormBuilder schemas instead of asking for `--force`.
|
|
8
|
+
|
|
9
|
+
- Field-DSL `schema` in the manifest pushed at a definition whose dashboard schema is in the FormBuilder format is merged by the server: a node per field the dashboard lacks, every existing node kept with its id, label and settings, nothing removed. The CLI's diff header says so (`the server will add the missing fields as FormBuilder nodes and keep the dashboard's (format kept; --force would replace it)`), lists the dashboard's extra nodes as "dashboard only, kept", asks as before, and then prints what the server did: `merged schema of <name> (form-builder): added ...; kept ...`, plus `the dashboard also defines: ... (not in the code)` when the dashboard has nodes the code does not declare. Same format: `replaced schema of <name>`. `changed: false`: `nothing to change`.
|
|
10
|
+
- A FormBuilder `schema` pushed at a field-DSL definition still needs `--force`: the CLI stops right after the diff with the hint, without asking or sending anything (the server would answer 409). `--force` replaces the schema outright whatever the formats, and the question says which format replaces which.
|
|
11
|
+
- `@botlr/sdk`: `SectionSchemaPutResponse` gains `mode: "replaced" | "merged"` (the `SectionSchemaPushMode` type), `added` and `dashboardOnly`.
|
|
12
|
+
|
|
3
13
|
## 0.5.0
|
|
4
14
|
|
|
5
15
|
### Minor Changes
|
package/dist/index.cjs
CHANGED
|
@@ -296,11 +296,15 @@ function createBotlrClient(config) {
|
|
|
296
296
|
});
|
|
297
297
|
},
|
|
298
298
|
/**
|
|
299
|
-
*
|
|
300
|
-
* (`write:sections`).
|
|
301
|
-
*
|
|
302
|
-
* `
|
|
303
|
-
*
|
|
299
|
+
* Write the definition's props schema with the personal access token
|
|
300
|
+
* (`write:sections`). Same format, or `force`: the schema is replaced
|
|
301
|
+
* (`mode: "replaced"`). Field-DSL fields at a FormBuilder schema without
|
|
302
|
+
* `force`: the missing fields are added as nodes and nothing is removed
|
|
303
|
+
* (`mode: "merged"`, with `added` and `dashboardOnly`). A FormBuilder
|
|
304
|
+
* schema at a field-DSL one without `force` throws
|
|
305
|
+
* `BotlrSchemaFormatConflictError`; a `BotlrError` with status 422 means
|
|
306
|
+
* the schema is not an object of fields or the definition is not
|
|
307
|
+
* project-scoped.
|
|
304
308
|
*/
|
|
305
309
|
async putSchema(name, body) {
|
|
306
310
|
if (!config.personalToken) {
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/retry.ts","../src/client.ts","../src/sections.ts","../src/assets.ts"],"sourcesContent":["export * from \"./client\";\nexport * from \"./types\";\nexport * from \"./errors\";\nexport * from \"./sections\";\nexport * from \"./assets\";\nexport { DEFAULT_RETRY_STATUSES } from \"./retry\";\n","/**\n * Thrown for every non-2xx response. `status` is the HTTP status and\n * `details` the parsed JSON body (or the raw text when the body is not JSON).\n */\nexport class BotlrError extends Error {\n readonly status: number | undefined;\n readonly details: unknown;\n\n constructor(message: string, status?: number, details?: unknown) {\n super(message);\n this.name = \"BotlrError\";\n this.status = status;\n this.details = details;\n }\n}\n\n/** The latest version of a section source, as reported on a push conflict. */\nexport type ConflictLatest = {\n version: number;\n contentHash: string;\n};\n\n/**\n * Thrown by `sections.push` on 409: `baseVersion` was not the latest version,\n * or a concurrent push won. `latest` is null when the server has no version\n * at all (the client sent a `baseVersion` above 0 for a section with no source).\n */\nexport class BotlrConflictError extends BotlrError {\n readonly latest: ConflictLatest | null;\n\n constructor(message: string, latest: ConflictLatest | null, details?: unknown) {\n super(message, 409, details);\n this.name = \"BotlrConflictError\";\n this.latest = latest;\n }\n}\n\n/**\n * Thrown by `sections.putSchema` on 409: the definition's current schema and\n * the new one are in different formats and `force` was not set.\n */\nexport class BotlrSchemaFormatConflictError extends BotlrError {\n readonly currentFormat: string;\n readonly newFormat: string;\n\n constructor(message: string, currentFormat: string, newFormat: string, details?: unknown) {\n super(message, 409, details);\n this.name = \"BotlrSchemaFormatConflictError\";\n this.currentFormat = currentFormat;\n this.newFormat = newFormat;\n }\n}\n\n/**\n * The human-readable message of an error response, when the server sent one:\n * `detail` as a string, `detail.message`, or the `msg` lines of a FastAPI\n * validation error. Undefined for anything else.\n */\nexport function botlrErrorMessage(error: unknown): string | undefined {\n if (!(error instanceof BotlrError)) return undefined;\n const details = error.details;\n if (!details || typeof details !== \"object\") return typeof details === \"string\" ? details : undefined;\n const detail = (details as { detail?: unknown }).detail;\n if (typeof detail === \"string\") return detail;\n if (Array.isArray(detail)) {\n const lines = detail\n .map((item) => (item && typeof item === \"object\" ? (item as { msg?: unknown }).msg : undefined))\n .filter((msg): msg is string => typeof msg === \"string\");\n return lines.length ? lines.join(\"; \") : undefined;\n }\n if (detail && typeof detail === \"object\") {\n const message = (detail as { message?: unknown }).message;\n return typeof message === \"string\" ? message : undefined;\n }\n return undefined;\n}\n","import type { BotlrRetryConfig, BotlrRetryInfo } from \"./types\";\n\n/** Statuses retried by default: timeouts, rate limits and server errors. */\nexport const DEFAULT_RETRY_STATUSES: readonly number[] = [408, 429, 500, 502, 503, 504];\n\nexport type ResolvedRetryConfig = {\n retries: number;\n delay: number;\n maxDelay: number;\n statuses: readonly number[];\n onRetry: ((info: BotlrRetryInfo) => void) | undefined;\n};\n\nexport function resolveRetryConfig(retry: BotlrRetryConfig | false | undefined): ResolvedRetryConfig {\n if (retry === false) {\n return { retries: 0, delay: 0, maxDelay: 0, statuses: [], onRetry: undefined };\n }\n return {\n retries: Math.max(0, retry?.retries ?? 2),\n delay: Math.max(0, retry?.delay ?? 300),\n maxDelay: Math.max(0, retry?.maxDelay ?? 5000),\n statuses: retry?.statuses ?? DEFAULT_RETRY_STATUSES,\n onRetry: retry?.onRetry,\n };\n}\n\n/** Only reads are retried: a repeated POST or PUT could submit twice. */\nexport function isRetryableMethod(method: string | undefined): boolean {\n const m = (method ?? \"GET\").toUpperCase();\n return m === \"GET\" || m === \"HEAD\";\n}\n\n/** `Retry-After` as milliseconds, from either the seconds or the date form. */\nexport function parseRetryAfter(value: string | null | undefined): number | undefined {\n if (!value) return undefined;\n const seconds = Number(value);\n if (Number.isFinite(seconds)) return Math.max(0, seconds * 1000);\n const date = Date.parse(value);\n if (Number.isFinite(date)) return Math.max(0, date - Date.now());\n return undefined;\n}\n\n/**\n * The wait before retry number `attempt + 1`: `Retry-After` when the server\n * sent one, else `delay * 2^attempt` with ±25% jitter, both capped at `maxDelay`.\n */\nexport function retryDelayMs(retry: ResolvedRetryConfig, attempt: number, retryAfter?: string | null): number {\n const fromHeader = parseRetryAfter(retryAfter);\n if (fromHeader !== undefined) return Math.min(fromHeader, retry.maxDelay);\n const base = Math.min(retry.maxDelay, retry.delay * 2 ** attempt);\n return Math.round(base * (0.75 + Math.random() * 0.5));\n}\n\nexport async function waitBeforeRetry(\n retry: ResolvedRetryConfig,\n attempt: number,\n error: unknown,\n url: string,\n retryAfter?: string | null,\n): Promise<void> {\n const delay = retryDelayMs(retry, attempt, retryAfter);\n retry.onRetry?.({ attempt: attempt + 1, delay, url, error });\n if (delay > 0) await new Promise<void>((resolve) => setTimeout(resolve, delay));\n}\n","import type {\n BotlrClientConfig,\n BotlrContext,\n BotlrEntry,\n BotlrQuery,\n BotlrQueryValue,\n FormResponse,\n GetEntriesParams,\n GetEntryBySlugParams,\n GetPageListParams,\n GetPageSlugListParams,\n GetSingletonParams,\n ListResponse,\n MenuResponse,\n PageListResponse,\n PageResponse,\n PageSlugListResponse,\n RedirectsResponse,\n ResolveResponse,\n} from \"./types\";\nimport type {\n SectionSchemaGetResponse,\n SectionSchemaPutBody,\n SectionSchemaPutResponse,\n SectionSourceListParams,\n SectionSourceListResponse,\n SectionSourcePullParams,\n SectionSourcePullResponse,\n SectionSourcePushBody,\n SectionSourcePushResponse,\n} from \"./sections\";\nimport { BotlrConflictError, BotlrError, BotlrSchemaFormatConflictError } from \"./errors\";\nimport { isRetryableMethod, resolveRetryConfig, waitBeforeRetry } from \"./retry\";\n\nexport type RequestOptions = Omit<RequestInit, \"headers\"> & {\n query?: BotlrQuery;\n context?: Partial<BotlrContext>;\n headers?: Record<string, string>;\n};\n\nexport function toQueryString(query: BotlrQuery): string {\n const params = new URLSearchParams();\n\n for (const [key, value] of Object.entries(query)) {\n if (value === undefined || value === null) continue;\n\n if (Array.isArray(value)) {\n for (const item of value) {\n if (item !== undefined && item !== null) {\n params.append(key, String(item));\n }\n }\n } else {\n params.set(key, String(value));\n }\n }\n\n const qs = params.toString();\n return qs ? `?${qs}` : \"\";\n}\n\nfunction mergeContext(\n base?: Partial<BotlrContext>,\n override?: Partial<BotlrContext>,\n): Partial<BotlrContext> | undefined {\n if (!base && !override) return undefined;\n return { ...base, ...override };\n}\n\nexport function contextToHeaders(context?: Partial<BotlrContext>): Record<string, string> {\n if (!context) return {};\n\n return {\n ...(context.appId ? { \"x-botlr-app-id\": context.appId } : {}),\n ...(context.locale ? { \"x-botlr-locale\": context.locale } : {}),\n ...(context.preview !== undefined ? { \"x-botlr-preview\": String(context.preview) } : {}),\n };\n}\n\nasync function errorDetails(response: Response): Promise<unknown> {\n const text = await response.text().catch(() => undefined);\n if (text === undefined || text === \"\") return undefined;\n try {\n return JSON.parse(text) as unknown;\n } catch {\n return text;\n }\n}\n\nfunction conflictLatest(details: unknown): { version: number; contentHash: string } | null {\n if (!details || typeof details !== \"object\") return null;\n const detail = (details as { detail?: unknown }).detail;\n if (!detail || typeof detail !== \"object\") return null;\n const latest = (detail as { latest?: unknown }).latest;\n if (!latest || typeof latest !== \"object\") return null;\n const { version, contentHash } = latest as { version?: unknown; contentHash?: unknown };\n if (typeof version !== \"number\" || typeof contentHash !== \"string\") return null;\n return { version, contentHash };\n}\n\nfunction conflictFormats(details: unknown): { currentFormat: string; newFormat: string } | null {\n if (!details || typeof details !== \"object\") return null;\n const detail = (details as { detail?: unknown }).detail;\n if (!detail || typeof detail !== \"object\") return null;\n const { currentFormat, newFormat } = detail as { currentFormat?: unknown; newFormat?: unknown };\n if (typeof currentFormat !== \"string\" || typeof newFormat !== \"string\") return null;\n return { currentFormat, newFormat };\n}\n\nfunction conflictMessage(details: unknown): string | undefined {\n if (!details || typeof details !== \"object\") return undefined;\n const detail = (details as { detail?: unknown }).detail;\n if (!detail || typeof detail !== \"object\") return undefined;\n const message = (detail as { message?: unknown }).message;\n return typeof message === \"string\" ? message : undefined;\n}\n\nexport type BotlrClient = ReturnType<typeof createBotlrClient>;\n\n/**\n * A Botlr client. Every call sends the site token as `Authorization: Bearer`\n * plus `x-botlr-app-id`, `x-botlr-locale` and `x-botlr-preview` from the\n * config; `sections.push` and `sections.putSchema` send the personal token instead.\n */\nexport function createBotlrClient(config: BotlrClientConfig) {\n const fetcher = config.fetch ?? globalThis.fetch;\n if (typeof fetcher !== \"function\") {\n throw new BotlrError(\"No fetch implementation: pass `fetch` in the client config\");\n }\n const baseUrl = config.baseUrl.replace(/\\/+$/, \"\");\n\n const baseContext: BotlrContext = {\n appId: config.appId,\n locale: config.locale,\n preview: config.preview,\n };\n\n const retry = resolveRetryConfig(config.retry);\n\n async function request<T>(path: string, options: RequestOptions = {}): Promise<T> {\n const { query, context: contextOverride, headers: extraHeaders, ...init } = options;\n const context = mergeContext(baseContext, contextOverride);\n const url = `${baseUrl}${path}${toQueryString(query ?? {})}`;\n const headers = {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n ...(config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}),\n ...contextToHeaders(context),\n ...(extraHeaders ?? {}),\n };\n const retryable = retry.retries > 0 && isRetryableMethod(init.method);\n\n for (let attempt = 0; ; attempt++) {\n const canRetry = retryable && attempt < retry.retries && !init.signal?.aborted;\n\n let response: Response;\n try {\n response = await fetcher(url, { ...init, headers });\n } catch (error) {\n if (!canRetry) throw error;\n await waitBeforeRetry(retry, attempt, error, url);\n continue;\n }\n\n if (response.ok) {\n if (response.status === 204) return undefined as T;\n return (await response.json()) as T;\n }\n\n const details = await errorDetails(response);\n const error =\n response.status === 409\n ? new BotlrConflictError(\n conflictMessage(details) ?? `Botlr request failed with status 409`,\n conflictLatest(details),\n details,\n )\n : new BotlrError(`Botlr request failed with status ${response.status}`, response.status, details);\n\n if (!canRetry || !retry.statuses.includes(response.status)) throw error;\n await waitBeforeRetry(retry, attempt, error, url, response.headers.get(\"retry-after\"));\n }\n }\n\n const sections = {\n /** Every section the app can pull for `framework`, with the latest version per scope. */\n list(params: SectionSourceListParams): Promise<SectionSourceListResponse> {\n return request<SectionSourceListResponse>(\"/api/sections/source\", {\n method: \"GET\",\n query: { framework: params.framework, appInstanceId: baseContext.appId },\n });\n },\n\n /** The section's source: this app's row first, the shared row second. */\n pull(name: string, params: SectionSourcePullParams): Promise<SectionSourcePullResponse> {\n return request<SectionSourcePullResponse>(`/api/sections/${encodeURIComponent(name)}/source`, {\n method: \"GET\",\n query: {\n framework: params.framework,\n appInstanceId: baseContext.appId,\n version: params.version,\n scope: params.scope,\n },\n });\n },\n\n /**\n * Push a new version with the personal access token (`write:sections`).\n * Resolves with `created: true` on 201 and `created: false` when nothing\n * changed; throws `BotlrConflictError` when `baseVersion` is not the latest.\n */\n push(name: string, body: SectionSourcePushBody): Promise<SectionSourcePushResponse> {\n if (!config.personalToken) {\n return Promise.reject(\n new BotlrError(\"sections.push needs `personalToken` (a personal access token with write:sections)\"),\n );\n }\n const { appInstanceId, ...rest } = body;\n return request<SectionSourcePushResponse>(`/api/sections/${encodeURIComponent(name)}/source`, {\n method: \"PUT\",\n headers: { Authorization: `Bearer ${config.personalToken}` },\n body: JSON.stringify({ appInstanceId: appInstanceId ?? baseContext.appId, ...rest }),\n });\n },\n\n /**\n * The section definition with its props schema and format, with the site\n * token. Works whether or not source has been pushed; 404 when the app has\n * no section type of that name.\n */\n getSchema(name: string): Promise<SectionSchemaGetResponse> {\n return request<SectionSchemaGetResponse>(`/api/sections/${encodeURIComponent(name)}/schema`, {\n method: \"GET\",\n query: { appInstanceId: baseContext.appId },\n });\n },\n\n /**\n * Replace the definition's props schema with the personal access token\n * (`write:sections`). Throws `BotlrSchemaFormatConflictError` when the\n * current schema is in the other format and `force` is not set, and a\n * `BotlrError` with status 422 when the schema is not an object of fields\n * or the definition is not project-scoped.\n */\n async putSchema(name: string, body: SectionSchemaPutBody): Promise<SectionSchemaPutResponse> {\n if (!config.personalToken) {\n throw new BotlrError(\"sections.putSchema needs `personalToken` (a personal access token with write:sections)\");\n }\n const { appInstanceId, schema, force } = body;\n try {\n return await request<SectionSchemaPutResponse>(`/api/sections/${encodeURIComponent(name)}/schema`, {\n method: \"PUT\",\n headers: { Authorization: `Bearer ${config.personalToken}` },\n body: JSON.stringify({ appInstanceId: appInstanceId ?? baseContext.appId, schema, force: force ?? false }),\n });\n } catch (error) {\n if (error instanceof BotlrConflictError) {\n const formats = conflictFormats(error.details);\n if (formats) {\n throw new BotlrSchemaFormatConflictError(error.message, formats.currentFormat, formats.newFormat, error.details);\n }\n }\n throw error;\n }\n },\n };\n\n return {\n request,\n\n /** The resolved config, for wrappers that need `baseUrl` or `appId`. */\n config: { ...config, baseUrl } as Readonly<BotlrClientConfig>,\n\n withContext(contextOverride: Partial<BotlrContext>) {\n return createBotlrClient({ ...config, ...contextOverride });\n },\n\n async getEntryBySlug<TFields>(params: GetEntryBySlugParams): Promise<BotlrEntry<TFields> | null> {\n const result = await request<{ item: BotlrEntry<TFields> | null }>(\"/entries/by-slug\", {\n method: \"GET\",\n query: { contentType: params.contentType, slug: params.slug, locale: params.locale },\n context: { locale: params.locale },\n });\n return result.item;\n },\n\n async getEntries<TFields>(\n params: GetEntriesParams,\n ): Promise<{ items: BotlrEntry<TFields>[]; total: number }> {\n const query: Record<string, BotlrQueryValue> = {\n contentType: params.contentType,\n locale: params.locale,\n limit: params.limit,\n offset: params.offset,\n sort: params.sort,\n };\n for (const [key, value] of Object.entries(params.filters ?? {})) {\n query[`filter.${key}`] = value;\n }\n return request<{ items: BotlrEntry<TFields>[]; total: number }>(\"/entries\", {\n method: \"GET\",\n query,\n context: { locale: params.locale },\n });\n },\n\n async getSingleton<TFields>(params: GetSingletonParams): Promise<BotlrEntry<TFields> | null> {\n const result = await request<{ item: BotlrEntry<TFields> | null }>(\"/singletons\", {\n method: \"GET\",\n query: { key: params.key, locale: params.locale },\n context: { locale: params.locale },\n });\n return result.item;\n },\n\n getPageBySlug(slug: string, locale?: string) {\n return request<PageResponse>(`/api/pages/${slug}`, {\n query: {\n appInstanceId: baseContext.appId,\n locale: locale || baseContext.locale,\n mode: baseContext.preview ? \"draft\" : \"published\",\n },\n });\n },\n\n resolveUrl(slug: string, locale?: string) {\n return request<ResolveResponse>(\"/api/resolve\", {\n query: {\n slug,\n appInstanceId: baseContext.appId,\n locale: locale || baseContext.locale,\n mode: baseContext.preview ? \"draft\" : \"published\",\n ...(baseContext.preview && config.previewToken ? { token: config.previewToken } : {}),\n },\n });\n },\n\n getPageList(params: GetPageListParams = {}) {\n return request<PageListResponse>(\"/api/page-lists\", {\n query: {\n appInstanceId: params.appInstanceId || baseContext.appId,\n locale: params.locale || baseContext.locale,\n parentId: params.parentId,\n tags: params.tags,\n limit: params.limit,\n offset: params.offset,\n type: params.type || \"page\",\n orderBy: params.orderBy,\n },\n });\n },\n\n getPageSlugList(params: GetPageSlugListParams = {}) {\n return request<PageSlugListResponse>(\"/api/page-lists/slugs\", {\n query: {\n appInstanceId: params.appInstanceId || baseContext.appId,\n locale: params.locale || baseContext.locale,\n limit: params.limit,\n offset: params.offset,\n },\n });\n },\n\n getMenu(key: string, locale?: string) {\n return request<MenuResponse>(\"/api/menus\", {\n query: { key, appInstanceId: baseContext.appId, locale: locale || baseContext.locale },\n });\n },\n\n getForm(key: string, locale?: string) {\n return request<FormResponse>(\"/api/forms\", {\n query: { key, appInstanceId: baseContext.appId, locale: locale || baseContext.locale },\n });\n },\n\n getList(appId: string, listId: string, locale?: string) {\n return request<ListResponse>(`/api/lists/${listId}/complete`, {\n query: { appInstanceId: appId, locale: locale || baseContext.locale },\n });\n },\n\n getConfigurator<T = unknown>(configuratorId: string, versionId?: string) {\n return request<T>(`/api/configurators/${configuratorId}`, {\n query: { versionId, appInstanceId: baseContext.appId, locale: baseContext.locale },\n });\n },\n\n /** GET `/api/{endpoint}` with the app and locale added to the query. */\n getData<T = unknown>(endpoint: string, queryParams?: BotlrQuery, locale?: string) {\n return request<T>(`/api/${endpoint}`, {\n method: \"GET\",\n query: { appInstanceId: baseContext.appId, locale: locale || baseContext.locale, ...queryParams },\n });\n },\n\n /** POST `data` as JSON to `/api/{endpoint}`, for forms and other submissions. */\n submitData<T = unknown>(\n endpoint: string,\n data: Record<string, unknown>,\n locale?: string,\n queryParams?: BotlrQuery,\n ) {\n return request<T>(`/api/${endpoint}`, {\n method: \"POST\",\n body: JSON.stringify(data),\n query: { appInstanceId: baseContext.appId, locale: locale || baseContext.locale, ...queryParams },\n });\n },\n\n getAppInfo<T = unknown>() {\n return request<T>(`/api/apps/${baseContext.appId}`);\n },\n\n getSitemap<T = unknown>() {\n return request<T>(`/api/sitemap/${baseContext.appId}`);\n },\n\n /** Every redirect for the app, across all locales. */\n getRedirects() {\n return request<RedirectsResponse>(`/api/redirects/${baseContext.appId}`);\n },\n\n getAssetById<T = unknown>(assetId: string) {\n return request<T>(`/api/assets/${assetId}`, {\n query: { appInstanceId: baseContext.appId },\n });\n },\n\n sections,\n };\n}\n\nexport function createBotlrFactory(defaults?: { fetch?: typeof fetch }) {\n return {\n create(config: Omit<BotlrClientConfig, \"fetch\">) {\n return createBotlrClient({ ...defaults, ...config });\n },\n };\n}\n","/**\n * Section source bridge: types and hash helpers.\n *\n * Mirrors the contract in xam-mono's docs/SECTION_SOURCE_BRIDGE.md. The hash\n * rules are reproduced here exactly as the server computes them, so a client\n * can tell \"clean\", \"modified\", \"behind\" and \"diverged\" apart without a\n * round trip:\n *\n * - `file.sha256` = sha256 of the UTF-8 content, hex, content exactly as sent\n * - `contentHash` = sha256 over `\"{path}\\n{sha256}\\n\"` for every file, sorted by path\n *\n * The manifest is not hashed.\n */\n\n/** The source variant a section is stored for, for example `react-next` or `html`. */\nexport type SectionFramework = string;\n\nexport type SectionSourceScope = \"instance\" | \"shared\";\n\n/** `<slug>.botlr.json`, kept next to the section's entry file. */\nexport interface SectionManifest {\n /** Section definition slug. Must equal the name used in the URL on push. */\n section: string;\n framework: SectionFramework;\n /** Entry file, relative to the manifest's directory. Must be listed in `files`. */\n entry: string;\n /** Named export of the component in `entry`. */\n exportName?: string;\n /** Every file of the section, relative to the manifest's directory, forward slashes only. */\n files: string[];\n title?: string;\n description?: string;\n category?: string;\n icon?: string;\n dependencies?: SectionDependencies;\n /**\n * The props the code expects, normally in the field DSL. The dashboard owns\n * the definition's schema: a source push never changes it (this is only a\n * seed when `--create` adds the definition, and what the CLI compares\n * against for a drift warning). `botlr schema pull` copies the dashboard's\n * schema here in whichever format it has; `botlr schema push` sends it back.\n */\n schema?: SectionSchema;\n /** Unknown keys are kept by the server. */\n [key: string]: unknown;\n}\n\nexport interface SectionDependencies {\n /** npm packages the section imports, with the ranges the app uses. */\n npm?: Record<string, string>;\n /** Local modules the section imports; the pulling app must already have them. */\n internal?: string[];\n}\n\n/**\n * Pull state keys the server strips from a manifest. The CLI keeps these in\n * `.botlr/sections.lock.json`, never in the manifest.\n */\nexport const MANIFEST_STATE_KEYS = [\"version\", \"hash\", \"contentHash\", \"pulledAt\", \"scope\"] as const;\n\nexport interface SectionSourceFileInput {\n path: string;\n content: string;\n}\n\nexport interface SectionSourceFile extends SectionSourceFileInput {\n sha256: string;\n}\n\nexport interface SectionDefinitionScope {\n tenantId: string;\n organizationId: string | null;\n projectId: string | null;\n}\n\n/**\n * One field of the dashboard's props schema (the field DSL): the value under\n * a field key in `SectionDefinition.schema`. Other keys are kept as given.\n */\nexport interface SectionSchemaField {\n type: string;\n label?: string;\n required?: boolean;\n /** For `select`: plain values or `{ value, label }` objects. */\n options?: Array<string | number | { value: string | number; label?: string }>;\n min?: number;\n max?: number;\n multiple?: boolean;\n [key: string]: unknown;\n}\n\n/** The field DSL: a map of field key to field. This is what the dashboard's properties editor stores. */\nexport type SectionFieldSchema = Record<string, SectionSchemaField>;\n\n/**\n * A node of the older FormBuilder schema format. A `field` node carries its\n * field type in `fieldType`; `section`, `fieldset` and `composite` nodes\n * group `children`; a `repeater` node lists its item `fields`.\n */\nexport interface SectionFormBuilderNode {\n type: string;\n fieldType?: string;\n name?: string;\n key?: string;\n label?: string;\n required?: boolean;\n options?: SectionSchemaField[\"options\"];\n children?: SectionFormBuilderNode[];\n fields?: SectionFormBuilderNode[];\n [key: string]: unknown;\n}\n\n/** The older FormBuilder schema format. */\nexport interface SectionFormBuilderSchema {\n nodes: SectionFormBuilderNode[];\n [key: string]: unknown;\n}\n\n/** A section definition's props schema, in either format the dashboard writes. */\nexport type SectionSchema = SectionFieldSchema | SectionFormBuilderSchema;\n\n/** True for the FormBuilder format (`{ nodes: [...] }`). */\nexport function isFormBuilderSchema(schema: unknown): schema is SectionFormBuilderSchema {\n return !!schema && typeof schema === \"object\" && Array.isArray((schema as { nodes?: unknown }).nodes);\n}\n\n/** True for the field DSL: an object that is not the FormBuilder format. Empty objects count. */\nexport function isFieldSchema(schema: unknown): schema is SectionFieldSchema {\n return !!schema && typeof schema === \"object\" && !Array.isArray(schema) && !isFormBuilderSchema(schema);\n}\n\n/** How a definition's props schema is stored: the field DSL or the older FormBuilder format. */\nexport type SectionSchemaFormat = \"field-dsl\" | \"form-builder\";\n\n/** The format of a schema object, or null for anything that is not a schema. */\nexport function schemaFormat(schema: unknown): SectionSchemaFormat | null {\n if (isFormBuilderSchema(schema)) return \"form-builder\";\n if (isFieldSchema(schema)) return \"field-dsl\";\n return null;\n}\n\nexport interface SectionDefinition {\n id: string;\n name: string;\n title: string;\n /** The props schema as the dashboard stores it; `{}` for a definition without one. */\n schema: SectionSchema | Record<string, unknown>;\n /** The schema's format. The schema routes always send it; the source routes may not. */\n format?: SectionSchemaFormat;\n scope: SectionDefinitionScope;\n}\n\n/** `GET /api/sections/{name}/schema` */\nexport interface SectionSchemaGetResponse {\n definition: SectionDefinition & { format: SectionSchemaFormat };\n}\n\n/** `PUT /api/sections/{name}/schema` */\nexport interface SectionSchemaPutBody {\n /** The new props schema, field DSL or FormBuilder. */\n schema: SectionSchema;\n /**\n * Replace the schema even when the current one is in the other format.\n * Without it the server answers 409 (`BotlrSchemaFormatConflictError`).\n */\n force?: boolean;\n /** Defaults to the client's `appId`. */\n appInstanceId?: string;\n}\n\nexport interface SectionSchemaPutResponse {\n definition: SectionDefinition & { format: SectionSchemaFormat };\n /** The format the definition had before this call. */\n previousFormat: SectionSchemaFormat;\n /** False when the new schema equals the stored one. */\n changed: boolean;\n}\n\nexport interface SectionSource {\n scope: SectionSourceScope;\n framework: SectionFramework;\n version: number;\n contentHash: string;\n manifest: SectionManifest;\n message: string | null;\n createdAt: string;\n files: SectionSourceFile[];\n}\n\nexport interface SectionSourceScopeSummary {\n version: number;\n contentHash: string;\n updatedAt: string;\n}\n\nexport interface SectionSourceSummary {\n name: string;\n title: string;\n definitionId: string;\n instance: SectionSourceScopeSummary | null;\n shared: SectionSourceScopeSummary | null;\n /** Which scope a pull without `scope` returns. */\n effective: SectionSourceScope;\n}\n\nexport interface SectionSourceListResponse {\n framework: SectionFramework;\n sections: SectionSourceSummary[];\n}\n\nexport interface SectionSourcePullResponse {\n /** null only for an instance row whose section no longer resolves to a definition */\n definition: SectionDefinition | null;\n source: SectionSource;\n}\n\nexport interface SectionSourceListParams {\n framework: SectionFramework;\n}\n\nexport interface SectionSourcePullParams {\n framework: SectionFramework;\n /** A specific version; latest when omitted. */\n version?: number;\n /** Force one scope instead of \"instance first, shared second\". */\n scope?: SectionSourceScope;\n}\n\nexport interface SectionSourcePushBody {\n framework: SectionFramework;\n manifest: SectionManifest;\n files: SectionSourceFileInput[];\n /** Defaults to the client's `appId`. */\n appInstanceId?: string;\n /** Write the shared row (per section definition) instead of this app's row. */\n shared?: boolean;\n /**\n * The version the push is based on; 0 means \"none yet\". Omit to force the\n * push without a conflict check.\n */\n baseVersion?: number;\n /** At most 500 characters. */\n message?: string;\n /** Add a project-scoped section definition when none exists. */\n create?: boolean;\n}\n\nexport interface SectionSourcePushResponse {\n /** true on 201 (new version), false on 200 (nothing changed) */\n created: boolean;\n scope: SectionSourceScope;\n definition: SectionDefinition;\n version: number;\n contentHash: string;\n}\n\n// ---------------------------------------------------------------------------\n// Limits, as enforced by the server\n// ---------------------------------------------------------------------------\n\nexport const SECTION_SOURCE_LIMITS = {\n maxFiles: 50,\n maxFileBytes: 512 * 1024,\n maxTotalBytes: 2 * 1024 * 1024,\n maxPathLength: 200,\n maxMessageLength: 500,\n} as const;\n\nexport const SECTION_PATH_RE = /^[A-Za-z0-9._-]+(?:\\/[A-Za-z0-9._-]+)*$/;\nexport const SECTION_FRAMEWORK_RE = /^[a-z0-9][a-z0-9-]{0,31}$/;\n\n/** True when `path` is a relative path the bridge accepts. */\nexport function isValidSectionPath(path: string): boolean {\n if (!path || path.length > SECTION_SOURCE_LIMITS.maxPathLength) return false;\n if (!SECTION_PATH_RE.test(path)) return false;\n return !path.split(\"/\").some((segment) => segment === \".\" || segment === \"..\");\n}\n\nexport function isValidFramework(framework: string): boolean {\n return SECTION_FRAMEWORK_RE.test(framework);\n}\n\n// ---------------------------------------------------------------------------\n// Hashing (Web Crypto, so it runs in the browser, Node 20+ and edge runtimes)\n// ---------------------------------------------------------------------------\n\nconst encoder = new TextEncoder();\n\nfunction subtle(): SubtleCrypto {\n const c = globalThis.crypto;\n if (!c || !c.subtle) {\n throw new Error(\"Web Crypto is not available in this runtime (globalThis.crypto.subtle)\");\n }\n return c.subtle;\n}\n\nfunction toHex(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let out = \"\";\n for (let i = 0; i < bytes.length; i++) {\n out += bytes[i]!.toString(16).padStart(2, \"0\");\n }\n return out;\n}\n\nasync function sha256Hex(input: string): Promise<string> {\n return toHex(await subtle().digest(\"SHA-256\", encoder.encode(input)));\n}\n\n/** sha256 of the UTF-8 encoding of `content`, as lowercase hex. */\nexport function fileSha256(content: string): Promise<string> {\n return sha256Hex(content);\n}\n\n/**\n * The content hash of a file set: sha256 over `\"{path}\\n{sha256}\\n\"` for every\n * file, sorted by path. Accepts files with a precomputed `sha256` or plain\n * `{ path, content }` pairs.\n */\nexport async function contentHash(\n files: ReadonlyArray<SectionSourceFileInput | SectionSourceFile>,\n): Promise<string> {\n const hashed = await hashFiles(files);\n const lines = hashed\n .map((f) => [f.path, f.sha256] as const)\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n .map(([path, sha]) => `${path}\\n${sha}\\n`)\n .join(\"\");\n return sha256Hex(lines);\n}\n\n/** Every file with its `sha256`, in the order given. Files that already carry one are kept as is. */\nexport async function hashFiles(\n files: ReadonlyArray<SectionSourceFileInput | SectionSourceFile>,\n): Promise<SectionSourceFile[]> {\n return Promise.all(\n files.map(async (f) => ({\n path: f.path,\n content: f.content,\n sha256: \"sha256\" in f && typeof f.sha256 === \"string\" ? f.sha256 : await fileSha256(f.content),\n })),\n );\n}\n\n/** A copy of `manifest` without the pull-state keys the server strips. */\nexport function stripManifestState<T extends Record<string, unknown>>(manifest: T): SectionManifest {\n const copy: Record<string, unknown> = { ...manifest };\n for (const key of MANIFEST_STATE_KEYS) delete copy[key];\n return copy as SectionManifest;\n}\n","import type { AssetData, PublisherScope } from \"./types\";\n\n/** An asset as a section prop: the legacy bare id, or the `AssetData` object. */\nexport type AssetRef = string | AssetData | null | undefined;\n\nexport function isAssetData(value: unknown): value is AssetData {\n return typeof value === \"object\" && value !== null && \"id\" in value && \"filename\" in value;\n}\n\nexport function getAssetId(asset: AssetRef): string | undefined {\n if (!asset) return undefined;\n return typeof asset === \"string\" ? asset : asset.id;\n}\n\n/** The filename, when the asset is an `AssetData` object. A bare id has none. */\nexport function getAssetFilename(asset: AssetRef): string | undefined {\n if (!asset || typeof asset === \"string\") return undefined;\n return asset.filename;\n}\n\nexport interface AssetUrlOptions {\n /** The media server origin, for example `https://media.example.com`. */\n mediaUrl: string;\n /** The image recipe segment; defaults to `view`. */\n recipe?: string;\n}\n\n/**\n * The media server URL of an asset:\n * `{mediaUrl}/images/{orgSlug}/{assetId}/{recipe}/{filename}`.\n *\n * Falls back to `image.jpg` for a bare id, and to `{mediaUrl}/images/{assetId}`\n * when no org slug is known.\n */\nexport function getAssetUrl(\n asset: AssetRef,\n scope: Partial<Pick<PublisherScope, \"orgSlug\">> | undefined,\n options: AssetUrlOptions,\n): string | undefined {\n const assetId = getAssetId(asset);\n if (!assetId) return undefined;\n\n const base = options.mediaUrl.replace(/\\/+$/, \"\");\n const recipe = options.recipe ?? \"view\";\n const filename = getAssetFilename(asset);\n\n if (scope?.orgSlug) {\n return `${base}/images/${scope.orgSlug}/${assetId}/${recipe}/${filename ?? \"image.jpg\"}`;\n }\n return `${base}/images/${assetId}`;\n}\n\n/**\n * `getAssetUrl` bound to a media server, for an app's own `getAssetUrl`:\n *\n * ```ts\n * export const getAssetUrl = createAssetUrl({ mediaUrl: process.env.NEXT_PUBLIC_MEDIA_URL! });\n * getAssetUrl(asset, scope, \"thumb\");\n * ```\n */\nexport function createAssetUrl(options: AssetUrlOptions) {\n return (\n asset: AssetRef,\n scope: Partial<Pick<PublisherScope, \"orgSlug\">> | undefined,\n recipe?: string,\n ): string | undefined => getAssetUrl(asset, scope, { ...options, recipe: recipe ?? options.recipe });\n}\n"],"mappings":"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","names":["contentHash","error"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/retry.ts","../src/client.ts","../src/sections.ts","../src/assets.ts"],"sourcesContent":["export * from \"./client\";\nexport * from \"./types\";\nexport * from \"./errors\";\nexport * from \"./sections\";\nexport * from \"./assets\";\nexport { DEFAULT_RETRY_STATUSES } from \"./retry\";\n","/**\n * Thrown for every non-2xx response. `status` is the HTTP status and\n * `details` the parsed JSON body (or the raw text when the body is not JSON).\n */\nexport class BotlrError extends Error {\n readonly status: number | undefined;\n readonly details: unknown;\n\n constructor(message: string, status?: number, details?: unknown) {\n super(message);\n this.name = \"BotlrError\";\n this.status = status;\n this.details = details;\n }\n}\n\n/** The latest version of a section source, as reported on a push conflict. */\nexport type ConflictLatest = {\n version: number;\n contentHash: string;\n};\n\n/**\n * Thrown by `sections.push` on 409: `baseVersion` was not the latest version,\n * or a concurrent push won. `latest` is null when the server has no version\n * at all (the client sent a `baseVersion` above 0 for a section with no source).\n */\nexport class BotlrConflictError extends BotlrError {\n readonly latest: ConflictLatest | null;\n\n constructor(message: string, latest: ConflictLatest | null, details?: unknown) {\n super(message, 409, details);\n this.name = \"BotlrConflictError\";\n this.latest = latest;\n }\n}\n\n/**\n * Thrown by `sections.putSchema` on 409: the definition's current schema and\n * the new one are in different formats and `force` was not set.\n */\nexport class BotlrSchemaFormatConflictError extends BotlrError {\n readonly currentFormat: string;\n readonly newFormat: string;\n\n constructor(message: string, currentFormat: string, newFormat: string, details?: unknown) {\n super(message, 409, details);\n this.name = \"BotlrSchemaFormatConflictError\";\n this.currentFormat = currentFormat;\n this.newFormat = newFormat;\n }\n}\n\n/**\n * The human-readable message of an error response, when the server sent one:\n * `detail` as a string, `detail.message`, or the `msg` lines of a FastAPI\n * validation error. Undefined for anything else.\n */\nexport function botlrErrorMessage(error: unknown): string | undefined {\n if (!(error instanceof BotlrError)) return undefined;\n const details = error.details;\n if (!details || typeof details !== \"object\") return typeof details === \"string\" ? details : undefined;\n const detail = (details as { detail?: unknown }).detail;\n if (typeof detail === \"string\") return detail;\n if (Array.isArray(detail)) {\n const lines = detail\n .map((item) => (item && typeof item === \"object\" ? (item as { msg?: unknown }).msg : undefined))\n .filter((msg): msg is string => typeof msg === \"string\");\n return lines.length ? lines.join(\"; \") : undefined;\n }\n if (detail && typeof detail === \"object\") {\n const message = (detail as { message?: unknown }).message;\n return typeof message === \"string\" ? message : undefined;\n }\n return undefined;\n}\n","import type { BotlrRetryConfig, BotlrRetryInfo } from \"./types\";\n\n/** Statuses retried by default: timeouts, rate limits and server errors. */\nexport const DEFAULT_RETRY_STATUSES: readonly number[] = [408, 429, 500, 502, 503, 504];\n\nexport type ResolvedRetryConfig = {\n retries: number;\n delay: number;\n maxDelay: number;\n statuses: readonly number[];\n onRetry: ((info: BotlrRetryInfo) => void) | undefined;\n};\n\nexport function resolveRetryConfig(retry: BotlrRetryConfig | false | undefined): ResolvedRetryConfig {\n if (retry === false) {\n return { retries: 0, delay: 0, maxDelay: 0, statuses: [], onRetry: undefined };\n }\n return {\n retries: Math.max(0, retry?.retries ?? 2),\n delay: Math.max(0, retry?.delay ?? 300),\n maxDelay: Math.max(0, retry?.maxDelay ?? 5000),\n statuses: retry?.statuses ?? DEFAULT_RETRY_STATUSES,\n onRetry: retry?.onRetry,\n };\n}\n\n/** Only reads are retried: a repeated POST or PUT could submit twice. */\nexport function isRetryableMethod(method: string | undefined): boolean {\n const m = (method ?? \"GET\").toUpperCase();\n return m === \"GET\" || m === \"HEAD\";\n}\n\n/** `Retry-After` as milliseconds, from either the seconds or the date form. */\nexport function parseRetryAfter(value: string | null | undefined): number | undefined {\n if (!value) return undefined;\n const seconds = Number(value);\n if (Number.isFinite(seconds)) return Math.max(0, seconds * 1000);\n const date = Date.parse(value);\n if (Number.isFinite(date)) return Math.max(0, date - Date.now());\n return undefined;\n}\n\n/**\n * The wait before retry number `attempt + 1`: `Retry-After` when the server\n * sent one, else `delay * 2^attempt` with ±25% jitter, both capped at `maxDelay`.\n */\nexport function retryDelayMs(retry: ResolvedRetryConfig, attempt: number, retryAfter?: string | null): number {\n const fromHeader = parseRetryAfter(retryAfter);\n if (fromHeader !== undefined) return Math.min(fromHeader, retry.maxDelay);\n const base = Math.min(retry.maxDelay, retry.delay * 2 ** attempt);\n return Math.round(base * (0.75 + Math.random() * 0.5));\n}\n\nexport async function waitBeforeRetry(\n retry: ResolvedRetryConfig,\n attempt: number,\n error: unknown,\n url: string,\n retryAfter?: string | null,\n): Promise<void> {\n const delay = retryDelayMs(retry, attempt, retryAfter);\n retry.onRetry?.({ attempt: attempt + 1, delay, url, error });\n if (delay > 0) await new Promise<void>((resolve) => setTimeout(resolve, delay));\n}\n","import type {\n BotlrClientConfig,\n BotlrContext,\n BotlrEntry,\n BotlrQuery,\n BotlrQueryValue,\n FormResponse,\n GetEntriesParams,\n GetEntryBySlugParams,\n GetPageListParams,\n GetPageSlugListParams,\n GetSingletonParams,\n ListResponse,\n MenuResponse,\n PageListResponse,\n PageResponse,\n PageSlugListResponse,\n RedirectsResponse,\n ResolveResponse,\n} from \"./types\";\nimport type {\n SectionSchemaGetResponse,\n SectionSchemaPutBody,\n SectionSchemaPutResponse,\n SectionSourceListParams,\n SectionSourceListResponse,\n SectionSourcePullParams,\n SectionSourcePullResponse,\n SectionSourcePushBody,\n SectionSourcePushResponse,\n} from \"./sections\";\nimport { BotlrConflictError, BotlrError, BotlrSchemaFormatConflictError } from \"./errors\";\nimport { isRetryableMethod, resolveRetryConfig, waitBeforeRetry } from \"./retry\";\n\nexport type RequestOptions = Omit<RequestInit, \"headers\"> & {\n query?: BotlrQuery;\n context?: Partial<BotlrContext>;\n headers?: Record<string, string>;\n};\n\nexport function toQueryString(query: BotlrQuery): string {\n const params = new URLSearchParams();\n\n for (const [key, value] of Object.entries(query)) {\n if (value === undefined || value === null) continue;\n\n if (Array.isArray(value)) {\n for (const item of value) {\n if (item !== undefined && item !== null) {\n params.append(key, String(item));\n }\n }\n } else {\n params.set(key, String(value));\n }\n }\n\n const qs = params.toString();\n return qs ? `?${qs}` : \"\";\n}\n\nfunction mergeContext(\n base?: Partial<BotlrContext>,\n override?: Partial<BotlrContext>,\n): Partial<BotlrContext> | undefined {\n if (!base && !override) return undefined;\n return { ...base, ...override };\n}\n\nexport function contextToHeaders(context?: Partial<BotlrContext>): Record<string, string> {\n if (!context) return {};\n\n return {\n ...(context.appId ? { \"x-botlr-app-id\": context.appId } : {}),\n ...(context.locale ? { \"x-botlr-locale\": context.locale } : {}),\n ...(context.preview !== undefined ? { \"x-botlr-preview\": String(context.preview) } : {}),\n };\n}\n\nasync function errorDetails(response: Response): Promise<unknown> {\n const text = await response.text().catch(() => undefined);\n if (text === undefined || text === \"\") return undefined;\n try {\n return JSON.parse(text) as unknown;\n } catch {\n return text;\n }\n}\n\nfunction conflictLatest(details: unknown): { version: number; contentHash: string } | null {\n if (!details || typeof details !== \"object\") return null;\n const detail = (details as { detail?: unknown }).detail;\n if (!detail || typeof detail !== \"object\") return null;\n const latest = (detail as { latest?: unknown }).latest;\n if (!latest || typeof latest !== \"object\") return null;\n const { version, contentHash } = latest as { version?: unknown; contentHash?: unknown };\n if (typeof version !== \"number\" || typeof contentHash !== \"string\") return null;\n return { version, contentHash };\n}\n\nfunction conflictFormats(details: unknown): { currentFormat: string; newFormat: string } | null {\n if (!details || typeof details !== \"object\") return null;\n const detail = (details as { detail?: unknown }).detail;\n if (!detail || typeof detail !== \"object\") return null;\n const { currentFormat, newFormat } = detail as { currentFormat?: unknown; newFormat?: unknown };\n if (typeof currentFormat !== \"string\" || typeof newFormat !== \"string\") return null;\n return { currentFormat, newFormat };\n}\n\nfunction conflictMessage(details: unknown): string | undefined {\n if (!details || typeof details !== \"object\") return undefined;\n const detail = (details as { detail?: unknown }).detail;\n if (!detail || typeof detail !== \"object\") return undefined;\n const message = (detail as { message?: unknown }).message;\n return typeof message === \"string\" ? message : undefined;\n}\n\nexport type BotlrClient = ReturnType<typeof createBotlrClient>;\n\n/**\n * A Botlr client. Every call sends the site token as `Authorization: Bearer`\n * plus `x-botlr-app-id`, `x-botlr-locale` and `x-botlr-preview` from the\n * config; `sections.push` and `sections.putSchema` send the personal token instead.\n */\nexport function createBotlrClient(config: BotlrClientConfig) {\n const fetcher = config.fetch ?? globalThis.fetch;\n if (typeof fetcher !== \"function\") {\n throw new BotlrError(\"No fetch implementation: pass `fetch` in the client config\");\n }\n const baseUrl = config.baseUrl.replace(/\\/+$/, \"\");\n\n const baseContext: BotlrContext = {\n appId: config.appId,\n locale: config.locale,\n preview: config.preview,\n };\n\n const retry = resolveRetryConfig(config.retry);\n\n async function request<T>(path: string, options: RequestOptions = {}): Promise<T> {\n const { query, context: contextOverride, headers: extraHeaders, ...init } = options;\n const context = mergeContext(baseContext, contextOverride);\n const url = `${baseUrl}${path}${toQueryString(query ?? {})}`;\n const headers = {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n ...(config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}),\n ...contextToHeaders(context),\n ...(extraHeaders ?? {}),\n };\n const retryable = retry.retries > 0 && isRetryableMethod(init.method);\n\n for (let attempt = 0; ; attempt++) {\n const canRetry = retryable && attempt < retry.retries && !init.signal?.aborted;\n\n let response: Response;\n try {\n response = await fetcher(url, { ...init, headers });\n } catch (error) {\n if (!canRetry) throw error;\n await waitBeforeRetry(retry, attempt, error, url);\n continue;\n }\n\n if (response.ok) {\n if (response.status === 204) return undefined as T;\n return (await response.json()) as T;\n }\n\n const details = await errorDetails(response);\n const error =\n response.status === 409\n ? new BotlrConflictError(\n conflictMessage(details) ?? `Botlr request failed with status 409`,\n conflictLatest(details),\n details,\n )\n : new BotlrError(`Botlr request failed with status ${response.status}`, response.status, details);\n\n if (!canRetry || !retry.statuses.includes(response.status)) throw error;\n await waitBeforeRetry(retry, attempt, error, url, response.headers.get(\"retry-after\"));\n }\n }\n\n const sections = {\n /** Every section the app can pull for `framework`, with the latest version per scope. */\n list(params: SectionSourceListParams): Promise<SectionSourceListResponse> {\n return request<SectionSourceListResponse>(\"/api/sections/source\", {\n method: \"GET\",\n query: { framework: params.framework, appInstanceId: baseContext.appId },\n });\n },\n\n /** The section's source: this app's row first, the shared row second. */\n pull(name: string, params: SectionSourcePullParams): Promise<SectionSourcePullResponse> {\n return request<SectionSourcePullResponse>(`/api/sections/${encodeURIComponent(name)}/source`, {\n method: \"GET\",\n query: {\n framework: params.framework,\n appInstanceId: baseContext.appId,\n version: params.version,\n scope: params.scope,\n },\n });\n },\n\n /**\n * Push a new version with the personal access token (`write:sections`).\n * Resolves with `created: true` on 201 and `created: false` when nothing\n * changed; throws `BotlrConflictError` when `baseVersion` is not the latest.\n */\n push(name: string, body: SectionSourcePushBody): Promise<SectionSourcePushResponse> {\n if (!config.personalToken) {\n return Promise.reject(\n new BotlrError(\"sections.push needs `personalToken` (a personal access token with write:sections)\"),\n );\n }\n const { appInstanceId, ...rest } = body;\n return request<SectionSourcePushResponse>(`/api/sections/${encodeURIComponent(name)}/source`, {\n method: \"PUT\",\n headers: { Authorization: `Bearer ${config.personalToken}` },\n body: JSON.stringify({ appInstanceId: appInstanceId ?? baseContext.appId, ...rest }),\n });\n },\n\n /**\n * The section definition with its props schema and format, with the site\n * token. Works whether or not source has been pushed; 404 when the app has\n * no section type of that name.\n */\n getSchema(name: string): Promise<SectionSchemaGetResponse> {\n return request<SectionSchemaGetResponse>(`/api/sections/${encodeURIComponent(name)}/schema`, {\n method: \"GET\",\n query: { appInstanceId: baseContext.appId },\n });\n },\n\n /**\n * Write the definition's props schema with the personal access token\n * (`write:sections`). Same format, or `force`: the schema is replaced\n * (`mode: \"replaced\"`). Field-DSL fields at a FormBuilder schema without\n * `force`: the missing fields are added as nodes and nothing is removed\n * (`mode: \"merged\"`, with `added` and `dashboardOnly`). A FormBuilder\n * schema at a field-DSL one without `force` throws\n * `BotlrSchemaFormatConflictError`; a `BotlrError` with status 422 means\n * the schema is not an object of fields or the definition is not\n * project-scoped.\n */\n async putSchema(name: string, body: SectionSchemaPutBody): Promise<SectionSchemaPutResponse> {\n if (!config.personalToken) {\n throw new BotlrError(\"sections.putSchema needs `personalToken` (a personal access token with write:sections)\");\n }\n const { appInstanceId, schema, force } = body;\n try {\n return await request<SectionSchemaPutResponse>(`/api/sections/${encodeURIComponent(name)}/schema`, {\n method: \"PUT\",\n headers: { Authorization: `Bearer ${config.personalToken}` },\n body: JSON.stringify({ appInstanceId: appInstanceId ?? baseContext.appId, schema, force: force ?? false }),\n });\n } catch (error) {\n if (error instanceof BotlrConflictError) {\n const formats = conflictFormats(error.details);\n if (formats) {\n throw new BotlrSchemaFormatConflictError(error.message, formats.currentFormat, formats.newFormat, error.details);\n }\n }\n throw error;\n }\n },\n };\n\n return {\n request,\n\n /** The resolved config, for wrappers that need `baseUrl` or `appId`. */\n config: { ...config, baseUrl } as Readonly<BotlrClientConfig>,\n\n withContext(contextOverride: Partial<BotlrContext>) {\n return createBotlrClient({ ...config, ...contextOverride });\n },\n\n async getEntryBySlug<TFields>(params: GetEntryBySlugParams): Promise<BotlrEntry<TFields> | null> {\n const result = await request<{ item: BotlrEntry<TFields> | null }>(\"/entries/by-slug\", {\n method: \"GET\",\n query: { contentType: params.contentType, slug: params.slug, locale: params.locale },\n context: { locale: params.locale },\n });\n return result.item;\n },\n\n async getEntries<TFields>(\n params: GetEntriesParams,\n ): Promise<{ items: BotlrEntry<TFields>[]; total: number }> {\n const query: Record<string, BotlrQueryValue> = {\n contentType: params.contentType,\n locale: params.locale,\n limit: params.limit,\n offset: params.offset,\n sort: params.sort,\n };\n for (const [key, value] of Object.entries(params.filters ?? {})) {\n query[`filter.${key}`] = value;\n }\n return request<{ items: BotlrEntry<TFields>[]; total: number }>(\"/entries\", {\n method: \"GET\",\n query,\n context: { locale: params.locale },\n });\n },\n\n async getSingleton<TFields>(params: GetSingletonParams): Promise<BotlrEntry<TFields> | null> {\n const result = await request<{ item: BotlrEntry<TFields> | null }>(\"/singletons\", {\n method: \"GET\",\n query: { key: params.key, locale: params.locale },\n context: { locale: params.locale },\n });\n return result.item;\n },\n\n getPageBySlug(slug: string, locale?: string) {\n return request<PageResponse>(`/api/pages/${slug}`, {\n query: {\n appInstanceId: baseContext.appId,\n locale: locale || baseContext.locale,\n mode: baseContext.preview ? \"draft\" : \"published\",\n },\n });\n },\n\n resolveUrl(slug: string, locale?: string) {\n return request<ResolveResponse>(\"/api/resolve\", {\n query: {\n slug,\n appInstanceId: baseContext.appId,\n locale: locale || baseContext.locale,\n mode: baseContext.preview ? \"draft\" : \"published\",\n ...(baseContext.preview && config.previewToken ? { token: config.previewToken } : {}),\n },\n });\n },\n\n getPageList(params: GetPageListParams = {}) {\n return request<PageListResponse>(\"/api/page-lists\", {\n query: {\n appInstanceId: params.appInstanceId || baseContext.appId,\n locale: params.locale || baseContext.locale,\n parentId: params.parentId,\n tags: params.tags,\n limit: params.limit,\n offset: params.offset,\n type: params.type || \"page\",\n orderBy: params.orderBy,\n },\n });\n },\n\n getPageSlugList(params: GetPageSlugListParams = {}) {\n return request<PageSlugListResponse>(\"/api/page-lists/slugs\", {\n query: {\n appInstanceId: params.appInstanceId || baseContext.appId,\n locale: params.locale || baseContext.locale,\n limit: params.limit,\n offset: params.offset,\n },\n });\n },\n\n getMenu(key: string, locale?: string) {\n return request<MenuResponse>(\"/api/menus\", {\n query: { key, appInstanceId: baseContext.appId, locale: locale || baseContext.locale },\n });\n },\n\n getForm(key: string, locale?: string) {\n return request<FormResponse>(\"/api/forms\", {\n query: { key, appInstanceId: baseContext.appId, locale: locale || baseContext.locale },\n });\n },\n\n getList(appId: string, listId: string, locale?: string) {\n return request<ListResponse>(`/api/lists/${listId}/complete`, {\n query: { appInstanceId: appId, locale: locale || baseContext.locale },\n });\n },\n\n getConfigurator<T = unknown>(configuratorId: string, versionId?: string) {\n return request<T>(`/api/configurators/${configuratorId}`, {\n query: { versionId, appInstanceId: baseContext.appId, locale: baseContext.locale },\n });\n },\n\n /** GET `/api/{endpoint}` with the app and locale added to the query. */\n getData<T = unknown>(endpoint: string, queryParams?: BotlrQuery, locale?: string) {\n return request<T>(`/api/${endpoint}`, {\n method: \"GET\",\n query: { appInstanceId: baseContext.appId, locale: locale || baseContext.locale, ...queryParams },\n });\n },\n\n /** POST `data` as JSON to `/api/{endpoint}`, for forms and other submissions. */\n submitData<T = unknown>(\n endpoint: string,\n data: Record<string, unknown>,\n locale?: string,\n queryParams?: BotlrQuery,\n ) {\n return request<T>(`/api/${endpoint}`, {\n method: \"POST\",\n body: JSON.stringify(data),\n query: { appInstanceId: baseContext.appId, locale: locale || baseContext.locale, ...queryParams },\n });\n },\n\n getAppInfo<T = unknown>() {\n return request<T>(`/api/apps/${baseContext.appId}`);\n },\n\n getSitemap<T = unknown>() {\n return request<T>(`/api/sitemap/${baseContext.appId}`);\n },\n\n /** Every redirect for the app, across all locales. */\n getRedirects() {\n return request<RedirectsResponse>(`/api/redirects/${baseContext.appId}`);\n },\n\n getAssetById<T = unknown>(assetId: string) {\n return request<T>(`/api/assets/${assetId}`, {\n query: { appInstanceId: baseContext.appId },\n });\n },\n\n sections,\n };\n}\n\nexport function createBotlrFactory(defaults?: { fetch?: typeof fetch }) {\n return {\n create(config: Omit<BotlrClientConfig, \"fetch\">) {\n return createBotlrClient({ ...defaults, ...config });\n },\n };\n}\n","/**\n * Section source bridge: types and hash helpers.\n *\n * Mirrors the contract in xam-mono's docs/SECTION_SOURCE_BRIDGE.md. The hash\n * rules are reproduced here exactly as the server computes them, so a client\n * can tell \"clean\", \"modified\", \"behind\" and \"diverged\" apart without a\n * round trip:\n *\n * - `file.sha256` = sha256 of the UTF-8 content, hex, content exactly as sent\n * - `contentHash` = sha256 over `\"{path}\\n{sha256}\\n\"` for every file, sorted by path\n *\n * The manifest is not hashed.\n */\n\n/** The source variant a section is stored for, for example `react-next` or `html`. */\nexport type SectionFramework = string;\n\nexport type SectionSourceScope = \"instance\" | \"shared\";\n\n/** `<slug>.botlr.json`, kept next to the section's entry file. */\nexport interface SectionManifest {\n /** Section definition slug. Must equal the name used in the URL on push. */\n section: string;\n framework: SectionFramework;\n /** Entry file, relative to the manifest's directory. Must be listed in `files`. */\n entry: string;\n /** Named export of the component in `entry`. */\n exportName?: string;\n /** Every file of the section, relative to the manifest's directory, forward slashes only. */\n files: string[];\n title?: string;\n description?: string;\n category?: string;\n icon?: string;\n dependencies?: SectionDependencies;\n /**\n * The props the code expects, normally in the field DSL. The dashboard owns\n * the definition's schema: a source push never changes it (this is only a\n * seed when `--create` adds the definition, and what the CLI compares\n * against for a drift warning). `botlr schema pull` copies the dashboard's\n * schema here in whichever format it has; `botlr schema push` sends it back.\n */\n schema?: SectionSchema;\n /** Unknown keys are kept by the server. */\n [key: string]: unknown;\n}\n\nexport interface SectionDependencies {\n /** npm packages the section imports, with the ranges the app uses. */\n npm?: Record<string, string>;\n /** Local modules the section imports; the pulling app must already have them. */\n internal?: string[];\n}\n\n/**\n * Pull state keys the server strips from a manifest. The CLI keeps these in\n * `.botlr/sections.lock.json`, never in the manifest.\n */\nexport const MANIFEST_STATE_KEYS = [\"version\", \"hash\", \"contentHash\", \"pulledAt\", \"scope\"] as const;\n\nexport interface SectionSourceFileInput {\n path: string;\n content: string;\n}\n\nexport interface SectionSourceFile extends SectionSourceFileInput {\n sha256: string;\n}\n\nexport interface SectionDefinitionScope {\n tenantId: string;\n organizationId: string | null;\n projectId: string | null;\n}\n\n/**\n * One field of the dashboard's props schema (the field DSL): the value under\n * a field key in `SectionDefinition.schema`. Other keys are kept as given.\n */\nexport interface SectionSchemaField {\n type: string;\n label?: string;\n required?: boolean;\n /** For `select`: plain values or `{ value, label }` objects. */\n options?: Array<string | number | { value: string | number; label?: string }>;\n min?: number;\n max?: number;\n multiple?: boolean;\n [key: string]: unknown;\n}\n\n/** The field DSL: a map of field key to field. This is what the dashboard's properties editor stores. */\nexport type SectionFieldSchema = Record<string, SectionSchemaField>;\n\n/**\n * A node of the older FormBuilder schema format. A `field` node carries its\n * field type in `fieldType`; `section`, `fieldset` and `composite` nodes\n * group `children`; a `repeater` node lists its item `fields`.\n */\nexport interface SectionFormBuilderNode {\n type: string;\n fieldType?: string;\n name?: string;\n key?: string;\n label?: string;\n required?: boolean;\n options?: SectionSchemaField[\"options\"];\n children?: SectionFormBuilderNode[];\n fields?: SectionFormBuilderNode[];\n [key: string]: unknown;\n}\n\n/** The older FormBuilder schema format. */\nexport interface SectionFormBuilderSchema {\n nodes: SectionFormBuilderNode[];\n [key: string]: unknown;\n}\n\n/** A section definition's props schema, in either format the dashboard writes. */\nexport type SectionSchema = SectionFieldSchema | SectionFormBuilderSchema;\n\n/** True for the FormBuilder format (`{ nodes: [...] }`). */\nexport function isFormBuilderSchema(schema: unknown): schema is SectionFormBuilderSchema {\n return !!schema && typeof schema === \"object\" && Array.isArray((schema as { nodes?: unknown }).nodes);\n}\n\n/** True for the field DSL: an object that is not the FormBuilder format. Empty objects count. */\nexport function isFieldSchema(schema: unknown): schema is SectionFieldSchema {\n return !!schema && typeof schema === \"object\" && !Array.isArray(schema) && !isFormBuilderSchema(schema);\n}\n\n/** How a definition's props schema is stored: the field DSL or the older FormBuilder format. */\nexport type SectionSchemaFormat = \"field-dsl\" | \"form-builder\";\n\n/** The format of a schema object, or null for anything that is not a schema. */\nexport function schemaFormat(schema: unknown): SectionSchemaFormat | null {\n if (isFormBuilderSchema(schema)) return \"form-builder\";\n if (isFieldSchema(schema)) return \"field-dsl\";\n return null;\n}\n\nexport interface SectionDefinition {\n id: string;\n name: string;\n title: string;\n /** The props schema as the dashboard stores it; `{}` for a definition without one. */\n schema: SectionSchema | Record<string, unknown>;\n /** The schema's format. The schema routes always send it; the source routes may not. */\n format?: SectionSchemaFormat;\n scope: SectionDefinitionScope;\n}\n\n/** `GET /api/sections/{name}/schema` */\nexport interface SectionSchemaGetResponse {\n definition: SectionDefinition & { format: SectionSchemaFormat };\n}\n\n/** `PUT /api/sections/{name}/schema` */\nexport interface SectionSchemaPutBody {\n /** The new props schema, field DSL or FormBuilder. */\n schema: SectionSchema;\n /**\n * Replace the schema outright whatever the formats. Without it, field-DSL\n * fields pushed at a FormBuilder schema are merged in as nodes, and a\n * FormBuilder schema pushed at a field-DSL one gets 409\n * (`BotlrSchemaFormatConflictError`).\n */\n force?: boolean;\n /** Defaults to the client's `appId`. */\n appInstanceId?: string;\n}\n\n/**\n * What a schema push did: `replaced` the stored schema (same format, or\n * `force`), or `merged` field-DSL fields into a FormBuilder schema as new\n * nodes, keeping every existing node.\n */\nexport type SectionSchemaPushMode = \"replaced\" | \"merged\";\n\nexport interface SectionSchemaPutResponse {\n definition: SectionDefinition & { format: SectionSchemaFormat };\n /** The format the definition had before this call. */\n previousFormat: SectionSchemaFormat;\n /** False when nothing had to change. */\n changed: boolean;\n mode: SectionSchemaPushMode;\n /** Field names that became new nodes (`merged`), else empty. */\n added: string[];\n /** Node names the dashboard has that the code does not declare, kept (`merged`), else empty. */\n dashboardOnly: string[];\n}\n\nexport interface SectionSource {\n scope: SectionSourceScope;\n framework: SectionFramework;\n version: number;\n contentHash: string;\n manifest: SectionManifest;\n message: string | null;\n createdAt: string;\n files: SectionSourceFile[];\n}\n\nexport interface SectionSourceScopeSummary {\n version: number;\n contentHash: string;\n updatedAt: string;\n}\n\nexport interface SectionSourceSummary {\n name: string;\n title: string;\n definitionId: string;\n instance: SectionSourceScopeSummary | null;\n shared: SectionSourceScopeSummary | null;\n /** Which scope a pull without `scope` returns. */\n effective: SectionSourceScope;\n}\n\nexport interface SectionSourceListResponse {\n framework: SectionFramework;\n sections: SectionSourceSummary[];\n}\n\nexport interface SectionSourcePullResponse {\n /** null only for an instance row whose section no longer resolves to a definition */\n definition: SectionDefinition | null;\n source: SectionSource;\n}\n\nexport interface SectionSourceListParams {\n framework: SectionFramework;\n}\n\nexport interface SectionSourcePullParams {\n framework: SectionFramework;\n /** A specific version; latest when omitted. */\n version?: number;\n /** Force one scope instead of \"instance first, shared second\". */\n scope?: SectionSourceScope;\n}\n\nexport interface SectionSourcePushBody {\n framework: SectionFramework;\n manifest: SectionManifest;\n files: SectionSourceFileInput[];\n /** Defaults to the client's `appId`. */\n appInstanceId?: string;\n /** Write the shared row (per section definition) instead of this app's row. */\n shared?: boolean;\n /**\n * The version the push is based on; 0 means \"none yet\". Omit to force the\n * push without a conflict check.\n */\n baseVersion?: number;\n /** At most 500 characters. */\n message?: string;\n /** Add a project-scoped section definition when none exists. */\n create?: boolean;\n}\n\nexport interface SectionSourcePushResponse {\n /** true on 201 (new version), false on 200 (nothing changed) */\n created: boolean;\n scope: SectionSourceScope;\n definition: SectionDefinition;\n version: number;\n contentHash: string;\n}\n\n// ---------------------------------------------------------------------------\n// Limits, as enforced by the server\n// ---------------------------------------------------------------------------\n\nexport const SECTION_SOURCE_LIMITS = {\n maxFiles: 50,\n maxFileBytes: 512 * 1024,\n maxTotalBytes: 2 * 1024 * 1024,\n maxPathLength: 200,\n maxMessageLength: 500,\n} as const;\n\nexport const SECTION_PATH_RE = /^[A-Za-z0-9._-]+(?:\\/[A-Za-z0-9._-]+)*$/;\nexport const SECTION_FRAMEWORK_RE = /^[a-z0-9][a-z0-9-]{0,31}$/;\n\n/** True when `path` is a relative path the bridge accepts. */\nexport function isValidSectionPath(path: string): boolean {\n if (!path || path.length > SECTION_SOURCE_LIMITS.maxPathLength) return false;\n if (!SECTION_PATH_RE.test(path)) return false;\n return !path.split(\"/\").some((segment) => segment === \".\" || segment === \"..\");\n}\n\nexport function isValidFramework(framework: string): boolean {\n return SECTION_FRAMEWORK_RE.test(framework);\n}\n\n// ---------------------------------------------------------------------------\n// Hashing (Web Crypto, so it runs in the browser, Node 20+ and edge runtimes)\n// ---------------------------------------------------------------------------\n\nconst encoder = new TextEncoder();\n\nfunction subtle(): SubtleCrypto {\n const c = globalThis.crypto;\n if (!c || !c.subtle) {\n throw new Error(\"Web Crypto is not available in this runtime (globalThis.crypto.subtle)\");\n }\n return c.subtle;\n}\n\nfunction toHex(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let out = \"\";\n for (let i = 0; i < bytes.length; i++) {\n out += bytes[i]!.toString(16).padStart(2, \"0\");\n }\n return out;\n}\n\nasync function sha256Hex(input: string): Promise<string> {\n return toHex(await subtle().digest(\"SHA-256\", encoder.encode(input)));\n}\n\n/** sha256 of the UTF-8 encoding of `content`, as lowercase hex. */\nexport function fileSha256(content: string): Promise<string> {\n return sha256Hex(content);\n}\n\n/**\n * The content hash of a file set: sha256 over `\"{path}\\n{sha256}\\n\"` for every\n * file, sorted by path. Accepts files with a precomputed `sha256` or plain\n * `{ path, content }` pairs.\n */\nexport async function contentHash(\n files: ReadonlyArray<SectionSourceFileInput | SectionSourceFile>,\n): Promise<string> {\n const hashed = await hashFiles(files);\n const lines = hashed\n .map((f) => [f.path, f.sha256] as const)\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n .map(([path, sha]) => `${path}\\n${sha}\\n`)\n .join(\"\");\n return sha256Hex(lines);\n}\n\n/** Every file with its `sha256`, in the order given. Files that already carry one are kept as is. */\nexport async function hashFiles(\n files: ReadonlyArray<SectionSourceFileInput | SectionSourceFile>,\n): Promise<SectionSourceFile[]> {\n return Promise.all(\n files.map(async (f) => ({\n path: f.path,\n content: f.content,\n sha256: \"sha256\" in f && typeof f.sha256 === \"string\" ? f.sha256 : await fileSha256(f.content),\n })),\n );\n}\n\n/** A copy of `manifest` without the pull-state keys the server strips. */\nexport function stripManifestState<T extends Record<string, unknown>>(manifest: T): SectionManifest {\n const copy: Record<string, unknown> = { ...manifest };\n for (const key of MANIFEST_STATE_KEYS) delete copy[key];\n return copy as SectionManifest;\n}\n","import type { AssetData, PublisherScope } from \"./types\";\n\n/** An asset as a section prop: the legacy bare id, or the `AssetData` object. */\nexport type AssetRef = string | AssetData | null | undefined;\n\nexport function isAssetData(value: unknown): value is AssetData {\n return typeof value === \"object\" && value !== null && \"id\" in value && \"filename\" in value;\n}\n\nexport function getAssetId(asset: AssetRef): string | undefined {\n if (!asset) return undefined;\n return typeof asset === \"string\" ? asset : asset.id;\n}\n\n/** The filename, when the asset is an `AssetData` object. A bare id has none. */\nexport function getAssetFilename(asset: AssetRef): string | undefined {\n if (!asset || typeof asset === \"string\") return undefined;\n return asset.filename;\n}\n\nexport interface AssetUrlOptions {\n /** The media server origin, for example `https://media.example.com`. */\n mediaUrl: string;\n /** The image recipe segment; defaults to `view`. */\n recipe?: string;\n}\n\n/**\n * The media server URL of an asset:\n * `{mediaUrl}/images/{orgSlug}/{assetId}/{recipe}/{filename}`.\n *\n * Falls back to `image.jpg` for a bare id, and to `{mediaUrl}/images/{assetId}`\n * when no org slug is known.\n */\nexport function getAssetUrl(\n asset: AssetRef,\n scope: Partial<Pick<PublisherScope, \"orgSlug\">> | undefined,\n options: AssetUrlOptions,\n): string | undefined {\n const assetId = getAssetId(asset);\n if (!assetId) return undefined;\n\n const base = options.mediaUrl.replace(/\\/+$/, \"\");\n const recipe = options.recipe ?? \"view\";\n const filename = getAssetFilename(asset);\n\n if (scope?.orgSlug) {\n return `${base}/images/${scope.orgSlug}/${assetId}/${recipe}/${filename ?? \"image.jpg\"}`;\n }\n return `${base}/images/${assetId}`;\n}\n\n/**\n * `getAssetUrl` bound to a media server, for an app's own `getAssetUrl`:\n *\n * ```ts\n * export const getAssetUrl = createAssetUrl({ mediaUrl: process.env.NEXT_PUBLIC_MEDIA_URL! });\n * getAssetUrl(asset, scope, \"thumb\");\n * ```\n */\nexport function createAssetUrl(options: AssetUrlOptions) {\n return (\n asset: AssetRef,\n scope: Partial<Pick<PublisherScope, \"orgSlug\">> | undefined,\n recipe?: string,\n ): string | undefined => getAssetUrl(asset, scope, { ...options, recipe: recipe ?? options.recipe });\n}\n"],"mappings":"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","names":["contentHash","error"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -471,21 +471,34 @@ interface SectionSchemaPutBody {
|
|
|
471
471
|
/** The new props schema, field DSL or FormBuilder. */
|
|
472
472
|
schema: SectionSchema;
|
|
473
473
|
/**
|
|
474
|
-
* Replace the schema
|
|
475
|
-
*
|
|
474
|
+
* Replace the schema outright whatever the formats. Without it, field-DSL
|
|
475
|
+
* fields pushed at a FormBuilder schema are merged in as nodes, and a
|
|
476
|
+
* FormBuilder schema pushed at a field-DSL one gets 409
|
|
477
|
+
* (`BotlrSchemaFormatConflictError`).
|
|
476
478
|
*/
|
|
477
479
|
force?: boolean;
|
|
478
480
|
/** Defaults to the client's `appId`. */
|
|
479
481
|
appInstanceId?: string;
|
|
480
482
|
}
|
|
483
|
+
/**
|
|
484
|
+
* What a schema push did: `replaced` the stored schema (same format, or
|
|
485
|
+
* `force`), or `merged` field-DSL fields into a FormBuilder schema as new
|
|
486
|
+
* nodes, keeping every existing node.
|
|
487
|
+
*/
|
|
488
|
+
type SectionSchemaPushMode = "replaced" | "merged";
|
|
481
489
|
interface SectionSchemaPutResponse {
|
|
482
490
|
definition: SectionDefinition & {
|
|
483
491
|
format: SectionSchemaFormat;
|
|
484
492
|
};
|
|
485
493
|
/** The format the definition had before this call. */
|
|
486
494
|
previousFormat: SectionSchemaFormat;
|
|
487
|
-
/** False when
|
|
495
|
+
/** False when nothing had to change. */
|
|
488
496
|
changed: boolean;
|
|
497
|
+
mode: SectionSchemaPushMode;
|
|
498
|
+
/** Field names that became new nodes (`merged`), else empty. */
|
|
499
|
+
added: string[];
|
|
500
|
+
/** Node names the dashboard has that the code does not declare, kept (`merged`), else empty. */
|
|
501
|
+
dashboardOnly: string[];
|
|
489
502
|
}
|
|
490
503
|
interface SectionSource {
|
|
491
504
|
scope: SectionSourceScope;
|
|
@@ -644,11 +657,15 @@ declare function createBotlrClient(config: BotlrClientConfig): {
|
|
|
644
657
|
*/
|
|
645
658
|
getSchema(name: string): Promise<SectionSchemaGetResponse>;
|
|
646
659
|
/**
|
|
647
|
-
*
|
|
648
|
-
* (`write:sections`).
|
|
649
|
-
*
|
|
650
|
-
* `
|
|
651
|
-
*
|
|
660
|
+
* Write the definition's props schema with the personal access token
|
|
661
|
+
* (`write:sections`). Same format, or `force`: the schema is replaced
|
|
662
|
+
* (`mode: "replaced"`). Field-DSL fields at a FormBuilder schema without
|
|
663
|
+
* `force`: the missing fields are added as nodes and nothing is removed
|
|
664
|
+
* (`mode: "merged"`, with `added` and `dashboardOnly`). A FormBuilder
|
|
665
|
+
* schema at a field-DSL one without `force` throws
|
|
666
|
+
* `BotlrSchemaFormatConflictError`; a `BotlrError` with status 422 means
|
|
667
|
+
* the schema is not an object of fields or the definition is not
|
|
668
|
+
* project-scoped.
|
|
652
669
|
*/
|
|
653
670
|
putSchema(name: string, body: SectionSchemaPutBody): Promise<SectionSchemaPutResponse>;
|
|
654
671
|
};
|
|
@@ -694,11 +711,15 @@ declare function createBotlrClient(config: BotlrClientConfig): {
|
|
|
694
711
|
*/
|
|
695
712
|
getSchema(name: string): Promise<SectionSchemaGetResponse>;
|
|
696
713
|
/**
|
|
697
|
-
*
|
|
698
|
-
* (`write:sections`).
|
|
699
|
-
*
|
|
700
|
-
* `
|
|
701
|
-
*
|
|
714
|
+
* Write the definition's props schema with the personal access token
|
|
715
|
+
* (`write:sections`). Same format, or `force`: the schema is replaced
|
|
716
|
+
* (`mode: "replaced"`). Field-DSL fields at a FormBuilder schema without
|
|
717
|
+
* `force`: the missing fields are added as nodes and nothing is removed
|
|
718
|
+
* (`mode: "merged"`, with `added` and `dashboardOnly`). A FormBuilder
|
|
719
|
+
* schema at a field-DSL one without `force` throws
|
|
720
|
+
* `BotlrSchemaFormatConflictError`; a `BotlrError` with status 422 means
|
|
721
|
+
* the schema is not an object of fields or the definition is not
|
|
722
|
+
* project-scoped.
|
|
702
723
|
*/
|
|
703
724
|
putSchema(name: string, body: SectionSchemaPutBody): Promise<SectionSchemaPutResponse>;
|
|
704
725
|
};
|
|
@@ -752,11 +773,15 @@ declare function createBotlrFactory(defaults?: {
|
|
|
752
773
|
*/
|
|
753
774
|
getSchema(name: string): Promise<SectionSchemaGetResponse>;
|
|
754
775
|
/**
|
|
755
|
-
*
|
|
756
|
-
* (`write:sections`).
|
|
757
|
-
*
|
|
758
|
-
* `
|
|
759
|
-
*
|
|
776
|
+
* Write the definition's props schema with the personal access token
|
|
777
|
+
* (`write:sections`). Same format, or `force`: the schema is replaced
|
|
778
|
+
* (`mode: "replaced"`). Field-DSL fields at a FormBuilder schema without
|
|
779
|
+
* `force`: the missing fields are added as nodes and nothing is removed
|
|
780
|
+
* (`mode: "merged"`, with `added` and `dashboardOnly`). A FormBuilder
|
|
781
|
+
* schema at a field-DSL one without `force` throws
|
|
782
|
+
* `BotlrSchemaFormatConflictError`; a `BotlrError` with status 422 means
|
|
783
|
+
* the schema is not an object of fields or the definition is not
|
|
784
|
+
* project-scoped.
|
|
760
785
|
*/
|
|
761
786
|
putSchema(name: string, body: SectionSchemaPutBody): Promise<SectionSchemaPutResponse>;
|
|
762
787
|
};
|
|
@@ -835,4 +860,4 @@ declare function createAssetUrl(options: AssetUrlOptions): (asset: AssetRef, sco
|
|
|
835
860
|
/** Statuses retried by default: timeouts, rate limits and server errors. */
|
|
836
861
|
declare const DEFAULT_RETRY_STATUSES: readonly number[];
|
|
837
862
|
|
|
838
|
-
export { type AlternateTranslation, type AssetData, type AssetRef, type AssetUrlOptions, type BotlrClient, type BotlrClientConfig, BotlrConflictError, type BotlrContext, type BotlrEntry, type BotlrEntryMeta, BotlrError, type BotlrQuery, type BotlrQueryValue, type BotlrRetryConfig, type BotlrRetryInfo, BotlrSchemaFormatConflictError, type BreadcrumbItem, type ConflictLatest, type ContentData, DEFAULT_RETRY_STATUSES, type FormResponse, type GetEntriesParams, type GetEntryBySlugParams, type GetPageListParams, type GetPageSlugListParams, type GetSingletonParams, type GridColumn, type GridContainerInstance, type InlineSectionInstance, type LayoutNode, type ListField, type ListResponse, MANIFEST_STATE_KEYS, type MenuResponse, type PageDiscoveryData, type PageListResponse, type PageResponse, type PageScope, type PageSlugListResponse, type PageTemplateData, type PublisherScope, type RedirectData, type RedirectEntry, type RedirectsResponse, type RequestOptions, type ResolveResponse, type ResponsiveGap, type ResponsiveSpan, SECTION_FRAMEWORK_RE, SECTION_PATH_RE, SECTION_SOURCE_LIMITS, type SectionDefinition, type SectionDefinitionScope, type SectionDependencies, type SectionFieldSchema, type SectionFormBuilderNode, type SectionFormBuilderSchema, type SectionFramework, type SectionManifest, type SectionSchema, type SectionSchemaField, type SectionSchemaFormat, type SectionSchemaGetResponse, type SectionSchemaPutBody, type SectionSchemaPutResponse, type SectionSource, type SectionSourceFile, type SectionSourceFileInput, type SectionSourceListParams, type SectionSourceListResponse, type SectionSourcePullParams, type SectionSourcePullResponse, type SectionSourcePushBody, type SectionSourcePushResponse, type SectionSourceScope, type SectionSourceScopeSummary, type SectionSourceSummary, type SharedSectionInstance, type SpacingSize, type ThemeData, type VisibilityRules, botlrErrorMessage, contentHash, contextToHeaders, createAssetUrl, createBotlrClient, createBotlrFactory, fileSha256, getAssetFilename, getAssetId, getAssetUrl, hashFiles, isAssetData, isFieldSchema, isFormBuilderSchema, isValidFramework, isValidSectionPath, schemaFormat, stripManifestState, toQueryString };
|
|
863
|
+
export { type AlternateTranslation, type AssetData, type AssetRef, type AssetUrlOptions, type BotlrClient, type BotlrClientConfig, BotlrConflictError, type BotlrContext, type BotlrEntry, type BotlrEntryMeta, BotlrError, type BotlrQuery, type BotlrQueryValue, type BotlrRetryConfig, type BotlrRetryInfo, BotlrSchemaFormatConflictError, type BreadcrumbItem, type ConflictLatest, type ContentData, DEFAULT_RETRY_STATUSES, type FormResponse, type GetEntriesParams, type GetEntryBySlugParams, type GetPageListParams, type GetPageSlugListParams, type GetSingletonParams, type GridColumn, type GridContainerInstance, type InlineSectionInstance, type LayoutNode, type ListField, type ListResponse, MANIFEST_STATE_KEYS, type MenuResponse, type PageDiscoveryData, type PageListResponse, type PageResponse, type PageScope, type PageSlugListResponse, type PageTemplateData, type PublisherScope, type RedirectData, type RedirectEntry, type RedirectsResponse, type RequestOptions, type ResolveResponse, type ResponsiveGap, type ResponsiveSpan, SECTION_FRAMEWORK_RE, SECTION_PATH_RE, SECTION_SOURCE_LIMITS, type SectionDefinition, type SectionDefinitionScope, type SectionDependencies, type SectionFieldSchema, type SectionFormBuilderNode, type SectionFormBuilderSchema, type SectionFramework, type SectionManifest, type SectionSchema, type SectionSchemaField, type SectionSchemaFormat, type SectionSchemaGetResponse, type SectionSchemaPushMode, type SectionSchemaPutBody, type SectionSchemaPutResponse, type SectionSource, type SectionSourceFile, type SectionSourceFileInput, type SectionSourceListParams, type SectionSourceListResponse, type SectionSourcePullParams, type SectionSourcePullResponse, type SectionSourcePushBody, type SectionSourcePushResponse, type SectionSourceScope, type SectionSourceScopeSummary, type SectionSourceSummary, type SharedSectionInstance, type SpacingSize, type ThemeData, type VisibilityRules, botlrErrorMessage, contentHash, contextToHeaders, createAssetUrl, createBotlrClient, createBotlrFactory, fileSha256, getAssetFilename, getAssetId, getAssetUrl, hashFiles, isAssetData, isFieldSchema, isFormBuilderSchema, isValidFramework, isValidSectionPath, schemaFormat, stripManifestState, toQueryString };
|
package/dist/index.d.ts
CHANGED
|
@@ -471,21 +471,34 @@ interface SectionSchemaPutBody {
|
|
|
471
471
|
/** The new props schema, field DSL or FormBuilder. */
|
|
472
472
|
schema: SectionSchema;
|
|
473
473
|
/**
|
|
474
|
-
* Replace the schema
|
|
475
|
-
*
|
|
474
|
+
* Replace the schema outright whatever the formats. Without it, field-DSL
|
|
475
|
+
* fields pushed at a FormBuilder schema are merged in as nodes, and a
|
|
476
|
+
* FormBuilder schema pushed at a field-DSL one gets 409
|
|
477
|
+
* (`BotlrSchemaFormatConflictError`).
|
|
476
478
|
*/
|
|
477
479
|
force?: boolean;
|
|
478
480
|
/** Defaults to the client's `appId`. */
|
|
479
481
|
appInstanceId?: string;
|
|
480
482
|
}
|
|
483
|
+
/**
|
|
484
|
+
* What a schema push did: `replaced` the stored schema (same format, or
|
|
485
|
+
* `force`), or `merged` field-DSL fields into a FormBuilder schema as new
|
|
486
|
+
* nodes, keeping every existing node.
|
|
487
|
+
*/
|
|
488
|
+
type SectionSchemaPushMode = "replaced" | "merged";
|
|
481
489
|
interface SectionSchemaPutResponse {
|
|
482
490
|
definition: SectionDefinition & {
|
|
483
491
|
format: SectionSchemaFormat;
|
|
484
492
|
};
|
|
485
493
|
/** The format the definition had before this call. */
|
|
486
494
|
previousFormat: SectionSchemaFormat;
|
|
487
|
-
/** False when
|
|
495
|
+
/** False when nothing had to change. */
|
|
488
496
|
changed: boolean;
|
|
497
|
+
mode: SectionSchemaPushMode;
|
|
498
|
+
/** Field names that became new nodes (`merged`), else empty. */
|
|
499
|
+
added: string[];
|
|
500
|
+
/** Node names the dashboard has that the code does not declare, kept (`merged`), else empty. */
|
|
501
|
+
dashboardOnly: string[];
|
|
489
502
|
}
|
|
490
503
|
interface SectionSource {
|
|
491
504
|
scope: SectionSourceScope;
|
|
@@ -644,11 +657,15 @@ declare function createBotlrClient(config: BotlrClientConfig): {
|
|
|
644
657
|
*/
|
|
645
658
|
getSchema(name: string): Promise<SectionSchemaGetResponse>;
|
|
646
659
|
/**
|
|
647
|
-
*
|
|
648
|
-
* (`write:sections`).
|
|
649
|
-
*
|
|
650
|
-
* `
|
|
651
|
-
*
|
|
660
|
+
* Write the definition's props schema with the personal access token
|
|
661
|
+
* (`write:sections`). Same format, or `force`: the schema is replaced
|
|
662
|
+
* (`mode: "replaced"`). Field-DSL fields at a FormBuilder schema without
|
|
663
|
+
* `force`: the missing fields are added as nodes and nothing is removed
|
|
664
|
+
* (`mode: "merged"`, with `added` and `dashboardOnly`). A FormBuilder
|
|
665
|
+
* schema at a field-DSL one without `force` throws
|
|
666
|
+
* `BotlrSchemaFormatConflictError`; a `BotlrError` with status 422 means
|
|
667
|
+
* the schema is not an object of fields or the definition is not
|
|
668
|
+
* project-scoped.
|
|
652
669
|
*/
|
|
653
670
|
putSchema(name: string, body: SectionSchemaPutBody): Promise<SectionSchemaPutResponse>;
|
|
654
671
|
};
|
|
@@ -694,11 +711,15 @@ declare function createBotlrClient(config: BotlrClientConfig): {
|
|
|
694
711
|
*/
|
|
695
712
|
getSchema(name: string): Promise<SectionSchemaGetResponse>;
|
|
696
713
|
/**
|
|
697
|
-
*
|
|
698
|
-
* (`write:sections`).
|
|
699
|
-
*
|
|
700
|
-
* `
|
|
701
|
-
*
|
|
714
|
+
* Write the definition's props schema with the personal access token
|
|
715
|
+
* (`write:sections`). Same format, or `force`: the schema is replaced
|
|
716
|
+
* (`mode: "replaced"`). Field-DSL fields at a FormBuilder schema without
|
|
717
|
+
* `force`: the missing fields are added as nodes and nothing is removed
|
|
718
|
+
* (`mode: "merged"`, with `added` and `dashboardOnly`). A FormBuilder
|
|
719
|
+
* schema at a field-DSL one without `force` throws
|
|
720
|
+
* `BotlrSchemaFormatConflictError`; a `BotlrError` with status 422 means
|
|
721
|
+
* the schema is not an object of fields or the definition is not
|
|
722
|
+
* project-scoped.
|
|
702
723
|
*/
|
|
703
724
|
putSchema(name: string, body: SectionSchemaPutBody): Promise<SectionSchemaPutResponse>;
|
|
704
725
|
};
|
|
@@ -752,11 +773,15 @@ declare function createBotlrFactory(defaults?: {
|
|
|
752
773
|
*/
|
|
753
774
|
getSchema(name: string): Promise<SectionSchemaGetResponse>;
|
|
754
775
|
/**
|
|
755
|
-
*
|
|
756
|
-
* (`write:sections`).
|
|
757
|
-
*
|
|
758
|
-
* `
|
|
759
|
-
*
|
|
776
|
+
* Write the definition's props schema with the personal access token
|
|
777
|
+
* (`write:sections`). Same format, or `force`: the schema is replaced
|
|
778
|
+
* (`mode: "replaced"`). Field-DSL fields at a FormBuilder schema without
|
|
779
|
+
* `force`: the missing fields are added as nodes and nothing is removed
|
|
780
|
+
* (`mode: "merged"`, with `added` and `dashboardOnly`). A FormBuilder
|
|
781
|
+
* schema at a field-DSL one without `force` throws
|
|
782
|
+
* `BotlrSchemaFormatConflictError`; a `BotlrError` with status 422 means
|
|
783
|
+
* the schema is not an object of fields or the definition is not
|
|
784
|
+
* project-scoped.
|
|
760
785
|
*/
|
|
761
786
|
putSchema(name: string, body: SectionSchemaPutBody): Promise<SectionSchemaPutResponse>;
|
|
762
787
|
};
|
|
@@ -835,4 +860,4 @@ declare function createAssetUrl(options: AssetUrlOptions): (asset: AssetRef, sco
|
|
|
835
860
|
/** Statuses retried by default: timeouts, rate limits and server errors. */
|
|
836
861
|
declare const DEFAULT_RETRY_STATUSES: readonly number[];
|
|
837
862
|
|
|
838
|
-
export { type AlternateTranslation, type AssetData, type AssetRef, type AssetUrlOptions, type BotlrClient, type BotlrClientConfig, BotlrConflictError, type BotlrContext, type BotlrEntry, type BotlrEntryMeta, BotlrError, type BotlrQuery, type BotlrQueryValue, type BotlrRetryConfig, type BotlrRetryInfo, BotlrSchemaFormatConflictError, type BreadcrumbItem, type ConflictLatest, type ContentData, DEFAULT_RETRY_STATUSES, type FormResponse, type GetEntriesParams, type GetEntryBySlugParams, type GetPageListParams, type GetPageSlugListParams, type GetSingletonParams, type GridColumn, type GridContainerInstance, type InlineSectionInstance, type LayoutNode, type ListField, type ListResponse, MANIFEST_STATE_KEYS, type MenuResponse, type PageDiscoveryData, type PageListResponse, type PageResponse, type PageScope, type PageSlugListResponse, type PageTemplateData, type PublisherScope, type RedirectData, type RedirectEntry, type RedirectsResponse, type RequestOptions, type ResolveResponse, type ResponsiveGap, type ResponsiveSpan, SECTION_FRAMEWORK_RE, SECTION_PATH_RE, SECTION_SOURCE_LIMITS, type SectionDefinition, type SectionDefinitionScope, type SectionDependencies, type SectionFieldSchema, type SectionFormBuilderNode, type SectionFormBuilderSchema, type SectionFramework, type SectionManifest, type SectionSchema, type SectionSchemaField, type SectionSchemaFormat, type SectionSchemaGetResponse, type SectionSchemaPutBody, type SectionSchemaPutResponse, type SectionSource, type SectionSourceFile, type SectionSourceFileInput, type SectionSourceListParams, type SectionSourceListResponse, type SectionSourcePullParams, type SectionSourcePullResponse, type SectionSourcePushBody, type SectionSourcePushResponse, type SectionSourceScope, type SectionSourceScopeSummary, type SectionSourceSummary, type SharedSectionInstance, type SpacingSize, type ThemeData, type VisibilityRules, botlrErrorMessage, contentHash, contextToHeaders, createAssetUrl, createBotlrClient, createBotlrFactory, fileSha256, getAssetFilename, getAssetId, getAssetUrl, hashFiles, isAssetData, isFieldSchema, isFormBuilderSchema, isValidFramework, isValidSectionPath, schemaFormat, stripManifestState, toQueryString };
|
|
863
|
+
export { type AlternateTranslation, type AssetData, type AssetRef, type AssetUrlOptions, type BotlrClient, type BotlrClientConfig, BotlrConflictError, type BotlrContext, type BotlrEntry, type BotlrEntryMeta, BotlrError, type BotlrQuery, type BotlrQueryValue, type BotlrRetryConfig, type BotlrRetryInfo, BotlrSchemaFormatConflictError, type BreadcrumbItem, type ConflictLatest, type ContentData, DEFAULT_RETRY_STATUSES, type FormResponse, type GetEntriesParams, type GetEntryBySlugParams, type GetPageListParams, type GetPageSlugListParams, type GetSingletonParams, type GridColumn, type GridContainerInstance, type InlineSectionInstance, type LayoutNode, type ListField, type ListResponse, MANIFEST_STATE_KEYS, type MenuResponse, type PageDiscoveryData, type PageListResponse, type PageResponse, type PageScope, type PageSlugListResponse, type PageTemplateData, type PublisherScope, type RedirectData, type RedirectEntry, type RedirectsResponse, type RequestOptions, type ResolveResponse, type ResponsiveGap, type ResponsiveSpan, SECTION_FRAMEWORK_RE, SECTION_PATH_RE, SECTION_SOURCE_LIMITS, type SectionDefinition, type SectionDefinitionScope, type SectionDependencies, type SectionFieldSchema, type SectionFormBuilderNode, type SectionFormBuilderSchema, type SectionFramework, type SectionManifest, type SectionSchema, type SectionSchemaField, type SectionSchemaFormat, type SectionSchemaGetResponse, type SectionSchemaPushMode, type SectionSchemaPutBody, type SectionSchemaPutResponse, type SectionSource, type SectionSourceFile, type SectionSourceFileInput, type SectionSourceListParams, type SectionSourceListResponse, type SectionSourcePullParams, type SectionSourcePullResponse, type SectionSourcePushBody, type SectionSourcePushResponse, type SectionSourceScope, type SectionSourceScopeSummary, type SectionSourceSummary, type SharedSectionInstance, type SpacingSize, type ThemeData, type VisibilityRules, botlrErrorMessage, contentHash, contextToHeaders, createAssetUrl, createBotlrClient, createBotlrFactory, fileSha256, getAssetFilename, getAssetId, getAssetUrl, hashFiles, isAssetData, isFieldSchema, isFormBuilderSchema, isValidFramework, isValidSectionPath, schemaFormat, stripManifestState, toQueryString };
|
package/dist/index.js
CHANGED
|
@@ -244,11 +244,15 @@ function createBotlrClient(config) {
|
|
|
244
244
|
});
|
|
245
245
|
},
|
|
246
246
|
/**
|
|
247
|
-
*
|
|
248
|
-
* (`write:sections`).
|
|
249
|
-
*
|
|
250
|
-
* `
|
|
251
|
-
*
|
|
247
|
+
* Write the definition's props schema with the personal access token
|
|
248
|
+
* (`write:sections`). Same format, or `force`: the schema is replaced
|
|
249
|
+
* (`mode: "replaced"`). Field-DSL fields at a FormBuilder schema without
|
|
250
|
+
* `force`: the missing fields are added as nodes and nothing is removed
|
|
251
|
+
* (`mode: "merged"`, with `added` and `dashboardOnly`). A FormBuilder
|
|
252
|
+
* schema at a field-DSL one without `force` throws
|
|
253
|
+
* `BotlrSchemaFormatConflictError`; a `BotlrError` with status 422 means
|
|
254
|
+
* the schema is not an object of fields or the definition is not
|
|
255
|
+
* project-scoped.
|
|
252
256
|
*/
|
|
253
257
|
async putSchema(name, body) {
|
|
254
258
|
if (!config.personalToken) {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/retry.ts","../src/client.ts","../src/sections.ts","../src/assets.ts"],"sourcesContent":["/**\n * Thrown for every non-2xx response. `status` is the HTTP status and\n * `details` the parsed JSON body (or the raw text when the body is not JSON).\n */\nexport class BotlrError extends Error {\n readonly status: number | undefined;\n readonly details: unknown;\n\n constructor(message: string, status?: number, details?: unknown) {\n super(message);\n this.name = \"BotlrError\";\n this.status = status;\n this.details = details;\n }\n}\n\n/** The latest version of a section source, as reported on a push conflict. */\nexport type ConflictLatest = {\n version: number;\n contentHash: string;\n};\n\n/**\n * Thrown by `sections.push` on 409: `baseVersion` was not the latest version,\n * or a concurrent push won. `latest` is null when the server has no version\n * at all (the client sent a `baseVersion` above 0 for a section with no source).\n */\nexport class BotlrConflictError extends BotlrError {\n readonly latest: ConflictLatest | null;\n\n constructor(message: string, latest: ConflictLatest | null, details?: unknown) {\n super(message, 409, details);\n this.name = \"BotlrConflictError\";\n this.latest = latest;\n }\n}\n\n/**\n * Thrown by `sections.putSchema` on 409: the definition's current schema and\n * the new one are in different formats and `force` was not set.\n */\nexport class BotlrSchemaFormatConflictError extends BotlrError {\n readonly currentFormat: string;\n readonly newFormat: string;\n\n constructor(message: string, currentFormat: string, newFormat: string, details?: unknown) {\n super(message, 409, details);\n this.name = \"BotlrSchemaFormatConflictError\";\n this.currentFormat = currentFormat;\n this.newFormat = newFormat;\n }\n}\n\n/**\n * The human-readable message of an error response, when the server sent one:\n * `detail` as a string, `detail.message`, or the `msg` lines of a FastAPI\n * validation error. Undefined for anything else.\n */\nexport function botlrErrorMessage(error: unknown): string | undefined {\n if (!(error instanceof BotlrError)) return undefined;\n const details = error.details;\n if (!details || typeof details !== \"object\") return typeof details === \"string\" ? details : undefined;\n const detail = (details as { detail?: unknown }).detail;\n if (typeof detail === \"string\") return detail;\n if (Array.isArray(detail)) {\n const lines = detail\n .map((item) => (item && typeof item === \"object\" ? (item as { msg?: unknown }).msg : undefined))\n .filter((msg): msg is string => typeof msg === \"string\");\n return lines.length ? lines.join(\"; \") : undefined;\n }\n if (detail && typeof detail === \"object\") {\n const message = (detail as { message?: unknown }).message;\n return typeof message === \"string\" ? message : undefined;\n }\n return undefined;\n}\n","import type { BotlrRetryConfig, BotlrRetryInfo } from \"./types\";\n\n/** Statuses retried by default: timeouts, rate limits and server errors. */\nexport const DEFAULT_RETRY_STATUSES: readonly number[] = [408, 429, 500, 502, 503, 504];\n\nexport type ResolvedRetryConfig = {\n retries: number;\n delay: number;\n maxDelay: number;\n statuses: readonly number[];\n onRetry: ((info: BotlrRetryInfo) => void) | undefined;\n};\n\nexport function resolveRetryConfig(retry: BotlrRetryConfig | false | undefined): ResolvedRetryConfig {\n if (retry === false) {\n return { retries: 0, delay: 0, maxDelay: 0, statuses: [], onRetry: undefined };\n }\n return {\n retries: Math.max(0, retry?.retries ?? 2),\n delay: Math.max(0, retry?.delay ?? 300),\n maxDelay: Math.max(0, retry?.maxDelay ?? 5000),\n statuses: retry?.statuses ?? DEFAULT_RETRY_STATUSES,\n onRetry: retry?.onRetry,\n };\n}\n\n/** Only reads are retried: a repeated POST or PUT could submit twice. */\nexport function isRetryableMethod(method: string | undefined): boolean {\n const m = (method ?? \"GET\").toUpperCase();\n return m === \"GET\" || m === \"HEAD\";\n}\n\n/** `Retry-After` as milliseconds, from either the seconds or the date form. */\nexport function parseRetryAfter(value: string | null | undefined): number | undefined {\n if (!value) return undefined;\n const seconds = Number(value);\n if (Number.isFinite(seconds)) return Math.max(0, seconds * 1000);\n const date = Date.parse(value);\n if (Number.isFinite(date)) return Math.max(0, date - Date.now());\n return undefined;\n}\n\n/**\n * The wait before retry number `attempt + 1`: `Retry-After` when the server\n * sent one, else `delay * 2^attempt` with ±25% jitter, both capped at `maxDelay`.\n */\nexport function retryDelayMs(retry: ResolvedRetryConfig, attempt: number, retryAfter?: string | null): number {\n const fromHeader = parseRetryAfter(retryAfter);\n if (fromHeader !== undefined) return Math.min(fromHeader, retry.maxDelay);\n const base = Math.min(retry.maxDelay, retry.delay * 2 ** attempt);\n return Math.round(base * (0.75 + Math.random() * 0.5));\n}\n\nexport async function waitBeforeRetry(\n retry: ResolvedRetryConfig,\n attempt: number,\n error: unknown,\n url: string,\n retryAfter?: string | null,\n): Promise<void> {\n const delay = retryDelayMs(retry, attempt, retryAfter);\n retry.onRetry?.({ attempt: attempt + 1, delay, url, error });\n if (delay > 0) await new Promise<void>((resolve) => setTimeout(resolve, delay));\n}\n","import type {\n BotlrClientConfig,\n BotlrContext,\n BotlrEntry,\n BotlrQuery,\n BotlrQueryValue,\n FormResponse,\n GetEntriesParams,\n GetEntryBySlugParams,\n GetPageListParams,\n GetPageSlugListParams,\n GetSingletonParams,\n ListResponse,\n MenuResponse,\n PageListResponse,\n PageResponse,\n PageSlugListResponse,\n RedirectsResponse,\n ResolveResponse,\n} from \"./types\";\nimport type {\n SectionSchemaGetResponse,\n SectionSchemaPutBody,\n SectionSchemaPutResponse,\n SectionSourceListParams,\n SectionSourceListResponse,\n SectionSourcePullParams,\n SectionSourcePullResponse,\n SectionSourcePushBody,\n SectionSourcePushResponse,\n} from \"./sections\";\nimport { BotlrConflictError, BotlrError, BotlrSchemaFormatConflictError } from \"./errors\";\nimport { isRetryableMethod, resolveRetryConfig, waitBeforeRetry } from \"./retry\";\n\nexport type RequestOptions = Omit<RequestInit, \"headers\"> & {\n query?: BotlrQuery;\n context?: Partial<BotlrContext>;\n headers?: Record<string, string>;\n};\n\nexport function toQueryString(query: BotlrQuery): string {\n const params = new URLSearchParams();\n\n for (const [key, value] of Object.entries(query)) {\n if (value === undefined || value === null) continue;\n\n if (Array.isArray(value)) {\n for (const item of value) {\n if (item !== undefined && item !== null) {\n params.append(key, String(item));\n }\n }\n } else {\n params.set(key, String(value));\n }\n }\n\n const qs = params.toString();\n return qs ? `?${qs}` : \"\";\n}\n\nfunction mergeContext(\n base?: Partial<BotlrContext>,\n override?: Partial<BotlrContext>,\n): Partial<BotlrContext> | undefined {\n if (!base && !override) return undefined;\n return { ...base, ...override };\n}\n\nexport function contextToHeaders(context?: Partial<BotlrContext>): Record<string, string> {\n if (!context) return {};\n\n return {\n ...(context.appId ? { \"x-botlr-app-id\": context.appId } : {}),\n ...(context.locale ? { \"x-botlr-locale\": context.locale } : {}),\n ...(context.preview !== undefined ? { \"x-botlr-preview\": String(context.preview) } : {}),\n };\n}\n\nasync function errorDetails(response: Response): Promise<unknown> {\n const text = await response.text().catch(() => undefined);\n if (text === undefined || text === \"\") return undefined;\n try {\n return JSON.parse(text) as unknown;\n } catch {\n return text;\n }\n}\n\nfunction conflictLatest(details: unknown): { version: number; contentHash: string } | null {\n if (!details || typeof details !== \"object\") return null;\n const detail = (details as { detail?: unknown }).detail;\n if (!detail || typeof detail !== \"object\") return null;\n const latest = (detail as { latest?: unknown }).latest;\n if (!latest || typeof latest !== \"object\") return null;\n const { version, contentHash } = latest as { version?: unknown; contentHash?: unknown };\n if (typeof version !== \"number\" || typeof contentHash !== \"string\") return null;\n return { version, contentHash };\n}\n\nfunction conflictFormats(details: unknown): { currentFormat: string; newFormat: string } | null {\n if (!details || typeof details !== \"object\") return null;\n const detail = (details as { detail?: unknown }).detail;\n if (!detail || typeof detail !== \"object\") return null;\n const { currentFormat, newFormat } = detail as { currentFormat?: unknown; newFormat?: unknown };\n if (typeof currentFormat !== \"string\" || typeof newFormat !== \"string\") return null;\n return { currentFormat, newFormat };\n}\n\nfunction conflictMessage(details: unknown): string | undefined {\n if (!details || typeof details !== \"object\") return undefined;\n const detail = (details as { detail?: unknown }).detail;\n if (!detail || typeof detail !== \"object\") return undefined;\n const message = (detail as { message?: unknown }).message;\n return typeof message === \"string\" ? message : undefined;\n}\n\nexport type BotlrClient = ReturnType<typeof createBotlrClient>;\n\n/**\n * A Botlr client. Every call sends the site token as `Authorization: Bearer`\n * plus `x-botlr-app-id`, `x-botlr-locale` and `x-botlr-preview` from the\n * config; `sections.push` and `sections.putSchema` send the personal token instead.\n */\nexport function createBotlrClient(config: BotlrClientConfig) {\n const fetcher = config.fetch ?? globalThis.fetch;\n if (typeof fetcher !== \"function\") {\n throw new BotlrError(\"No fetch implementation: pass `fetch` in the client config\");\n }\n const baseUrl = config.baseUrl.replace(/\\/+$/, \"\");\n\n const baseContext: BotlrContext = {\n appId: config.appId,\n locale: config.locale,\n preview: config.preview,\n };\n\n const retry = resolveRetryConfig(config.retry);\n\n async function request<T>(path: string, options: RequestOptions = {}): Promise<T> {\n const { query, context: contextOverride, headers: extraHeaders, ...init } = options;\n const context = mergeContext(baseContext, contextOverride);\n const url = `${baseUrl}${path}${toQueryString(query ?? {})}`;\n const headers = {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n ...(config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}),\n ...contextToHeaders(context),\n ...(extraHeaders ?? {}),\n };\n const retryable = retry.retries > 0 && isRetryableMethod(init.method);\n\n for (let attempt = 0; ; attempt++) {\n const canRetry = retryable && attempt < retry.retries && !init.signal?.aborted;\n\n let response: Response;\n try {\n response = await fetcher(url, { ...init, headers });\n } catch (error) {\n if (!canRetry) throw error;\n await waitBeforeRetry(retry, attempt, error, url);\n continue;\n }\n\n if (response.ok) {\n if (response.status === 204) return undefined as T;\n return (await response.json()) as T;\n }\n\n const details = await errorDetails(response);\n const error =\n response.status === 409\n ? new BotlrConflictError(\n conflictMessage(details) ?? `Botlr request failed with status 409`,\n conflictLatest(details),\n details,\n )\n : new BotlrError(`Botlr request failed with status ${response.status}`, response.status, details);\n\n if (!canRetry || !retry.statuses.includes(response.status)) throw error;\n await waitBeforeRetry(retry, attempt, error, url, response.headers.get(\"retry-after\"));\n }\n }\n\n const sections = {\n /** Every section the app can pull for `framework`, with the latest version per scope. */\n list(params: SectionSourceListParams): Promise<SectionSourceListResponse> {\n return request<SectionSourceListResponse>(\"/api/sections/source\", {\n method: \"GET\",\n query: { framework: params.framework, appInstanceId: baseContext.appId },\n });\n },\n\n /** The section's source: this app's row first, the shared row second. */\n pull(name: string, params: SectionSourcePullParams): Promise<SectionSourcePullResponse> {\n return request<SectionSourcePullResponse>(`/api/sections/${encodeURIComponent(name)}/source`, {\n method: \"GET\",\n query: {\n framework: params.framework,\n appInstanceId: baseContext.appId,\n version: params.version,\n scope: params.scope,\n },\n });\n },\n\n /**\n * Push a new version with the personal access token (`write:sections`).\n * Resolves with `created: true` on 201 and `created: false` when nothing\n * changed; throws `BotlrConflictError` when `baseVersion` is not the latest.\n */\n push(name: string, body: SectionSourcePushBody): Promise<SectionSourcePushResponse> {\n if (!config.personalToken) {\n return Promise.reject(\n new BotlrError(\"sections.push needs `personalToken` (a personal access token with write:sections)\"),\n );\n }\n const { appInstanceId, ...rest } = body;\n return request<SectionSourcePushResponse>(`/api/sections/${encodeURIComponent(name)}/source`, {\n method: \"PUT\",\n headers: { Authorization: `Bearer ${config.personalToken}` },\n body: JSON.stringify({ appInstanceId: appInstanceId ?? baseContext.appId, ...rest }),\n });\n },\n\n /**\n * The section definition with its props schema and format, with the site\n * token. Works whether or not source has been pushed; 404 when the app has\n * no section type of that name.\n */\n getSchema(name: string): Promise<SectionSchemaGetResponse> {\n return request<SectionSchemaGetResponse>(`/api/sections/${encodeURIComponent(name)}/schema`, {\n method: \"GET\",\n query: { appInstanceId: baseContext.appId },\n });\n },\n\n /**\n * Replace the definition's props schema with the personal access token\n * (`write:sections`). Throws `BotlrSchemaFormatConflictError` when the\n * current schema is in the other format and `force` is not set, and a\n * `BotlrError` with status 422 when the schema is not an object of fields\n * or the definition is not project-scoped.\n */\n async putSchema(name: string, body: SectionSchemaPutBody): Promise<SectionSchemaPutResponse> {\n if (!config.personalToken) {\n throw new BotlrError(\"sections.putSchema needs `personalToken` (a personal access token with write:sections)\");\n }\n const { appInstanceId, schema, force } = body;\n try {\n return await request<SectionSchemaPutResponse>(`/api/sections/${encodeURIComponent(name)}/schema`, {\n method: \"PUT\",\n headers: { Authorization: `Bearer ${config.personalToken}` },\n body: JSON.stringify({ appInstanceId: appInstanceId ?? baseContext.appId, schema, force: force ?? false }),\n });\n } catch (error) {\n if (error instanceof BotlrConflictError) {\n const formats = conflictFormats(error.details);\n if (formats) {\n throw new BotlrSchemaFormatConflictError(error.message, formats.currentFormat, formats.newFormat, error.details);\n }\n }\n throw error;\n }\n },\n };\n\n return {\n request,\n\n /** The resolved config, for wrappers that need `baseUrl` or `appId`. */\n config: { ...config, baseUrl } as Readonly<BotlrClientConfig>,\n\n withContext(contextOverride: Partial<BotlrContext>) {\n return createBotlrClient({ ...config, ...contextOverride });\n },\n\n async getEntryBySlug<TFields>(params: GetEntryBySlugParams): Promise<BotlrEntry<TFields> | null> {\n const result = await request<{ item: BotlrEntry<TFields> | null }>(\"/entries/by-slug\", {\n method: \"GET\",\n query: { contentType: params.contentType, slug: params.slug, locale: params.locale },\n context: { locale: params.locale },\n });\n return result.item;\n },\n\n async getEntries<TFields>(\n params: GetEntriesParams,\n ): Promise<{ items: BotlrEntry<TFields>[]; total: number }> {\n const query: Record<string, BotlrQueryValue> = {\n contentType: params.contentType,\n locale: params.locale,\n limit: params.limit,\n offset: params.offset,\n sort: params.sort,\n };\n for (const [key, value] of Object.entries(params.filters ?? {})) {\n query[`filter.${key}`] = value;\n }\n return request<{ items: BotlrEntry<TFields>[]; total: number }>(\"/entries\", {\n method: \"GET\",\n query,\n context: { locale: params.locale },\n });\n },\n\n async getSingleton<TFields>(params: GetSingletonParams): Promise<BotlrEntry<TFields> | null> {\n const result = await request<{ item: BotlrEntry<TFields> | null }>(\"/singletons\", {\n method: \"GET\",\n query: { key: params.key, locale: params.locale },\n context: { locale: params.locale },\n });\n return result.item;\n },\n\n getPageBySlug(slug: string, locale?: string) {\n return request<PageResponse>(`/api/pages/${slug}`, {\n query: {\n appInstanceId: baseContext.appId,\n locale: locale || baseContext.locale,\n mode: baseContext.preview ? \"draft\" : \"published\",\n },\n });\n },\n\n resolveUrl(slug: string, locale?: string) {\n return request<ResolveResponse>(\"/api/resolve\", {\n query: {\n slug,\n appInstanceId: baseContext.appId,\n locale: locale || baseContext.locale,\n mode: baseContext.preview ? \"draft\" : \"published\",\n ...(baseContext.preview && config.previewToken ? { token: config.previewToken } : {}),\n },\n });\n },\n\n getPageList(params: GetPageListParams = {}) {\n return request<PageListResponse>(\"/api/page-lists\", {\n query: {\n appInstanceId: params.appInstanceId || baseContext.appId,\n locale: params.locale || baseContext.locale,\n parentId: params.parentId,\n tags: params.tags,\n limit: params.limit,\n offset: params.offset,\n type: params.type || \"page\",\n orderBy: params.orderBy,\n },\n });\n },\n\n getPageSlugList(params: GetPageSlugListParams = {}) {\n return request<PageSlugListResponse>(\"/api/page-lists/slugs\", {\n query: {\n appInstanceId: params.appInstanceId || baseContext.appId,\n locale: params.locale || baseContext.locale,\n limit: params.limit,\n offset: params.offset,\n },\n });\n },\n\n getMenu(key: string, locale?: string) {\n return request<MenuResponse>(\"/api/menus\", {\n query: { key, appInstanceId: baseContext.appId, locale: locale || baseContext.locale },\n });\n },\n\n getForm(key: string, locale?: string) {\n return request<FormResponse>(\"/api/forms\", {\n query: { key, appInstanceId: baseContext.appId, locale: locale || baseContext.locale },\n });\n },\n\n getList(appId: string, listId: string, locale?: string) {\n return request<ListResponse>(`/api/lists/${listId}/complete`, {\n query: { appInstanceId: appId, locale: locale || baseContext.locale },\n });\n },\n\n getConfigurator<T = unknown>(configuratorId: string, versionId?: string) {\n return request<T>(`/api/configurators/${configuratorId}`, {\n query: { versionId, appInstanceId: baseContext.appId, locale: baseContext.locale },\n });\n },\n\n /** GET `/api/{endpoint}` with the app and locale added to the query. */\n getData<T = unknown>(endpoint: string, queryParams?: BotlrQuery, locale?: string) {\n return request<T>(`/api/${endpoint}`, {\n method: \"GET\",\n query: { appInstanceId: baseContext.appId, locale: locale || baseContext.locale, ...queryParams },\n });\n },\n\n /** POST `data` as JSON to `/api/{endpoint}`, for forms and other submissions. */\n submitData<T = unknown>(\n endpoint: string,\n data: Record<string, unknown>,\n locale?: string,\n queryParams?: BotlrQuery,\n ) {\n return request<T>(`/api/${endpoint}`, {\n method: \"POST\",\n body: JSON.stringify(data),\n query: { appInstanceId: baseContext.appId, locale: locale || baseContext.locale, ...queryParams },\n });\n },\n\n getAppInfo<T = unknown>() {\n return request<T>(`/api/apps/${baseContext.appId}`);\n },\n\n getSitemap<T = unknown>() {\n return request<T>(`/api/sitemap/${baseContext.appId}`);\n },\n\n /** Every redirect for the app, across all locales. */\n getRedirects() {\n return request<RedirectsResponse>(`/api/redirects/${baseContext.appId}`);\n },\n\n getAssetById<T = unknown>(assetId: string) {\n return request<T>(`/api/assets/${assetId}`, {\n query: { appInstanceId: baseContext.appId },\n });\n },\n\n sections,\n };\n}\n\nexport function createBotlrFactory(defaults?: { fetch?: typeof fetch }) {\n return {\n create(config: Omit<BotlrClientConfig, \"fetch\">) {\n return createBotlrClient({ ...defaults, ...config });\n },\n };\n}\n","/**\n * Section source bridge: types and hash helpers.\n *\n * Mirrors the contract in xam-mono's docs/SECTION_SOURCE_BRIDGE.md. The hash\n * rules are reproduced here exactly as the server computes them, so a client\n * can tell \"clean\", \"modified\", \"behind\" and \"diverged\" apart without a\n * round trip:\n *\n * - `file.sha256` = sha256 of the UTF-8 content, hex, content exactly as sent\n * - `contentHash` = sha256 over `\"{path}\\n{sha256}\\n\"` for every file, sorted by path\n *\n * The manifest is not hashed.\n */\n\n/** The source variant a section is stored for, for example `react-next` or `html`. */\nexport type SectionFramework = string;\n\nexport type SectionSourceScope = \"instance\" | \"shared\";\n\n/** `<slug>.botlr.json`, kept next to the section's entry file. */\nexport interface SectionManifest {\n /** Section definition slug. Must equal the name used in the URL on push. */\n section: string;\n framework: SectionFramework;\n /** Entry file, relative to the manifest's directory. Must be listed in `files`. */\n entry: string;\n /** Named export of the component in `entry`. */\n exportName?: string;\n /** Every file of the section, relative to the manifest's directory, forward slashes only. */\n files: string[];\n title?: string;\n description?: string;\n category?: string;\n icon?: string;\n dependencies?: SectionDependencies;\n /**\n * The props the code expects, normally in the field DSL. The dashboard owns\n * the definition's schema: a source push never changes it (this is only a\n * seed when `--create` adds the definition, and what the CLI compares\n * against for a drift warning). `botlr schema pull` copies the dashboard's\n * schema here in whichever format it has; `botlr schema push` sends it back.\n */\n schema?: SectionSchema;\n /** Unknown keys are kept by the server. */\n [key: string]: unknown;\n}\n\nexport interface SectionDependencies {\n /** npm packages the section imports, with the ranges the app uses. */\n npm?: Record<string, string>;\n /** Local modules the section imports; the pulling app must already have them. */\n internal?: string[];\n}\n\n/**\n * Pull state keys the server strips from a manifest. The CLI keeps these in\n * `.botlr/sections.lock.json`, never in the manifest.\n */\nexport const MANIFEST_STATE_KEYS = [\"version\", \"hash\", \"contentHash\", \"pulledAt\", \"scope\"] as const;\n\nexport interface SectionSourceFileInput {\n path: string;\n content: string;\n}\n\nexport interface SectionSourceFile extends SectionSourceFileInput {\n sha256: string;\n}\n\nexport interface SectionDefinitionScope {\n tenantId: string;\n organizationId: string | null;\n projectId: string | null;\n}\n\n/**\n * One field of the dashboard's props schema (the field DSL): the value under\n * a field key in `SectionDefinition.schema`. Other keys are kept as given.\n */\nexport interface SectionSchemaField {\n type: string;\n label?: string;\n required?: boolean;\n /** For `select`: plain values or `{ value, label }` objects. */\n options?: Array<string | number | { value: string | number; label?: string }>;\n min?: number;\n max?: number;\n multiple?: boolean;\n [key: string]: unknown;\n}\n\n/** The field DSL: a map of field key to field. This is what the dashboard's properties editor stores. */\nexport type SectionFieldSchema = Record<string, SectionSchemaField>;\n\n/**\n * A node of the older FormBuilder schema format. A `field` node carries its\n * field type in `fieldType`; `section`, `fieldset` and `composite` nodes\n * group `children`; a `repeater` node lists its item `fields`.\n */\nexport interface SectionFormBuilderNode {\n type: string;\n fieldType?: string;\n name?: string;\n key?: string;\n label?: string;\n required?: boolean;\n options?: SectionSchemaField[\"options\"];\n children?: SectionFormBuilderNode[];\n fields?: SectionFormBuilderNode[];\n [key: string]: unknown;\n}\n\n/** The older FormBuilder schema format. */\nexport interface SectionFormBuilderSchema {\n nodes: SectionFormBuilderNode[];\n [key: string]: unknown;\n}\n\n/** A section definition's props schema, in either format the dashboard writes. */\nexport type SectionSchema = SectionFieldSchema | SectionFormBuilderSchema;\n\n/** True for the FormBuilder format (`{ nodes: [...] }`). */\nexport function isFormBuilderSchema(schema: unknown): schema is SectionFormBuilderSchema {\n return !!schema && typeof schema === \"object\" && Array.isArray((schema as { nodes?: unknown }).nodes);\n}\n\n/** True for the field DSL: an object that is not the FormBuilder format. Empty objects count. */\nexport function isFieldSchema(schema: unknown): schema is SectionFieldSchema {\n return !!schema && typeof schema === \"object\" && !Array.isArray(schema) && !isFormBuilderSchema(schema);\n}\n\n/** How a definition's props schema is stored: the field DSL or the older FormBuilder format. */\nexport type SectionSchemaFormat = \"field-dsl\" | \"form-builder\";\n\n/** The format of a schema object, or null for anything that is not a schema. */\nexport function schemaFormat(schema: unknown): SectionSchemaFormat | null {\n if (isFormBuilderSchema(schema)) return \"form-builder\";\n if (isFieldSchema(schema)) return \"field-dsl\";\n return null;\n}\n\nexport interface SectionDefinition {\n id: string;\n name: string;\n title: string;\n /** The props schema as the dashboard stores it; `{}` for a definition without one. */\n schema: SectionSchema | Record<string, unknown>;\n /** The schema's format. The schema routes always send it; the source routes may not. */\n format?: SectionSchemaFormat;\n scope: SectionDefinitionScope;\n}\n\n/** `GET /api/sections/{name}/schema` */\nexport interface SectionSchemaGetResponse {\n definition: SectionDefinition & { format: SectionSchemaFormat };\n}\n\n/** `PUT /api/sections/{name}/schema` */\nexport interface SectionSchemaPutBody {\n /** The new props schema, field DSL or FormBuilder. */\n schema: SectionSchema;\n /**\n * Replace the schema even when the current one is in the other format.\n * Without it the server answers 409 (`BotlrSchemaFormatConflictError`).\n */\n force?: boolean;\n /** Defaults to the client's `appId`. */\n appInstanceId?: string;\n}\n\nexport interface SectionSchemaPutResponse {\n definition: SectionDefinition & { format: SectionSchemaFormat };\n /** The format the definition had before this call. */\n previousFormat: SectionSchemaFormat;\n /** False when the new schema equals the stored one. */\n changed: boolean;\n}\n\nexport interface SectionSource {\n scope: SectionSourceScope;\n framework: SectionFramework;\n version: number;\n contentHash: string;\n manifest: SectionManifest;\n message: string | null;\n createdAt: string;\n files: SectionSourceFile[];\n}\n\nexport interface SectionSourceScopeSummary {\n version: number;\n contentHash: string;\n updatedAt: string;\n}\n\nexport interface SectionSourceSummary {\n name: string;\n title: string;\n definitionId: string;\n instance: SectionSourceScopeSummary | null;\n shared: SectionSourceScopeSummary | null;\n /** Which scope a pull without `scope` returns. */\n effective: SectionSourceScope;\n}\n\nexport interface SectionSourceListResponse {\n framework: SectionFramework;\n sections: SectionSourceSummary[];\n}\n\nexport interface SectionSourcePullResponse {\n /** null only for an instance row whose section no longer resolves to a definition */\n definition: SectionDefinition | null;\n source: SectionSource;\n}\n\nexport interface SectionSourceListParams {\n framework: SectionFramework;\n}\n\nexport interface SectionSourcePullParams {\n framework: SectionFramework;\n /** A specific version; latest when omitted. */\n version?: number;\n /** Force one scope instead of \"instance first, shared second\". */\n scope?: SectionSourceScope;\n}\n\nexport interface SectionSourcePushBody {\n framework: SectionFramework;\n manifest: SectionManifest;\n files: SectionSourceFileInput[];\n /** Defaults to the client's `appId`. */\n appInstanceId?: string;\n /** Write the shared row (per section definition) instead of this app's row. */\n shared?: boolean;\n /**\n * The version the push is based on; 0 means \"none yet\". Omit to force the\n * push without a conflict check.\n */\n baseVersion?: number;\n /** At most 500 characters. */\n message?: string;\n /** Add a project-scoped section definition when none exists. */\n create?: boolean;\n}\n\nexport interface SectionSourcePushResponse {\n /** true on 201 (new version), false on 200 (nothing changed) */\n created: boolean;\n scope: SectionSourceScope;\n definition: SectionDefinition;\n version: number;\n contentHash: string;\n}\n\n// ---------------------------------------------------------------------------\n// Limits, as enforced by the server\n// ---------------------------------------------------------------------------\n\nexport const SECTION_SOURCE_LIMITS = {\n maxFiles: 50,\n maxFileBytes: 512 * 1024,\n maxTotalBytes: 2 * 1024 * 1024,\n maxPathLength: 200,\n maxMessageLength: 500,\n} as const;\n\nexport const SECTION_PATH_RE = /^[A-Za-z0-9._-]+(?:\\/[A-Za-z0-9._-]+)*$/;\nexport const SECTION_FRAMEWORK_RE = /^[a-z0-9][a-z0-9-]{0,31}$/;\n\n/** True when `path` is a relative path the bridge accepts. */\nexport function isValidSectionPath(path: string): boolean {\n if (!path || path.length > SECTION_SOURCE_LIMITS.maxPathLength) return false;\n if (!SECTION_PATH_RE.test(path)) return false;\n return !path.split(\"/\").some((segment) => segment === \".\" || segment === \"..\");\n}\n\nexport function isValidFramework(framework: string): boolean {\n return SECTION_FRAMEWORK_RE.test(framework);\n}\n\n// ---------------------------------------------------------------------------\n// Hashing (Web Crypto, so it runs in the browser, Node 20+ and edge runtimes)\n// ---------------------------------------------------------------------------\n\nconst encoder = new TextEncoder();\n\nfunction subtle(): SubtleCrypto {\n const c = globalThis.crypto;\n if (!c || !c.subtle) {\n throw new Error(\"Web Crypto is not available in this runtime (globalThis.crypto.subtle)\");\n }\n return c.subtle;\n}\n\nfunction toHex(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let out = \"\";\n for (let i = 0; i < bytes.length; i++) {\n out += bytes[i]!.toString(16).padStart(2, \"0\");\n }\n return out;\n}\n\nasync function sha256Hex(input: string): Promise<string> {\n return toHex(await subtle().digest(\"SHA-256\", encoder.encode(input)));\n}\n\n/** sha256 of the UTF-8 encoding of `content`, as lowercase hex. */\nexport function fileSha256(content: string): Promise<string> {\n return sha256Hex(content);\n}\n\n/**\n * The content hash of a file set: sha256 over `\"{path}\\n{sha256}\\n\"` for every\n * file, sorted by path. Accepts files with a precomputed `sha256` or plain\n * `{ path, content }` pairs.\n */\nexport async function contentHash(\n files: ReadonlyArray<SectionSourceFileInput | SectionSourceFile>,\n): Promise<string> {\n const hashed = await hashFiles(files);\n const lines = hashed\n .map((f) => [f.path, f.sha256] as const)\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n .map(([path, sha]) => `${path}\\n${sha}\\n`)\n .join(\"\");\n return sha256Hex(lines);\n}\n\n/** Every file with its `sha256`, in the order given. Files that already carry one are kept as is. */\nexport async function hashFiles(\n files: ReadonlyArray<SectionSourceFileInput | SectionSourceFile>,\n): Promise<SectionSourceFile[]> {\n return Promise.all(\n files.map(async (f) => ({\n path: f.path,\n content: f.content,\n sha256: \"sha256\" in f && typeof f.sha256 === \"string\" ? f.sha256 : await fileSha256(f.content),\n })),\n );\n}\n\n/** A copy of `manifest` without the pull-state keys the server strips. */\nexport function stripManifestState<T extends Record<string, unknown>>(manifest: T): SectionManifest {\n const copy: Record<string, unknown> = { ...manifest };\n for (const key of MANIFEST_STATE_KEYS) delete copy[key];\n return copy as SectionManifest;\n}\n","import type { AssetData, PublisherScope } from \"./types\";\n\n/** An asset as a section prop: the legacy bare id, or the `AssetData` object. */\nexport type AssetRef = string | AssetData | null | undefined;\n\nexport function isAssetData(value: unknown): value is AssetData {\n return typeof value === \"object\" && value !== null && \"id\" in value && \"filename\" in value;\n}\n\nexport function getAssetId(asset: AssetRef): string | undefined {\n if (!asset) return undefined;\n return typeof asset === \"string\" ? asset : asset.id;\n}\n\n/** The filename, when the asset is an `AssetData` object. A bare id has none. */\nexport function getAssetFilename(asset: AssetRef): string | undefined {\n if (!asset || typeof asset === \"string\") return undefined;\n return asset.filename;\n}\n\nexport interface AssetUrlOptions {\n /** The media server origin, for example `https://media.example.com`. */\n mediaUrl: string;\n /** The image recipe segment; defaults to `view`. */\n recipe?: string;\n}\n\n/**\n * The media server URL of an asset:\n * `{mediaUrl}/images/{orgSlug}/{assetId}/{recipe}/{filename}`.\n *\n * Falls back to `image.jpg` for a bare id, and to `{mediaUrl}/images/{assetId}`\n * when no org slug is known.\n */\nexport function getAssetUrl(\n asset: AssetRef,\n scope: Partial<Pick<PublisherScope, \"orgSlug\">> | undefined,\n options: AssetUrlOptions,\n): string | undefined {\n const assetId = getAssetId(asset);\n if (!assetId) return undefined;\n\n const base = options.mediaUrl.replace(/\\/+$/, \"\");\n const recipe = options.recipe ?? \"view\";\n const filename = getAssetFilename(asset);\n\n if (scope?.orgSlug) {\n return `${base}/images/${scope.orgSlug}/${assetId}/${recipe}/${filename ?? \"image.jpg\"}`;\n }\n return `${base}/images/${assetId}`;\n}\n\n/**\n * `getAssetUrl` bound to a media server, for an app's own `getAssetUrl`:\n *\n * ```ts\n * export const getAssetUrl = createAssetUrl({ mediaUrl: process.env.NEXT_PUBLIC_MEDIA_URL! });\n * getAssetUrl(asset, scope, \"thumb\");\n * ```\n */\nexport function createAssetUrl(options: AssetUrlOptions) {\n return (\n asset: AssetRef,\n scope: Partial<Pick<PublisherScope, \"orgSlug\">> | undefined,\n recipe?: string,\n ): string | undefined => getAssetUrl(asset, scope, { ...options, recipe: recipe ?? options.recipe });\n}\n"],"mappings":";AAIO,IAAM,aAAN,cAAyB,MAAM;AAAA,EAC3B;AAAA,EACA;AAAA,EAET,YAAY,SAAiB,QAAiB,SAAmB;AAC/D,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,UAAU;AAAA,EACjB;AACF;AAaO,IAAM,qBAAN,cAAiC,WAAW;AAAA,EACxC;AAAA,EAET,YAAY,SAAiB,QAA+B,SAAmB;AAC7E,UAAM,SAAS,KAAK,OAAO;AAC3B,SAAK,OAAO;AACZ,SAAK,SAAS;AAAA,EAChB;AACF;AAMO,IAAM,iCAAN,cAA6C,WAAW;AAAA,EACpD;AAAA,EACA;AAAA,EAET,YAAY,SAAiB,eAAuB,WAAmB,SAAmB;AACxF,UAAM,SAAS,KAAK,OAAO;AAC3B,SAAK,OAAO;AACZ,SAAK,gBAAgB;AACrB,SAAK,YAAY;AAAA,EACnB;AACF;AAOO,SAAS,kBAAkB,OAAoC;AACpE,MAAI,EAAE,iBAAiB,YAAa,QAAO;AAC3C,QAAM,UAAU,MAAM;AACtB,MAAI,CAAC,WAAW,OAAO,YAAY,SAAU,QAAO,OAAO,YAAY,WAAW,UAAU;AAC5F,QAAM,SAAU,QAAiC;AACjD,MAAI,OAAO,WAAW,SAAU,QAAO;AACvC,MAAI,MAAM,QAAQ,MAAM,GAAG;AACzB,UAAM,QAAQ,OACX,IAAI,CAAC,SAAU,QAAQ,OAAO,SAAS,WAAY,KAA2B,MAAM,MAAU,EAC9F,OAAO,CAAC,QAAuB,OAAO,QAAQ,QAAQ;AACzD,WAAO,MAAM,SAAS,MAAM,KAAK,IAAI,IAAI;AAAA,EAC3C;AACA,MAAI,UAAU,OAAO,WAAW,UAAU;AACxC,UAAM,UAAW,OAAiC;AAClD,WAAO,OAAO,YAAY,WAAW,UAAU;AAAA,EACjD;AACA,SAAO;AACT;;;ACxEO,IAAM,yBAA4C,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AAU/E,SAAS,mBAAmB,OAAkE;AACnG,MAAI,UAAU,OAAO;AACnB,WAAO,EAAE,SAAS,GAAG,OAAO,GAAG,UAAU,GAAG,UAAU,CAAC,GAAG,SAAS,OAAU;AAAA,EAC/E;AACA,SAAO;AAAA,IACL,SAAS,KAAK,IAAI,GAAG,OAAO,WAAW,CAAC;AAAA,IACxC,OAAO,KAAK,IAAI,GAAG,OAAO,SAAS,GAAG;AAAA,IACtC,UAAU,KAAK,IAAI,GAAG,OAAO,YAAY,GAAI;AAAA,IAC7C,UAAU,OAAO,YAAY;AAAA,IAC7B,SAAS,OAAO;AAAA,EAClB;AACF;AAGO,SAAS,kBAAkB,QAAqC;AACrE,QAAM,KAAK,UAAU,OAAO,YAAY;AACxC,SAAO,MAAM,SAAS,MAAM;AAC9B;AAGO,SAAS,gBAAgB,OAAsD;AACpF,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,UAAU,OAAO,KAAK;AAC5B,MAAI,OAAO,SAAS,OAAO,EAAG,QAAO,KAAK,IAAI,GAAG,UAAU,GAAI;AAC/D,QAAM,OAAO,KAAK,MAAM,KAAK;AAC7B,MAAI,OAAO,SAAS,IAAI,EAAG,QAAO,KAAK,IAAI,GAAG,OAAO,KAAK,IAAI,CAAC;AAC/D,SAAO;AACT;AAMO,SAAS,aAAa,OAA4B,SAAiB,YAAoC;AAC5G,QAAM,aAAa,gBAAgB,UAAU;AAC7C,MAAI,eAAe,OAAW,QAAO,KAAK,IAAI,YAAY,MAAM,QAAQ;AACxE,QAAM,OAAO,KAAK,IAAI,MAAM,UAAU,MAAM,QAAQ,KAAK,OAAO;AAChE,SAAO,KAAK,MAAM,QAAQ,OAAO,KAAK,OAAO,IAAI,IAAI;AACvD;AAEA,eAAsB,gBACpB,OACA,SACA,OACA,KACA,YACe;AACf,QAAM,QAAQ,aAAa,OAAO,SAAS,UAAU;AACrD,QAAM,UAAU,EAAE,SAAS,UAAU,GAAG,OAAO,KAAK,MAAM,CAAC;AAC3D,MAAI,QAAQ,EAAG,OAAM,IAAI,QAAc,CAAC,YAAY,WAAW,SAAS,KAAK,CAAC;AAChF;;;ACvBO,SAAS,cAAc,OAA2B;AACvD,QAAM,SAAS,IAAI,gBAAgB;AAEnC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,GAAG;AAChD,QAAI,UAAU,UAAa,UAAU,KAAM;AAE3C,QAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,iBAAW,QAAQ,OAAO;AACxB,YAAI,SAAS,UAAa,SAAS,MAAM;AACvC,iBAAO,OAAO,KAAK,OAAO,IAAI,CAAC;AAAA,QACjC;AAAA,MACF;AAAA,IACF,OAAO;AACL,aAAO,IAAI,KAAK,OAAO,KAAK,CAAC;AAAA,IAC/B;AAAA,EACF;AAEA,QAAM,KAAK,OAAO,SAAS;AAC3B,SAAO,KAAK,IAAI,EAAE,KAAK;AACzB;AAEA,SAAS,aACP,MACA,UACmC;AACnC,MAAI,CAAC,QAAQ,CAAC,SAAU,QAAO;AAC/B,SAAO,EAAE,GAAG,MAAM,GAAG,SAAS;AAChC;AAEO,SAAS,iBAAiB,SAAyD;AACxF,MAAI,CAAC,QAAS,QAAO,CAAC;AAEtB,SAAO;AAAA,IACL,GAAI,QAAQ,QAAQ,EAAE,kBAAkB,QAAQ,MAAM,IAAI,CAAC;AAAA,IAC3D,GAAI,QAAQ,SAAS,EAAE,kBAAkB,QAAQ,OAAO,IAAI,CAAC;AAAA,IAC7D,GAAI,QAAQ,YAAY,SAAY,EAAE,mBAAmB,OAAO,QAAQ,OAAO,EAAE,IAAI,CAAC;AAAA,EACxF;AACF;AAEA,eAAe,aAAa,UAAsC;AAChE,QAAM,OAAO,MAAM,SAAS,KAAK,EAAE,MAAM,MAAM,MAAS;AACxD,MAAI,SAAS,UAAa,SAAS,GAAI,QAAO;AAC9C,MAAI;AACF,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,eAAe,SAAmE;AACzF,MAAI,CAAC,WAAW,OAAO,YAAY,SAAU,QAAO;AACpD,QAAM,SAAU,QAAiC;AACjD,MAAI,CAAC,UAAU,OAAO,WAAW,SAAU,QAAO;AAClD,QAAM,SAAU,OAAgC;AAChD,MAAI,CAAC,UAAU,OAAO,WAAW,SAAU,QAAO;AAClD,QAAM,EAAE,SAAS,aAAAA,aAAY,IAAI;AACjC,MAAI,OAAO,YAAY,YAAY,OAAOA,iBAAgB,SAAU,QAAO;AAC3E,SAAO,EAAE,SAAS,aAAAA,aAAY;AAChC;AAEA,SAAS,gBAAgB,SAAuE;AAC9F,MAAI,CAAC,WAAW,OAAO,YAAY,SAAU,QAAO;AACpD,QAAM,SAAU,QAAiC;AACjD,MAAI,CAAC,UAAU,OAAO,WAAW,SAAU,QAAO;AAClD,QAAM,EAAE,eAAe,UAAU,IAAI;AACrC,MAAI,OAAO,kBAAkB,YAAY,OAAO,cAAc,SAAU,QAAO;AAC/E,SAAO,EAAE,eAAe,UAAU;AACpC;AAEA,SAAS,gBAAgB,SAAsC;AAC7D,MAAI,CAAC,WAAW,OAAO,YAAY,SAAU,QAAO;AACpD,QAAM,SAAU,QAAiC;AACjD,MAAI,CAAC,UAAU,OAAO,WAAW,SAAU,QAAO;AAClD,QAAM,UAAW,OAAiC;AAClD,SAAO,OAAO,YAAY,WAAW,UAAU;AACjD;AASO,SAAS,kBAAkB,QAA2B;AAC3D,QAAM,UAAU,OAAO,SAAS,WAAW;AAC3C,MAAI,OAAO,YAAY,YAAY;AACjC,UAAM,IAAI,WAAW,4DAA4D;AAAA,EACnF;AACA,QAAM,UAAU,OAAO,QAAQ,QAAQ,QAAQ,EAAE;AAEjD,QAAM,cAA4B;AAAA,IAChC,OAAO,OAAO;AAAA,IACd,QAAQ,OAAO;AAAA,IACf,SAAS,OAAO;AAAA,EAClB;AAEA,QAAM,QAAQ,mBAAmB,OAAO,KAAK;AAE7C,iBAAe,QAAW,MAAc,UAA0B,CAAC,GAAe;AAChF,UAAM,EAAE,OAAO,SAAS,iBAAiB,SAAS,cAAc,GAAG,KAAK,IAAI;AAC5E,UAAM,UAAU,aAAa,aAAa,eAAe;AACzD,UAAM,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,cAAc,SAAS,CAAC,CAAC,CAAC;AAC1D,UAAM,UAAU;AAAA,MACd,QAAQ;AAAA,MACR,gBAAgB;AAAA,MAChB,GAAI,OAAO,SAAS,EAAE,eAAe,UAAU,OAAO,MAAM,GAAG,IAAI,CAAC;AAAA,MACpE,GAAG,iBAAiB,OAAO;AAAA,MAC3B,GAAI,gBAAgB,CAAC;AAAA,IACvB;AACA,UAAM,YAAY,MAAM,UAAU,KAAK,kBAAkB,KAAK,MAAM;AAEpE,aAAS,UAAU,KAAK,WAAW;AACjC,YAAM,WAAW,aAAa,UAAU,MAAM,WAAW,CAAC,KAAK,QAAQ;AAEvE,UAAI;AACJ,UAAI;AACF,mBAAW,MAAM,QAAQ,KAAK,EAAE,GAAG,MAAM,QAAQ,CAAC;AAAA,MACpD,SAASC,QAAO;AACd,YAAI,CAAC,SAAU,OAAMA;AACrB,cAAM,gBAAgB,OAAO,SAASA,QAAO,GAAG;AAChD;AAAA,MACF;AAEA,UAAI,SAAS,IAAI;AACf,YAAI,SAAS,WAAW,IAAK,QAAO;AACpC,eAAQ,MAAM,SAAS,KAAK;AAAA,MAC9B;AAEA,YAAM,UAAU,MAAM,aAAa,QAAQ;AAC3C,YAAM,QACJ,SAAS,WAAW,MAChB,IAAI;AAAA,QACF,gBAAgB,OAAO,KAAK;AAAA,QAC5B,eAAe,OAAO;AAAA,QACtB;AAAA,MACF,IACA,IAAI,WAAW,oCAAoC,SAAS,MAAM,IAAI,SAAS,QAAQ,OAAO;AAEpG,UAAI,CAAC,YAAY,CAAC,MAAM,SAAS,SAAS,SAAS,MAAM,EAAG,OAAM;AAClE,YAAM,gBAAgB,OAAO,SAAS,OAAO,KAAK,SAAS,QAAQ,IAAI,aAAa,CAAC;AAAA,IACvF;AAAA,EACF;AAEA,QAAM,WAAW;AAAA;AAAA,IAEf,KAAK,QAAqE;AACxE,aAAO,QAAmC,wBAAwB;AAAA,QAChE,QAAQ;AAAA,QACR,OAAO,EAAE,WAAW,OAAO,WAAW,eAAe,YAAY,MAAM;AAAA,MACzE,CAAC;AAAA,IACH;AAAA;AAAA,IAGA,KAAK,MAAc,QAAqE;AACtF,aAAO,QAAmC,iBAAiB,mBAAmB,IAAI,CAAC,WAAW;AAAA,QAC5F,QAAQ;AAAA,QACR,OAAO;AAAA,UACL,WAAW,OAAO;AAAA,UAClB,eAAe,YAAY;AAAA,UAC3B,SAAS,OAAO;AAAA,UAChB,OAAO,OAAO;AAAA,QAChB;AAAA,MACF,CAAC;AAAA,IACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOA,KAAK,MAAc,MAAiE;AAClF,UAAI,CAAC,OAAO,eAAe;AACzB,eAAO,QAAQ;AAAA,UACb,IAAI,WAAW,mFAAmF;AAAA,QACpG;AAAA,MACF;AACA,YAAM,EAAE,eAAe,GAAG,KAAK,IAAI;AACnC,aAAO,QAAmC,iBAAiB,mBAAmB,IAAI,CAAC,WAAW;AAAA,QAC5F,QAAQ;AAAA,QACR,SAAS,EAAE,eAAe,UAAU,OAAO,aAAa,GAAG;AAAA,QAC3D,MAAM,KAAK,UAAU,EAAE,eAAe,iBAAiB,YAAY,OAAO,GAAG,KAAK,CAAC;AAAA,MACrF,CAAC;AAAA,IACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOA,UAAU,MAAiD;AACzD,aAAO,QAAkC,iBAAiB,mBAAmB,IAAI,CAAC,WAAW;AAAA,QAC3F,QAAQ;AAAA,QACR,OAAO,EAAE,eAAe,YAAY,MAAM;AAAA,MAC5C,CAAC;AAAA,IACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IASA,MAAM,UAAU,MAAc,MAA+D;AAC3F,UAAI,CAAC,OAAO,eAAe;AACzB,cAAM,IAAI,WAAW,wFAAwF;AAAA,MAC/G;AACA,YAAM,EAAE,eAAe,QAAQ,MAAM,IAAI;AACzC,UAAI;AACF,eAAO,MAAM,QAAkC,iBAAiB,mBAAmB,IAAI,CAAC,WAAW;AAAA,UACjG,QAAQ;AAAA,UACR,SAAS,EAAE,eAAe,UAAU,OAAO,aAAa,GAAG;AAAA,UAC3D,MAAM,KAAK,UAAU,EAAE,eAAe,iBAAiB,YAAY,OAAO,QAAQ,OAAO,SAAS,MAAM,CAAC;AAAA,QAC3G,CAAC;AAAA,MACH,SAAS,OAAO;AACd,YAAI,iBAAiB,oBAAoB;AACvC,gBAAM,UAAU,gBAAgB,MAAM,OAAO;AAC7C,cAAI,SAAS;AACX,kBAAM,IAAI,+BAA+B,MAAM,SAAS,QAAQ,eAAe,QAAQ,WAAW,MAAM,OAAO;AAAA,UACjH;AAAA,QACF;AACA,cAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA;AAAA,IAGA,QAAQ,EAAE,GAAG,QAAQ,QAAQ;AAAA,IAE7B,YAAY,iBAAwC;AAClD,aAAO,kBAAkB,EAAE,GAAG,QAAQ,GAAG,gBAAgB,CAAC;AAAA,IAC5D;AAAA,IAEA,MAAM,eAAwB,QAAmE;AAC/F,YAAM,SAAS,MAAM,QAA8C,oBAAoB;AAAA,QACrF,QAAQ;AAAA,QACR,OAAO,EAAE,aAAa,OAAO,aAAa,MAAM,OAAO,MAAM,QAAQ,OAAO,OAAO;AAAA,QACnF,SAAS,EAAE,QAAQ,OAAO,OAAO;AAAA,MACnC,CAAC;AACD,aAAO,OAAO;AAAA,IAChB;AAAA,IAEA,MAAM,WACJ,QAC0D;AAC1D,YAAM,QAAyC;AAAA,QAC7C,aAAa,OAAO;AAAA,QACpB,QAAQ,OAAO;AAAA,QACf,OAAO,OAAO;AAAA,QACd,QAAQ,OAAO;AAAA,QACf,MAAM,OAAO;AAAA,MACf;AACA,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,OAAO,WAAW,CAAC,CAAC,GAAG;AAC/D,cAAM,UAAU,GAAG,EAAE,IAAI;AAAA,MAC3B;AACA,aAAO,QAAyD,YAAY;AAAA,QAC1E,QAAQ;AAAA,QACR;AAAA,QACA,SAAS,EAAE,QAAQ,OAAO,OAAO;AAAA,MACnC,CAAC;AAAA,IACH;AAAA,IAEA,MAAM,aAAsB,QAAiE;AAC3F,YAAM,SAAS,MAAM,QAA8C,eAAe;AAAA,QAChF,QAAQ;AAAA,QACR,OAAO,EAAE,KAAK,OAAO,KAAK,QAAQ,OAAO,OAAO;AAAA,QAChD,SAAS,EAAE,QAAQ,OAAO,OAAO;AAAA,MACnC,CAAC;AACD,aAAO,OAAO;AAAA,IAChB;AAAA,IAEA,cAAc,MAAc,QAAiB;AAC3C,aAAO,QAAsB,cAAc,IAAI,IAAI;AAAA,QACjD,OAAO;AAAA,UACL,eAAe,YAAY;AAAA,UAC3B,QAAQ,UAAU,YAAY;AAAA,UAC9B,MAAM,YAAY,UAAU,UAAU;AAAA,QACxC;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IAEA,WAAW,MAAc,QAAiB;AACxC,aAAO,QAAyB,gBAAgB;AAAA,QAC9C,OAAO;AAAA,UACL;AAAA,UACA,eAAe,YAAY;AAAA,UAC3B,QAAQ,UAAU,YAAY;AAAA,UAC9B,MAAM,YAAY,UAAU,UAAU;AAAA,UACtC,GAAI,YAAY,WAAW,OAAO,eAAe,EAAE,OAAO,OAAO,aAAa,IAAI,CAAC;AAAA,QACrF;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IAEA,YAAY,SAA4B,CAAC,GAAG;AAC1C,aAAO,QAA0B,mBAAmB;AAAA,QAClD,OAAO;AAAA,UACL,eAAe,OAAO,iBAAiB,YAAY;AAAA,UACnD,QAAQ,OAAO,UAAU,YAAY;AAAA,UACrC,UAAU,OAAO;AAAA,UACjB,MAAM,OAAO;AAAA,UACb,OAAO,OAAO;AAAA,UACd,QAAQ,OAAO;AAAA,UACf,MAAM,OAAO,QAAQ;AAAA,UACrB,SAAS,OAAO;AAAA,QAClB;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IAEA,gBAAgB,SAAgC,CAAC,GAAG;AAClD,aAAO,QAA8B,yBAAyB;AAAA,QAC5D,OAAO;AAAA,UACL,eAAe,OAAO,iBAAiB,YAAY;AAAA,UACnD,QAAQ,OAAO,UAAU,YAAY;AAAA,UACrC,OAAO,OAAO;AAAA,UACd,QAAQ,OAAO;AAAA,QACjB;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IAEA,QAAQ,KAAa,QAAiB;AACpC,aAAO,QAAsB,cAAc;AAAA,QACzC,OAAO,EAAE,KAAK,eAAe,YAAY,OAAO,QAAQ,UAAU,YAAY,OAAO;AAAA,MACvF,CAAC;AAAA,IACH;AAAA,IAEA,QAAQ,KAAa,QAAiB;AACpC,aAAO,QAAsB,cAAc;AAAA,QACzC,OAAO,EAAE,KAAK,eAAe,YAAY,OAAO,QAAQ,UAAU,YAAY,OAAO;AAAA,MACvF,CAAC;AAAA,IACH;AAAA,IAEA,QAAQ,OAAe,QAAgB,QAAiB;AACtD,aAAO,QAAsB,cAAc,MAAM,aAAa;AAAA,QAC5D,OAAO,EAAE,eAAe,OAAO,QAAQ,UAAU,YAAY,OAAO;AAAA,MACtE,CAAC;AAAA,IACH;AAAA,IAEA,gBAA6B,gBAAwB,WAAoB;AACvE,aAAO,QAAW,sBAAsB,cAAc,IAAI;AAAA,QACxD,OAAO,EAAE,WAAW,eAAe,YAAY,OAAO,QAAQ,YAAY,OAAO;AAAA,MACnF,CAAC;AAAA,IACH;AAAA;AAAA,IAGA,QAAqB,UAAkB,aAA0B,QAAiB;AAChF,aAAO,QAAW,QAAQ,QAAQ,IAAI;AAAA,QACpC,QAAQ;AAAA,QACR,OAAO,EAAE,eAAe,YAAY,OAAO,QAAQ,UAAU,YAAY,QAAQ,GAAG,YAAY;AAAA,MAClG,CAAC;AAAA,IACH;AAAA;AAAA,IAGA,WACE,UACA,MACA,QACA,aACA;AACA,aAAO,QAAW,QAAQ,QAAQ,IAAI;AAAA,QACpC,QAAQ;AAAA,QACR,MAAM,KAAK,UAAU,IAAI;AAAA,QACzB,OAAO,EAAE,eAAe,YAAY,OAAO,QAAQ,UAAU,YAAY,QAAQ,GAAG,YAAY;AAAA,MAClG,CAAC;AAAA,IACH;AAAA,IAEA,aAA0B;AACxB,aAAO,QAAW,aAAa,YAAY,KAAK,EAAE;AAAA,IACpD;AAAA,IAEA,aAA0B;AACxB,aAAO,QAAW,gBAAgB,YAAY,KAAK,EAAE;AAAA,IACvD;AAAA;AAAA,IAGA,eAAe;AACb,aAAO,QAA2B,kBAAkB,YAAY,KAAK,EAAE;AAAA,IACzE;AAAA,IAEA,aAA0B,SAAiB;AACzC,aAAO,QAAW,eAAe,OAAO,IAAI;AAAA,QAC1C,OAAO,EAAE,eAAe,YAAY,MAAM;AAAA,MAC5C,CAAC;AAAA,IACH;AAAA,IAEA;AAAA,EACF;AACF;AAEO,SAAS,mBAAmB,UAAqC;AACtE,SAAO;AAAA,IACL,OAAO,QAA0C;AAC/C,aAAO,kBAAkB,EAAE,GAAG,UAAU,GAAG,OAAO,CAAC;AAAA,IACrD;AAAA,EACF;AACF;;;AC5XO,IAAM,sBAAsB,CAAC,WAAW,QAAQ,eAAe,YAAY,OAAO;AAgElF,SAAS,oBAAoB,QAAqD;AACvF,SAAO,CAAC,CAAC,UAAU,OAAO,WAAW,YAAY,MAAM,QAAS,OAA+B,KAAK;AACtG;AAGO,SAAS,cAAc,QAA+C;AAC3E,SAAO,CAAC,CAAC,UAAU,OAAO,WAAW,YAAY,CAAC,MAAM,QAAQ,MAAM,KAAK,CAAC,oBAAoB,MAAM;AACxG;AAMO,SAAS,aAAa,QAA6C;AACxE,MAAI,oBAAoB,MAAM,EAAG,QAAO;AACxC,MAAI,cAAc,MAAM,EAAG,QAAO;AAClC,SAAO;AACT;AAyHO,IAAM,wBAAwB;AAAA,EACnC,UAAU;AAAA,EACV,cAAc,MAAM;AAAA,EACpB,eAAe,IAAI,OAAO;AAAA,EAC1B,eAAe;AAAA,EACf,kBAAkB;AACpB;AAEO,IAAM,kBAAkB;AACxB,IAAM,uBAAuB;AAG7B,SAAS,mBAAmB,MAAuB;AACxD,MAAI,CAAC,QAAQ,KAAK,SAAS,sBAAsB,cAAe,QAAO;AACvE,MAAI,CAAC,gBAAgB,KAAK,IAAI,EAAG,QAAO;AACxC,SAAO,CAAC,KAAK,MAAM,GAAG,EAAE,KAAK,CAAC,YAAY,YAAY,OAAO,YAAY,IAAI;AAC/E;AAEO,SAAS,iBAAiB,WAA4B;AAC3D,SAAO,qBAAqB,KAAK,SAAS;AAC5C;AAMA,IAAM,UAAU,IAAI,YAAY;AAEhC,SAAS,SAAuB;AAC9B,QAAM,IAAI,WAAW;AACrB,MAAI,CAAC,KAAK,CAAC,EAAE,QAAQ;AACnB,UAAM,IAAI,MAAM,wEAAwE;AAAA,EAC1F;AACA,SAAO,EAAE;AACX;AAEA,SAAS,MAAM,QAA6B;AAC1C,QAAM,QAAQ,IAAI,WAAW,MAAM;AACnC,MAAI,MAAM;AACV,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,WAAO,MAAM,CAAC,EAAG,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AAAA,EAC/C;AACA,SAAO;AACT;AAEA,eAAe,UAAU,OAAgC;AACvD,SAAO,MAAM,MAAM,OAAO,EAAE,OAAO,WAAW,QAAQ,OAAO,KAAK,CAAC,CAAC;AACtE;AAGO,SAAS,WAAW,SAAkC;AAC3D,SAAO,UAAU,OAAO;AAC1B;AAOA,eAAsB,YACpB,OACiB;AACjB,QAAM,SAAS,MAAM,UAAU,KAAK;AACpC,QAAM,QAAQ,OACX,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,CAAU,EACtC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAO,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,CAAE,EAC/C,IAAI,CAAC,CAAC,MAAM,GAAG,MAAM,GAAG,IAAI;AAAA,EAAK,GAAG;AAAA,CAAI,EACxC,KAAK,EAAE;AACV,SAAO,UAAU,KAAK;AACxB;AAGA,eAAsB,UACpB,OAC8B;AAC9B,SAAO,QAAQ;AAAA,IACb,MAAM,IAAI,OAAO,OAAO;AAAA,MACtB,MAAM,EAAE;AAAA,MACR,SAAS,EAAE;AAAA,MACX,QAAQ,YAAY,KAAK,OAAO,EAAE,WAAW,WAAW,EAAE,SAAS,MAAM,WAAW,EAAE,OAAO;AAAA,IAC/F,EAAE;AAAA,EACJ;AACF;AAGO,SAAS,mBAAsD,UAA8B;AAClG,QAAM,OAAgC,EAAE,GAAG,SAAS;AACpD,aAAW,OAAO,oBAAqB,QAAO,KAAK,GAAG;AACtD,SAAO;AACT;;;ACxVO,SAAS,YAAY,OAAoC;AAC9D,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,QAAQ,SAAS,cAAc;AACvF;AAEO,SAAS,WAAW,OAAqC;AAC9D,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,OAAO,UAAU,WAAW,QAAQ,MAAM;AACnD;AAGO,SAAS,iBAAiB,OAAqC;AACpE,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,SAAO,MAAM;AACf;AAgBO,SAAS,YACd,OACA,OACA,SACoB;AACpB,QAAM,UAAU,WAAW,KAAK;AAChC,MAAI,CAAC,QAAS,QAAO;AAErB,QAAM,OAAO,QAAQ,SAAS,QAAQ,QAAQ,EAAE;AAChD,QAAM,SAAS,QAAQ,UAAU;AACjC,QAAM,WAAW,iBAAiB,KAAK;AAEvC,MAAI,OAAO,SAAS;AAClB,WAAO,GAAG,IAAI,WAAW,MAAM,OAAO,IAAI,OAAO,IAAI,MAAM,IAAI,YAAY,WAAW;AAAA,EACxF;AACA,SAAO,GAAG,IAAI,WAAW,OAAO;AAClC;AAUO,SAAS,eAAe,SAA0B;AACvD,SAAO,CACL,OACA,OACA,WACuB,YAAY,OAAO,OAAO,EAAE,GAAG,SAAS,QAAQ,UAAU,QAAQ,OAAO,CAAC;AACrG;","names":["contentHash","error"]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/retry.ts","../src/client.ts","../src/sections.ts","../src/assets.ts"],"sourcesContent":["/**\n * Thrown for every non-2xx response. `status` is the HTTP status and\n * `details` the parsed JSON body (or the raw text when the body is not JSON).\n */\nexport class BotlrError extends Error {\n readonly status: number | undefined;\n readonly details: unknown;\n\n constructor(message: string, status?: number, details?: unknown) {\n super(message);\n this.name = \"BotlrError\";\n this.status = status;\n this.details = details;\n }\n}\n\n/** The latest version of a section source, as reported on a push conflict. */\nexport type ConflictLatest = {\n version: number;\n contentHash: string;\n};\n\n/**\n * Thrown by `sections.push` on 409: `baseVersion` was not the latest version,\n * or a concurrent push won. `latest` is null when the server has no version\n * at all (the client sent a `baseVersion` above 0 for a section with no source).\n */\nexport class BotlrConflictError extends BotlrError {\n readonly latest: ConflictLatest | null;\n\n constructor(message: string, latest: ConflictLatest | null, details?: unknown) {\n super(message, 409, details);\n this.name = \"BotlrConflictError\";\n this.latest = latest;\n }\n}\n\n/**\n * Thrown by `sections.putSchema` on 409: the definition's current schema and\n * the new one are in different formats and `force` was not set.\n */\nexport class BotlrSchemaFormatConflictError extends BotlrError {\n readonly currentFormat: string;\n readonly newFormat: string;\n\n constructor(message: string, currentFormat: string, newFormat: string, details?: unknown) {\n super(message, 409, details);\n this.name = \"BotlrSchemaFormatConflictError\";\n this.currentFormat = currentFormat;\n this.newFormat = newFormat;\n }\n}\n\n/**\n * The human-readable message of an error response, when the server sent one:\n * `detail` as a string, `detail.message`, or the `msg` lines of a FastAPI\n * validation error. Undefined for anything else.\n */\nexport function botlrErrorMessage(error: unknown): string | undefined {\n if (!(error instanceof BotlrError)) return undefined;\n const details = error.details;\n if (!details || typeof details !== \"object\") return typeof details === \"string\" ? details : undefined;\n const detail = (details as { detail?: unknown }).detail;\n if (typeof detail === \"string\") return detail;\n if (Array.isArray(detail)) {\n const lines = detail\n .map((item) => (item && typeof item === \"object\" ? (item as { msg?: unknown }).msg : undefined))\n .filter((msg): msg is string => typeof msg === \"string\");\n return lines.length ? lines.join(\"; \") : undefined;\n }\n if (detail && typeof detail === \"object\") {\n const message = (detail as { message?: unknown }).message;\n return typeof message === \"string\" ? message : undefined;\n }\n return undefined;\n}\n","import type { BotlrRetryConfig, BotlrRetryInfo } from \"./types\";\n\n/** Statuses retried by default: timeouts, rate limits and server errors. */\nexport const DEFAULT_RETRY_STATUSES: readonly number[] = [408, 429, 500, 502, 503, 504];\n\nexport type ResolvedRetryConfig = {\n retries: number;\n delay: number;\n maxDelay: number;\n statuses: readonly number[];\n onRetry: ((info: BotlrRetryInfo) => void) | undefined;\n};\n\nexport function resolveRetryConfig(retry: BotlrRetryConfig | false | undefined): ResolvedRetryConfig {\n if (retry === false) {\n return { retries: 0, delay: 0, maxDelay: 0, statuses: [], onRetry: undefined };\n }\n return {\n retries: Math.max(0, retry?.retries ?? 2),\n delay: Math.max(0, retry?.delay ?? 300),\n maxDelay: Math.max(0, retry?.maxDelay ?? 5000),\n statuses: retry?.statuses ?? DEFAULT_RETRY_STATUSES,\n onRetry: retry?.onRetry,\n };\n}\n\n/** Only reads are retried: a repeated POST or PUT could submit twice. */\nexport function isRetryableMethod(method: string | undefined): boolean {\n const m = (method ?? \"GET\").toUpperCase();\n return m === \"GET\" || m === \"HEAD\";\n}\n\n/** `Retry-After` as milliseconds, from either the seconds or the date form. */\nexport function parseRetryAfter(value: string | null | undefined): number | undefined {\n if (!value) return undefined;\n const seconds = Number(value);\n if (Number.isFinite(seconds)) return Math.max(0, seconds * 1000);\n const date = Date.parse(value);\n if (Number.isFinite(date)) return Math.max(0, date - Date.now());\n return undefined;\n}\n\n/**\n * The wait before retry number `attempt + 1`: `Retry-After` when the server\n * sent one, else `delay * 2^attempt` with ±25% jitter, both capped at `maxDelay`.\n */\nexport function retryDelayMs(retry: ResolvedRetryConfig, attempt: number, retryAfter?: string | null): number {\n const fromHeader = parseRetryAfter(retryAfter);\n if (fromHeader !== undefined) return Math.min(fromHeader, retry.maxDelay);\n const base = Math.min(retry.maxDelay, retry.delay * 2 ** attempt);\n return Math.round(base * (0.75 + Math.random() * 0.5));\n}\n\nexport async function waitBeforeRetry(\n retry: ResolvedRetryConfig,\n attempt: number,\n error: unknown,\n url: string,\n retryAfter?: string | null,\n): Promise<void> {\n const delay = retryDelayMs(retry, attempt, retryAfter);\n retry.onRetry?.({ attempt: attempt + 1, delay, url, error });\n if (delay > 0) await new Promise<void>((resolve) => setTimeout(resolve, delay));\n}\n","import type {\n BotlrClientConfig,\n BotlrContext,\n BotlrEntry,\n BotlrQuery,\n BotlrQueryValue,\n FormResponse,\n GetEntriesParams,\n GetEntryBySlugParams,\n GetPageListParams,\n GetPageSlugListParams,\n GetSingletonParams,\n ListResponse,\n MenuResponse,\n PageListResponse,\n PageResponse,\n PageSlugListResponse,\n RedirectsResponse,\n ResolveResponse,\n} from \"./types\";\nimport type {\n SectionSchemaGetResponse,\n SectionSchemaPutBody,\n SectionSchemaPutResponse,\n SectionSourceListParams,\n SectionSourceListResponse,\n SectionSourcePullParams,\n SectionSourcePullResponse,\n SectionSourcePushBody,\n SectionSourcePushResponse,\n} from \"./sections\";\nimport { BotlrConflictError, BotlrError, BotlrSchemaFormatConflictError } from \"./errors\";\nimport { isRetryableMethod, resolveRetryConfig, waitBeforeRetry } from \"./retry\";\n\nexport type RequestOptions = Omit<RequestInit, \"headers\"> & {\n query?: BotlrQuery;\n context?: Partial<BotlrContext>;\n headers?: Record<string, string>;\n};\n\nexport function toQueryString(query: BotlrQuery): string {\n const params = new URLSearchParams();\n\n for (const [key, value] of Object.entries(query)) {\n if (value === undefined || value === null) continue;\n\n if (Array.isArray(value)) {\n for (const item of value) {\n if (item !== undefined && item !== null) {\n params.append(key, String(item));\n }\n }\n } else {\n params.set(key, String(value));\n }\n }\n\n const qs = params.toString();\n return qs ? `?${qs}` : \"\";\n}\n\nfunction mergeContext(\n base?: Partial<BotlrContext>,\n override?: Partial<BotlrContext>,\n): Partial<BotlrContext> | undefined {\n if (!base && !override) return undefined;\n return { ...base, ...override };\n}\n\nexport function contextToHeaders(context?: Partial<BotlrContext>): Record<string, string> {\n if (!context) return {};\n\n return {\n ...(context.appId ? { \"x-botlr-app-id\": context.appId } : {}),\n ...(context.locale ? { \"x-botlr-locale\": context.locale } : {}),\n ...(context.preview !== undefined ? { \"x-botlr-preview\": String(context.preview) } : {}),\n };\n}\n\nasync function errorDetails(response: Response): Promise<unknown> {\n const text = await response.text().catch(() => undefined);\n if (text === undefined || text === \"\") return undefined;\n try {\n return JSON.parse(text) as unknown;\n } catch {\n return text;\n }\n}\n\nfunction conflictLatest(details: unknown): { version: number; contentHash: string } | null {\n if (!details || typeof details !== \"object\") return null;\n const detail = (details as { detail?: unknown }).detail;\n if (!detail || typeof detail !== \"object\") return null;\n const latest = (detail as { latest?: unknown }).latest;\n if (!latest || typeof latest !== \"object\") return null;\n const { version, contentHash } = latest as { version?: unknown; contentHash?: unknown };\n if (typeof version !== \"number\" || typeof contentHash !== \"string\") return null;\n return { version, contentHash };\n}\n\nfunction conflictFormats(details: unknown): { currentFormat: string; newFormat: string } | null {\n if (!details || typeof details !== \"object\") return null;\n const detail = (details as { detail?: unknown }).detail;\n if (!detail || typeof detail !== \"object\") return null;\n const { currentFormat, newFormat } = detail as { currentFormat?: unknown; newFormat?: unknown };\n if (typeof currentFormat !== \"string\" || typeof newFormat !== \"string\") return null;\n return { currentFormat, newFormat };\n}\n\nfunction conflictMessage(details: unknown): string | undefined {\n if (!details || typeof details !== \"object\") return undefined;\n const detail = (details as { detail?: unknown }).detail;\n if (!detail || typeof detail !== \"object\") return undefined;\n const message = (detail as { message?: unknown }).message;\n return typeof message === \"string\" ? message : undefined;\n}\n\nexport type BotlrClient = ReturnType<typeof createBotlrClient>;\n\n/**\n * A Botlr client. Every call sends the site token as `Authorization: Bearer`\n * plus `x-botlr-app-id`, `x-botlr-locale` and `x-botlr-preview` from the\n * config; `sections.push` and `sections.putSchema` send the personal token instead.\n */\nexport function createBotlrClient(config: BotlrClientConfig) {\n const fetcher = config.fetch ?? globalThis.fetch;\n if (typeof fetcher !== \"function\") {\n throw new BotlrError(\"No fetch implementation: pass `fetch` in the client config\");\n }\n const baseUrl = config.baseUrl.replace(/\\/+$/, \"\");\n\n const baseContext: BotlrContext = {\n appId: config.appId,\n locale: config.locale,\n preview: config.preview,\n };\n\n const retry = resolveRetryConfig(config.retry);\n\n async function request<T>(path: string, options: RequestOptions = {}): Promise<T> {\n const { query, context: contextOverride, headers: extraHeaders, ...init } = options;\n const context = mergeContext(baseContext, contextOverride);\n const url = `${baseUrl}${path}${toQueryString(query ?? {})}`;\n const headers = {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n ...(config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}),\n ...contextToHeaders(context),\n ...(extraHeaders ?? {}),\n };\n const retryable = retry.retries > 0 && isRetryableMethod(init.method);\n\n for (let attempt = 0; ; attempt++) {\n const canRetry = retryable && attempt < retry.retries && !init.signal?.aborted;\n\n let response: Response;\n try {\n response = await fetcher(url, { ...init, headers });\n } catch (error) {\n if (!canRetry) throw error;\n await waitBeforeRetry(retry, attempt, error, url);\n continue;\n }\n\n if (response.ok) {\n if (response.status === 204) return undefined as T;\n return (await response.json()) as T;\n }\n\n const details = await errorDetails(response);\n const error =\n response.status === 409\n ? new BotlrConflictError(\n conflictMessage(details) ?? `Botlr request failed with status 409`,\n conflictLatest(details),\n details,\n )\n : new BotlrError(`Botlr request failed with status ${response.status}`, response.status, details);\n\n if (!canRetry || !retry.statuses.includes(response.status)) throw error;\n await waitBeforeRetry(retry, attempt, error, url, response.headers.get(\"retry-after\"));\n }\n }\n\n const sections = {\n /** Every section the app can pull for `framework`, with the latest version per scope. */\n list(params: SectionSourceListParams): Promise<SectionSourceListResponse> {\n return request<SectionSourceListResponse>(\"/api/sections/source\", {\n method: \"GET\",\n query: { framework: params.framework, appInstanceId: baseContext.appId },\n });\n },\n\n /** The section's source: this app's row first, the shared row second. */\n pull(name: string, params: SectionSourcePullParams): Promise<SectionSourcePullResponse> {\n return request<SectionSourcePullResponse>(`/api/sections/${encodeURIComponent(name)}/source`, {\n method: \"GET\",\n query: {\n framework: params.framework,\n appInstanceId: baseContext.appId,\n version: params.version,\n scope: params.scope,\n },\n });\n },\n\n /**\n * Push a new version with the personal access token (`write:sections`).\n * Resolves with `created: true` on 201 and `created: false` when nothing\n * changed; throws `BotlrConflictError` when `baseVersion` is not the latest.\n */\n push(name: string, body: SectionSourcePushBody): Promise<SectionSourcePushResponse> {\n if (!config.personalToken) {\n return Promise.reject(\n new BotlrError(\"sections.push needs `personalToken` (a personal access token with write:sections)\"),\n );\n }\n const { appInstanceId, ...rest } = body;\n return request<SectionSourcePushResponse>(`/api/sections/${encodeURIComponent(name)}/source`, {\n method: \"PUT\",\n headers: { Authorization: `Bearer ${config.personalToken}` },\n body: JSON.stringify({ appInstanceId: appInstanceId ?? baseContext.appId, ...rest }),\n });\n },\n\n /**\n * The section definition with its props schema and format, with the site\n * token. Works whether or not source has been pushed; 404 when the app has\n * no section type of that name.\n */\n getSchema(name: string): Promise<SectionSchemaGetResponse> {\n return request<SectionSchemaGetResponse>(`/api/sections/${encodeURIComponent(name)}/schema`, {\n method: \"GET\",\n query: { appInstanceId: baseContext.appId },\n });\n },\n\n /**\n * Write the definition's props schema with the personal access token\n * (`write:sections`). Same format, or `force`: the schema is replaced\n * (`mode: \"replaced\"`). Field-DSL fields at a FormBuilder schema without\n * `force`: the missing fields are added as nodes and nothing is removed\n * (`mode: \"merged\"`, with `added` and `dashboardOnly`). A FormBuilder\n * schema at a field-DSL one without `force` throws\n * `BotlrSchemaFormatConflictError`; a `BotlrError` with status 422 means\n * the schema is not an object of fields or the definition is not\n * project-scoped.\n */\n async putSchema(name: string, body: SectionSchemaPutBody): Promise<SectionSchemaPutResponse> {\n if (!config.personalToken) {\n throw new BotlrError(\"sections.putSchema needs `personalToken` (a personal access token with write:sections)\");\n }\n const { appInstanceId, schema, force } = body;\n try {\n return await request<SectionSchemaPutResponse>(`/api/sections/${encodeURIComponent(name)}/schema`, {\n method: \"PUT\",\n headers: { Authorization: `Bearer ${config.personalToken}` },\n body: JSON.stringify({ appInstanceId: appInstanceId ?? baseContext.appId, schema, force: force ?? false }),\n });\n } catch (error) {\n if (error instanceof BotlrConflictError) {\n const formats = conflictFormats(error.details);\n if (formats) {\n throw new BotlrSchemaFormatConflictError(error.message, formats.currentFormat, formats.newFormat, error.details);\n }\n }\n throw error;\n }\n },\n };\n\n return {\n request,\n\n /** The resolved config, for wrappers that need `baseUrl` or `appId`. */\n config: { ...config, baseUrl } as Readonly<BotlrClientConfig>,\n\n withContext(contextOverride: Partial<BotlrContext>) {\n return createBotlrClient({ ...config, ...contextOverride });\n },\n\n async getEntryBySlug<TFields>(params: GetEntryBySlugParams): Promise<BotlrEntry<TFields> | null> {\n const result = await request<{ item: BotlrEntry<TFields> | null }>(\"/entries/by-slug\", {\n method: \"GET\",\n query: { contentType: params.contentType, slug: params.slug, locale: params.locale },\n context: { locale: params.locale },\n });\n return result.item;\n },\n\n async getEntries<TFields>(\n params: GetEntriesParams,\n ): Promise<{ items: BotlrEntry<TFields>[]; total: number }> {\n const query: Record<string, BotlrQueryValue> = {\n contentType: params.contentType,\n locale: params.locale,\n limit: params.limit,\n offset: params.offset,\n sort: params.sort,\n };\n for (const [key, value] of Object.entries(params.filters ?? {})) {\n query[`filter.${key}`] = value;\n }\n return request<{ items: BotlrEntry<TFields>[]; total: number }>(\"/entries\", {\n method: \"GET\",\n query,\n context: { locale: params.locale },\n });\n },\n\n async getSingleton<TFields>(params: GetSingletonParams): Promise<BotlrEntry<TFields> | null> {\n const result = await request<{ item: BotlrEntry<TFields> | null }>(\"/singletons\", {\n method: \"GET\",\n query: { key: params.key, locale: params.locale },\n context: { locale: params.locale },\n });\n return result.item;\n },\n\n getPageBySlug(slug: string, locale?: string) {\n return request<PageResponse>(`/api/pages/${slug}`, {\n query: {\n appInstanceId: baseContext.appId,\n locale: locale || baseContext.locale,\n mode: baseContext.preview ? \"draft\" : \"published\",\n },\n });\n },\n\n resolveUrl(slug: string, locale?: string) {\n return request<ResolveResponse>(\"/api/resolve\", {\n query: {\n slug,\n appInstanceId: baseContext.appId,\n locale: locale || baseContext.locale,\n mode: baseContext.preview ? \"draft\" : \"published\",\n ...(baseContext.preview && config.previewToken ? { token: config.previewToken } : {}),\n },\n });\n },\n\n getPageList(params: GetPageListParams = {}) {\n return request<PageListResponse>(\"/api/page-lists\", {\n query: {\n appInstanceId: params.appInstanceId || baseContext.appId,\n locale: params.locale || baseContext.locale,\n parentId: params.parentId,\n tags: params.tags,\n limit: params.limit,\n offset: params.offset,\n type: params.type || \"page\",\n orderBy: params.orderBy,\n },\n });\n },\n\n getPageSlugList(params: GetPageSlugListParams = {}) {\n return request<PageSlugListResponse>(\"/api/page-lists/slugs\", {\n query: {\n appInstanceId: params.appInstanceId || baseContext.appId,\n locale: params.locale || baseContext.locale,\n limit: params.limit,\n offset: params.offset,\n },\n });\n },\n\n getMenu(key: string, locale?: string) {\n return request<MenuResponse>(\"/api/menus\", {\n query: { key, appInstanceId: baseContext.appId, locale: locale || baseContext.locale },\n });\n },\n\n getForm(key: string, locale?: string) {\n return request<FormResponse>(\"/api/forms\", {\n query: { key, appInstanceId: baseContext.appId, locale: locale || baseContext.locale },\n });\n },\n\n getList(appId: string, listId: string, locale?: string) {\n return request<ListResponse>(`/api/lists/${listId}/complete`, {\n query: { appInstanceId: appId, locale: locale || baseContext.locale },\n });\n },\n\n getConfigurator<T = unknown>(configuratorId: string, versionId?: string) {\n return request<T>(`/api/configurators/${configuratorId}`, {\n query: { versionId, appInstanceId: baseContext.appId, locale: baseContext.locale },\n });\n },\n\n /** GET `/api/{endpoint}` with the app and locale added to the query. */\n getData<T = unknown>(endpoint: string, queryParams?: BotlrQuery, locale?: string) {\n return request<T>(`/api/${endpoint}`, {\n method: \"GET\",\n query: { appInstanceId: baseContext.appId, locale: locale || baseContext.locale, ...queryParams },\n });\n },\n\n /** POST `data` as JSON to `/api/{endpoint}`, for forms and other submissions. */\n submitData<T = unknown>(\n endpoint: string,\n data: Record<string, unknown>,\n locale?: string,\n queryParams?: BotlrQuery,\n ) {\n return request<T>(`/api/${endpoint}`, {\n method: \"POST\",\n body: JSON.stringify(data),\n query: { appInstanceId: baseContext.appId, locale: locale || baseContext.locale, ...queryParams },\n });\n },\n\n getAppInfo<T = unknown>() {\n return request<T>(`/api/apps/${baseContext.appId}`);\n },\n\n getSitemap<T = unknown>() {\n return request<T>(`/api/sitemap/${baseContext.appId}`);\n },\n\n /** Every redirect for the app, across all locales. */\n getRedirects() {\n return request<RedirectsResponse>(`/api/redirects/${baseContext.appId}`);\n },\n\n getAssetById<T = unknown>(assetId: string) {\n return request<T>(`/api/assets/${assetId}`, {\n query: { appInstanceId: baseContext.appId },\n });\n },\n\n sections,\n };\n}\n\nexport function createBotlrFactory(defaults?: { fetch?: typeof fetch }) {\n return {\n create(config: Omit<BotlrClientConfig, \"fetch\">) {\n return createBotlrClient({ ...defaults, ...config });\n },\n };\n}\n","/**\n * Section source bridge: types and hash helpers.\n *\n * Mirrors the contract in xam-mono's docs/SECTION_SOURCE_BRIDGE.md. The hash\n * rules are reproduced here exactly as the server computes them, so a client\n * can tell \"clean\", \"modified\", \"behind\" and \"diverged\" apart without a\n * round trip:\n *\n * - `file.sha256` = sha256 of the UTF-8 content, hex, content exactly as sent\n * - `contentHash` = sha256 over `\"{path}\\n{sha256}\\n\"` for every file, sorted by path\n *\n * The manifest is not hashed.\n */\n\n/** The source variant a section is stored for, for example `react-next` or `html`. */\nexport type SectionFramework = string;\n\nexport type SectionSourceScope = \"instance\" | \"shared\";\n\n/** `<slug>.botlr.json`, kept next to the section's entry file. */\nexport interface SectionManifest {\n /** Section definition slug. Must equal the name used in the URL on push. */\n section: string;\n framework: SectionFramework;\n /** Entry file, relative to the manifest's directory. Must be listed in `files`. */\n entry: string;\n /** Named export of the component in `entry`. */\n exportName?: string;\n /** Every file of the section, relative to the manifest's directory, forward slashes only. */\n files: string[];\n title?: string;\n description?: string;\n category?: string;\n icon?: string;\n dependencies?: SectionDependencies;\n /**\n * The props the code expects, normally in the field DSL. The dashboard owns\n * the definition's schema: a source push never changes it (this is only a\n * seed when `--create` adds the definition, and what the CLI compares\n * against for a drift warning). `botlr schema pull` copies the dashboard's\n * schema here in whichever format it has; `botlr schema push` sends it back.\n */\n schema?: SectionSchema;\n /** Unknown keys are kept by the server. */\n [key: string]: unknown;\n}\n\nexport interface SectionDependencies {\n /** npm packages the section imports, with the ranges the app uses. */\n npm?: Record<string, string>;\n /** Local modules the section imports; the pulling app must already have them. */\n internal?: string[];\n}\n\n/**\n * Pull state keys the server strips from a manifest. The CLI keeps these in\n * `.botlr/sections.lock.json`, never in the manifest.\n */\nexport const MANIFEST_STATE_KEYS = [\"version\", \"hash\", \"contentHash\", \"pulledAt\", \"scope\"] as const;\n\nexport interface SectionSourceFileInput {\n path: string;\n content: string;\n}\n\nexport interface SectionSourceFile extends SectionSourceFileInput {\n sha256: string;\n}\n\nexport interface SectionDefinitionScope {\n tenantId: string;\n organizationId: string | null;\n projectId: string | null;\n}\n\n/**\n * One field of the dashboard's props schema (the field DSL): the value under\n * a field key in `SectionDefinition.schema`. Other keys are kept as given.\n */\nexport interface SectionSchemaField {\n type: string;\n label?: string;\n required?: boolean;\n /** For `select`: plain values or `{ value, label }` objects. */\n options?: Array<string | number | { value: string | number; label?: string }>;\n min?: number;\n max?: number;\n multiple?: boolean;\n [key: string]: unknown;\n}\n\n/** The field DSL: a map of field key to field. This is what the dashboard's properties editor stores. */\nexport type SectionFieldSchema = Record<string, SectionSchemaField>;\n\n/**\n * A node of the older FormBuilder schema format. A `field` node carries its\n * field type in `fieldType`; `section`, `fieldset` and `composite` nodes\n * group `children`; a `repeater` node lists its item `fields`.\n */\nexport interface SectionFormBuilderNode {\n type: string;\n fieldType?: string;\n name?: string;\n key?: string;\n label?: string;\n required?: boolean;\n options?: SectionSchemaField[\"options\"];\n children?: SectionFormBuilderNode[];\n fields?: SectionFormBuilderNode[];\n [key: string]: unknown;\n}\n\n/** The older FormBuilder schema format. */\nexport interface SectionFormBuilderSchema {\n nodes: SectionFormBuilderNode[];\n [key: string]: unknown;\n}\n\n/** A section definition's props schema, in either format the dashboard writes. */\nexport type SectionSchema = SectionFieldSchema | SectionFormBuilderSchema;\n\n/** True for the FormBuilder format (`{ nodes: [...] }`). */\nexport function isFormBuilderSchema(schema: unknown): schema is SectionFormBuilderSchema {\n return !!schema && typeof schema === \"object\" && Array.isArray((schema as { nodes?: unknown }).nodes);\n}\n\n/** True for the field DSL: an object that is not the FormBuilder format. Empty objects count. */\nexport function isFieldSchema(schema: unknown): schema is SectionFieldSchema {\n return !!schema && typeof schema === \"object\" && !Array.isArray(schema) && !isFormBuilderSchema(schema);\n}\n\n/** How a definition's props schema is stored: the field DSL or the older FormBuilder format. */\nexport type SectionSchemaFormat = \"field-dsl\" | \"form-builder\";\n\n/** The format of a schema object, or null for anything that is not a schema. */\nexport function schemaFormat(schema: unknown): SectionSchemaFormat | null {\n if (isFormBuilderSchema(schema)) return \"form-builder\";\n if (isFieldSchema(schema)) return \"field-dsl\";\n return null;\n}\n\nexport interface SectionDefinition {\n id: string;\n name: string;\n title: string;\n /** The props schema as the dashboard stores it; `{}` for a definition without one. */\n schema: SectionSchema | Record<string, unknown>;\n /** The schema's format. The schema routes always send it; the source routes may not. */\n format?: SectionSchemaFormat;\n scope: SectionDefinitionScope;\n}\n\n/** `GET /api/sections/{name}/schema` */\nexport interface SectionSchemaGetResponse {\n definition: SectionDefinition & { format: SectionSchemaFormat };\n}\n\n/** `PUT /api/sections/{name}/schema` */\nexport interface SectionSchemaPutBody {\n /** The new props schema, field DSL or FormBuilder. */\n schema: SectionSchema;\n /**\n * Replace the schema outright whatever the formats. Without it, field-DSL\n * fields pushed at a FormBuilder schema are merged in as nodes, and a\n * FormBuilder schema pushed at a field-DSL one gets 409\n * (`BotlrSchemaFormatConflictError`).\n */\n force?: boolean;\n /** Defaults to the client's `appId`. */\n appInstanceId?: string;\n}\n\n/**\n * What a schema push did: `replaced` the stored schema (same format, or\n * `force`), or `merged` field-DSL fields into a FormBuilder schema as new\n * nodes, keeping every existing node.\n */\nexport type SectionSchemaPushMode = \"replaced\" | \"merged\";\n\nexport interface SectionSchemaPutResponse {\n definition: SectionDefinition & { format: SectionSchemaFormat };\n /** The format the definition had before this call. */\n previousFormat: SectionSchemaFormat;\n /** False when nothing had to change. */\n changed: boolean;\n mode: SectionSchemaPushMode;\n /** Field names that became new nodes (`merged`), else empty. */\n added: string[];\n /** Node names the dashboard has that the code does not declare, kept (`merged`), else empty. */\n dashboardOnly: string[];\n}\n\nexport interface SectionSource {\n scope: SectionSourceScope;\n framework: SectionFramework;\n version: number;\n contentHash: string;\n manifest: SectionManifest;\n message: string | null;\n createdAt: string;\n files: SectionSourceFile[];\n}\n\nexport interface SectionSourceScopeSummary {\n version: number;\n contentHash: string;\n updatedAt: string;\n}\n\nexport interface SectionSourceSummary {\n name: string;\n title: string;\n definitionId: string;\n instance: SectionSourceScopeSummary | null;\n shared: SectionSourceScopeSummary | null;\n /** Which scope a pull without `scope` returns. */\n effective: SectionSourceScope;\n}\n\nexport interface SectionSourceListResponse {\n framework: SectionFramework;\n sections: SectionSourceSummary[];\n}\n\nexport interface SectionSourcePullResponse {\n /** null only for an instance row whose section no longer resolves to a definition */\n definition: SectionDefinition | null;\n source: SectionSource;\n}\n\nexport interface SectionSourceListParams {\n framework: SectionFramework;\n}\n\nexport interface SectionSourcePullParams {\n framework: SectionFramework;\n /** A specific version; latest when omitted. */\n version?: number;\n /** Force one scope instead of \"instance first, shared second\". */\n scope?: SectionSourceScope;\n}\n\nexport interface SectionSourcePushBody {\n framework: SectionFramework;\n manifest: SectionManifest;\n files: SectionSourceFileInput[];\n /** Defaults to the client's `appId`. */\n appInstanceId?: string;\n /** Write the shared row (per section definition) instead of this app's row. */\n shared?: boolean;\n /**\n * The version the push is based on; 0 means \"none yet\". Omit to force the\n * push without a conflict check.\n */\n baseVersion?: number;\n /** At most 500 characters. */\n message?: string;\n /** Add a project-scoped section definition when none exists. */\n create?: boolean;\n}\n\nexport interface SectionSourcePushResponse {\n /** true on 201 (new version), false on 200 (nothing changed) */\n created: boolean;\n scope: SectionSourceScope;\n definition: SectionDefinition;\n version: number;\n contentHash: string;\n}\n\n// ---------------------------------------------------------------------------\n// Limits, as enforced by the server\n// ---------------------------------------------------------------------------\n\nexport const SECTION_SOURCE_LIMITS = {\n maxFiles: 50,\n maxFileBytes: 512 * 1024,\n maxTotalBytes: 2 * 1024 * 1024,\n maxPathLength: 200,\n maxMessageLength: 500,\n} as const;\n\nexport const SECTION_PATH_RE = /^[A-Za-z0-9._-]+(?:\\/[A-Za-z0-9._-]+)*$/;\nexport const SECTION_FRAMEWORK_RE = /^[a-z0-9][a-z0-9-]{0,31}$/;\n\n/** True when `path` is a relative path the bridge accepts. */\nexport function isValidSectionPath(path: string): boolean {\n if (!path || path.length > SECTION_SOURCE_LIMITS.maxPathLength) return false;\n if (!SECTION_PATH_RE.test(path)) return false;\n return !path.split(\"/\").some((segment) => segment === \".\" || segment === \"..\");\n}\n\nexport function isValidFramework(framework: string): boolean {\n return SECTION_FRAMEWORK_RE.test(framework);\n}\n\n// ---------------------------------------------------------------------------\n// Hashing (Web Crypto, so it runs in the browser, Node 20+ and edge runtimes)\n// ---------------------------------------------------------------------------\n\nconst encoder = new TextEncoder();\n\nfunction subtle(): SubtleCrypto {\n const c = globalThis.crypto;\n if (!c || !c.subtle) {\n throw new Error(\"Web Crypto is not available in this runtime (globalThis.crypto.subtle)\");\n }\n return c.subtle;\n}\n\nfunction toHex(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let out = \"\";\n for (let i = 0; i < bytes.length; i++) {\n out += bytes[i]!.toString(16).padStart(2, \"0\");\n }\n return out;\n}\n\nasync function sha256Hex(input: string): Promise<string> {\n return toHex(await subtle().digest(\"SHA-256\", encoder.encode(input)));\n}\n\n/** sha256 of the UTF-8 encoding of `content`, as lowercase hex. */\nexport function fileSha256(content: string): Promise<string> {\n return sha256Hex(content);\n}\n\n/**\n * The content hash of a file set: sha256 over `\"{path}\\n{sha256}\\n\"` for every\n * file, sorted by path. Accepts files with a precomputed `sha256` or plain\n * `{ path, content }` pairs.\n */\nexport async function contentHash(\n files: ReadonlyArray<SectionSourceFileInput | SectionSourceFile>,\n): Promise<string> {\n const hashed = await hashFiles(files);\n const lines = hashed\n .map((f) => [f.path, f.sha256] as const)\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n .map(([path, sha]) => `${path}\\n${sha}\\n`)\n .join(\"\");\n return sha256Hex(lines);\n}\n\n/** Every file with its `sha256`, in the order given. Files that already carry one are kept as is. */\nexport async function hashFiles(\n files: ReadonlyArray<SectionSourceFileInput | SectionSourceFile>,\n): Promise<SectionSourceFile[]> {\n return Promise.all(\n files.map(async (f) => ({\n path: f.path,\n content: f.content,\n sha256: \"sha256\" in f && typeof f.sha256 === \"string\" ? f.sha256 : await fileSha256(f.content),\n })),\n );\n}\n\n/** A copy of `manifest` without the pull-state keys the server strips. */\nexport function stripManifestState<T extends Record<string, unknown>>(manifest: T): SectionManifest {\n const copy: Record<string, unknown> = { ...manifest };\n for (const key of MANIFEST_STATE_KEYS) delete copy[key];\n return copy as SectionManifest;\n}\n","import type { AssetData, PublisherScope } from \"./types\";\n\n/** An asset as a section prop: the legacy bare id, or the `AssetData` object. */\nexport type AssetRef = string | AssetData | null | undefined;\n\nexport function isAssetData(value: unknown): value is AssetData {\n return typeof value === \"object\" && value !== null && \"id\" in value && \"filename\" in value;\n}\n\nexport function getAssetId(asset: AssetRef): string | undefined {\n if (!asset) return undefined;\n return typeof asset === \"string\" ? asset : asset.id;\n}\n\n/** The filename, when the asset is an `AssetData` object. A bare id has none. */\nexport function getAssetFilename(asset: AssetRef): string | undefined {\n if (!asset || typeof asset === \"string\") return undefined;\n return asset.filename;\n}\n\nexport interface AssetUrlOptions {\n /** The media server origin, for example `https://media.example.com`. */\n mediaUrl: string;\n /** The image recipe segment; defaults to `view`. */\n recipe?: string;\n}\n\n/**\n * The media server URL of an asset:\n * `{mediaUrl}/images/{orgSlug}/{assetId}/{recipe}/{filename}`.\n *\n * Falls back to `image.jpg` for a bare id, and to `{mediaUrl}/images/{assetId}`\n * when no org slug is known.\n */\nexport function getAssetUrl(\n asset: AssetRef,\n scope: Partial<Pick<PublisherScope, \"orgSlug\">> | undefined,\n options: AssetUrlOptions,\n): string | undefined {\n const assetId = getAssetId(asset);\n if (!assetId) return undefined;\n\n const base = options.mediaUrl.replace(/\\/+$/, \"\");\n const recipe = options.recipe ?? \"view\";\n const filename = getAssetFilename(asset);\n\n if (scope?.orgSlug) {\n return `${base}/images/${scope.orgSlug}/${assetId}/${recipe}/${filename ?? \"image.jpg\"}`;\n }\n return `${base}/images/${assetId}`;\n}\n\n/**\n * `getAssetUrl` bound to a media server, for an app's own `getAssetUrl`:\n *\n * ```ts\n * export const getAssetUrl = createAssetUrl({ mediaUrl: process.env.NEXT_PUBLIC_MEDIA_URL! });\n * getAssetUrl(asset, scope, \"thumb\");\n * ```\n */\nexport function createAssetUrl(options: AssetUrlOptions) {\n return (\n asset: AssetRef,\n scope: Partial<Pick<PublisherScope, \"orgSlug\">> | undefined,\n recipe?: string,\n ): string | undefined => getAssetUrl(asset, scope, { ...options, recipe: recipe ?? options.recipe });\n}\n"],"mappings":"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","names":["contentHash","error"]}
|