@bettercms-ai/sdk 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +126 -0
- package/dist/index.cjs +1616 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1072 -0
- package/dist/index.d.ts +1072 -0
- package/dist/index.js +1507 -0
- package/dist/index.js.map +1 -0
- package/package.json +39 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1507 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var ErrorCodes = {
|
|
3
|
+
CONTENT_NOT_FOUND: "CONTENT_NOT_FOUND",
|
|
4
|
+
UNAUTHORIZED: "UNAUTHORIZED",
|
|
5
|
+
FORBIDDEN: "FORBIDDEN",
|
|
6
|
+
RATE_LIMITED: "RATE_LIMITED",
|
|
7
|
+
INTERNAL_ERROR: "INTERNAL_ERROR",
|
|
8
|
+
VALIDATION_ERROR: "VALIDATION_ERROR",
|
|
9
|
+
NETWORK_ERROR: "NETWORK_ERROR",
|
|
10
|
+
ADMIN_NOT_IMPLEMENTED: "ADMIN_NOT_IMPLEMENTED"
|
|
11
|
+
};
|
|
12
|
+
function statusToCode(status) {
|
|
13
|
+
if (status === 404) return ErrorCodes.CONTENT_NOT_FOUND;
|
|
14
|
+
if (status === 401) return ErrorCodes.UNAUTHORIZED;
|
|
15
|
+
if (status === 403) return ErrorCodes.FORBIDDEN;
|
|
16
|
+
if (status === 429) return ErrorCodes.RATE_LIMITED;
|
|
17
|
+
if (status >= 500) return ErrorCodes.INTERNAL_ERROR;
|
|
18
|
+
if (status === 422) return ErrorCodes.VALIDATION_ERROR;
|
|
19
|
+
return ErrorCodes.INTERNAL_ERROR;
|
|
20
|
+
}
|
|
21
|
+
var BetterCMSError = class _BetterCMSError extends Error {
|
|
22
|
+
status;
|
|
23
|
+
code;
|
|
24
|
+
/**
|
|
25
|
+
* Raw machine-readable `code` from the response body (e.g. "PROJECT_DELETED"),
|
|
26
|
+
* when the API supplies one. Distinct from `code` (which is derived from the
|
|
27
|
+
* HTTP status), this lets callers branch on a specific server condition that
|
|
28
|
+
* shares a status with others (e.g. 409 slug-conflict vs 409 project-deleted).
|
|
29
|
+
*/
|
|
30
|
+
bodyCode;
|
|
31
|
+
constructor(message, status, code, bodyCode) {
|
|
32
|
+
super(message);
|
|
33
|
+
this.name = "BetterCMSError";
|
|
34
|
+
this.status = status;
|
|
35
|
+
this.code = code;
|
|
36
|
+
this.bodyCode = bodyCode;
|
|
37
|
+
if (Error.captureStackTrace) {
|
|
38
|
+
Error.captureStackTrace(this, _BetterCMSError);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
toJSON() {
|
|
42
|
+
return {
|
|
43
|
+
name: this.name,
|
|
44
|
+
message: this.message,
|
|
45
|
+
status: this.status,
|
|
46
|
+
code: this.code,
|
|
47
|
+
bodyCode: this.bodyCode
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Factory — creates a BetterCMSError from a failed fetch Response. Reads the
|
|
52
|
+
* body's `message` (or `error`) for the human message and `code` for a
|
|
53
|
+
* machine-readable condition the SDK surfaces as `bodyCode`.
|
|
54
|
+
*/
|
|
55
|
+
static async from(res) {
|
|
56
|
+
let message = res.statusText || "An error occurred";
|
|
57
|
+
let bodyCode;
|
|
58
|
+
try {
|
|
59
|
+
const body = await res.json();
|
|
60
|
+
if (body?.message) message = body.message;
|
|
61
|
+
else if (body?.error) message = body.error;
|
|
62
|
+
if (body?.code) bodyCode = body.code;
|
|
63
|
+
} catch {
|
|
64
|
+
}
|
|
65
|
+
return new _BetterCMSError(message, res.status, statusToCode(res.status), bodyCode);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// src/methods/getContent.ts
|
|
70
|
+
async function getContent(client, slug) {
|
|
71
|
+
const data = await client.fetchJSON(client.url(slug));
|
|
72
|
+
return data.data;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/methods/listContent.ts
|
|
76
|
+
async function listContent(client, opts) {
|
|
77
|
+
const params2 = new URLSearchParams({
|
|
78
|
+
page: String(opts?.page ?? 1),
|
|
79
|
+
perPage: String(opts?.perPage ?? 20)
|
|
80
|
+
});
|
|
81
|
+
const data = await client.fetchJSON(
|
|
82
|
+
`${client.url("")}?${params2}`
|
|
83
|
+
);
|
|
84
|
+
return data.data;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/methods/listContentAll.ts
|
|
88
|
+
function listContentAll(client, opts) {
|
|
89
|
+
const perPage = opts?.perPage ?? 100;
|
|
90
|
+
return {
|
|
91
|
+
/**
|
|
92
|
+
* Async iterator — yields Content items page by page.
|
|
93
|
+
*/
|
|
94
|
+
async *[Symbol.asyncIterator]() {
|
|
95
|
+
let page = opts?.page ?? 1;
|
|
96
|
+
while (true) {
|
|
97
|
+
if (page > 100) {
|
|
98
|
+
throw new Error(
|
|
99
|
+
"listContentAll: exceeded 100 pages \u2014 possible infinite loop"
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
const params2 = new URLSearchParams({
|
|
103
|
+
page: String(page),
|
|
104
|
+
perPage: String(perPage)
|
|
105
|
+
});
|
|
106
|
+
const result = await client.fetchJSON(
|
|
107
|
+
`${client.url("")}?${params2}`
|
|
108
|
+
);
|
|
109
|
+
const pageData = result.data;
|
|
110
|
+
if (!pageData?.items?.length) break;
|
|
111
|
+
for (const item of pageData.items) {
|
|
112
|
+
yield item;
|
|
113
|
+
}
|
|
114
|
+
if (!pageData?.hasNextPage) break;
|
|
115
|
+
page++;
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
/**
|
|
119
|
+
* Convenience: collect all items into an array, optionally limited.
|
|
120
|
+
*/
|
|
121
|
+
async all(options) {
|
|
122
|
+
const items = [];
|
|
123
|
+
for await (const item of this) {
|
|
124
|
+
items.push(item);
|
|
125
|
+
if (options?.limit && items.length >= options.limit) break;
|
|
126
|
+
}
|
|
127
|
+
return items;
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// src/delivery-client.ts
|
|
133
|
+
var BetterCMSDeliveryClient = class {
|
|
134
|
+
workspace;
|
|
135
|
+
baseUrl;
|
|
136
|
+
apiKey;
|
|
137
|
+
constructor(opts) {
|
|
138
|
+
this.workspace = opts.workspace;
|
|
139
|
+
this.baseUrl = opts.baseUrl;
|
|
140
|
+
this.apiKey = opts.apiKey;
|
|
141
|
+
}
|
|
142
|
+
getContent(slug) {
|
|
143
|
+
return getContent(this, slug);
|
|
144
|
+
}
|
|
145
|
+
listContent(opts) {
|
|
146
|
+
return listContent(this, opts);
|
|
147
|
+
}
|
|
148
|
+
listContentAll(opts) {
|
|
149
|
+
return listContentAll(this, opts);
|
|
150
|
+
}
|
|
151
|
+
/** Build the full URL for a content path. */
|
|
152
|
+
url(path) {
|
|
153
|
+
return `${this.baseUrl}/${this.workspace}/content/${path}`;
|
|
154
|
+
}
|
|
155
|
+
/** Build request headers, including auth if provided. */
|
|
156
|
+
headers() {
|
|
157
|
+
const headers = { "Content-Type": "application/json" };
|
|
158
|
+
if (this.apiKey) {
|
|
159
|
+
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
160
|
+
}
|
|
161
|
+
return headers;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Generic fetch helper — wraps a request with network-error handling
|
|
165
|
+
* and throws a typed BetterCMSError on non-OK responses.
|
|
166
|
+
*/
|
|
167
|
+
async fetchJSON(url) {
|
|
168
|
+
let res;
|
|
169
|
+
try {
|
|
170
|
+
res = await globalThis.fetch(url, { headers: this.headers() });
|
|
171
|
+
} catch (err) {
|
|
172
|
+
throw new BetterCMSError(
|
|
173
|
+
`Network error: ${err instanceof Error ? err.message : String(err)}`,
|
|
174
|
+
0,
|
|
175
|
+
"NETWORK_ERROR"
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
if (!res.ok) {
|
|
179
|
+
throw await BetterCMSError.from(res);
|
|
180
|
+
}
|
|
181
|
+
return await res.json();
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
// src/methods/webhooks.ts
|
|
186
|
+
function addWebhookMethods(client) {
|
|
187
|
+
return {
|
|
188
|
+
/**
|
|
189
|
+
* List webhooks for a project.
|
|
190
|
+
*/
|
|
191
|
+
list: async (options) => {
|
|
192
|
+
const params2 = new URLSearchParams();
|
|
193
|
+
if (options?.page) params2.set("page", String(options.page));
|
|
194
|
+
if (options?.limit) params2.set("limit", String(options.limit));
|
|
195
|
+
return client.fetchJSON(
|
|
196
|
+
client.url(`/webhooks?${params2}`),
|
|
197
|
+
{ method: "GET" }
|
|
198
|
+
);
|
|
199
|
+
},
|
|
200
|
+
/**
|
|
201
|
+
* Create a new webhook. Returns the webhook with secret (shown once).
|
|
202
|
+
*/
|
|
203
|
+
create: async (input) => {
|
|
204
|
+
return client.fetchJSON(
|
|
205
|
+
client.url("/webhooks"),
|
|
206
|
+
{
|
|
207
|
+
method: "POST",
|
|
208
|
+
headers: { "Content-Type": "application/json" },
|
|
209
|
+
body: JSON.stringify(input)
|
|
210
|
+
}
|
|
211
|
+
);
|
|
212
|
+
},
|
|
213
|
+
/**
|
|
214
|
+
* Get a single webhook.
|
|
215
|
+
*/
|
|
216
|
+
get: async (webhookId) => {
|
|
217
|
+
return client.fetchJSON(
|
|
218
|
+
client.url(`/webhooks/${webhookId}`),
|
|
219
|
+
{ method: "GET" }
|
|
220
|
+
);
|
|
221
|
+
},
|
|
222
|
+
/**
|
|
223
|
+
* Update a webhook.
|
|
224
|
+
*/
|
|
225
|
+
update: async (webhookId, input) => {
|
|
226
|
+
return client.fetchJSON(
|
|
227
|
+
client.url(`/webhooks/${webhookId}`),
|
|
228
|
+
{
|
|
229
|
+
method: "PATCH",
|
|
230
|
+
headers: { "Content-Type": "application/json" },
|
|
231
|
+
body: JSON.stringify(input)
|
|
232
|
+
}
|
|
233
|
+
);
|
|
234
|
+
},
|
|
235
|
+
/**
|
|
236
|
+
* Delete a webhook.
|
|
237
|
+
*/
|
|
238
|
+
delete: async (webhookId) => {
|
|
239
|
+
return client.fetchJSON(
|
|
240
|
+
client.url(`/webhooks/${webhookId}`),
|
|
241
|
+
{ method: "DELETE" }
|
|
242
|
+
);
|
|
243
|
+
},
|
|
244
|
+
/**
|
|
245
|
+
* Test a webhook delivery.
|
|
246
|
+
*/
|
|
247
|
+
test: async (webhookId, event) => {
|
|
248
|
+
return client.fetchJSON(
|
|
249
|
+
client.url(`/webhooks/${webhookId}/test`),
|
|
250
|
+
{
|
|
251
|
+
method: "POST",
|
|
252
|
+
headers: { "Content-Type": "application/json" },
|
|
253
|
+
body: JSON.stringify({ event: event ?? "content.publish" })
|
|
254
|
+
}
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// src/admin-client.ts
|
|
261
|
+
var DEFAULT_BASE_URL = "https://api.bettercms.ai/v1";
|
|
262
|
+
var DEFAULT_TIMEOUT = 1e4;
|
|
263
|
+
var RETRY_STATUSES = [429, 503];
|
|
264
|
+
var RETRY_DELAYS = [1e3, 2e3, 4e3];
|
|
265
|
+
var BetterCMSAdminClient = class extends BetterCMSDeliveryClient {
|
|
266
|
+
timeout;
|
|
267
|
+
_token;
|
|
268
|
+
constructor(options) {
|
|
269
|
+
super({
|
|
270
|
+
workspace: "",
|
|
271
|
+
baseUrl: options.baseUrl ?? DEFAULT_BASE_URL
|
|
272
|
+
});
|
|
273
|
+
this._token = options.token;
|
|
274
|
+
this.timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
275
|
+
}
|
|
276
|
+
headers() {
|
|
277
|
+
return {
|
|
278
|
+
...super.headers(),
|
|
279
|
+
Authorization: `Bearer ${this._token}`,
|
|
280
|
+
"Content-Type": "application/json"
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Admin fetchJSON with retry (429, 503) and per-request timeout (AbortController).
|
|
285
|
+
* Each retry gets a fresh timeout.
|
|
286
|
+
*/
|
|
287
|
+
async fetchJSON(url, init) {
|
|
288
|
+
let lastError;
|
|
289
|
+
for (let attempt = 0; attempt <= 3; attempt++) {
|
|
290
|
+
const controller = new AbortController();
|
|
291
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
292
|
+
let nonRetryable = false;
|
|
293
|
+
try {
|
|
294
|
+
const res = await globalThis.fetch(url, {
|
|
295
|
+
...init,
|
|
296
|
+
headers: { ...this.headers(), ...init?.headers ?? {} },
|
|
297
|
+
signal: controller.signal
|
|
298
|
+
});
|
|
299
|
+
clearTimeout(timeoutId);
|
|
300
|
+
if (res.ok) {
|
|
301
|
+
return res.json();
|
|
302
|
+
}
|
|
303
|
+
const shouldRetry = attempt < 3 && RETRY_STATUSES.includes(res.status);
|
|
304
|
+
if (!shouldRetry) {
|
|
305
|
+
nonRetryable = true;
|
|
306
|
+
throw await BetterCMSError.from(res);
|
|
307
|
+
}
|
|
308
|
+
await sleep(RETRY_DELAYS[attempt] ?? 0);
|
|
309
|
+
} catch (err) {
|
|
310
|
+
clearTimeout(timeoutId);
|
|
311
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
312
|
+
throw new BetterCMSError("Request timeout", 408, "NETWORK_ERROR");
|
|
313
|
+
}
|
|
314
|
+
if (nonRetryable) {
|
|
315
|
+
throw err;
|
|
316
|
+
}
|
|
317
|
+
lastError = err;
|
|
318
|
+
if (attempt < 3) {
|
|
319
|
+
await sleep(RETRY_DELAYS[attempt] ?? 0);
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
throw err;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
throw lastError ?? new BetterCMSError("Unreachable", 0, "NETWORK_ERROR");
|
|
326
|
+
}
|
|
327
|
+
/** Build the admin API URL. */
|
|
328
|
+
url(path) {
|
|
329
|
+
return `${this.baseUrl}${path}`;
|
|
330
|
+
}
|
|
331
|
+
/** Webhook management methods. */
|
|
332
|
+
webhooks() {
|
|
333
|
+
return addWebhookMethods(this);
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
function sleep(ms) {
|
|
337
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// src/methods/management/content.ts
|
|
341
|
+
async function listModels(client) {
|
|
342
|
+
const res = await client.fetchJSON(
|
|
343
|
+
client.url("/management/content/models"),
|
|
344
|
+
{ method: "GET" }
|
|
345
|
+
);
|
|
346
|
+
return res.data;
|
|
347
|
+
}
|
|
348
|
+
async function getModel(client, id) {
|
|
349
|
+
const res = await client.fetchJSON(
|
|
350
|
+
client.url(`/management/content/models/${id}`),
|
|
351
|
+
{ method: "GET" }
|
|
352
|
+
);
|
|
353
|
+
return res.data;
|
|
354
|
+
}
|
|
355
|
+
async function listManagedPages(client) {
|
|
356
|
+
const res = await client.fetchJSON(
|
|
357
|
+
client.url("/management/pages"),
|
|
358
|
+
{ method: "GET" }
|
|
359
|
+
);
|
|
360
|
+
return res.data;
|
|
361
|
+
}
|
|
362
|
+
async function createManagedPage(client, input) {
|
|
363
|
+
const res = await client.fetchJSON(
|
|
364
|
+
client.url("/management/pages"),
|
|
365
|
+
{ method: "POST", body: JSON.stringify(input) }
|
|
366
|
+
);
|
|
367
|
+
return res.data;
|
|
368
|
+
}
|
|
369
|
+
async function addPageFields(client, id, input) {
|
|
370
|
+
const res = await client.fetchJSON(
|
|
371
|
+
client.url(`/management/pages/${id}`),
|
|
372
|
+
{ method: "PATCH", body: JSON.stringify(input) }
|
|
373
|
+
);
|
|
374
|
+
return res.data;
|
|
375
|
+
}
|
|
376
|
+
async function getManagedPage(client, id) {
|
|
377
|
+
const res = await client.fetchJSON(
|
|
378
|
+
client.url(`/management/pages/${id}`),
|
|
379
|
+
{ method: "GET" }
|
|
380
|
+
);
|
|
381
|
+
return res.data;
|
|
382
|
+
}
|
|
383
|
+
async function setPageContent(client, id, input) {
|
|
384
|
+
const res = await client.fetchJSON(
|
|
385
|
+
client.url(`/management/pages/${id}/content`),
|
|
386
|
+
{ method: "PUT", body: JSON.stringify(input) }
|
|
387
|
+
);
|
|
388
|
+
return res.data;
|
|
389
|
+
}
|
|
390
|
+
async function addModelFields(client, id, input) {
|
|
391
|
+
const res = await client.fetchJSON(
|
|
392
|
+
client.url(`/management/content/models/${id}/fields`),
|
|
393
|
+
{ method: "POST", body: JSON.stringify(input) }
|
|
394
|
+
);
|
|
395
|
+
return res.data;
|
|
396
|
+
}
|
|
397
|
+
async function listEntries(client, filter) {
|
|
398
|
+
const q = new URLSearchParams();
|
|
399
|
+
if (filter?.modelId) q.set("modelId", filter.modelId);
|
|
400
|
+
if (filter?.pageId) q.set("pageId", filter.pageId);
|
|
401
|
+
if (filter?.status) q.set("status", filter.status);
|
|
402
|
+
const qs3 = q.toString();
|
|
403
|
+
const res = await client.fetchJSON(
|
|
404
|
+
client.url(`/management/content/entries${qs3 ? `?${qs3}` : ""}`),
|
|
405
|
+
{ method: "GET" }
|
|
406
|
+
);
|
|
407
|
+
return res.data;
|
|
408
|
+
}
|
|
409
|
+
async function getEntry(client, id) {
|
|
410
|
+
const res = await client.fetchJSON(
|
|
411
|
+
client.url(`/management/content/entries/${id}`),
|
|
412
|
+
{ method: "GET" }
|
|
413
|
+
);
|
|
414
|
+
return res.data;
|
|
415
|
+
}
|
|
416
|
+
async function createModel(client, input) {
|
|
417
|
+
const res = await client.fetchJSON(
|
|
418
|
+
client.url("/management/content/models"),
|
|
419
|
+
{ method: "POST", body: JSON.stringify(input) }
|
|
420
|
+
);
|
|
421
|
+
return res.data;
|
|
422
|
+
}
|
|
423
|
+
async function updateModel(client, id, input) {
|
|
424
|
+
const res = await client.fetchJSON(
|
|
425
|
+
client.url(`/management/content/models/${id}`),
|
|
426
|
+
{ method: "PATCH", body: JSON.stringify(input) }
|
|
427
|
+
);
|
|
428
|
+
return res.data;
|
|
429
|
+
}
|
|
430
|
+
async function createEntry(client, input) {
|
|
431
|
+
const res = await client.fetchJSON(
|
|
432
|
+
client.url("/management/content/entries"),
|
|
433
|
+
{ method: "POST", body: JSON.stringify(input) }
|
|
434
|
+
);
|
|
435
|
+
return res.data;
|
|
436
|
+
}
|
|
437
|
+
async function updateEntry(client, id, input, opts) {
|
|
438
|
+
const headers = {};
|
|
439
|
+
if (opts?.ifMatch !== void 0) headers["If-Match"] = `W/"${opts.ifMatch}"`;
|
|
440
|
+
const res = await client.fetchJSON(
|
|
441
|
+
client.url(`/management/content/entries/${id}`),
|
|
442
|
+
{ method: "PATCH", body: JSON.stringify(input), headers }
|
|
443
|
+
);
|
|
444
|
+
return res.data;
|
|
445
|
+
}
|
|
446
|
+
async function deleteManagedPage(client, id) {
|
|
447
|
+
const res = await client.fetchJSON(
|
|
448
|
+
client.url(`/management/pages/${id}`),
|
|
449
|
+
{ method: "DELETE" }
|
|
450
|
+
);
|
|
451
|
+
return res.data;
|
|
452
|
+
}
|
|
453
|
+
async function deleteEntry(client, id) {
|
|
454
|
+
const res = await client.fetchJSON(
|
|
455
|
+
client.url(`/management/content/entries/${id}`),
|
|
456
|
+
{ method: "DELETE" }
|
|
457
|
+
);
|
|
458
|
+
return res.data;
|
|
459
|
+
}
|
|
460
|
+
async function deleteModel(client, id) {
|
|
461
|
+
const res = await client.fetchJSON(
|
|
462
|
+
client.url(`/management/content/models/${id}`),
|
|
463
|
+
{ method: "DELETE" }
|
|
464
|
+
);
|
|
465
|
+
return res.data;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// src/methods/management/media.ts
|
|
469
|
+
var MIME_BY_EXT = {
|
|
470
|
+
png: "image/png",
|
|
471
|
+
jpg: "image/jpeg",
|
|
472
|
+
jpeg: "image/jpeg",
|
|
473
|
+
gif: "image/gif",
|
|
474
|
+
webp: "image/webp",
|
|
475
|
+
svg: "image/svg+xml",
|
|
476
|
+
avif: "image/avif",
|
|
477
|
+
mp4: "video/mp4",
|
|
478
|
+
webm: "video/webm",
|
|
479
|
+
pdf: "application/pdf"
|
|
480
|
+
};
|
|
481
|
+
function basenameOf(p) {
|
|
482
|
+
const clean = p.split(/[?#]/)[0] ?? p;
|
|
483
|
+
const seg = clean.split(/[\\/]/).pop();
|
|
484
|
+
return seg && seg.length > 0 ? seg : "asset";
|
|
485
|
+
}
|
|
486
|
+
function mimeFromName(name) {
|
|
487
|
+
const ext = name.split(".").pop()?.toLowerCase() ?? "";
|
|
488
|
+
return MIME_BY_EXT[ext] ?? "application/octet-stream";
|
|
489
|
+
}
|
|
490
|
+
var MAX_REDIRECTS = 5;
|
|
491
|
+
function isPrivateIp(ip) {
|
|
492
|
+
const addr = ip.trim().toLowerCase();
|
|
493
|
+
const mapped = addr.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/);
|
|
494
|
+
const v4 = mapped ? mapped[1] : addr;
|
|
495
|
+
if (/^\d+\.\d+\.\d+\.\d+$/.test(v4)) {
|
|
496
|
+
const o = v4.split(".").map((n) => Number(n));
|
|
497
|
+
if (o.some((n) => Number.isNaN(n) || n < 0 || n > 255)) return true;
|
|
498
|
+
const [a, b] = o;
|
|
499
|
+
if (a === 0 || a === 127) return true;
|
|
500
|
+
if (a === 10) return true;
|
|
501
|
+
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
502
|
+
if (a === 192 && b === 168) return true;
|
|
503
|
+
if (a === 169 && b === 254) return true;
|
|
504
|
+
if (a === 100 && b >= 64 && b <= 127) return true;
|
|
505
|
+
if (a >= 224) return true;
|
|
506
|
+
return false;
|
|
507
|
+
}
|
|
508
|
+
if (addr === "::" || addr === "::1") return true;
|
|
509
|
+
if (addr.startsWith("fe80") || addr.startsWith("fe9") || addr.startsWith("fea") || addr.startsWith("feb")) return true;
|
|
510
|
+
if (/^f[cd]/.test(addr)) return true;
|
|
511
|
+
return false;
|
|
512
|
+
}
|
|
513
|
+
async function assertPublicHttpUrl(raw) {
|
|
514
|
+
let u;
|
|
515
|
+
try {
|
|
516
|
+
u = new URL(raw);
|
|
517
|
+
} catch {
|
|
518
|
+
throw new Error(`Invalid URL: ${raw}`);
|
|
519
|
+
}
|
|
520
|
+
if (u.protocol !== "http:" && u.protocol !== "https:") {
|
|
521
|
+
throw new Error(`Refusing non-http(s) URL scheme: ${u.protocol}`);
|
|
522
|
+
}
|
|
523
|
+
const host = u.hostname.replace(/^\[|\]$/g, "");
|
|
524
|
+
if (/^\d+\.\d+\.\d+\.\d+$/.test(host) || host.includes(":")) {
|
|
525
|
+
if (isPrivateIp(host)) throw new Error(`Refusing private/loopback address: ${host}`);
|
|
526
|
+
return u;
|
|
527
|
+
}
|
|
528
|
+
const { lookup } = await import("dns/promises");
|
|
529
|
+
const records = await lookup(host, { all: true });
|
|
530
|
+
if (records.length === 0) throw new Error(`Could not resolve host: ${host}`);
|
|
531
|
+
for (const r of records) {
|
|
532
|
+
if (isPrivateIp(r.address)) {
|
|
533
|
+
throw new Error(`Host ${host} resolves to a private/loopback address (${r.address}); refusing`);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
return u;
|
|
537
|
+
}
|
|
538
|
+
async function safeFetch(raw) {
|
|
539
|
+
let target = raw;
|
|
540
|
+
for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {
|
|
541
|
+
await assertPublicHttpUrl(target);
|
|
542
|
+
const res = await globalThis.fetch(target, { redirect: "manual" });
|
|
543
|
+
if (res.status >= 300 && res.status < 400) {
|
|
544
|
+
const loc = res.headers.get("location");
|
|
545
|
+
if (!loc) return res;
|
|
546
|
+
target = new URL(loc, target).toString();
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
return res;
|
|
550
|
+
}
|
|
551
|
+
throw new Error(`Too many redirects ingesting ${raw}`);
|
|
552
|
+
}
|
|
553
|
+
async function uploadAsset(client, input) {
|
|
554
|
+
let bytes;
|
|
555
|
+
let mime;
|
|
556
|
+
let filename;
|
|
557
|
+
if (input.localPath) {
|
|
558
|
+
if (input.localPath.includes("..") || /(^|[\\/])~($|[\\/])/.test(input.localPath)) {
|
|
559
|
+
throw new Error(`Refusing localPath with traversal or home-dir reference: ${input.localPath}`);
|
|
560
|
+
}
|
|
561
|
+
const { readFile } = await import("fs/promises");
|
|
562
|
+
const buf = await readFile(input.localPath);
|
|
563
|
+
bytes = new Uint8Array(buf);
|
|
564
|
+
filename = input.filename ?? basenameOf(input.localPath);
|
|
565
|
+
mime = mimeFromName(filename);
|
|
566
|
+
} else if (input.url) {
|
|
567
|
+
const res = await safeFetch(input.url);
|
|
568
|
+
if (!res.ok) {
|
|
569
|
+
throw new Error(`Failed to fetch ${input.url}: ${res.status}`);
|
|
570
|
+
}
|
|
571
|
+
bytes = new Uint8Array(await res.arrayBuffer());
|
|
572
|
+
filename = input.filename ?? basenameOf(input.url);
|
|
573
|
+
mime = res.headers.get("content-type")?.split(";")[0]?.trim() || mimeFromName(filename);
|
|
574
|
+
} else {
|
|
575
|
+
throw new Error("uploadAsset requires either localPath or url");
|
|
576
|
+
}
|
|
577
|
+
const params2 = new URLSearchParams({ filename });
|
|
578
|
+
if (input.altText) params2.set("altText", input.altText);
|
|
579
|
+
if (input.caption) params2.set("caption", input.caption);
|
|
580
|
+
if (input.folderId) params2.set("folderId", input.folderId);
|
|
581
|
+
const { data } = await client.fetchJSON(
|
|
582
|
+
client.url(`/management/media?${params2.toString()}`),
|
|
583
|
+
{
|
|
584
|
+
method: "POST",
|
|
585
|
+
headers: { "Content-Type": mime },
|
|
586
|
+
body: bytes
|
|
587
|
+
}
|
|
588
|
+
);
|
|
589
|
+
return data;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// src/methods/management/forms.ts
|
|
593
|
+
async function listForms(client) {
|
|
594
|
+
const res = await client.fetchJSON(
|
|
595
|
+
client.url("/management/forms"),
|
|
596
|
+
{ method: "GET" }
|
|
597
|
+
);
|
|
598
|
+
return res.data;
|
|
599
|
+
}
|
|
600
|
+
async function getForm(client, id) {
|
|
601
|
+
const res = await client.fetchJSON(
|
|
602
|
+
client.url(`/management/forms/${id}`),
|
|
603
|
+
{ method: "GET" }
|
|
604
|
+
);
|
|
605
|
+
return res.data;
|
|
606
|
+
}
|
|
607
|
+
async function createForm(client, input) {
|
|
608
|
+
const res = await client.fetchJSON(client.url("/management/forms"), {
|
|
609
|
+
method: "POST",
|
|
610
|
+
body: JSON.stringify(input)
|
|
611
|
+
});
|
|
612
|
+
return res.data;
|
|
613
|
+
}
|
|
614
|
+
async function updateForm(client, id, input) {
|
|
615
|
+
const res = await client.fetchJSON(client.url(`/management/forms/${id}`), {
|
|
616
|
+
method: "PATCH",
|
|
617
|
+
body: JSON.stringify(input)
|
|
618
|
+
});
|
|
619
|
+
return res.data;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// src/methods/management/components.ts
|
|
623
|
+
async function listComponents(client) {
|
|
624
|
+
const res = await client.fetchJSON(
|
|
625
|
+
client.url("/management/components"),
|
|
626
|
+
{ method: "GET" }
|
|
627
|
+
);
|
|
628
|
+
return res.data;
|
|
629
|
+
}
|
|
630
|
+
async function getComponent(client, id) {
|
|
631
|
+
const res = await client.fetchJSON(
|
|
632
|
+
client.url(`/management/components/${id}`),
|
|
633
|
+
{ method: "GET" }
|
|
634
|
+
);
|
|
635
|
+
return res.data;
|
|
636
|
+
}
|
|
637
|
+
async function createComponent(client, input) {
|
|
638
|
+
const res = await client.fetchJSON(client.url("/management/components"), {
|
|
639
|
+
method: "POST",
|
|
640
|
+
body: JSON.stringify(input)
|
|
641
|
+
});
|
|
642
|
+
return res.data;
|
|
643
|
+
}
|
|
644
|
+
async function updateComponent(client, id, input) {
|
|
645
|
+
const res = await client.fetchJSON(
|
|
646
|
+
client.url(`/management/components/${id}`),
|
|
647
|
+
{ method: "PATCH", body: JSON.stringify(input) }
|
|
648
|
+
);
|
|
649
|
+
return res.data;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// src/management-client.ts
|
|
653
|
+
var DEFAULT_BASE_URL2 = "https://api.bettercms.ai/v1";
|
|
654
|
+
var DEFAULT_TIMEOUT2 = 1e4;
|
|
655
|
+
var RETRY_STATUSES2 = [429, 503];
|
|
656
|
+
var RETRY_DELAYS2 = [1e3, 2e3, 4e3];
|
|
657
|
+
var BetterCMSManagementClient = class extends BetterCMSDeliveryClient {
|
|
658
|
+
timeout;
|
|
659
|
+
_apiKey;
|
|
660
|
+
constructor(options) {
|
|
661
|
+
super({ workspace: "", baseUrl: options.baseUrl ?? DEFAULT_BASE_URL2 });
|
|
662
|
+
this._apiKey = options.apiKey;
|
|
663
|
+
this.timeout = options.timeout ?? DEFAULT_TIMEOUT2;
|
|
664
|
+
}
|
|
665
|
+
headers() {
|
|
666
|
+
return {
|
|
667
|
+
"Content-Type": "application/json",
|
|
668
|
+
"X-API-Key": this._apiKey
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
/** Management API URL: baseUrl + path (path is workspace-scoped via the key). */
|
|
672
|
+
url(path) {
|
|
673
|
+
return `${this.baseUrl}${path}`;
|
|
674
|
+
}
|
|
675
|
+
/** fetchJSON with retry (429, 503) + per-request timeout (mirrors the admin client). */
|
|
676
|
+
async fetchJSON(url, init) {
|
|
677
|
+
let lastError;
|
|
678
|
+
for (let attempt = 0; attempt <= 3; attempt++) {
|
|
679
|
+
const controller = new AbortController();
|
|
680
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
681
|
+
let nonRetryable = false;
|
|
682
|
+
try {
|
|
683
|
+
const res = await globalThis.fetch(url, {
|
|
684
|
+
...init,
|
|
685
|
+
headers: { ...this.headers(), ...init?.headers ?? {} },
|
|
686
|
+
signal: controller.signal
|
|
687
|
+
});
|
|
688
|
+
clearTimeout(timeoutId);
|
|
689
|
+
if (res.ok) {
|
|
690
|
+
return res.json();
|
|
691
|
+
}
|
|
692
|
+
const shouldRetry = attempt < 3 && RETRY_STATUSES2.includes(res.status);
|
|
693
|
+
if (!shouldRetry) {
|
|
694
|
+
nonRetryable = true;
|
|
695
|
+
throw await BetterCMSError.from(res);
|
|
696
|
+
}
|
|
697
|
+
await sleep2(RETRY_DELAYS2[attempt] ?? 0);
|
|
698
|
+
} catch (err) {
|
|
699
|
+
clearTimeout(timeoutId);
|
|
700
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
701
|
+
throw new BetterCMSError("Request timeout", 408, "NETWORK_ERROR");
|
|
702
|
+
}
|
|
703
|
+
if (nonRetryable) throw err;
|
|
704
|
+
lastError = err;
|
|
705
|
+
if (attempt < 3) {
|
|
706
|
+
await sleep2(RETRY_DELAYS2[attempt] ?? 0);
|
|
707
|
+
continue;
|
|
708
|
+
}
|
|
709
|
+
throw err;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
throw lastError ?? new BetterCMSError("Unreachable", 0, "NETWORK_ERROR");
|
|
713
|
+
}
|
|
714
|
+
// ── Schema authoring ──────────────────────────────────────────────────────
|
|
715
|
+
listModels() {
|
|
716
|
+
return listModels(this);
|
|
717
|
+
}
|
|
718
|
+
getModel(id) {
|
|
719
|
+
return getModel(this, id);
|
|
720
|
+
}
|
|
721
|
+
createModel(input) {
|
|
722
|
+
return createModel(this, input);
|
|
723
|
+
}
|
|
724
|
+
updateModel(id, input) {
|
|
725
|
+
return updateModel(this, id, input);
|
|
726
|
+
}
|
|
727
|
+
// ── Page authoring (page-first: singleton + dynamic) ──────────────────────
|
|
728
|
+
listPages() {
|
|
729
|
+
return listManagedPages(this);
|
|
730
|
+
}
|
|
731
|
+
/** Get one page by id INCLUDING its field schema + pageType. */
|
|
732
|
+
getPage(id) {
|
|
733
|
+
return getManagedPage(this, id);
|
|
734
|
+
}
|
|
735
|
+
createPage(input) {
|
|
736
|
+
return createManagedPage(this, input);
|
|
737
|
+
}
|
|
738
|
+
/** Append fields to an existing page (additive — the API rejects existing keys). */
|
|
739
|
+
addPageFields(id, input) {
|
|
740
|
+
return addPageFields(this, id, input);
|
|
741
|
+
}
|
|
742
|
+
/** Set a page's field VALUES — singleton pages get/update their one entry. */
|
|
743
|
+
setPageContent(id, input) {
|
|
744
|
+
return setPageContent(this, id, input);
|
|
745
|
+
}
|
|
746
|
+
// ── Schema authoring (additive field append) ──────────────────────────────
|
|
747
|
+
/** Append fields to a content model (additive — rejects existing keys). */
|
|
748
|
+
addModelFields(id, input) {
|
|
749
|
+
return addModelFields(this, id, input);
|
|
750
|
+
}
|
|
751
|
+
// ── Content authoring ─────────────────────────────────────────────────────
|
|
752
|
+
createEntry(input) {
|
|
753
|
+
return createEntry(this, input);
|
|
754
|
+
}
|
|
755
|
+
updateEntry(id, input, opts) {
|
|
756
|
+
return updateEntry(this, id, input, opts);
|
|
757
|
+
}
|
|
758
|
+
// ── Delete (soft-delete, reversible, audit-logged) ─────────────────────────
|
|
759
|
+
/** Soft-delete a page and its content entries. */
|
|
760
|
+
deletePage(id) {
|
|
761
|
+
return deleteManagedPage(this, id);
|
|
762
|
+
}
|
|
763
|
+
/** Soft-delete a single content entry. */
|
|
764
|
+
deleteEntry(id) {
|
|
765
|
+
return deleteEntry(this, id);
|
|
766
|
+
}
|
|
767
|
+
/** Soft-delete a content model and its entries. */
|
|
768
|
+
deleteModel(id) {
|
|
769
|
+
return deleteModel(this, id);
|
|
770
|
+
}
|
|
771
|
+
/** List content entries (incl. drafts), optionally filtered by model/page/status. */
|
|
772
|
+
listEntries(filter) {
|
|
773
|
+
return listEntries(this, filter);
|
|
774
|
+
}
|
|
775
|
+
/** Get one content entry by id INCLUDING its data (incl. drafts). */
|
|
776
|
+
getEntry(id) {
|
|
777
|
+
return getEntry(this, id);
|
|
778
|
+
}
|
|
779
|
+
// ── Media (P2): upload an asset from a local path or URL, get its CDN URL ──
|
|
780
|
+
uploadAsset(input) {
|
|
781
|
+
return uploadAsset(this, input);
|
|
782
|
+
}
|
|
783
|
+
// ── Forms (read-only): discover dashboard-built forms to embed into site code ──
|
|
784
|
+
/** List forms in the key's project (or workspace-level). */
|
|
785
|
+
listForms() {
|
|
786
|
+
return listForms(this);
|
|
787
|
+
}
|
|
788
|
+
/** Get one form by id INCLUDING its field schema + settings. */
|
|
789
|
+
getForm(id) {
|
|
790
|
+
return getForm(this, id);
|
|
791
|
+
}
|
|
792
|
+
/** Create a form in the key's project. */
|
|
793
|
+
createForm(input) {
|
|
794
|
+
return createForm(this, input);
|
|
795
|
+
}
|
|
796
|
+
/** Update a form by id (passing `fields` replaces the array). */
|
|
797
|
+
updateForm(id, input) {
|
|
798
|
+
return updateForm(this, id, input);
|
|
799
|
+
}
|
|
800
|
+
// ── Components: discover + author reusable symbols ──
|
|
801
|
+
listComponents() {
|
|
802
|
+
return listComponents(this);
|
|
803
|
+
}
|
|
804
|
+
getComponent(id) {
|
|
805
|
+
return getComponent(this, id);
|
|
806
|
+
}
|
|
807
|
+
createComponent(input) {
|
|
808
|
+
return createComponent(this, input);
|
|
809
|
+
}
|
|
810
|
+
updateComponent(id, input) {
|
|
811
|
+
return updateComponent(this, id, input);
|
|
812
|
+
}
|
|
813
|
+
};
|
|
814
|
+
function sleep2(ms) {
|
|
815
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// src/auth.ts
|
|
819
|
+
var AUTH_BASE_URL = "https://api.bettercms.ai/api/v1/auth";
|
|
820
|
+
var AUTH_URL = "https://api.bettercms.ai/api/auth";
|
|
821
|
+
async function authFetch(url, body) {
|
|
822
|
+
const res = await globalThis.fetch(url, {
|
|
823
|
+
method: "POST",
|
|
824
|
+
headers: { "Content-Type": "application/json" },
|
|
825
|
+
body: JSON.stringify(body)
|
|
826
|
+
});
|
|
827
|
+
if (!res.ok) {
|
|
828
|
+
throw await BetterCMSError.from(res);
|
|
829
|
+
}
|
|
830
|
+
return res.json();
|
|
831
|
+
}
|
|
832
|
+
async function signIn(input) {
|
|
833
|
+
const data = await authFetch(`${AUTH_BASE_URL}/sign-in`, input);
|
|
834
|
+
return buildAuthResult(data);
|
|
835
|
+
}
|
|
836
|
+
async function signUp(input) {
|
|
837
|
+
const data = await authFetch(`${AUTH_BASE_URL}/sign-up`, input);
|
|
838
|
+
return buildAuthResult(data);
|
|
839
|
+
}
|
|
840
|
+
async function signInWithGoogle(options) {
|
|
841
|
+
const res = await globalThis.fetch(`${AUTH_URL}/sign-in/social`, {
|
|
842
|
+
method: "POST",
|
|
843
|
+
headers: { "Content-Type": "application/json" },
|
|
844
|
+
body: JSON.stringify({
|
|
845
|
+
provider: "google",
|
|
846
|
+
callbackURL: options?.callbackURL,
|
|
847
|
+
idToken: options?.idToken,
|
|
848
|
+
disableRedirect: !!options?.idToken
|
|
849
|
+
})
|
|
850
|
+
});
|
|
851
|
+
if (!res.ok) {
|
|
852
|
+
throw await BetterCMSError.from(res);
|
|
853
|
+
}
|
|
854
|
+
const data = await res.json();
|
|
855
|
+
if (options?.idToken) {
|
|
856
|
+
return { ...buildAuthResult(data), url: data.url, redirect: data.redirect };
|
|
857
|
+
}
|
|
858
|
+
return { ...buildAuthResult(data), url: data.url, redirect: data.redirect };
|
|
859
|
+
}
|
|
860
|
+
async function signInWithGithub(options) {
|
|
861
|
+
const res = await globalThis.fetch(`${AUTH_URL}/sign-in/social`, {
|
|
862
|
+
method: "POST",
|
|
863
|
+
headers: { "Content-Type": "application/json" },
|
|
864
|
+
body: JSON.stringify({
|
|
865
|
+
provider: "github",
|
|
866
|
+
callbackURL: options?.callbackURL,
|
|
867
|
+
idToken: options?.idToken,
|
|
868
|
+
disableRedirect: !!options?.idToken
|
|
869
|
+
})
|
|
870
|
+
});
|
|
871
|
+
if (!res.ok) {
|
|
872
|
+
throw await BetterCMSError.from(res);
|
|
873
|
+
}
|
|
874
|
+
const data = await res.json();
|
|
875
|
+
if (options?.idToken) {
|
|
876
|
+
return { ...buildAuthResult(data), url: data.url, redirect: data.redirect };
|
|
877
|
+
}
|
|
878
|
+
return { ...buildAuthResult(data), url: data.url, redirect: data.redirect };
|
|
879
|
+
}
|
|
880
|
+
function buildAuthResult(data) {
|
|
881
|
+
const admin = new BetterCMSAdminClient({ token: data.token });
|
|
882
|
+
return {
|
|
883
|
+
token: data.token,
|
|
884
|
+
user: data.user,
|
|
885
|
+
session: data.session,
|
|
886
|
+
admin,
|
|
887
|
+
signOut: async () => {
|
|
888
|
+
await globalThis.fetch(`${AUTH_BASE_URL}/sign-out`, {
|
|
889
|
+
method: "POST",
|
|
890
|
+
headers: { Authorization: `Bearer ${data.token}` }
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// src/client.ts
|
|
897
|
+
var DEFAULT_BASE_URL3 = "https://api.bettercms.ai/v1";
|
|
898
|
+
var BetterCMS = {
|
|
899
|
+
/**
|
|
900
|
+
* Returns a client for fetching published content.
|
|
901
|
+
* `apiKey` is optional — public content works without one.
|
|
902
|
+
*/
|
|
903
|
+
site(options) {
|
|
904
|
+
return new BetterCMSDeliveryClient({
|
|
905
|
+
workspace: options.workspace,
|
|
906
|
+
apiKey: options.apiKey,
|
|
907
|
+
baseUrl: options.baseUrl ?? DEFAULT_BASE_URL3
|
|
908
|
+
});
|
|
909
|
+
},
|
|
910
|
+
/**
|
|
911
|
+
* Returns a typed client for admin CRUD operations.
|
|
912
|
+
* Requires a BetterAuth session Bearer token.
|
|
913
|
+
*/
|
|
914
|
+
admin(options) {
|
|
915
|
+
return new BetterCMSAdminClient(options);
|
|
916
|
+
},
|
|
917
|
+
/**
|
|
918
|
+
* Returns a typed client for the Management API (programmatic schema + content
|
|
919
|
+
* authoring). Requires a content:manage scoped API key.
|
|
920
|
+
*/
|
|
921
|
+
management(options) {
|
|
922
|
+
return new BetterCMSManagementClient(options);
|
|
923
|
+
},
|
|
924
|
+
/**
|
|
925
|
+
* Sign in with email + password. Returns a session ready for API calls.
|
|
926
|
+
*/
|
|
927
|
+
async auth(options) {
|
|
928
|
+
return signIn(options);
|
|
929
|
+
},
|
|
930
|
+
/**
|
|
931
|
+
* Sign up with email + password + name. Returns a session ready for API calls.
|
|
932
|
+
*/
|
|
933
|
+
async signUp(options) {
|
|
934
|
+
return signUp(options);
|
|
935
|
+
}
|
|
936
|
+
};
|
|
937
|
+
|
|
938
|
+
// src/read-client.ts
|
|
939
|
+
var RETRY_STATUSES3 = [429, 503];
|
|
940
|
+
var RETRY_DELAYS3 = [250, 750, 1500];
|
|
941
|
+
var DEFAULT_TIMEOUT3 = 1e4;
|
|
942
|
+
var sleep3 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
943
|
+
function qs(params2) {
|
|
944
|
+
const sp = new URLSearchParams();
|
|
945
|
+
for (const [k, v] of Object.entries(params2)) {
|
|
946
|
+
if (v !== void 0 && v !== "") sp.set(k, String(v));
|
|
947
|
+
}
|
|
948
|
+
const s = sp.toString();
|
|
949
|
+
return s ? `?${s}` : "";
|
|
950
|
+
}
|
|
951
|
+
function unwrap(json) {
|
|
952
|
+
if (json && typeof json === "object") {
|
|
953
|
+
const o = json;
|
|
954
|
+
const isPayload = "id" in o || "slug" in o || "items" in o;
|
|
955
|
+
if (!isPayload && "data" in o) return o.data;
|
|
956
|
+
}
|
|
957
|
+
return json;
|
|
958
|
+
}
|
|
959
|
+
function createClient(options) {
|
|
960
|
+
const apiUrl = options.apiUrl.replace(/\/+$/, "");
|
|
961
|
+
const { workspace, apiKey, perspective = "published", previewToken } = options;
|
|
962
|
+
const defaultProjectId = options.projectId;
|
|
963
|
+
const timeout = options.timeout ?? DEFAULT_TIMEOUT3;
|
|
964
|
+
const base = `${apiUrl}/api/v1/delivery/${workspace}`;
|
|
965
|
+
const headers = () => {
|
|
966
|
+
const h = { "Content-Type": "application/json" };
|
|
967
|
+
if (apiKey) h["X-API-Key"] = apiKey;
|
|
968
|
+
return h;
|
|
969
|
+
};
|
|
970
|
+
async function fetchJSON(url) {
|
|
971
|
+
let lastError;
|
|
972
|
+
for (let attempt = 0; attempt <= RETRY_DELAYS3.length; attempt++) {
|
|
973
|
+
const controller = new AbortController();
|
|
974
|
+
const timer = setTimeout(() => controller.abort(), timeout);
|
|
975
|
+
try {
|
|
976
|
+
const res = await globalThis.fetch(url, {
|
|
977
|
+
headers: headers(),
|
|
978
|
+
signal: controller.signal
|
|
979
|
+
});
|
|
980
|
+
clearTimeout(timer);
|
|
981
|
+
if (res.ok) return await res.json();
|
|
982
|
+
const retryable = attempt < RETRY_DELAYS3.length && RETRY_STATUSES3.includes(res.status);
|
|
983
|
+
if (!retryable) throw await BetterCMSError.from(res);
|
|
984
|
+
await sleep3(RETRY_DELAYS3[attempt] ?? 0);
|
|
985
|
+
} catch (err) {
|
|
986
|
+
clearTimeout(timer);
|
|
987
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
988
|
+
throw new BetterCMSError("Request timeout", 408, "NETWORK_ERROR");
|
|
989
|
+
}
|
|
990
|
+
if (err instanceof BetterCMSError) throw err;
|
|
991
|
+
lastError = err;
|
|
992
|
+
if (attempt < RETRY_DELAYS3.length) {
|
|
993
|
+
await sleep3(RETRY_DELAYS3[attempt] ?? 0);
|
|
994
|
+
continue;
|
|
995
|
+
}
|
|
996
|
+
throw new BetterCMSError(
|
|
997
|
+
`Network error: ${err instanceof Error ? err.message : String(err)}`,
|
|
998
|
+
0,
|
|
999
|
+
"NETWORK_ERROR"
|
|
1000
|
+
);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
throw lastError ?? new BetterCMSError("Unreachable", 0, "NETWORK_ERROR");
|
|
1004
|
+
}
|
|
1005
|
+
const draft = perspective === "drafts";
|
|
1006
|
+
return {
|
|
1007
|
+
perspective,
|
|
1008
|
+
async getEntry(slug, opts) {
|
|
1009
|
+
const query = qs({
|
|
1010
|
+
depth: opts?.depth,
|
|
1011
|
+
select: opts?.select?.join(",")
|
|
1012
|
+
});
|
|
1013
|
+
const url = draft && previewToken ? `${apiUrl}/api/v1/preview/${encodeURIComponent(slug)}${qs({
|
|
1014
|
+
token: previewToken,
|
|
1015
|
+
depth: opts?.depth,
|
|
1016
|
+
select: opts?.select?.join(","),
|
|
1017
|
+
stega: options.stega ? "1" : void 0
|
|
1018
|
+
})}` : `${base}/content-entries/${encodeURIComponent(slug)}${query}`;
|
|
1019
|
+
return unwrap(await fetchJSON(url));
|
|
1020
|
+
},
|
|
1021
|
+
async listEntries(opts) {
|
|
1022
|
+
const url = `${base}/content-entries${qs({
|
|
1023
|
+
model: opts?.model,
|
|
1024
|
+
page: opts?.page,
|
|
1025
|
+
perPage: opts?.perPage,
|
|
1026
|
+
depth: opts?.depth,
|
|
1027
|
+
select: opts?.select?.join(",")
|
|
1028
|
+
})}`;
|
|
1029
|
+
return unwrap(await fetchJSON(url));
|
|
1030
|
+
},
|
|
1031
|
+
async getPage(slug) {
|
|
1032
|
+
let page = 1;
|
|
1033
|
+
for (; ; ) {
|
|
1034
|
+
const list = await this.listPages({ page, perPage: 100 });
|
|
1035
|
+
const found = list.items.find((p) => p.slug === slug);
|
|
1036
|
+
if (found) return found;
|
|
1037
|
+
if (!list.hasNextPage) return null;
|
|
1038
|
+
page += 1;
|
|
1039
|
+
}
|
|
1040
|
+
},
|
|
1041
|
+
async listPages(opts) {
|
|
1042
|
+
const url = `${base}/pages${qs({
|
|
1043
|
+
page: opts?.page,
|
|
1044
|
+
perPage: opts?.perPage
|
|
1045
|
+
})}`;
|
|
1046
|
+
return unwrap(await fetchJSON(url));
|
|
1047
|
+
},
|
|
1048
|
+
async listForms(opts) {
|
|
1049
|
+
const url = `${base}/forms${qs({
|
|
1050
|
+
projectId: opts?.projectId ?? defaultProjectId
|
|
1051
|
+
})}`;
|
|
1052
|
+
return unwrap(
|
|
1053
|
+
await fetchJSON(url)
|
|
1054
|
+
);
|
|
1055
|
+
},
|
|
1056
|
+
tag(resource, id) {
|
|
1057
|
+
return id ? `bcms:${workspace}:${resource}:${id}` : `bcms:${workspace}:${resource}`;
|
|
1058
|
+
}
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
// src/stega.ts
|
|
1063
|
+
var ZERO = "\u200B";
|
|
1064
|
+
var ONE = "\u200C";
|
|
1065
|
+
var START = "\u2060";
|
|
1066
|
+
var END = "\u2061";
|
|
1067
|
+
var FRAME = new RegExp(`${START}([${ZERO}${ONE}]*)${END}`);
|
|
1068
|
+
function toBits(s) {
|
|
1069
|
+
const bytes = new TextEncoder().encode(s);
|
|
1070
|
+
let out = "";
|
|
1071
|
+
for (const b of bytes) {
|
|
1072
|
+
for (let i = 7; i >= 0; i--) out += b >> i & 1 ? ONE : ZERO;
|
|
1073
|
+
}
|
|
1074
|
+
return out;
|
|
1075
|
+
}
|
|
1076
|
+
function fromBits(bits) {
|
|
1077
|
+
const bytes = [];
|
|
1078
|
+
for (let i = 0; i + 8 <= bits.length; i += 8) {
|
|
1079
|
+
let b = 0;
|
|
1080
|
+
for (let j = 0; j < 8; j++) b = b << 1 | (bits[i + j] === ONE ? 1 : 0);
|
|
1081
|
+
bytes.push(b);
|
|
1082
|
+
}
|
|
1083
|
+
return new TextDecoder().decode(new Uint8Array(bytes));
|
|
1084
|
+
}
|
|
1085
|
+
function encodeStega(clean, payload) {
|
|
1086
|
+
return `${clean}${START}${toBits(JSON.stringify(payload))}${END}`;
|
|
1087
|
+
}
|
|
1088
|
+
function decodeStega(value) {
|
|
1089
|
+
const m = value.match(FRAME);
|
|
1090
|
+
if (!m || m[1] == null) return null;
|
|
1091
|
+
try {
|
|
1092
|
+
const payload = JSON.parse(fromBits(m[1]));
|
|
1093
|
+
return { clean: value.replace(FRAME, ""), payload };
|
|
1094
|
+
} catch {
|
|
1095
|
+
return null;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
function stripStega(value) {
|
|
1099
|
+
return value.replace(FRAME, "");
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
// src/seo.ts
|
|
1103
|
+
function jsonLdNodes(schema) {
|
|
1104
|
+
if (!schema) return [];
|
|
1105
|
+
const arr = Array.isArray(schema) ? schema : [schema];
|
|
1106
|
+
return arr.filter((node) => node && Object.keys(node).length > 0);
|
|
1107
|
+
}
|
|
1108
|
+
function resolveSeo(page, defaults = {}) {
|
|
1109
|
+
const meta = page.metaJson ?? {};
|
|
1110
|
+
const title = page.metaTitle || page.title || "";
|
|
1111
|
+
const description = page.metaDescription || defaults.metaDescription || "";
|
|
1112
|
+
const ogImage = meta.og?.image || defaults.og?.image || defaults.ogImage || "";
|
|
1113
|
+
const ogTitle = meta.og?.title || title;
|
|
1114
|
+
const ogDescription = meta.og?.description || description;
|
|
1115
|
+
const twImage = meta.twitter?.image || ogImage;
|
|
1116
|
+
const canonical = meta.canonical || "";
|
|
1117
|
+
return {
|
|
1118
|
+
title,
|
|
1119
|
+
description,
|
|
1120
|
+
canonical,
|
|
1121
|
+
og: {
|
|
1122
|
+
title: ogTitle,
|
|
1123
|
+
description: ogDescription,
|
|
1124
|
+
type: meta.og?.type || defaults.og?.type || "website",
|
|
1125
|
+
image: ogImage,
|
|
1126
|
+
url: canonical
|
|
1127
|
+
},
|
|
1128
|
+
twitter: {
|
|
1129
|
+
card: meta.twitter?.card || defaults.twitter?.card || (twImage ? "summary_large_image" : "summary"),
|
|
1130
|
+
title: meta.twitter?.title || ogTitle,
|
|
1131
|
+
description: meta.twitter?.description || ogDescription,
|
|
1132
|
+
image: twImage,
|
|
1133
|
+
site: defaults.twitterHandle || ""
|
|
1134
|
+
},
|
|
1135
|
+
jsonLd: [...jsonLdNodes(defaults.siteSchema), ...jsonLdNodes(meta.schema)]
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
// src/search.ts
|
|
1140
|
+
var DEFAULT_BASE_URL4 = "https://api.bettercms.ai";
|
|
1141
|
+
async function search(opts) {
|
|
1142
|
+
const q = opts.q.trim();
|
|
1143
|
+
if (!q) return [];
|
|
1144
|
+
const baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL4).replace(/\/+$/, "");
|
|
1145
|
+
const doFetch = opts.fetchImpl ?? globalThis.fetch;
|
|
1146
|
+
const params2 = new URLSearchParams({ project: opts.project, q });
|
|
1147
|
+
if (opts.limit) params2.set("limit", String(opts.limit));
|
|
1148
|
+
try {
|
|
1149
|
+
const res = await doFetch(`${baseUrl}/api/v1/delivery/search?${params2}`, { signal: opts.signal });
|
|
1150
|
+
if (!res.ok) return [];
|
|
1151
|
+
const json = await res.json().catch(() => ({}));
|
|
1152
|
+
return json.hits ?? [];
|
|
1153
|
+
} catch {
|
|
1154
|
+
return [];
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
// src/index.ts
|
|
1159
|
+
import { default as default2, imageUrl } from "@bettercms-ai/image-url";
|
|
1160
|
+
|
|
1161
|
+
// src/methods/admin/pages.ts
|
|
1162
|
+
function params(opts) {
|
|
1163
|
+
const p = new URLSearchParams();
|
|
1164
|
+
if (opts?.page != null) p.set("page", String(opts.page));
|
|
1165
|
+
if (opts?.limit != null) p.set("limit", String(opts.limit));
|
|
1166
|
+
return p.toString() ? `?${p}` : "";
|
|
1167
|
+
}
|
|
1168
|
+
async function listPages(client, opts) {
|
|
1169
|
+
const res = await client.fetchJSON(
|
|
1170
|
+
client.url(`/pages${params(opts)}`)
|
|
1171
|
+
);
|
|
1172
|
+
return res.data;
|
|
1173
|
+
}
|
|
1174
|
+
async function getPage(client, id) {
|
|
1175
|
+
const res = await client.fetchJSON(client.url(`/pages/${id}`));
|
|
1176
|
+
return res.data;
|
|
1177
|
+
}
|
|
1178
|
+
async function createPage(client, data) {
|
|
1179
|
+
const res = await client.fetchJSON(client.url("/pages"), {
|
|
1180
|
+
method: "POST",
|
|
1181
|
+
body: JSON.stringify(data)
|
|
1182
|
+
});
|
|
1183
|
+
return res.data;
|
|
1184
|
+
}
|
|
1185
|
+
async function updatePage(client, id, data) {
|
|
1186
|
+
const res = await client.fetchJSON(client.url(`/pages/${id}`), {
|
|
1187
|
+
method: "PATCH",
|
|
1188
|
+
body: JSON.stringify(data)
|
|
1189
|
+
});
|
|
1190
|
+
return res.data;
|
|
1191
|
+
}
|
|
1192
|
+
async function deletePage(client, id) {
|
|
1193
|
+
await client.fetchJSON(client.url(`/pages/${id}`), { method: "DELETE" });
|
|
1194
|
+
}
|
|
1195
|
+
async function publishPage(client, id) {
|
|
1196
|
+
const res = await client.fetchJSON(client.url(`/pages/${id}/publish`), {
|
|
1197
|
+
method: "POST"
|
|
1198
|
+
});
|
|
1199
|
+
return res.data;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
// src/methods/admin/media.ts
|
|
1203
|
+
async function listMedia(client, opts) {
|
|
1204
|
+
const p = new URLSearchParams();
|
|
1205
|
+
if (opts?.page != null) p.set("page", String(opts.page));
|
|
1206
|
+
if (opts?.limit != null) p.set("limit", String(opts.limit));
|
|
1207
|
+
const qs3 = p.toString() ? `?${p}` : "";
|
|
1208
|
+
const res = await client.fetchJSON(
|
|
1209
|
+
client.url(`/media${qs3}`)
|
|
1210
|
+
);
|
|
1211
|
+
return res.data;
|
|
1212
|
+
}
|
|
1213
|
+
async function getMedia(client, id) {
|
|
1214
|
+
const res = await client.fetchJSON(client.url(`/media/${id}`));
|
|
1215
|
+
return res.data;
|
|
1216
|
+
}
|
|
1217
|
+
async function uploadMedia(client, file, metadata) {
|
|
1218
|
+
const formData = new FormData();
|
|
1219
|
+
formData.append("file", file);
|
|
1220
|
+
if (metadata?.alt) formData.append("alt", metadata.alt);
|
|
1221
|
+
if (metadata?.caption) formData.append("caption", metadata.caption);
|
|
1222
|
+
const res = await client.fetchJSON(client.url("/media"), {
|
|
1223
|
+
method: "POST",
|
|
1224
|
+
body: formData
|
|
1225
|
+
});
|
|
1226
|
+
return res.data;
|
|
1227
|
+
}
|
|
1228
|
+
async function deleteMedia(client, id) {
|
|
1229
|
+
await client.fetchJSON(client.url(`/media/${id}`), { method: "DELETE" });
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
// src/methods/admin/forms.ts
|
|
1233
|
+
async function listForms2(client, opts) {
|
|
1234
|
+
const p = new URLSearchParams();
|
|
1235
|
+
if (opts?.page != null) p.set("page", String(opts.page));
|
|
1236
|
+
if (opts?.limit != null) p.set("limit", String(opts.limit));
|
|
1237
|
+
const qs3 = p.toString() ? `?${p}` : "";
|
|
1238
|
+
const res = await client.fetchJSON(
|
|
1239
|
+
client.url(`/forms${qs3}`)
|
|
1240
|
+
);
|
|
1241
|
+
return res.data;
|
|
1242
|
+
}
|
|
1243
|
+
async function getForm2(client, id) {
|
|
1244
|
+
const res = await client.fetchJSON(client.url(`/forms/${id}`));
|
|
1245
|
+
return res.data;
|
|
1246
|
+
}
|
|
1247
|
+
async function createForm2(client, data) {
|
|
1248
|
+
const res = await client.fetchJSON(client.url("/forms"), {
|
|
1249
|
+
method: "POST",
|
|
1250
|
+
body: JSON.stringify(data)
|
|
1251
|
+
});
|
|
1252
|
+
return res.data;
|
|
1253
|
+
}
|
|
1254
|
+
async function updateForm2(client, id, data) {
|
|
1255
|
+
const res = await client.fetchJSON(client.url(`/forms/${id}`), {
|
|
1256
|
+
method: "PATCH",
|
|
1257
|
+
body: JSON.stringify(data)
|
|
1258
|
+
});
|
|
1259
|
+
return res.data;
|
|
1260
|
+
}
|
|
1261
|
+
async function deleteForm(client, id) {
|
|
1262
|
+
await client.fetchJSON(client.url(`/forms/${id}`), { method: "DELETE" });
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
// src/methods/admin/submissions.ts
|
|
1266
|
+
async function listSubmissions(client, opts) {
|
|
1267
|
+
const p = new URLSearchParams();
|
|
1268
|
+
if (opts?.page != null) p.set("page", String(opts.page));
|
|
1269
|
+
if (opts?.limit != null) p.set("limit", String(opts.limit));
|
|
1270
|
+
const qs3 = p.toString() ? `?${p}` : "";
|
|
1271
|
+
const res = await client.fetchJSON(
|
|
1272
|
+
client.url(`/submissions${qs3}`)
|
|
1273
|
+
);
|
|
1274
|
+
return res.data;
|
|
1275
|
+
}
|
|
1276
|
+
async function getSubmission(client, id) {
|
|
1277
|
+
const res = await client.fetchJSON(client.url(`/submissions/${id}`));
|
|
1278
|
+
return res.data;
|
|
1279
|
+
}
|
|
1280
|
+
async function deleteSubmission(client, id) {
|
|
1281
|
+
await client.fetchJSON(client.url(`/submissions/${id}`), { method: "DELETE" });
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
// src/methods/admin/workspaces.ts
|
|
1285
|
+
async function listWorkspaces(client) {
|
|
1286
|
+
const res = await client.fetchJSON(client.url("/workspaces"));
|
|
1287
|
+
return res.data;
|
|
1288
|
+
}
|
|
1289
|
+
async function getWorkspace(client, id) {
|
|
1290
|
+
const res = await client.fetchJSON(client.url(`/workspaces/${id}`));
|
|
1291
|
+
return res.data;
|
|
1292
|
+
}
|
|
1293
|
+
async function createWorkspace(client, data) {
|
|
1294
|
+
const res = await client.fetchJSON(client.url("/workspaces"), {
|
|
1295
|
+
method: "POST",
|
|
1296
|
+
body: JSON.stringify(data)
|
|
1297
|
+
});
|
|
1298
|
+
return res.data;
|
|
1299
|
+
}
|
|
1300
|
+
async function updateWorkspace(client, id, data) {
|
|
1301
|
+
const res = await client.fetchJSON(client.url(`/workspaces/${id}`), {
|
|
1302
|
+
method: "PATCH",
|
|
1303
|
+
body: JSON.stringify(data)
|
|
1304
|
+
});
|
|
1305
|
+
return res.data;
|
|
1306
|
+
}
|
|
1307
|
+
async function deleteWorkspace(client, id) {
|
|
1308
|
+
await client.fetchJSON(client.url(`/workspaces/${id}`), { method: "DELETE" });
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
// src/methods/admin/api-keys.ts
|
|
1312
|
+
async function listApiKeys(client) {
|
|
1313
|
+
const res = await client.fetchJSON(client.url("/api-keys"));
|
|
1314
|
+
return res.data;
|
|
1315
|
+
}
|
|
1316
|
+
async function createApiKey(client, data) {
|
|
1317
|
+
const res = await client.fetchJSON(client.url("/api-keys"), {
|
|
1318
|
+
method: "POST",
|
|
1319
|
+
body: JSON.stringify(data)
|
|
1320
|
+
});
|
|
1321
|
+
return res.data;
|
|
1322
|
+
}
|
|
1323
|
+
async function updateApiKey(client, id, data) {
|
|
1324
|
+
const res = await client.fetchJSON(client.url(`/api-keys/${id}`), {
|
|
1325
|
+
method: "PATCH",
|
|
1326
|
+
body: JSON.stringify(data)
|
|
1327
|
+
});
|
|
1328
|
+
return res.data;
|
|
1329
|
+
}
|
|
1330
|
+
async function regenerateApiKey(client, id) {
|
|
1331
|
+
const res = await client.fetchJSON(client.url(`/api-keys/${id}/regenerate`), {
|
|
1332
|
+
method: "POST"
|
|
1333
|
+
});
|
|
1334
|
+
return res.data;
|
|
1335
|
+
}
|
|
1336
|
+
async function getApiKeyUsage(client, id) {
|
|
1337
|
+
const res = await client.fetchJSON(client.url(`/api-keys/${id}/usage`));
|
|
1338
|
+
return res.data;
|
|
1339
|
+
}
|
|
1340
|
+
async function revokeApiKey(client, id) {
|
|
1341
|
+
await client.fetchJSON(client.url(`/api-keys/${id}`), { method: "DELETE" });
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
// src/methods/admin/members.ts
|
|
1345
|
+
function qs2(opts) {
|
|
1346
|
+
const p = new URLSearchParams();
|
|
1347
|
+
if (opts?.page != null) p.set("page", String(opts.page));
|
|
1348
|
+
if (opts?.limit != null) p.set("limit", String(opts.limit));
|
|
1349
|
+
return p.toString() ? `?${p}` : "";
|
|
1350
|
+
}
|
|
1351
|
+
async function listMembers(client, workspaceId, opts) {
|
|
1352
|
+
const res = await client.fetchJSON(
|
|
1353
|
+
client.url(`/workspaces/${workspaceId}/members${qs2(opts)}`)
|
|
1354
|
+
);
|
|
1355
|
+
return res.data;
|
|
1356
|
+
}
|
|
1357
|
+
async function getMember(client, workspaceId, memberId) {
|
|
1358
|
+
const res = await client.fetchJSON(
|
|
1359
|
+
client.url(`/workspaces/${workspaceId}/members/${memberId}`)
|
|
1360
|
+
);
|
|
1361
|
+
return res.data;
|
|
1362
|
+
}
|
|
1363
|
+
async function inviteMember(client, workspaceId, data) {
|
|
1364
|
+
const res = await client.fetchJSON(
|
|
1365
|
+
client.url(`/workspaces/${workspaceId}/members/invite`),
|
|
1366
|
+
{ method: "POST", body: JSON.stringify(data) }
|
|
1367
|
+
);
|
|
1368
|
+
return res.data;
|
|
1369
|
+
}
|
|
1370
|
+
async function updateMemberRole(client, workspaceId, memberId, role) {
|
|
1371
|
+
const res = await client.fetchJSON(
|
|
1372
|
+
client.url(`/workspaces/${workspaceId}/members/${memberId}`),
|
|
1373
|
+
{ method: "PATCH", body: JSON.stringify({ role }) }
|
|
1374
|
+
);
|
|
1375
|
+
return res.data;
|
|
1376
|
+
}
|
|
1377
|
+
async function removeMember(client, workspaceId, memberId) {
|
|
1378
|
+
await client.fetchJSON(
|
|
1379
|
+
client.url(`/workspaces/${workspaceId}/members/${memberId}`),
|
|
1380
|
+
{ method: "DELETE" }
|
|
1381
|
+
);
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
// src/forms.ts
|
|
1385
|
+
var DEFAULT_BASE_URL5 = "https://api.bettercms.ai";
|
|
1386
|
+
function shouldShowField(field, values) {
|
|
1387
|
+
if (!field.showIf) return true;
|
|
1388
|
+
return String(values[field.showIf.field] ?? "") === field.showIf.equals;
|
|
1389
|
+
}
|
|
1390
|
+
function formInitialValues(form, prefill = {}) {
|
|
1391
|
+
const values = {};
|
|
1392
|
+
for (const field of form.fields) {
|
|
1393
|
+
if (field.type === "checkbox" || field.type === "consent") {
|
|
1394
|
+
values[field.key] = false;
|
|
1395
|
+
} else {
|
|
1396
|
+
values[field.key] = prefill[field.key] ?? field.defaultValue ?? "";
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
return values;
|
|
1400
|
+
}
|
|
1401
|
+
async function submitForm(opts) {
|
|
1402
|
+
const baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL5).replace(/\/+$/, "");
|
|
1403
|
+
const doFetch = opts.fetchImpl ?? globalThis.fetch;
|
|
1404
|
+
const body = { data: opts.data };
|
|
1405
|
+
if (opts.turnstileToken) body["cf-turnstile-response"] = opts.turnstileToken;
|
|
1406
|
+
let res;
|
|
1407
|
+
try {
|
|
1408
|
+
res = await doFetch(`${baseUrl}/api/v1/forms/public/${opts.formId}/submissions`, {
|
|
1409
|
+
method: "POST",
|
|
1410
|
+
headers: { "Content-Type": "application/json" },
|
|
1411
|
+
body: JSON.stringify(body)
|
|
1412
|
+
});
|
|
1413
|
+
} catch (err) {
|
|
1414
|
+
throw new BetterCMSError(
|
|
1415
|
+
`Network error: ${err instanceof Error ? err.message : String(err)}`,
|
|
1416
|
+
0,
|
|
1417
|
+
ErrorCodes.NETWORK_ERROR
|
|
1418
|
+
);
|
|
1419
|
+
}
|
|
1420
|
+
if (!res.ok) {
|
|
1421
|
+
const error = await BetterCMSError.from(res.clone());
|
|
1422
|
+
const body2 = await res.json().catch(() => ({}));
|
|
1423
|
+
if (body2.errors) error.fieldErrors = body2.errors;
|
|
1424
|
+
throw error;
|
|
1425
|
+
}
|
|
1426
|
+
const json = await res.json().catch(() => ({}));
|
|
1427
|
+
return { id: String(json.id ?? "") };
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
// src/index.ts
|
|
1431
|
+
import { isBlock, getBlockType } from "@bettercms-ai/types";
|
|
1432
|
+
export {
|
|
1433
|
+
BetterCMSError as BCMSClientError,
|
|
1434
|
+
ErrorCodes as BCMSErrorCodes,
|
|
1435
|
+
BetterCMS,
|
|
1436
|
+
BetterCMSAdminClient,
|
|
1437
|
+
BetterCMSDeliveryClient,
|
|
1438
|
+
BetterCMSError,
|
|
1439
|
+
BetterCMSManagementClient,
|
|
1440
|
+
ErrorCodes,
|
|
1441
|
+
addModelFields,
|
|
1442
|
+
addPageFields,
|
|
1443
|
+
createApiKey,
|
|
1444
|
+
createClient,
|
|
1445
|
+
createEntry,
|
|
1446
|
+
createForm2 as createForm,
|
|
1447
|
+
createManagedPage,
|
|
1448
|
+
createModel,
|
|
1449
|
+
createPage,
|
|
1450
|
+
createWorkspace,
|
|
1451
|
+
decodeStega,
|
|
1452
|
+
deleteForm,
|
|
1453
|
+
deleteMedia,
|
|
1454
|
+
deletePage,
|
|
1455
|
+
deleteSubmission,
|
|
1456
|
+
deleteWorkspace,
|
|
1457
|
+
encodeStega,
|
|
1458
|
+
formInitialValues,
|
|
1459
|
+
getApiKeyUsage,
|
|
1460
|
+
getBlockType,
|
|
1461
|
+
getEntry,
|
|
1462
|
+
getForm2 as getForm,
|
|
1463
|
+
getManagedPage,
|
|
1464
|
+
getMedia,
|
|
1465
|
+
getMember,
|
|
1466
|
+
getModel,
|
|
1467
|
+
getPage,
|
|
1468
|
+
getSubmission,
|
|
1469
|
+
getWorkspace,
|
|
1470
|
+
imageUrl,
|
|
1471
|
+
default2 as imageUrlBuilder,
|
|
1472
|
+
inviteMember,
|
|
1473
|
+
isBlock,
|
|
1474
|
+
listApiKeys,
|
|
1475
|
+
listEntries,
|
|
1476
|
+
listForms2 as listForms,
|
|
1477
|
+
listManagedPages,
|
|
1478
|
+
listMedia,
|
|
1479
|
+
listMembers,
|
|
1480
|
+
listModels,
|
|
1481
|
+
listPages,
|
|
1482
|
+
listSubmissions,
|
|
1483
|
+
listWorkspaces,
|
|
1484
|
+
publishPage,
|
|
1485
|
+
regenerateApiKey,
|
|
1486
|
+
removeMember,
|
|
1487
|
+
resolveSeo,
|
|
1488
|
+
revokeApiKey,
|
|
1489
|
+
search,
|
|
1490
|
+
setPageContent,
|
|
1491
|
+
shouldShowField,
|
|
1492
|
+
signIn,
|
|
1493
|
+
signInWithGithub,
|
|
1494
|
+
signInWithGoogle,
|
|
1495
|
+
signUp,
|
|
1496
|
+
stripStega,
|
|
1497
|
+
submitForm,
|
|
1498
|
+
updateApiKey,
|
|
1499
|
+
updateEntry,
|
|
1500
|
+
updateForm2 as updateForm,
|
|
1501
|
+
updateMemberRole,
|
|
1502
|
+
updateModel,
|
|
1503
|
+
updatePage,
|
|
1504
|
+
updateWorkspace,
|
|
1505
|
+
uploadMedia
|
|
1506
|
+
};
|
|
1507
|
+
//# sourceMappingURL=index.js.map
|