@botlr/sdk 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,26 @@
1
+ # @botlr/sdk
2
+
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Page helpers extracted from the satellite apps.
8
+
9
+ `@botlr/sdk`: `PageResponse` gains optional `full_slug`, `breadcrumbs` and `alternate_translations` (new `AlternateTranslation` type, also used by `ResolveResponse`). New asset helpers `getAssetUrl`, `createAssetUrl`, `getAssetId`, `getAssetFilename` and `isAssetData` take the media server URL explicitly instead of reading `NEXT_PUBLIC_MEDIA_URL`.
10
+
11
+ `@botlr/next`: `resolvePage` and `createPageResolver`, `buildPageMetadata`, `renderCmsPage` with `themeSettingsToCss`, `serveStaticTextFile` and `defaultRobotsContent`, `createLocalePage` and `createLocaleAliasPage`, `rateLimit` and `getClientIp`, plus `scopeFromEnv` and `environmentFromEnv`. Every helper takes its client and component registry as arguments. `html-react-parser` is a new dependency.
12
+
13
+ ## 0.1.0
14
+
15
+ - First release. Ports the `lib/botlr-sdk` client from the publisher and
16
+ satellite apps: `createBotlrClient`, `createBotlrFactory`, the context
17
+ headers, `BotlrError`, and every endpoint helper including `getData`,
18
+ `submitData`, `getPageSlugList`, `getRedirects`, `getAppInfo` and
19
+ `getSitemap`.
20
+ - Adds `personalToken` to the config and a `sections` namespace (`list`,
21
+ `pull`, `push`) for the section source bridge, with `BotlrConflictError`
22
+ for 409 responses.
23
+ - Exports the section manifest and file types and the `fileSha256` /
24
+ `contentHash` helpers that match the server's hash rules.
25
+ - Shares `PublisherScope`, `PageScope`, `BreadcrumbItem`, the layout node
26
+ types and `VisibilityRules` with the renderers.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Botlr
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,67 @@
1
- # Temporary Holding Version
1
+ # @botlr/sdk
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Fetch-only client for the Botlr API plus the types every other `@botlr/*`
4
+ package shares. No React, no Node-only APIs: it runs in the browser, in Node
5
+ 20+ and on edge runtimes.
6
+
7
+ ```ts
8
+ import { createBotlrClient } from "@botlr/sdk";
9
+
10
+ const botlr = createBotlrClient({
11
+ baseUrl: "https://api.botlr.io",
12
+ appId: process.env.BOTLR_APP_ID!,
13
+ apiKey: process.env.BOTLR_API_KEY, // site token; omit in dev mode
14
+ locale: "da",
15
+ preview: false,
16
+ });
17
+
18
+ const page = await botlr.getPageBySlug("home");
19
+ const resolved = await botlr.resolveUrl("about/team");
20
+ ```
21
+
22
+ Every request carries `Authorization: Bearer <apiKey>`, `x-botlr-app-id`,
23
+ `x-botlr-locale` and `x-botlr-preview`. Non-2xx responses throw `BotlrError`
24
+ with `status` and the parsed body in `details`.
25
+
26
+ ## Section source bridge
27
+
28
+ `botlr.sections` talks to the section source bridge (`/api/sections/...`):
29
+
30
+ ```ts
31
+ const { sections } = await botlr.sections.list({ framework: "react-next" });
32
+ const { source } = await botlr.sections.pull("hero", { framework: "react-next" });
33
+
34
+ // push needs a personal access token with write:sections
35
+ const writer = createBotlrClient({ ...config, personalToken: process.env.BOTLR_PAT });
36
+ await writer.sections.push("hero", {
37
+ framework: "react-next",
38
+ manifest,
39
+ files: [{ path: "Hero.tsx", content }],
40
+ baseVersion: 3, // omit to force
41
+ message: "tighten spacing",
42
+ });
43
+ ```
44
+
45
+ `fileSha256(content)` and `contentHash(files)` reproduce the server's hash
46
+ rules exactly (sha256 hex of the UTF-8 content; content hash = sha256 over
47
+ `"{path}\n{sha256}\n"` for files sorted by path), so a client can compare its
48
+ local tree with the lock file and the server without a round trip. Both are
49
+ async and use Web Crypto.
50
+
51
+ ## Asset URLs
52
+
53
+ Section props hold an asset either as a bare id or as an `AssetData` object.
54
+ The helpers build the media server URL
55
+ `{mediaUrl}/images/{orgSlug}/{assetId}/{recipe}/{filename}` from either, with
56
+ the media server passed in rather than read from the environment:
57
+
58
+ ```ts
59
+ import { createAssetUrl } from "@botlr/sdk";
60
+
61
+ export const getAssetUrl = createAssetUrl({ mediaUrl: process.env.NEXT_PUBLIC_MEDIA_URL! });
62
+ getAssetUrl(section.image, scope); // view recipe
63
+ getAssetUrl(section.image, scope, "thumb");
64
+ ```
65
+
66
+ `getAssetUrl(asset, scope, { mediaUrl, recipe })`, `getAssetId`,
67
+ `getAssetFilename` and `isAssetData` are the unbound building blocks.
package/dist/index.cjs ADDED
@@ -0,0 +1,437 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ BotlrConflictError: () => BotlrConflictError,
24
+ BotlrError: () => BotlrError,
25
+ MANIFEST_STATE_KEYS: () => MANIFEST_STATE_KEYS,
26
+ SECTION_FRAMEWORK_RE: () => SECTION_FRAMEWORK_RE,
27
+ SECTION_PATH_RE: () => SECTION_PATH_RE,
28
+ SECTION_SOURCE_LIMITS: () => SECTION_SOURCE_LIMITS,
29
+ contentHash: () => contentHash,
30
+ contextToHeaders: () => contextToHeaders,
31
+ createAssetUrl: () => createAssetUrl,
32
+ createBotlrClient: () => createBotlrClient,
33
+ createBotlrFactory: () => createBotlrFactory,
34
+ fileSha256: () => fileSha256,
35
+ getAssetFilename: () => getAssetFilename,
36
+ getAssetId: () => getAssetId,
37
+ getAssetUrl: () => getAssetUrl,
38
+ hashFiles: () => hashFiles,
39
+ isAssetData: () => isAssetData,
40
+ isValidFramework: () => isValidFramework,
41
+ isValidSectionPath: () => isValidSectionPath,
42
+ stripManifestState: () => stripManifestState,
43
+ toQueryString: () => toQueryString
44
+ });
45
+ module.exports = __toCommonJS(index_exports);
46
+
47
+ // src/errors.ts
48
+ var BotlrError = class extends Error {
49
+ status;
50
+ details;
51
+ constructor(message, status, details) {
52
+ super(message);
53
+ this.name = "BotlrError";
54
+ this.status = status;
55
+ this.details = details;
56
+ }
57
+ };
58
+ var BotlrConflictError = class extends BotlrError {
59
+ latest;
60
+ constructor(message, latest, details) {
61
+ super(message, 409, details);
62
+ this.name = "BotlrConflictError";
63
+ this.latest = latest;
64
+ }
65
+ };
66
+
67
+ // src/client.ts
68
+ function toQueryString(query) {
69
+ const params = new URLSearchParams();
70
+ for (const [key, value] of Object.entries(query)) {
71
+ if (value === void 0 || value === null) continue;
72
+ if (Array.isArray(value)) {
73
+ for (const item of value) {
74
+ if (item !== void 0 && item !== null) {
75
+ params.append(key, String(item));
76
+ }
77
+ }
78
+ } else {
79
+ params.set(key, String(value));
80
+ }
81
+ }
82
+ const qs = params.toString();
83
+ return qs ? `?${qs}` : "";
84
+ }
85
+ function mergeContext(base, override) {
86
+ if (!base && !override) return void 0;
87
+ return { ...base, ...override };
88
+ }
89
+ function contextToHeaders(context) {
90
+ if (!context) return {};
91
+ return {
92
+ ...context.appId ? { "x-botlr-app-id": context.appId } : {},
93
+ ...context.locale ? { "x-botlr-locale": context.locale } : {},
94
+ ...context.preview !== void 0 ? { "x-botlr-preview": String(context.preview) } : {}
95
+ };
96
+ }
97
+ async function errorDetails(response) {
98
+ const text = await response.text().catch(() => void 0);
99
+ if (text === void 0 || text === "") return void 0;
100
+ try {
101
+ return JSON.parse(text);
102
+ } catch {
103
+ return text;
104
+ }
105
+ }
106
+ function conflictLatest(details) {
107
+ if (!details || typeof details !== "object") return null;
108
+ const detail = details.detail;
109
+ if (!detail || typeof detail !== "object") return null;
110
+ const latest = detail.latest;
111
+ if (!latest || typeof latest !== "object") return null;
112
+ const { version, contentHash: contentHash2 } = latest;
113
+ if (typeof version !== "number" || typeof contentHash2 !== "string") return null;
114
+ return { version, contentHash: contentHash2 };
115
+ }
116
+ function conflictMessage(details) {
117
+ if (!details || typeof details !== "object") return void 0;
118
+ const detail = details.detail;
119
+ if (!detail || typeof detail !== "object") return void 0;
120
+ const message = detail.message;
121
+ return typeof message === "string" ? message : void 0;
122
+ }
123
+ function createBotlrClient(config) {
124
+ const fetcher = config.fetch ?? globalThis.fetch;
125
+ if (typeof fetcher !== "function") {
126
+ throw new BotlrError("No fetch implementation: pass `fetch` in the client config");
127
+ }
128
+ const baseUrl = config.baseUrl.replace(/\/+$/, "");
129
+ const baseContext = {
130
+ appId: config.appId,
131
+ locale: config.locale,
132
+ preview: config.preview
133
+ };
134
+ async function request(path, options = {}) {
135
+ const { query, context: contextOverride, headers: extraHeaders, ...init } = options;
136
+ const context = mergeContext(baseContext, contextOverride);
137
+ const url = `${baseUrl}${path}${toQueryString(query ?? {})}`;
138
+ const response = await fetcher(url, {
139
+ ...init,
140
+ headers: {
141
+ Accept: "application/json",
142
+ "Content-Type": "application/json",
143
+ ...config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {},
144
+ ...contextToHeaders(context),
145
+ ...extraHeaders ?? {}
146
+ }
147
+ });
148
+ if (!response.ok) {
149
+ const details = await errorDetails(response);
150
+ if (response.status === 409) {
151
+ throw new BotlrConflictError(
152
+ conflictMessage(details) ?? `Botlr request failed with status 409`,
153
+ conflictLatest(details),
154
+ details
155
+ );
156
+ }
157
+ throw new BotlrError(`Botlr request failed with status ${response.status}`, response.status, details);
158
+ }
159
+ if (response.status === 204) {
160
+ return void 0;
161
+ }
162
+ return await response.json();
163
+ }
164
+ const sections = {
165
+ /** Every section the app can pull for `framework`, with the latest version per scope. */
166
+ list(params) {
167
+ return request("/api/sections/source", {
168
+ method: "GET",
169
+ query: { framework: params.framework, appInstanceId: baseContext.appId }
170
+ });
171
+ },
172
+ /** The section's source: this app's row first, the shared row second. */
173
+ pull(name, params) {
174
+ return request(`/api/sections/${encodeURIComponent(name)}/source`, {
175
+ method: "GET",
176
+ query: {
177
+ framework: params.framework,
178
+ appInstanceId: baseContext.appId,
179
+ version: params.version,
180
+ scope: params.scope
181
+ }
182
+ });
183
+ },
184
+ /**
185
+ * Push a new version with the personal access token (`write:sections`).
186
+ * Resolves with `created: true` on 201 and `created: false` when nothing
187
+ * changed; throws `BotlrConflictError` when `baseVersion` is not the latest.
188
+ */
189
+ push(name, body) {
190
+ if (!config.personalToken) {
191
+ return Promise.reject(
192
+ new BotlrError("sections.push needs `personalToken` (a personal access token with write:sections)")
193
+ );
194
+ }
195
+ const { appInstanceId, ...rest } = body;
196
+ return request(`/api/sections/${encodeURIComponent(name)}/source`, {
197
+ method: "PUT",
198
+ headers: { Authorization: `Bearer ${config.personalToken}` },
199
+ body: JSON.stringify({ appInstanceId: appInstanceId ?? baseContext.appId, ...rest })
200
+ });
201
+ }
202
+ };
203
+ return {
204
+ request,
205
+ /** The resolved config, for wrappers that need `baseUrl` or `appId`. */
206
+ config: { ...config, baseUrl },
207
+ withContext(contextOverride) {
208
+ return createBotlrClient({ ...config, ...contextOverride });
209
+ },
210
+ async getEntryBySlug(params) {
211
+ const result = await request("/entries/by-slug", {
212
+ method: "GET",
213
+ query: { contentType: params.contentType, slug: params.slug, locale: params.locale },
214
+ context: { locale: params.locale }
215
+ });
216
+ return result.item;
217
+ },
218
+ async getEntries(params) {
219
+ const query = {
220
+ contentType: params.contentType,
221
+ locale: params.locale,
222
+ limit: params.limit,
223
+ offset: params.offset,
224
+ sort: params.sort
225
+ };
226
+ for (const [key, value] of Object.entries(params.filters ?? {})) {
227
+ query[`filter.${key}`] = value;
228
+ }
229
+ return request("/entries", {
230
+ method: "GET",
231
+ query,
232
+ context: { locale: params.locale }
233
+ });
234
+ },
235
+ async getSingleton(params) {
236
+ const result = await request("/singletons", {
237
+ method: "GET",
238
+ query: { key: params.key, locale: params.locale },
239
+ context: { locale: params.locale }
240
+ });
241
+ return result.item;
242
+ },
243
+ getPageBySlug(slug, locale) {
244
+ return request(`/api/pages/${slug}`, {
245
+ query: {
246
+ appInstanceId: baseContext.appId,
247
+ locale: locale || baseContext.locale,
248
+ mode: baseContext.preview ? "draft" : "published"
249
+ }
250
+ });
251
+ },
252
+ resolveUrl(slug, locale) {
253
+ return request("/api/resolve", {
254
+ query: {
255
+ slug,
256
+ appInstanceId: baseContext.appId,
257
+ locale: locale || baseContext.locale,
258
+ mode: baseContext.preview ? "draft" : "published",
259
+ ...baseContext.preview && config.previewToken ? { token: config.previewToken } : {}
260
+ }
261
+ });
262
+ },
263
+ getPageList(params = {}) {
264
+ return request("/api/page-lists", {
265
+ query: {
266
+ appInstanceId: params.appInstanceId || baseContext.appId,
267
+ locale: params.locale || baseContext.locale,
268
+ parentId: params.parentId,
269
+ tags: params.tags,
270
+ limit: params.limit,
271
+ offset: params.offset,
272
+ type: params.type || "page",
273
+ orderBy: params.orderBy
274
+ }
275
+ });
276
+ },
277
+ getPageSlugList(params = {}) {
278
+ return request("/api/page-lists/slugs", {
279
+ query: {
280
+ appInstanceId: params.appInstanceId || baseContext.appId,
281
+ locale: params.locale || baseContext.locale,
282
+ limit: params.limit,
283
+ offset: params.offset
284
+ }
285
+ });
286
+ },
287
+ getMenu(key, locale) {
288
+ return request("/api/menus", {
289
+ query: { key, appInstanceId: baseContext.appId, locale: locale || baseContext.locale }
290
+ });
291
+ },
292
+ getForm(key, locale) {
293
+ return request("/api/forms", {
294
+ query: { key, appInstanceId: baseContext.appId, locale: locale || baseContext.locale }
295
+ });
296
+ },
297
+ getList(appId, listId, locale) {
298
+ return request(`/api/lists/${listId}/complete`, {
299
+ query: { appInstanceId: appId, locale: locale || baseContext.locale }
300
+ });
301
+ },
302
+ getConfigurator(configuratorId, versionId) {
303
+ return request(`/api/configurators/${configuratorId}`, {
304
+ query: { versionId, appInstanceId: baseContext.appId, locale: baseContext.locale }
305
+ });
306
+ },
307
+ /** GET `/api/{endpoint}` with the app and locale added to the query. */
308
+ getData(endpoint, queryParams, locale) {
309
+ return request(`/api/${endpoint}`, {
310
+ method: "GET",
311
+ query: { appInstanceId: baseContext.appId, locale: locale || baseContext.locale, ...queryParams }
312
+ });
313
+ },
314
+ /** POST `data` as JSON to `/api/{endpoint}`, for forms and other submissions. */
315
+ submitData(endpoint, data, locale, queryParams) {
316
+ return request(`/api/${endpoint}`, {
317
+ method: "POST",
318
+ body: JSON.stringify(data),
319
+ query: { appInstanceId: baseContext.appId, locale: locale || baseContext.locale, ...queryParams }
320
+ });
321
+ },
322
+ getAppInfo() {
323
+ return request(`/api/apps/${baseContext.appId}`);
324
+ },
325
+ getSitemap() {
326
+ return request(`/api/sitemap/${baseContext.appId}`);
327
+ },
328
+ /** Every redirect for the app, across all locales. */
329
+ getRedirects() {
330
+ return request(`/api/redirects/${baseContext.appId}`);
331
+ },
332
+ getAssetById(assetId) {
333
+ return request(`/api/assets/${assetId}`, {
334
+ query: { appInstanceId: baseContext.appId }
335
+ });
336
+ },
337
+ sections
338
+ };
339
+ }
340
+ function createBotlrFactory(defaults) {
341
+ return {
342
+ create(config) {
343
+ return createBotlrClient({ ...defaults, ...config });
344
+ }
345
+ };
346
+ }
347
+
348
+ // src/sections.ts
349
+ var MANIFEST_STATE_KEYS = ["version", "hash", "contentHash", "pulledAt", "scope"];
350
+ var SECTION_SOURCE_LIMITS = {
351
+ maxFiles: 50,
352
+ maxFileBytes: 512 * 1024,
353
+ maxTotalBytes: 2 * 1024 * 1024,
354
+ maxPathLength: 200,
355
+ maxMessageLength: 500
356
+ };
357
+ var SECTION_PATH_RE = /^[A-Za-z0-9._-]+(?:\/[A-Za-z0-9._-]+)*$/;
358
+ var SECTION_FRAMEWORK_RE = /^[a-z0-9][a-z0-9-]{0,31}$/;
359
+ function isValidSectionPath(path) {
360
+ if (!path || path.length > SECTION_SOURCE_LIMITS.maxPathLength) return false;
361
+ if (!SECTION_PATH_RE.test(path)) return false;
362
+ return !path.split("/").some((segment) => segment === "." || segment === "..");
363
+ }
364
+ function isValidFramework(framework) {
365
+ return SECTION_FRAMEWORK_RE.test(framework);
366
+ }
367
+ var encoder = new TextEncoder();
368
+ function subtle() {
369
+ const c = globalThis.crypto;
370
+ if (!c || !c.subtle) {
371
+ throw new Error("Web Crypto is not available in this runtime (globalThis.crypto.subtle)");
372
+ }
373
+ return c.subtle;
374
+ }
375
+ function toHex(buffer) {
376
+ const bytes = new Uint8Array(buffer);
377
+ let out = "";
378
+ for (let i = 0; i < bytes.length; i++) {
379
+ out += bytes[i].toString(16).padStart(2, "0");
380
+ }
381
+ return out;
382
+ }
383
+ async function sha256Hex(input) {
384
+ return toHex(await subtle().digest("SHA-256", encoder.encode(input)));
385
+ }
386
+ function fileSha256(content) {
387
+ return sha256Hex(content);
388
+ }
389
+ async function contentHash(files) {
390
+ const hashed = await hashFiles(files);
391
+ const lines = hashed.map((f) => [f.path, f.sha256]).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([path, sha]) => `${path}
392
+ ${sha}
393
+ `).join("");
394
+ return sha256Hex(lines);
395
+ }
396
+ async function hashFiles(files) {
397
+ return Promise.all(
398
+ files.map(async (f) => ({
399
+ path: f.path,
400
+ content: f.content,
401
+ sha256: "sha256" in f && typeof f.sha256 === "string" ? f.sha256 : await fileSha256(f.content)
402
+ }))
403
+ );
404
+ }
405
+ function stripManifestState(manifest) {
406
+ const copy = { ...manifest };
407
+ for (const key of MANIFEST_STATE_KEYS) delete copy[key];
408
+ return copy;
409
+ }
410
+
411
+ // src/assets.ts
412
+ function isAssetData(value) {
413
+ return typeof value === "object" && value !== null && "id" in value && "filename" in value;
414
+ }
415
+ function getAssetId(asset) {
416
+ if (!asset) return void 0;
417
+ return typeof asset === "string" ? asset : asset.id;
418
+ }
419
+ function getAssetFilename(asset) {
420
+ if (!asset || typeof asset === "string") return void 0;
421
+ return asset.filename;
422
+ }
423
+ function getAssetUrl(asset, scope, options) {
424
+ const assetId = getAssetId(asset);
425
+ if (!assetId) return void 0;
426
+ const base = options.mediaUrl.replace(/\/+$/, "");
427
+ const recipe = options.recipe ?? "view";
428
+ const filename = getAssetFilename(asset);
429
+ if (scope?.orgSlug) {
430
+ return `${base}/images/${scope.orgSlug}/${assetId}/${recipe}/${filename ?? "image.jpg"}`;
431
+ }
432
+ return `${base}/images/${assetId}`;
433
+ }
434
+ function createAssetUrl(options) {
435
+ return (asset, scope, recipe) => getAssetUrl(asset, scope, { ...options, recipe: recipe ?? options.recipe });
436
+ }
437
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/errors.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\";\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 {\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\";\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 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\n const response = await fetcher(url, {\n ...init,\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n ...(config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}),\n ...contextToHeaders(context),\n ...(extraHeaders ?? {}),\n },\n });\n\n if (!response.ok) {\n const details = await errorDetails(response);\n if (response.status === 409) {\n throw new BotlrConflictError(\n conflictMessage(details) ?? `Botlr request failed with status 409`,\n conflictLatest(details),\n details,\n );\n }\n throw new BotlrError(`Botlr request failed with status ${response.status}`, response.status, details);\n }\n\n if (response.status === 204) {\n return undefined as T;\n }\n\n return (await response.json()) as T;\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 /** 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\nexport interface SectionDefinition {\n id: string;\n name: string;\n title: string;\n schema: 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"]}