@botlr/sdk 0.4.0 → 0.5.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 +11 -0
- package/README.md +28 -6
- package/dist/index.cjs +81 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +99 -7
- package/dist/index.d.ts +99 -7
- package/dist/index.js +81 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# @botlr/sdk
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- `botlr schema pull` and `botlr schema push`: the explicit way to resolve props schema drift in either direction. A code push still never changes the schema.
|
|
8
|
+
|
|
9
|
+
- `botlr schema pull <name...>` (or `--all` for every manifest in `sectionsDir`) fetches the section definition's props schema from the dashboard and writes it as `schema` into `<name>.botlr.json`, in the CLI's fixed key order and in whichever format the dashboard has (field DSL or FormBuilder). It says "unchanged" when the manifest already holds the same schema, and regenerates `<name>.props.ts` afterwards unless `--no-types` or `"types": false`.
|
|
10
|
+
- `botlr schema push <name> [--force] [--yes]` reads `schema` from the manifest (an error when it is missing), fetches the dashboard's current schema, prints both formats and a diff of the field keys (added, removed, kept; node names for FormBuilder schemas), asks for confirmation unless `--yes`, then replaces the dashboard's schema (needs `BOTLR_PAT`). When the two schemas are in different formats the server answers 409 and the CLI prints both formats and says to rerun with `--force`; a 422 (not an object of fields, or the definition is not project-scoped) prints the server's message. A successful push regenerates the props file.
|
|
11
|
+
- The drift warning of `push` and `sections status` now names both commands: `botlr schema pull <name>` to take the dashboard's schema, `botlr schema push <name>` to send the code's. A manifest `schema` in the FormBuilder format now takes part in the drift comparison too (by node names), since `schema pull` can write one.
|
|
12
|
+
- `@botlr/sdk`: `client.sections.getSchema(name)` (site token, `GET /api/sections/{name}/schema`) and `client.sections.putSchema(name, { schema, force })` (personal token, `PUT /api/sections/{name}/schema`), with the `SectionSchemaFormat`, `SectionSchemaGetResponse`, `SectionSchemaPutBody` and `SectionSchemaPutResponse` types, `schemaFormat(schema)`, `SectionDefinition.format`, `BotlrSchemaFormatConflictError` (the 409 with `currentFormat` and `newFormat`) and `botlrErrorMessage(error)` to read a server's `detail` message. `SectionManifest.schema` is typed as `SectionSchema` (either format).
|
|
13
|
+
|
|
3
14
|
## 0.4.0
|
|
4
15
|
|
|
5
16
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -66,12 +66,34 @@ await writer.sections.push("hero", {
|
|
|
66
66
|
A pull also returns `definition`: the section definition with its props
|
|
67
67
|
`schema` as the dashboard stores it, in either the field DSL (a map of
|
|
68
68
|
field key to `{ type, label, required, options, ... }`) or the older
|
|
69
|
-
FormBuilder format (`{ nodes: [...] }`). `isFieldSchema
|
|
70
|
-
`isFormBuilderSchema`
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
the
|
|
69
|
+
FormBuilder format (`{ nodes: [...] }`). `isFieldSchema`,
|
|
70
|
+
`isFormBuilderSchema` and `schemaFormat` (`"field-dsl" | "form-builder"`,
|
|
71
|
+
the `SectionSchemaFormat` type) tell the two apart; `@botlr/cli` generates
|
|
72
|
+
prop types from it. The dashboard owns that schema: a source push never
|
|
73
|
+
changes it. A manifest may carry its own `schema` as the props the code
|
|
74
|
+
expects: the server uses it as the seed when `push` creates a definition,
|
|
75
|
+
and the CLI compares it with the definition's schema to warn about drift.
|
|
76
|
+
|
|
77
|
+
The schema itself moves through two dedicated calls, which work whether or
|
|
78
|
+
not source has been pushed:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
// site token: the definition with its schema and format
|
|
82
|
+
const { definition } = await botlr.sections.getSchema("hero");
|
|
83
|
+
definition.format; // "field-dsl" | "form-builder"
|
|
84
|
+
|
|
85
|
+
// personal token: replace the dashboard's schema
|
|
86
|
+
const result = await writer.sections.putSchema("hero", { schema, force: false });
|
|
87
|
+
result.changed; // false when the stored schema already equalled `schema`
|
|
88
|
+
result.previousFormat;
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`putSchema` throws `BotlrSchemaFormatConflictError` (status 409, with
|
|
92
|
+
`currentFormat` and `newFormat`) when the stored schema is in the other
|
|
93
|
+
format and `force` is not set, and a `BotlrError` with status 422 when the
|
|
94
|
+
schema is not an object of field definitions or the definition is not
|
|
95
|
+
project-scoped. `botlrErrorMessage(error)` returns the server's `detail`
|
|
96
|
+
message for any `BotlrError`, when there is one.
|
|
75
97
|
|
|
76
98
|
`fileSha256(content)` and `contentHash(files)` reproduce the server's hash
|
|
77
99
|
rules exactly (sha256 hex of the UTF-8 content; content hash = sha256 over
|
package/dist/index.cjs
CHANGED
|
@@ -22,11 +22,13 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
BotlrConflictError: () => BotlrConflictError,
|
|
24
24
|
BotlrError: () => BotlrError,
|
|
25
|
+
BotlrSchemaFormatConflictError: () => BotlrSchemaFormatConflictError,
|
|
25
26
|
DEFAULT_RETRY_STATUSES: () => DEFAULT_RETRY_STATUSES,
|
|
26
27
|
MANIFEST_STATE_KEYS: () => MANIFEST_STATE_KEYS,
|
|
27
28
|
SECTION_FRAMEWORK_RE: () => SECTION_FRAMEWORK_RE,
|
|
28
29
|
SECTION_PATH_RE: () => SECTION_PATH_RE,
|
|
29
30
|
SECTION_SOURCE_LIMITS: () => SECTION_SOURCE_LIMITS,
|
|
31
|
+
botlrErrorMessage: () => botlrErrorMessage,
|
|
30
32
|
contentHash: () => contentHash,
|
|
31
33
|
contextToHeaders: () => contextToHeaders,
|
|
32
34
|
createAssetUrl: () => createAssetUrl,
|
|
@@ -42,6 +44,7 @@ __export(index_exports, {
|
|
|
42
44
|
isFormBuilderSchema: () => isFormBuilderSchema,
|
|
43
45
|
isValidFramework: () => isValidFramework,
|
|
44
46
|
isValidSectionPath: () => isValidSectionPath,
|
|
47
|
+
schemaFormat: () => schemaFormat,
|
|
45
48
|
stripManifestState: () => stripManifestState,
|
|
46
49
|
toQueryString: () => toQueryString
|
|
47
50
|
});
|
|
@@ -66,6 +69,32 @@ var BotlrConflictError = class extends BotlrError {
|
|
|
66
69
|
this.latest = latest;
|
|
67
70
|
}
|
|
68
71
|
};
|
|
72
|
+
var BotlrSchemaFormatConflictError = class extends BotlrError {
|
|
73
|
+
currentFormat;
|
|
74
|
+
newFormat;
|
|
75
|
+
constructor(message, currentFormat, newFormat, details) {
|
|
76
|
+
super(message, 409, details);
|
|
77
|
+
this.name = "BotlrSchemaFormatConflictError";
|
|
78
|
+
this.currentFormat = currentFormat;
|
|
79
|
+
this.newFormat = newFormat;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
function botlrErrorMessage(error) {
|
|
83
|
+
if (!(error instanceof BotlrError)) return void 0;
|
|
84
|
+
const details = error.details;
|
|
85
|
+
if (!details || typeof details !== "object") return typeof details === "string" ? details : void 0;
|
|
86
|
+
const detail = details.detail;
|
|
87
|
+
if (typeof detail === "string") return detail;
|
|
88
|
+
if (Array.isArray(detail)) {
|
|
89
|
+
const lines = detail.map((item) => item && typeof item === "object" ? item.msg : void 0).filter((msg) => typeof msg === "string");
|
|
90
|
+
return lines.length ? lines.join("; ") : void 0;
|
|
91
|
+
}
|
|
92
|
+
if (detail && typeof detail === "object") {
|
|
93
|
+
const message = detail.message;
|
|
94
|
+
return typeof message === "string" ? message : void 0;
|
|
95
|
+
}
|
|
96
|
+
return void 0;
|
|
97
|
+
}
|
|
69
98
|
|
|
70
99
|
// src/retry.ts
|
|
71
100
|
var DEFAULT_RETRY_STATUSES = [408, 429, 500, 502, 503, 504];
|
|
@@ -154,6 +183,14 @@ function conflictLatest(details) {
|
|
|
154
183
|
if (typeof version !== "number" || typeof contentHash2 !== "string") return null;
|
|
155
184
|
return { version, contentHash: contentHash2 };
|
|
156
185
|
}
|
|
186
|
+
function conflictFormats(details) {
|
|
187
|
+
if (!details || typeof details !== "object") return null;
|
|
188
|
+
const detail = details.detail;
|
|
189
|
+
if (!detail || typeof detail !== "object") return null;
|
|
190
|
+
const { currentFormat, newFormat } = detail;
|
|
191
|
+
if (typeof currentFormat !== "string" || typeof newFormat !== "string") return null;
|
|
192
|
+
return { currentFormat, newFormat };
|
|
193
|
+
}
|
|
157
194
|
function conflictMessage(details) {
|
|
158
195
|
if (!details || typeof details !== "object") return void 0;
|
|
159
196
|
const detail = details.detail;
|
|
@@ -246,6 +283,45 @@ function createBotlrClient(config) {
|
|
|
246
283
|
headers: { Authorization: `Bearer ${config.personalToken}` },
|
|
247
284
|
body: JSON.stringify({ appInstanceId: appInstanceId ?? baseContext.appId, ...rest })
|
|
248
285
|
});
|
|
286
|
+
},
|
|
287
|
+
/**
|
|
288
|
+
* The section definition with its props schema and format, with the site
|
|
289
|
+
* token. Works whether or not source has been pushed; 404 when the app has
|
|
290
|
+
* no section type of that name.
|
|
291
|
+
*/
|
|
292
|
+
getSchema(name) {
|
|
293
|
+
return request(`/api/sections/${encodeURIComponent(name)}/schema`, {
|
|
294
|
+
method: "GET",
|
|
295
|
+
query: { appInstanceId: baseContext.appId }
|
|
296
|
+
});
|
|
297
|
+
},
|
|
298
|
+
/**
|
|
299
|
+
* Replace the definition's props schema with the personal access token
|
|
300
|
+
* (`write:sections`). Throws `BotlrSchemaFormatConflictError` when the
|
|
301
|
+
* current schema is in the other format and `force` is not set, and a
|
|
302
|
+
* `BotlrError` with status 422 when the schema is not an object of fields
|
|
303
|
+
* or the definition is not project-scoped.
|
|
304
|
+
*/
|
|
305
|
+
async putSchema(name, body) {
|
|
306
|
+
if (!config.personalToken) {
|
|
307
|
+
throw new BotlrError("sections.putSchema needs `personalToken` (a personal access token with write:sections)");
|
|
308
|
+
}
|
|
309
|
+
const { appInstanceId, schema, force } = body;
|
|
310
|
+
try {
|
|
311
|
+
return await request(`/api/sections/${encodeURIComponent(name)}/schema`, {
|
|
312
|
+
method: "PUT",
|
|
313
|
+
headers: { Authorization: `Bearer ${config.personalToken}` },
|
|
314
|
+
body: JSON.stringify({ appInstanceId: appInstanceId ?? baseContext.appId, schema, force: force ?? false })
|
|
315
|
+
});
|
|
316
|
+
} catch (error) {
|
|
317
|
+
if (error instanceof BotlrConflictError) {
|
|
318
|
+
const formats = conflictFormats(error.details);
|
|
319
|
+
if (formats) {
|
|
320
|
+
throw new BotlrSchemaFormatConflictError(error.message, formats.currentFormat, formats.newFormat, error.details);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
throw error;
|
|
324
|
+
}
|
|
249
325
|
}
|
|
250
326
|
};
|
|
251
327
|
return {
|
|
@@ -401,6 +477,11 @@ function isFormBuilderSchema(schema) {
|
|
|
401
477
|
function isFieldSchema(schema) {
|
|
402
478
|
return !!schema && typeof schema === "object" && !Array.isArray(schema) && !isFormBuilderSchema(schema);
|
|
403
479
|
}
|
|
480
|
+
function schemaFormat(schema) {
|
|
481
|
+
if (isFormBuilderSchema(schema)) return "form-builder";
|
|
482
|
+
if (isFieldSchema(schema)) return "field-dsl";
|
|
483
|
+
return null;
|
|
484
|
+
}
|
|
404
485
|
var SECTION_SOURCE_LIMITS = {
|
|
405
486
|
maxFiles: 50,
|
|
406
487
|
maxFileBytes: 512 * 1024,
|
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","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 SectionSourceListParams,\n SectionSourceListResponse,\n SectionSourcePullParams,\n SectionSourcePullResponse,\n SectionSourcePushBody,\n SectionSourcePushResponse,\n} from \"./sections\";\nimport { BotlrConflictError, BotlrError } 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 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` sends 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 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, in the field DSL. The dashboard owns the\n * definition's schema; this is only a seed when `--create` adds the\n * definition, and what the CLI compares against for a drift warning.\n */\n schema?: SectionFieldSchema;\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\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 scope: SectionDefinitionScope;\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 * 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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACIO,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"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -13,7 +13,7 @@ type BotlrClientConfig = {
|
|
|
13
13
|
/** Preview JWT, passed as `token` to `/api/resolve` when `preview` is on. */
|
|
14
14
|
previewToken?: string;
|
|
15
15
|
/**
|
|
16
|
-
* Personal access token with `write:sections`. Used only by `sections.push`;
|
|
16
|
+
* Personal access token with `write:sections`. Used only by `sections.push` and `sections.putSchema`;
|
|
17
17
|
* the server never accepts it for reads.
|
|
18
18
|
*/
|
|
19
19
|
personalToken?: string;
|
|
@@ -365,11 +365,13 @@ interface SectionManifest {
|
|
|
365
365
|
icon?: string;
|
|
366
366
|
dependencies?: SectionDependencies;
|
|
367
367
|
/**
|
|
368
|
-
* The props the code expects, in the field DSL. The dashboard owns
|
|
369
|
-
* definition's schema
|
|
370
|
-
* definition, and what the CLI compares
|
|
368
|
+
* The props the code expects, normally in the field DSL. The dashboard owns
|
|
369
|
+
* the definition's schema: a source push never changes it (this is only a
|
|
370
|
+
* seed when `--create` adds the definition, and what the CLI compares
|
|
371
|
+
* against for a drift warning). `botlr schema pull` copies the dashboard's
|
|
372
|
+
* schema here in whichever format it has; `botlr schema push` sends it back.
|
|
371
373
|
*/
|
|
372
|
-
schema?:
|
|
374
|
+
schema?: SectionSchema;
|
|
373
375
|
/** Unknown keys are kept by the server. */
|
|
374
376
|
[key: string]: unknown;
|
|
375
377
|
}
|
|
@@ -444,14 +446,47 @@ type SectionSchema = SectionFieldSchema | SectionFormBuilderSchema;
|
|
|
444
446
|
declare function isFormBuilderSchema(schema: unknown): schema is SectionFormBuilderSchema;
|
|
445
447
|
/** True for the field DSL: an object that is not the FormBuilder format. Empty objects count. */
|
|
446
448
|
declare function isFieldSchema(schema: unknown): schema is SectionFieldSchema;
|
|
449
|
+
/** How a definition's props schema is stored: the field DSL or the older FormBuilder format. */
|
|
450
|
+
type SectionSchemaFormat = "field-dsl" | "form-builder";
|
|
451
|
+
/** The format of a schema object, or null for anything that is not a schema. */
|
|
452
|
+
declare function schemaFormat(schema: unknown): SectionSchemaFormat | null;
|
|
447
453
|
interface SectionDefinition {
|
|
448
454
|
id: string;
|
|
449
455
|
name: string;
|
|
450
456
|
title: string;
|
|
451
457
|
/** The props schema as the dashboard stores it; `{}` for a definition without one. */
|
|
452
458
|
schema: SectionSchema | Record<string, unknown>;
|
|
459
|
+
/** The schema's format. The schema routes always send it; the source routes may not. */
|
|
460
|
+
format?: SectionSchemaFormat;
|
|
453
461
|
scope: SectionDefinitionScope;
|
|
454
462
|
}
|
|
463
|
+
/** `GET /api/sections/{name}/schema` */
|
|
464
|
+
interface SectionSchemaGetResponse {
|
|
465
|
+
definition: SectionDefinition & {
|
|
466
|
+
format: SectionSchemaFormat;
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
/** `PUT /api/sections/{name}/schema` */
|
|
470
|
+
interface SectionSchemaPutBody {
|
|
471
|
+
/** The new props schema, field DSL or FormBuilder. */
|
|
472
|
+
schema: SectionSchema;
|
|
473
|
+
/**
|
|
474
|
+
* Replace the schema even when the current one is in the other format.
|
|
475
|
+
* Without it the server answers 409 (`BotlrSchemaFormatConflictError`).
|
|
476
|
+
*/
|
|
477
|
+
force?: boolean;
|
|
478
|
+
/** Defaults to the client's `appId`. */
|
|
479
|
+
appInstanceId?: string;
|
|
480
|
+
}
|
|
481
|
+
interface SectionSchemaPutResponse {
|
|
482
|
+
definition: SectionDefinition & {
|
|
483
|
+
format: SectionSchemaFormat;
|
|
484
|
+
};
|
|
485
|
+
/** The format the definition had before this call. */
|
|
486
|
+
previousFormat: SectionSchemaFormat;
|
|
487
|
+
/** False when the new schema equals the stored one. */
|
|
488
|
+
changed: boolean;
|
|
489
|
+
}
|
|
455
490
|
interface SectionSource {
|
|
456
491
|
scope: SectionSourceScope;
|
|
457
492
|
framework: SectionFramework;
|
|
@@ -557,7 +592,7 @@ type BotlrClient = ReturnType<typeof createBotlrClient>;
|
|
|
557
592
|
/**
|
|
558
593
|
* A Botlr client. Every call sends the site token as `Authorization: Bearer`
|
|
559
594
|
* plus `x-botlr-app-id`, `x-botlr-locale` and `x-botlr-preview` from the
|
|
560
|
-
* config; `sections.push`
|
|
595
|
+
* config; `sections.push` and `sections.putSchema` send the personal token instead.
|
|
561
596
|
*/
|
|
562
597
|
declare function createBotlrClient(config: BotlrClientConfig): {
|
|
563
598
|
request: <T>(path: string, options?: RequestOptions) => Promise<T>;
|
|
@@ -602,6 +637,20 @@ declare function createBotlrClient(config: BotlrClientConfig): {
|
|
|
602
637
|
* changed; throws `BotlrConflictError` when `baseVersion` is not the latest.
|
|
603
638
|
*/
|
|
604
639
|
push(name: string, body: SectionSourcePushBody): Promise<SectionSourcePushResponse>;
|
|
640
|
+
/**
|
|
641
|
+
* The section definition with its props schema and format, with the site
|
|
642
|
+
* token. Works whether or not source has been pushed; 404 when the app has
|
|
643
|
+
* no section type of that name.
|
|
644
|
+
*/
|
|
645
|
+
getSchema(name: string): Promise<SectionSchemaGetResponse>;
|
|
646
|
+
/**
|
|
647
|
+
* Replace the definition's props schema with the personal access token
|
|
648
|
+
* (`write:sections`). Throws `BotlrSchemaFormatConflictError` when the
|
|
649
|
+
* current schema is in the other format and `force` is not set, and a
|
|
650
|
+
* `BotlrError` with status 422 when the schema is not an object of fields
|
|
651
|
+
* or the definition is not project-scoped.
|
|
652
|
+
*/
|
|
653
|
+
putSchema(name: string, body: SectionSchemaPutBody): Promise<SectionSchemaPutResponse>;
|
|
605
654
|
};
|
|
606
655
|
};
|
|
607
656
|
getEntryBySlug<TFields>(params: GetEntryBySlugParams): Promise<BotlrEntry<TFields> | null>;
|
|
@@ -638,6 +687,20 @@ declare function createBotlrClient(config: BotlrClientConfig): {
|
|
|
638
687
|
* changed; throws `BotlrConflictError` when `baseVersion` is not the latest.
|
|
639
688
|
*/
|
|
640
689
|
push(name: string, body: SectionSourcePushBody): Promise<SectionSourcePushResponse>;
|
|
690
|
+
/**
|
|
691
|
+
* The section definition with its props schema and format, with the site
|
|
692
|
+
* token. Works whether or not source has been pushed; 404 when the app has
|
|
693
|
+
* no section type of that name.
|
|
694
|
+
*/
|
|
695
|
+
getSchema(name: string): Promise<SectionSchemaGetResponse>;
|
|
696
|
+
/**
|
|
697
|
+
* Replace the definition's props schema with the personal access token
|
|
698
|
+
* (`write:sections`). Throws `BotlrSchemaFormatConflictError` when the
|
|
699
|
+
* current schema is in the other format and `force` is not set, and a
|
|
700
|
+
* `BotlrError` with status 422 when the schema is not an object of fields
|
|
701
|
+
* or the definition is not project-scoped.
|
|
702
|
+
*/
|
|
703
|
+
putSchema(name: string, body: SectionSchemaPutBody): Promise<SectionSchemaPutResponse>;
|
|
641
704
|
};
|
|
642
705
|
};
|
|
643
706
|
declare function createBotlrFactory(defaults?: {
|
|
@@ -682,6 +745,20 @@ declare function createBotlrFactory(defaults?: {
|
|
|
682
745
|
* changed; throws `BotlrConflictError` when `baseVersion` is not the latest.
|
|
683
746
|
*/
|
|
684
747
|
push(name: string, body: SectionSourcePushBody): Promise<SectionSourcePushResponse>;
|
|
748
|
+
/**
|
|
749
|
+
* The section definition with its props schema and format, with the site
|
|
750
|
+
* token. Works whether or not source has been pushed; 404 when the app has
|
|
751
|
+
* no section type of that name.
|
|
752
|
+
*/
|
|
753
|
+
getSchema(name: string): Promise<SectionSchemaGetResponse>;
|
|
754
|
+
/**
|
|
755
|
+
* Replace the definition's props schema with the personal access token
|
|
756
|
+
* (`write:sections`). Throws `BotlrSchemaFormatConflictError` when the
|
|
757
|
+
* current schema is in the other format and `force` is not set, and a
|
|
758
|
+
* `BotlrError` with status 422 when the schema is not an object of fields
|
|
759
|
+
* or the definition is not project-scoped.
|
|
760
|
+
*/
|
|
761
|
+
putSchema(name: string, body: SectionSchemaPutBody): Promise<SectionSchemaPutResponse>;
|
|
685
762
|
};
|
|
686
763
|
};
|
|
687
764
|
};
|
|
@@ -709,6 +786,21 @@ declare class BotlrConflictError extends BotlrError {
|
|
|
709
786
|
readonly latest: ConflictLatest | null;
|
|
710
787
|
constructor(message: string, latest: ConflictLatest | null, details?: unknown);
|
|
711
788
|
}
|
|
789
|
+
/**
|
|
790
|
+
* Thrown by `sections.putSchema` on 409: the definition's current schema and
|
|
791
|
+
* the new one are in different formats and `force` was not set.
|
|
792
|
+
*/
|
|
793
|
+
declare class BotlrSchemaFormatConflictError extends BotlrError {
|
|
794
|
+
readonly currentFormat: string;
|
|
795
|
+
readonly newFormat: string;
|
|
796
|
+
constructor(message: string, currentFormat: string, newFormat: string, details?: unknown);
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* The human-readable message of an error response, when the server sent one:
|
|
800
|
+
* `detail` as a string, `detail.message`, or the `msg` lines of a FastAPI
|
|
801
|
+
* validation error. Undefined for anything else.
|
|
802
|
+
*/
|
|
803
|
+
declare function botlrErrorMessage(error: unknown): string | undefined;
|
|
712
804
|
|
|
713
805
|
/** An asset as a section prop: the legacy bare id, or the `AssetData` object. */
|
|
714
806
|
type AssetRef = string | AssetData | null | undefined;
|
|
@@ -743,4 +835,4 @@ declare function createAssetUrl(options: AssetUrlOptions): (asset: AssetRef, sco
|
|
|
743
835
|
/** Statuses retried by default: timeouts, rate limits and server errors. */
|
|
744
836
|
declare const DEFAULT_RETRY_STATUSES: readonly number[];
|
|
745
837
|
|
|
746
|
-
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, 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 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, contentHash, contextToHeaders, createAssetUrl, createBotlrClient, createBotlrFactory, fileSha256, getAssetFilename, getAssetId, getAssetUrl, hashFiles, isAssetData, isFieldSchema, isFormBuilderSchema, isValidFramework, isValidSectionPath, stripManifestState, toQueryString };
|
|
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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ type BotlrClientConfig = {
|
|
|
13
13
|
/** Preview JWT, passed as `token` to `/api/resolve` when `preview` is on. */
|
|
14
14
|
previewToken?: string;
|
|
15
15
|
/**
|
|
16
|
-
* Personal access token with `write:sections`. Used only by `sections.push`;
|
|
16
|
+
* Personal access token with `write:sections`. Used only by `sections.push` and `sections.putSchema`;
|
|
17
17
|
* the server never accepts it for reads.
|
|
18
18
|
*/
|
|
19
19
|
personalToken?: string;
|
|
@@ -365,11 +365,13 @@ interface SectionManifest {
|
|
|
365
365
|
icon?: string;
|
|
366
366
|
dependencies?: SectionDependencies;
|
|
367
367
|
/**
|
|
368
|
-
* The props the code expects, in the field DSL. The dashboard owns
|
|
369
|
-
* definition's schema
|
|
370
|
-
* definition, and what the CLI compares
|
|
368
|
+
* The props the code expects, normally in the field DSL. The dashboard owns
|
|
369
|
+
* the definition's schema: a source push never changes it (this is only a
|
|
370
|
+
* seed when `--create` adds the definition, and what the CLI compares
|
|
371
|
+
* against for a drift warning). `botlr schema pull` copies the dashboard's
|
|
372
|
+
* schema here in whichever format it has; `botlr schema push` sends it back.
|
|
371
373
|
*/
|
|
372
|
-
schema?:
|
|
374
|
+
schema?: SectionSchema;
|
|
373
375
|
/** Unknown keys are kept by the server. */
|
|
374
376
|
[key: string]: unknown;
|
|
375
377
|
}
|
|
@@ -444,14 +446,47 @@ type SectionSchema = SectionFieldSchema | SectionFormBuilderSchema;
|
|
|
444
446
|
declare function isFormBuilderSchema(schema: unknown): schema is SectionFormBuilderSchema;
|
|
445
447
|
/** True for the field DSL: an object that is not the FormBuilder format. Empty objects count. */
|
|
446
448
|
declare function isFieldSchema(schema: unknown): schema is SectionFieldSchema;
|
|
449
|
+
/** How a definition's props schema is stored: the field DSL or the older FormBuilder format. */
|
|
450
|
+
type SectionSchemaFormat = "field-dsl" | "form-builder";
|
|
451
|
+
/** The format of a schema object, or null for anything that is not a schema. */
|
|
452
|
+
declare function schemaFormat(schema: unknown): SectionSchemaFormat | null;
|
|
447
453
|
interface SectionDefinition {
|
|
448
454
|
id: string;
|
|
449
455
|
name: string;
|
|
450
456
|
title: string;
|
|
451
457
|
/** The props schema as the dashboard stores it; `{}` for a definition without one. */
|
|
452
458
|
schema: SectionSchema | Record<string, unknown>;
|
|
459
|
+
/** The schema's format. The schema routes always send it; the source routes may not. */
|
|
460
|
+
format?: SectionSchemaFormat;
|
|
453
461
|
scope: SectionDefinitionScope;
|
|
454
462
|
}
|
|
463
|
+
/** `GET /api/sections/{name}/schema` */
|
|
464
|
+
interface SectionSchemaGetResponse {
|
|
465
|
+
definition: SectionDefinition & {
|
|
466
|
+
format: SectionSchemaFormat;
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
/** `PUT /api/sections/{name}/schema` */
|
|
470
|
+
interface SectionSchemaPutBody {
|
|
471
|
+
/** The new props schema, field DSL or FormBuilder. */
|
|
472
|
+
schema: SectionSchema;
|
|
473
|
+
/**
|
|
474
|
+
* Replace the schema even when the current one is in the other format.
|
|
475
|
+
* Without it the server answers 409 (`BotlrSchemaFormatConflictError`).
|
|
476
|
+
*/
|
|
477
|
+
force?: boolean;
|
|
478
|
+
/** Defaults to the client's `appId`. */
|
|
479
|
+
appInstanceId?: string;
|
|
480
|
+
}
|
|
481
|
+
interface SectionSchemaPutResponse {
|
|
482
|
+
definition: SectionDefinition & {
|
|
483
|
+
format: SectionSchemaFormat;
|
|
484
|
+
};
|
|
485
|
+
/** The format the definition had before this call. */
|
|
486
|
+
previousFormat: SectionSchemaFormat;
|
|
487
|
+
/** False when the new schema equals the stored one. */
|
|
488
|
+
changed: boolean;
|
|
489
|
+
}
|
|
455
490
|
interface SectionSource {
|
|
456
491
|
scope: SectionSourceScope;
|
|
457
492
|
framework: SectionFramework;
|
|
@@ -557,7 +592,7 @@ type BotlrClient = ReturnType<typeof createBotlrClient>;
|
|
|
557
592
|
/**
|
|
558
593
|
* A Botlr client. Every call sends the site token as `Authorization: Bearer`
|
|
559
594
|
* plus `x-botlr-app-id`, `x-botlr-locale` and `x-botlr-preview` from the
|
|
560
|
-
* config; `sections.push`
|
|
595
|
+
* config; `sections.push` and `sections.putSchema` send the personal token instead.
|
|
561
596
|
*/
|
|
562
597
|
declare function createBotlrClient(config: BotlrClientConfig): {
|
|
563
598
|
request: <T>(path: string, options?: RequestOptions) => Promise<T>;
|
|
@@ -602,6 +637,20 @@ declare function createBotlrClient(config: BotlrClientConfig): {
|
|
|
602
637
|
* changed; throws `BotlrConflictError` when `baseVersion` is not the latest.
|
|
603
638
|
*/
|
|
604
639
|
push(name: string, body: SectionSourcePushBody): Promise<SectionSourcePushResponse>;
|
|
640
|
+
/**
|
|
641
|
+
* The section definition with its props schema and format, with the site
|
|
642
|
+
* token. Works whether or not source has been pushed; 404 when the app has
|
|
643
|
+
* no section type of that name.
|
|
644
|
+
*/
|
|
645
|
+
getSchema(name: string): Promise<SectionSchemaGetResponse>;
|
|
646
|
+
/**
|
|
647
|
+
* Replace the definition's props schema with the personal access token
|
|
648
|
+
* (`write:sections`). Throws `BotlrSchemaFormatConflictError` when the
|
|
649
|
+
* current schema is in the other format and `force` is not set, and a
|
|
650
|
+
* `BotlrError` with status 422 when the schema is not an object of fields
|
|
651
|
+
* or the definition is not project-scoped.
|
|
652
|
+
*/
|
|
653
|
+
putSchema(name: string, body: SectionSchemaPutBody): Promise<SectionSchemaPutResponse>;
|
|
605
654
|
};
|
|
606
655
|
};
|
|
607
656
|
getEntryBySlug<TFields>(params: GetEntryBySlugParams): Promise<BotlrEntry<TFields> | null>;
|
|
@@ -638,6 +687,20 @@ declare function createBotlrClient(config: BotlrClientConfig): {
|
|
|
638
687
|
* changed; throws `BotlrConflictError` when `baseVersion` is not the latest.
|
|
639
688
|
*/
|
|
640
689
|
push(name: string, body: SectionSourcePushBody): Promise<SectionSourcePushResponse>;
|
|
690
|
+
/**
|
|
691
|
+
* The section definition with its props schema and format, with the site
|
|
692
|
+
* token. Works whether or not source has been pushed; 404 when the app has
|
|
693
|
+
* no section type of that name.
|
|
694
|
+
*/
|
|
695
|
+
getSchema(name: string): Promise<SectionSchemaGetResponse>;
|
|
696
|
+
/**
|
|
697
|
+
* Replace the definition's props schema with the personal access token
|
|
698
|
+
* (`write:sections`). Throws `BotlrSchemaFormatConflictError` when the
|
|
699
|
+
* current schema is in the other format and `force` is not set, and a
|
|
700
|
+
* `BotlrError` with status 422 when the schema is not an object of fields
|
|
701
|
+
* or the definition is not project-scoped.
|
|
702
|
+
*/
|
|
703
|
+
putSchema(name: string, body: SectionSchemaPutBody): Promise<SectionSchemaPutResponse>;
|
|
641
704
|
};
|
|
642
705
|
};
|
|
643
706
|
declare function createBotlrFactory(defaults?: {
|
|
@@ -682,6 +745,20 @@ declare function createBotlrFactory(defaults?: {
|
|
|
682
745
|
* changed; throws `BotlrConflictError` when `baseVersion` is not the latest.
|
|
683
746
|
*/
|
|
684
747
|
push(name: string, body: SectionSourcePushBody): Promise<SectionSourcePushResponse>;
|
|
748
|
+
/**
|
|
749
|
+
* The section definition with its props schema and format, with the site
|
|
750
|
+
* token. Works whether or not source has been pushed; 404 when the app has
|
|
751
|
+
* no section type of that name.
|
|
752
|
+
*/
|
|
753
|
+
getSchema(name: string): Promise<SectionSchemaGetResponse>;
|
|
754
|
+
/**
|
|
755
|
+
* Replace the definition's props schema with the personal access token
|
|
756
|
+
* (`write:sections`). Throws `BotlrSchemaFormatConflictError` when the
|
|
757
|
+
* current schema is in the other format and `force` is not set, and a
|
|
758
|
+
* `BotlrError` with status 422 when the schema is not an object of fields
|
|
759
|
+
* or the definition is not project-scoped.
|
|
760
|
+
*/
|
|
761
|
+
putSchema(name: string, body: SectionSchemaPutBody): Promise<SectionSchemaPutResponse>;
|
|
685
762
|
};
|
|
686
763
|
};
|
|
687
764
|
};
|
|
@@ -709,6 +786,21 @@ declare class BotlrConflictError extends BotlrError {
|
|
|
709
786
|
readonly latest: ConflictLatest | null;
|
|
710
787
|
constructor(message: string, latest: ConflictLatest | null, details?: unknown);
|
|
711
788
|
}
|
|
789
|
+
/**
|
|
790
|
+
* Thrown by `sections.putSchema` on 409: the definition's current schema and
|
|
791
|
+
* the new one are in different formats and `force` was not set.
|
|
792
|
+
*/
|
|
793
|
+
declare class BotlrSchemaFormatConflictError extends BotlrError {
|
|
794
|
+
readonly currentFormat: string;
|
|
795
|
+
readonly newFormat: string;
|
|
796
|
+
constructor(message: string, currentFormat: string, newFormat: string, details?: unknown);
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* The human-readable message of an error response, when the server sent one:
|
|
800
|
+
* `detail` as a string, `detail.message`, or the `msg` lines of a FastAPI
|
|
801
|
+
* validation error. Undefined for anything else.
|
|
802
|
+
*/
|
|
803
|
+
declare function botlrErrorMessage(error: unknown): string | undefined;
|
|
712
804
|
|
|
713
805
|
/** An asset as a section prop: the legacy bare id, or the `AssetData` object. */
|
|
714
806
|
type AssetRef = string | AssetData | null | undefined;
|
|
@@ -743,4 +835,4 @@ declare function createAssetUrl(options: AssetUrlOptions): (asset: AssetRef, sco
|
|
|
743
835
|
/** Statuses retried by default: timeouts, rate limits and server errors. */
|
|
744
836
|
declare const DEFAULT_RETRY_STATUSES: readonly number[];
|
|
745
837
|
|
|
746
|
-
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, 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 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, contentHash, contextToHeaders, createAssetUrl, createBotlrClient, createBotlrFactory, fileSha256, getAssetFilename, getAssetId, getAssetUrl, hashFiles, isAssetData, isFieldSchema, isFormBuilderSchema, isValidFramework, isValidSectionPath, stripManifestState, toQueryString };
|
|
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 };
|
package/dist/index.js
CHANGED
|
@@ -17,6 +17,32 @@ var BotlrConflictError = class extends BotlrError {
|
|
|
17
17
|
this.latest = latest;
|
|
18
18
|
}
|
|
19
19
|
};
|
|
20
|
+
var BotlrSchemaFormatConflictError = class extends BotlrError {
|
|
21
|
+
currentFormat;
|
|
22
|
+
newFormat;
|
|
23
|
+
constructor(message, currentFormat, newFormat, details) {
|
|
24
|
+
super(message, 409, details);
|
|
25
|
+
this.name = "BotlrSchemaFormatConflictError";
|
|
26
|
+
this.currentFormat = currentFormat;
|
|
27
|
+
this.newFormat = newFormat;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
function botlrErrorMessage(error) {
|
|
31
|
+
if (!(error instanceof BotlrError)) return void 0;
|
|
32
|
+
const details = error.details;
|
|
33
|
+
if (!details || typeof details !== "object") return typeof details === "string" ? details : void 0;
|
|
34
|
+
const detail = details.detail;
|
|
35
|
+
if (typeof detail === "string") return detail;
|
|
36
|
+
if (Array.isArray(detail)) {
|
|
37
|
+
const lines = detail.map((item) => item && typeof item === "object" ? item.msg : void 0).filter((msg) => typeof msg === "string");
|
|
38
|
+
return lines.length ? lines.join("; ") : void 0;
|
|
39
|
+
}
|
|
40
|
+
if (detail && typeof detail === "object") {
|
|
41
|
+
const message = detail.message;
|
|
42
|
+
return typeof message === "string" ? message : void 0;
|
|
43
|
+
}
|
|
44
|
+
return void 0;
|
|
45
|
+
}
|
|
20
46
|
|
|
21
47
|
// src/retry.ts
|
|
22
48
|
var DEFAULT_RETRY_STATUSES = [408, 429, 500, 502, 503, 504];
|
|
@@ -105,6 +131,14 @@ function conflictLatest(details) {
|
|
|
105
131
|
if (typeof version !== "number" || typeof contentHash2 !== "string") return null;
|
|
106
132
|
return { version, contentHash: contentHash2 };
|
|
107
133
|
}
|
|
134
|
+
function conflictFormats(details) {
|
|
135
|
+
if (!details || typeof details !== "object") return null;
|
|
136
|
+
const detail = details.detail;
|
|
137
|
+
if (!detail || typeof detail !== "object") return null;
|
|
138
|
+
const { currentFormat, newFormat } = detail;
|
|
139
|
+
if (typeof currentFormat !== "string" || typeof newFormat !== "string") return null;
|
|
140
|
+
return { currentFormat, newFormat };
|
|
141
|
+
}
|
|
108
142
|
function conflictMessage(details) {
|
|
109
143
|
if (!details || typeof details !== "object") return void 0;
|
|
110
144
|
const detail = details.detail;
|
|
@@ -197,6 +231,45 @@ function createBotlrClient(config) {
|
|
|
197
231
|
headers: { Authorization: `Bearer ${config.personalToken}` },
|
|
198
232
|
body: JSON.stringify({ appInstanceId: appInstanceId ?? baseContext.appId, ...rest })
|
|
199
233
|
});
|
|
234
|
+
},
|
|
235
|
+
/**
|
|
236
|
+
* The section definition with its props schema and format, with the site
|
|
237
|
+
* token. Works whether or not source has been pushed; 404 when the app has
|
|
238
|
+
* no section type of that name.
|
|
239
|
+
*/
|
|
240
|
+
getSchema(name) {
|
|
241
|
+
return request(`/api/sections/${encodeURIComponent(name)}/schema`, {
|
|
242
|
+
method: "GET",
|
|
243
|
+
query: { appInstanceId: baseContext.appId }
|
|
244
|
+
});
|
|
245
|
+
},
|
|
246
|
+
/**
|
|
247
|
+
* Replace the definition's props schema with the personal access token
|
|
248
|
+
* (`write:sections`). Throws `BotlrSchemaFormatConflictError` when the
|
|
249
|
+
* current schema is in the other format and `force` is not set, and a
|
|
250
|
+
* `BotlrError` with status 422 when the schema is not an object of fields
|
|
251
|
+
* or the definition is not project-scoped.
|
|
252
|
+
*/
|
|
253
|
+
async putSchema(name, body) {
|
|
254
|
+
if (!config.personalToken) {
|
|
255
|
+
throw new BotlrError("sections.putSchema needs `personalToken` (a personal access token with write:sections)");
|
|
256
|
+
}
|
|
257
|
+
const { appInstanceId, schema, force } = body;
|
|
258
|
+
try {
|
|
259
|
+
return await request(`/api/sections/${encodeURIComponent(name)}/schema`, {
|
|
260
|
+
method: "PUT",
|
|
261
|
+
headers: { Authorization: `Bearer ${config.personalToken}` },
|
|
262
|
+
body: JSON.stringify({ appInstanceId: appInstanceId ?? baseContext.appId, schema, force: force ?? false })
|
|
263
|
+
});
|
|
264
|
+
} catch (error) {
|
|
265
|
+
if (error instanceof BotlrConflictError) {
|
|
266
|
+
const formats = conflictFormats(error.details);
|
|
267
|
+
if (formats) {
|
|
268
|
+
throw new BotlrSchemaFormatConflictError(error.message, formats.currentFormat, formats.newFormat, error.details);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
throw error;
|
|
272
|
+
}
|
|
200
273
|
}
|
|
201
274
|
};
|
|
202
275
|
return {
|
|
@@ -352,6 +425,11 @@ function isFormBuilderSchema(schema) {
|
|
|
352
425
|
function isFieldSchema(schema) {
|
|
353
426
|
return !!schema && typeof schema === "object" && !Array.isArray(schema) && !isFormBuilderSchema(schema);
|
|
354
427
|
}
|
|
428
|
+
function schemaFormat(schema) {
|
|
429
|
+
if (isFormBuilderSchema(schema)) return "form-builder";
|
|
430
|
+
if (isFieldSchema(schema)) return "field-dsl";
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
355
433
|
var SECTION_SOURCE_LIMITS = {
|
|
356
434
|
maxFiles: 50,
|
|
357
435
|
maxFileBytes: 512 * 1024,
|
|
@@ -442,11 +520,13 @@ function createAssetUrl(options) {
|
|
|
442
520
|
export {
|
|
443
521
|
BotlrConflictError,
|
|
444
522
|
BotlrError,
|
|
523
|
+
BotlrSchemaFormatConflictError,
|
|
445
524
|
DEFAULT_RETRY_STATUSES,
|
|
446
525
|
MANIFEST_STATE_KEYS,
|
|
447
526
|
SECTION_FRAMEWORK_RE,
|
|
448
527
|
SECTION_PATH_RE,
|
|
449
528
|
SECTION_SOURCE_LIMITS,
|
|
529
|
+
botlrErrorMessage,
|
|
450
530
|
contentHash,
|
|
451
531
|
contextToHeaders,
|
|
452
532
|
createAssetUrl,
|
|
@@ -462,6 +542,7 @@ export {
|
|
|
462
542
|
isFormBuilderSchema,
|
|
463
543
|
isValidFramework,
|
|
464
544
|
isValidSectionPath,
|
|
545
|
+
schemaFormat,
|
|
465
546
|
stripManifestState,
|
|
466
547
|
toQueryString
|
|
467
548
|
};
|
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","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 SectionSourceListParams,\n SectionSourceListResponse,\n SectionSourcePullParams,\n SectionSourcePullResponse,\n SectionSourcePushBody,\n SectionSourcePushResponse,\n} from \"./sections\";\nimport { BotlrConflictError, BotlrError } 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 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` sends 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 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, in the field DSL. The dashboard owns the\n * definition's schema; this is only a seed when `--create` adds the\n * definition, and what the CLI compares against for a drift warning.\n */\n schema?: SectionFieldSchema;\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\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 scope: SectionDefinitionScope;\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;;;AChCO,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;;;AC1BO,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,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,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;;;ACzUO,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;AA6FO,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;;;AChTO,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 * 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"]}
|