@shapio/client 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/dist/index.js ADDED
@@ -0,0 +1,1009 @@
1
+ import { a as toQueryString, i as resolveUploadUrl, n as buildUploadForm, o as ADMIN_PATHS, r as createMediaApi, s as withId, t as ShapioApiError } from "./errors-BXMW0adL.js";
2
+ //#region src/admin/appUsers.ts
3
+ /** App users (Users → App users) and app roles (Settings → Roles → App roles). */
4
+ const createAppUsersApi = (request) => ({
5
+ appUsers: {
6
+ list: (query = {}) => request(`${ADMIN_PATHS.appUsers}${toQueryString(query)}`),
7
+ get: (id) => request(withId(ADMIN_PATHS.appUsers, id)),
8
+ /** Block/unblock and replace custom roles. Blocking takes effect on the user's next request. */
9
+ update: (id, body) => request(withId(ADMIN_PATHS.appUsers, id), {
10
+ method: "PATCH",
11
+ body
12
+ }),
13
+ remove: (id) => request(withId(ADMIN_PATHS.appUsers, id), { method: "DELETE" }),
14
+ /** 409 NOT_CONFIGURED without APP_AUTH_CONFIRM_EMAIL_URL; 409 ALREADY_CONFIRMED. */
15
+ resendConfirmation: (id) => request(`${withId(ADMIN_PATHS.appUsers, id)}/resend-confirmation`, { method: "POST" })
16
+ },
17
+ appRoles: {
18
+ list: () => request(ADMIN_PATHS.appRoles),
19
+ get: (id) => request(withId(ADMIN_PATHS.appRoles, id)),
20
+ create: (body) => request(ADMIN_PATHS.appRoles, {
21
+ method: "POST",
22
+ body
23
+ }),
24
+ /** 409 VERSION_CONFLICT when `expectedVersion` is stale. */
25
+ update: (id, body) => request(withId(ADMIN_PATHS.appRoles, id), {
26
+ method: "PATCH",
27
+ body
28
+ }),
29
+ remove: (id) => request(withId(ADMIN_PATHS.appRoles, id), { method: "DELETE" })
30
+ }
31
+ });
32
+ //#endregion
33
+ //#region src/admin/assist.ts
34
+ const ASSIST_PATHS = {
35
+ status: "/api/admin/assist/status",
36
+ altText: "/api/admin/assist/alt-text",
37
+ summarize: "/api/admin/assist/summarize",
38
+ translate: "/api/admin/assist/translate",
39
+ rewrite: "/api/admin/assist/rewrite",
40
+ schemaDraft: "/api/admin/assist/schema/draft",
41
+ contentOpsPropose: "/api/admin/assist/content-ops/propose",
42
+ contentOps: "/api/admin/assist/content-ops"
43
+ };
44
+ /** Editor assists with the instance's own model provider (documentation/assist.md). */
45
+ const createAssistApi = (request) => ({ assist: {
46
+ status: () => request(ASSIST_PATHS.status),
47
+ altText: (body) => request(ASSIST_PATHS.altText, {
48
+ method: "POST",
49
+ body
50
+ }),
51
+ summarize: (body) => request(ASSIST_PATHS.summarize, {
52
+ method: "POST",
53
+ body
54
+ }),
55
+ translate: (body) => request(ASSIST_PATHS.translate, {
56
+ method: "POST",
57
+ body
58
+ }),
59
+ rewrite: (body) => request(ASSIST_PATHS.rewrite, {
60
+ method: "POST",
61
+ body
62
+ }),
63
+ schemaDraft: (body) => request(ASSIST_PATHS.schemaDraft, {
64
+ method: "POST",
65
+ body
66
+ }),
67
+ /** Starts a background run (202); poll it with `contentOpsRun`. */
68
+ proposeContentOps: (body) => request(ASSIST_PATHS.contentOpsPropose, {
69
+ method: "POST",
70
+ body
71
+ }),
72
+ contentOpsRun: (runId) => request(withId(ASSIST_PATHS.contentOps, runId))
73
+ } });
74
+ //#endregion
75
+ //#region src/admin/changeSets.ts
76
+ const setPath = (id) => withId(ADMIN_PATHS.changeSets, id);
77
+ const draftPath = (id, definitionId) => `${setPath(id)}/schema/${encodeURIComponent(definitionId)}`;
78
+ /** Change sets (schema + content shipped as one snapshot) and the snapshot ledger. */
79
+ const createChangeSetsApi = (request) => ({
80
+ changeSets: {
81
+ list: (query = {}) => request(`${ADMIN_PATHS.changeSets}${toQueryString(query)}`),
82
+ get: (id) => request(setPath(id)),
83
+ create: (body) => request(ADMIN_PATHS.changeSets, {
84
+ method: "POST",
85
+ body
86
+ }),
87
+ update: (id, body) => request(setPath(id), {
88
+ method: "PATCH",
89
+ body
90
+ }),
91
+ /** Keeps the set as history (`discarded`) and deletes its schema drafts. */
92
+ discard: (id) => request(`${setPath(id)}/discard`, { method: "POST" }),
93
+ addEntry: (id, body) => request(`${setPath(id)}/items`, {
94
+ method: "POST",
95
+ body
96
+ }),
97
+ removeItem: (id, itemId) => request(`${setPath(id)}/items/${encodeURIComponent(itemId)}`, { method: "DELETE" }),
98
+ getSchemaDraft: (id, definitionId) => request(draftPath(id, definitionId)),
99
+ putSchemaDraft: (id, definitionId, body) => request(draftPath(id, definitionId), {
100
+ method: "PUT",
101
+ body
102
+ }),
103
+ removeSchemaDraft: (id, definitionId) => request(draftPath(id, definitionId), { method: "DELETE" }),
104
+ review: (id) => request(`${setPath(id)}/review`),
105
+ /**
106
+ * Resolves with the set: `shipped` (or `failed`) when it shipped inline, `shipping` (HTTP 202) when
107
+ * prerequisites run in the background. Poll `get` until it leaves `shipping`.
108
+ */
109
+ ship: (id, body) => request(`${setPath(id)}/ship`, {
110
+ method: "POST",
111
+ body
112
+ }),
113
+ schedule: (id, body) => request(`${setPath(id)}/schedule`, {
114
+ method: "POST",
115
+ body
116
+ }),
117
+ unschedule: (id) => request(`${setPath(id)}/unschedule`, { method: "POST" }),
118
+ timeline: async (id) => (await request(`${setPath(id)}/timeline`)).items,
119
+ unassigned: (query = {}) => request(`${ADMIN_PATHS.changeSets}/unassigned${toQueryString(query)}`)
120
+ },
121
+ snapshots: {
122
+ list: (query = {}) => request(`${ADMIN_PATHS.snapshots}${toQueryString(query)}`),
123
+ get: (seq) => request(`${ADMIN_PATHS.snapshots}/${seq}`),
124
+ /** Creates an open restore change set (reviewable); nothing goes live until it ships. */
125
+ restore: (seq) => request(`${ADMIN_PATHS.snapshots}/${seq}/restore`, { method: "POST" })
126
+ }
127
+ });
128
+ //#endregion
129
+ //#region src/admin/contentQuery.ts
130
+ /**
131
+ * Serialises a content list query into the bracket querystring the content compiler parses
132
+ * (`filters[title][$eq]=A&sort=title:asc&page=2`). Lists repeat the key (`filters[id][$in]=a&filters[id][$in]=b`).
133
+ */
134
+ const appendFilter = (params, prefix, value) => {
135
+ if (value === void 0 || value === null) return;
136
+ if (Array.isArray(value)) {
137
+ value.forEach((item, index) => {
138
+ if (typeof item === "object" && item !== null) appendFilter(params, `${prefix}[${index}]`, item);
139
+ else params.append(prefix, String(item));
140
+ });
141
+ return;
142
+ }
143
+ if (typeof value === "object") {
144
+ for (const [key, child] of Object.entries(value)) appendFilter(params, `${prefix}[${key}]`, child);
145
+ return;
146
+ }
147
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") params.append(prefix, String(value));
148
+ };
149
+ const toContentQueryString = (query = {}) => {
150
+ const params = new URLSearchParams();
151
+ if (query.filters) appendFilter(params, "filters", query.filters);
152
+ for (const term of query.sort ?? []) params.append("sort", `${term.field}:${term.direction}`);
153
+ if (query.page !== void 0) params.set("page", String(query.page));
154
+ if (query.pageSize !== void 0) params.set("pageSize", String(query.pageSize));
155
+ if (query.q) params.set("q", query.q);
156
+ if (query.locale) params.set("locale", query.locale);
157
+ for (const field of query.fields ?? []) params.append("fields", field);
158
+ for (const path of query.populate ?? []) params.append("populate", path);
159
+ if (query.status) params.set("status", query.status);
160
+ if (query.author) params.set("author", query.author);
161
+ const serialised = params.toString();
162
+ return serialised ? `?${serialised}` : "";
163
+ };
164
+ //#endregion
165
+ //#region src/admin/content.ts
166
+ const modelPath = (modelKey) => withId(ADMIN_PATHS.content, modelKey);
167
+ const entryPath$1 = (modelKey, id) => withId(modelPath(modelKey), id);
168
+ const revisionsPath = (modelKey, id) => `${entryPath$1(modelKey, id)}/revisions`;
169
+ /**
170
+ * Content of any model through the generic admin routes (`/api/admin/content/:modelKey`, where `modelKey`
171
+ * is the model's singular API ID for collections too; only the delivery API uses the plural). Drafts are read
172
+ * and written here; publishing is per locale. Writes carry the draft `version` the editor saw and fail with
173
+ * 409 CONTENT_VERSION_CONFLICT (or SCHEMA_CHANGED) when it moved.
174
+ */
175
+ const createContentApi = (request) => ({
176
+ content: {
177
+ list: (modelKey, query = {}) => request(`${modelPath(modelKey)}${toContentQueryString(query)}`),
178
+ get: (modelKey, id, { locale } = {}) => request(`${entryPath$1(modelKey, id)}${toQueryString({ locale })}`),
179
+ /** Fully validated (`required` included); 422 CONTENT_INVALID with `details.issues`. */
180
+ create: (modelKey, body) => request(modelPath(modelKey), {
181
+ method: "POST",
182
+ body
183
+ }),
184
+ /** Saves a patch of one locale's draft; `autosave` skips `required` and creates no revision. */
185
+ update: (modelKey, id, body) => request(entryPath$1(modelKey, id), {
186
+ method: "PUT",
187
+ body
188
+ }),
189
+ /** 409 ENTRY_REFERENCED with `details.referrers` while other entries point at it. */
190
+ remove: (modelKey, id) => request(entryPath$1(modelKey, id), { method: "DELETE" }),
191
+ duplicate: (modelKey, id) => request(`${entryPath$1(modelKey, id)}/duplicate`, { method: "POST" }),
192
+ publish: (modelKey, id, body = {}) => request(`${entryPath$1(modelKey, id)}/publish`, {
193
+ method: "POST",
194
+ body
195
+ }),
196
+ unpublish: (modelKey, id, body = {}) => request(`${entryPath$1(modelKey, id)}/unpublish`, {
197
+ method: "POST",
198
+ body
199
+ }),
200
+ revisions: async (modelKey, id, { locale } = {}) => (await request(`${revisionsPath(modelKey, id)}${toQueryString({ locale })}`)).items,
201
+ revision: (modelKey, id, revisionId) => request(withId(revisionsPath(modelKey, id), revisionId)),
202
+ /** Brings an older revision back as the new draft of its locale (a new revision). */
203
+ restore: (modelKey, id, revisionId, expectedVersion) => request(`${withId(revisionsPath(modelKey, id), revisionId)}/restore`, {
204
+ method: "POST",
205
+ body: { expectedVersion }
206
+ })
207
+ },
208
+ extensions: {
209
+ /** Custom field editors the project installed (`shapio.config` + `extensions/editors/*.js`). */
210
+ editors: () => request(ADMIN_PATHS.editorManifest) }
211
+ });
212
+ //#endregion
213
+ //#region src/admin/editing.ts
214
+ const entryPath = (base, modelKey, id) => withId(withId(base, modelKey), id);
215
+ /** The entry document's endpoints: pre-flight, content health (the Inbox), presence and counts. */
216
+ const createEditingApi = (request) => ({
217
+ preflight: {
218
+ /** What publishing these locales would run into, without publishing (no hooks, no locks). */
219
+ run: (modelKey, id, body = {}) => request(`${entryPath(ADMIN_PATHS.content, modelKey, id)}/preflight`, {
220
+ method: "POST",
221
+ body
222
+ }) },
223
+ contentHealth: {
224
+ list: (query = {}) => request(`${ADMIN_PATHS.contentHealth}${toQueryString(query)}`),
225
+ summary: () => request(`${ADMIN_PATHS.contentHealth}/summary`)
226
+ },
227
+ presence: {
228
+ /** Heartbeat (every 15s while the document is visible); returns who else has the entry open. */
229
+ heartbeat: (modelKey, id, body) => request(entryPath(ADMIN_PATHS.presence, modelKey, id), {
230
+ method: "PUT",
231
+ body
232
+ }),
233
+ entry: (modelKey, id) => request(entryPath(ADMIN_PATHS.presence, modelKey, id)),
234
+ /** Everyone editing an entry of the model (list rows). */
235
+ model: (modelKey) => request(withId(ADMIN_PATHS.presence, modelKey)),
236
+ leave: (modelKey, id, tabId) => request(`${entryPath(ADMIN_PATHS.presence, modelKey, id)}${toQueryString({ tabId })}`, { method: "DELETE" })
237
+ },
238
+ contentCounts: { list: () => request(ADMIN_PATHS.contentCounts) }
239
+ });
240
+ //#endregion
241
+ //#region src/admin/publishing.ts
242
+ /** Jobs, scheduled publications, webhooks, deployment connections and runs, preview (package H). */
243
+ const createPublishingApi = (request) => ({
244
+ jobs: {
245
+ list: (query = {}) => request(`${ADMIN_PATHS.jobs}${toQueryString(query)}`),
246
+ summary: () => request(`${ADMIN_PATHS.jobs}/summary`),
247
+ get: (id) => request(withId(ADMIN_PATHS.jobs, id)),
248
+ /** Dead jobs only: runs again from attempt 1. 409 JOB_NOT_RETRYABLE otherwise. */
249
+ retry: (id) => request(`${withId(ADMIN_PATHS.jobs, id)}/retry`, { method: "POST" })
250
+ },
251
+ schedules: {
252
+ list: (query = {}) => request(`${ADMIN_PATHS.schedules}${toQueryString(query)}`),
253
+ create: (body) => request(ADMIN_PATHS.schedules, {
254
+ method: "POST",
255
+ body
256
+ }),
257
+ cancel: (id) => request(withId(ADMIN_PATHS.schedules, id), { method: "DELETE" })
258
+ },
259
+ webhooks: {
260
+ list: () => request(ADMIN_PATHS.webhooks),
261
+ events: async () => (await request(`${ADMIN_PATHS.webhooks}/events`)).items,
262
+ get: (id) => request(withId(ADMIN_PATHS.webhooks, id)),
263
+ create: (body) => request(ADMIN_PATHS.webhooks, {
264
+ method: "POST",
265
+ body
266
+ }),
267
+ update: (id, body) => request(withId(ADMIN_PATHS.webhooks, id), {
268
+ method: "PATCH",
269
+ body
270
+ }),
271
+ remove: (id) => request(withId(ADMIN_PATHS.webhooks, id), { method: "DELETE" }),
272
+ rotateSecret: (id) => request(`${withId(ADMIN_PATHS.webhooks, id)}/rotate-secret`, { method: "POST" }),
273
+ /** Queues a `webhook.test` delivery; poll the delivery log for its outcome. */
274
+ test: (id) => request(`${withId(ADMIN_PATHS.webhooks, id)}/test`, { method: "POST" }),
275
+ deliveries: (id, query = {}) => request(`${withId(ADMIN_PATHS.webhooks, id)}/deliveries${toQueryString(query)}`),
276
+ /** Sends the same payload again as a new delivery. */
277
+ redeliver: (id, deliveryId) => request(`${withId(ADMIN_PATHS.webhooks, id)}/deliveries/${encodeURIComponent(deliveryId)}/redeliver`, { method: "POST" })
278
+ },
279
+ deployments: {
280
+ connections: {
281
+ list: () => request(ADMIN_PATHS.deploymentConnections),
282
+ get: (id) => request(withId(ADMIN_PATHS.deploymentConnections, id)),
283
+ create: (body) => request(ADMIN_PATHS.deploymentConnections, {
284
+ method: "POST",
285
+ body
286
+ }),
287
+ update: (id, body) => request(withId(ADMIN_PATHS.deploymentConnections, id), {
288
+ method: "PATCH",
289
+ body
290
+ }),
291
+ remove: (id) => request(withId(ADMIN_PATHS.deploymentConnections, id), { method: "DELETE" }),
292
+ test: (id) => request(`${withId(ADMIN_PATHS.deploymentConnections, id)}/test`, { method: "POST" }),
293
+ /** A manual run at the current publication snapshot. */
294
+ trigger: (id) => request(`${withId(ADMIN_PATHS.deploymentConnections, id)}/runs`, { method: "POST" })
295
+ },
296
+ runs: {
297
+ list: (query = {}) => request(`${ADMIN_PATHS.deploymentRuns}${toQueryString(query)}`),
298
+ get: (id) => request(withId(ADMIN_PATHS.deploymentRuns, id)),
299
+ /** A new run (linked by `retryOf`) at the current snapshot. */
300
+ retry: (id) => request(`${withId(ADMIN_PATHS.deploymentRuns, id)}/retry`, { method: "POST" })
301
+ }
302
+ },
303
+ preview: {
304
+ tokens: {
305
+ list: (query = {}) => request(`${ADMIN_PATHS.previewTokens}${toQueryString(query)}`),
306
+ create: (body) => request(ADMIN_PATHS.previewTokens, {
307
+ method: "POST",
308
+ body
309
+ }),
310
+ revoke: (id) => request(withId(ADMIN_PATHS.previewTokens, id), { method: "DELETE" })
311
+ },
312
+ /** The site's connections with a preview URL (the entry form's Preview pane). */
313
+ targets: () => request(`${ADMIN_PATHS.preview}/targets`),
314
+ /** The entry form's Preview action: a short-lived entry token and the rendered preview URL. */
315
+ open: (body) => request(`${ADMIN_PATHS.preview}/open`, {
316
+ method: "POST",
317
+ body
318
+ })
319
+ }
320
+ });
321
+ //#endregion
322
+ //#region src/admin/schema.ts
323
+ const DEFINITION_PATHS = {
324
+ model: ADMIN_PATHS.models,
325
+ component: ADMIN_PATHS.components
326
+ };
327
+ /** Models (collections and singletons) and components share one set of endpoints under their own base. */
328
+ const createDefinitionApi = (request, category) => {
329
+ const base = DEFINITION_PATHS[category];
330
+ return {
331
+ list: async () => (await request(base)).items,
332
+ get: (id) => request(withId(base, id)),
333
+ /** Creates and activates a definition (`pending` when prerequisites must run first). */
334
+ create: (body) => request(base, {
335
+ method: "POST",
336
+ body
337
+ }),
338
+ /** Plan preview of a create: classification and impact, nothing written. */
339
+ planCreate: (definition) => request(`${base}/plan`, {
340
+ method: "POST",
341
+ body: { definition }
342
+ }),
343
+ /** Changes a definition; 409 SCHEMA_VERSION_CONFLICT when `expectedVersion` is stale. */
344
+ update: (id, body) => request(withId(base, id), {
345
+ method: "PUT",
346
+ body
347
+ }),
348
+ planUpdate: (id, body) => request(`${withId(base, id)}/plan`, {
349
+ method: "POST",
350
+ body
351
+ }),
352
+ /** Soft delete: entries are kept and reappear if the definition is applied again. */
353
+ remove: (id, expectedVersion) => request(`${withId(base, id)}${toQueryString({ expectedVersion })}`, { method: "DELETE" }),
354
+ revisions: async (id) => (await request(`${withId(base, id)}/revisions`)).items,
355
+ changes: async (id) => (await request(`${withId(base, id)}/changes`)).items
356
+ };
357
+ };
358
+ /** Schema-wide endpoints: the version summary, planned-change status, the read-only lock, locales. */
359
+ const createSchemaApi = (request) => ({
360
+ models: createDefinitionApi(request, "model"),
361
+ components: createDefinitionApi(request, "component"),
362
+ schema: {
363
+ /** The global schema version and every visible definition's version (cheap; poll it for changes). */
364
+ summary: () => request(ADMIN_PATHS.schema),
365
+ change: (changeId) => request(withId(ADMIN_PATHS.schemaChanges, changeId)),
366
+ /** Every definition in the pull format, with the schema version they come from (`shapio schema pull`). */
367
+ export: () => request(ADMIN_PATHS.schemaExport),
368
+ /** Applies definition files live through the change planner; refuses when the base moved (409). */
369
+ apply: (body) => request(ADMIN_PATHS.schemaApply, {
370
+ method: "POST",
371
+ body
372
+ }),
373
+ settings: () => request(ADMIN_PATHS.schemaSettings),
374
+ updateSettings: (body) => request(ADMIN_PATHS.schemaSettings, {
375
+ method: "PUT",
376
+ body
377
+ })
378
+ },
379
+ locales: {
380
+ list: async () => (await request(ADMIN_PATHS.locales)).items,
381
+ create: (body) => request(ADMIN_PATHS.locales, {
382
+ method: "POST",
383
+ body
384
+ }),
385
+ update: (code, body) => request(withId(ADMIN_PATHS.locales, code), {
386
+ method: "PUT",
387
+ body
388
+ }),
389
+ /** A contract change: without `acknowledgeBreaking` the server answers 409 SCHEMA_CHANGE_NOT_ACKNOWLEDGED. */
390
+ setDefault: (code, acknowledgeBreaking) => request(`${withId(ADMIN_PATHS.locales, code)}/default`, {
391
+ method: "POST",
392
+ body: { acknowledgeBreaking }
393
+ }),
394
+ /**
395
+ * Deletes a locale and purges its content. When content exists and `acknowledgeDestructive` is false,
396
+ * the server answers 409 SCHEMA_CHANGE_NOT_ACKNOWLEDGED with `details.plan.impact.affectedHeads`.
397
+ */
398
+ remove: (code, acknowledgeDestructive) => request(`${withId(ADMIN_PATHS.locales, code)}${toQueryString({ acknowledgeDestructive: acknowledgeDestructive || void 0 })}`, { method: "DELETE" })
399
+ }
400
+ });
401
+ //#endregion
402
+ //#region src/admin/sites.ts
403
+ const SITES_PATHS = { sites: "/api/admin/sites" };
404
+ /** The admin API header that names the site a site-scoped request is about (default: the primary site). */
405
+ const SITE_HEADER = "shapio-site";
406
+ const sitePath = (id) => `${SITES_PATHS.sites}/${encodeURIComponent(id)}`;
407
+ /** Sites (network routes): listing shows the sites the caller holds a role on; changes need `sites.manage`. */
408
+ const createSitesApi = (request) => ({ sites: {
409
+ list: () => request(SITES_PATHS.sites),
410
+ get: (id) => request(sitePath(id)),
411
+ create: (body) => request(SITES_PATHS.sites, {
412
+ method: "POST",
413
+ body
414
+ }),
415
+ update: (id, body) => request(sitePath(id), {
416
+ method: "PATCH",
417
+ body
418
+ }),
419
+ /** Only an empty site (409 `SITE_NOT_EMPTY`); never the primary site (409 `SITE_IS_PRIMARY`). */
420
+ remove: (id) => request(sitePath(id), { method: "DELETE" }),
421
+ /** The app roles bound to the site's anonymous callers (`public`) and signed-in app users (`authenticated`). */
422
+ appRoles: {
423
+ get: (id) => request(`${sitePath(id)}/app-roles`),
424
+ /** Replaces both bindings (`roles.manage`). */
425
+ set: (id, body) => request(`${sitePath(id)}/app-roles`, {
426
+ method: "PUT",
427
+ body
428
+ })
429
+ }
430
+ } });
431
+ //#endregion
432
+ //#region src/admin/usage.ts
433
+ const USAGE_PATHS = { fields: "/api/admin/usage/fields" };
434
+ /** Who reads which fields (needs `tokens.manage`). */
435
+ const createUsageApi = (request) => ({ usage: { fields: (query) => request(`${USAGE_PATHS.fields}${toQueryString(query)}`) } });
436
+ //#endregion
437
+ //#region src/admin/types.ts
438
+ const CONTENT_ACTIONS = [
439
+ "read",
440
+ "create",
441
+ "update",
442
+ "delete",
443
+ "publish",
444
+ "schemaManage"
445
+ ];
446
+ /**
447
+ * Network actions: about the whole instance. Only roles assigned on every site grant them, so a role held
448
+ * on one site never reaches the network.
449
+ */
450
+ const NETWORK_ACTIONS = [
451
+ "schema.create",
452
+ "users.manage",
453
+ "roles.manage",
454
+ "audit.read",
455
+ "sites.manage"
456
+ ];
457
+ /** Site actions: about one site's library, tokens and publishing. */
458
+ const SITE_ACTIONS = [
459
+ "tokens.manage",
460
+ "media.read",
461
+ "media.write",
462
+ "media.manage",
463
+ "publishing.manage",
464
+ "webhooks.manage",
465
+ "deployments.manage",
466
+ "deployments.trigger",
467
+ "changes.manage",
468
+ "changes.ship"
469
+ ];
470
+ /** Actions not scoped to a model: network actions and site actions. */
471
+ const GLOBAL_ACTIONS = [
472
+ "schema.create",
473
+ "users.manage",
474
+ "roles.manage",
475
+ "tokens.manage",
476
+ "audit.read",
477
+ "sites.manage",
478
+ "media.read",
479
+ "media.write",
480
+ "media.manage",
481
+ "publishing.manage",
482
+ "webhooks.manage",
483
+ "deployments.manage",
484
+ "deployments.trigger",
485
+ "changes.manage",
486
+ "changes.ship"
487
+ ];
488
+ //#endregion
489
+ //#region src/admin/publishingTypes.ts
490
+ const JOB_STATUSES = [
491
+ "pending",
492
+ "running",
493
+ "succeeded",
494
+ "dead"
495
+ ];
496
+ const PUBLICATION_ACTIONS = ["publish", "unpublish"];
497
+ const SCHEDULE_STATUSES = [
498
+ "scheduled",
499
+ "done",
500
+ "failed",
501
+ "cancelled"
502
+ ];
503
+ const DEPLOYMENT_PROVIDERS = [
504
+ "generic_webhook",
505
+ "cloudflare_pages",
506
+ "vercel",
507
+ "netlify",
508
+ "github"
509
+ ];
510
+ const DEPLOYMENT_TRIGGERS = [
511
+ "publish",
512
+ "change_set",
513
+ "schema",
514
+ "manual"
515
+ ];
516
+ const DEPLOYMENT_RUN_STATUSES = [
517
+ "queued",
518
+ "triggered",
519
+ "building",
520
+ "deployed",
521
+ "failed",
522
+ "unknown"
523
+ ];
524
+ //#endregion
525
+ //#region src/admin/appUsersTypes.ts
526
+ /** What app roles may grant: content actions only. */
527
+ const APP_CONTENT_ACTIONS = [
528
+ "read",
529
+ "create",
530
+ "update",
531
+ "delete",
532
+ "publish"
533
+ ];
534
+ /** Built-in app role keys: `public` applies to anonymous callers, `authenticated` to every app user. */
535
+ const BUILT_IN_APP_ROLE_KEYS = ["public", "authenticated"];
536
+ //#endregion
537
+ //#region src/admin/changeSetTypes.ts
538
+ /**
539
+ * Change sets and snapshots (developer-face plan §5), mirroring the TypeBox route schemas in
540
+ * apps/api/src/routes/admin/{changeSets,snapshots}/schemas.ts. Dates are ISO-8601 strings.
541
+ */
542
+ const CHANGE_SET_STATUSES = [
543
+ "open",
544
+ "scheduled",
545
+ "shipping",
546
+ "shipped",
547
+ "failed",
548
+ "discarded"
549
+ ];
550
+ const NOT_RESTORABLE_REASONS = [
551
+ "entry_deleted",
552
+ "model_missing",
553
+ "publishing_disabled",
554
+ "localization_changed",
555
+ "locale_missing",
556
+ "invalid",
557
+ "unique_conflict"
558
+ ];
559
+ const SNAPSHOT_SOURCES = [
560
+ "publish",
561
+ "unpublish",
562
+ "delete",
563
+ "schedule",
564
+ "change_set",
565
+ "schema",
566
+ "conversion",
567
+ "import",
568
+ "legacy"
569
+ ];
570
+ //#endregion
571
+ //#region src/admin/editingTypes.ts
572
+ /**
573
+ * Shapes of the entry document's endpoints (editor experience, E0b): publish pre-flight, content health
574
+ * findings, presence and per-model counts. Sentences are never sent: the admin builds them from `rule` and
575
+ * `params` with its translations.
576
+ */
577
+ /** Health rules evaluated per entry (`apps/api/src/services/contentHealth.ts`). */
578
+ const HEALTH_RULES = [
579
+ "requiredEmpty",
580
+ "altMissing",
581
+ "localeMissing",
582
+ "relationMissing",
583
+ "relationUnpublished",
584
+ "uniqueConflict",
585
+ "staleDraft",
586
+ "unpublishedChanges"
587
+ ];
588
+ //#endregion
589
+ //#region src/admin/assistTypes.ts
590
+ const CONTENT_OPS_RULES = ["altMissing", "localeMissing"];
591
+ //#endregion
592
+ //#region src/admin/index.ts
593
+ /**
594
+ * Admin endpoints: identity (setup, auth, sessions, users, invitations, roles, tokens, audit) and the schema
595
+ * registry (models, components, schema, locales), the media library and content.
596
+ */
597
+ const createAdminApi = (request) => ({
598
+ setup: {
599
+ status: () => request(ADMIN_PATHS.setup),
600
+ complete: (body) => request(ADMIN_PATHS.setup, {
601
+ method: "POST",
602
+ body
603
+ })
604
+ },
605
+ auth: {
606
+ csrf: () => request(ADMIN_PATHS.csrf),
607
+ login: (body) => request(ADMIN_PATHS.login, {
608
+ method: "POST",
609
+ body
610
+ }),
611
+ logout: () => request(ADMIN_PATHS.logout, { method: "POST" }),
612
+ me: () => request(ADMIN_PATHS.me),
613
+ updateProfile: (body) => request(ADMIN_PATHS.me, {
614
+ method: "PATCH",
615
+ body
616
+ }),
617
+ /** The session is rotated: the response carries the new session's CSRF token. */
618
+ changePassword: (body) => request(ADMIN_PATHS.password, {
619
+ method: "POST",
620
+ body
621
+ }),
622
+ requestPasswordReset: (body) => request(ADMIN_PATHS.passwordReset, {
623
+ method: "POST",
624
+ body
625
+ }),
626
+ confirmPasswordReset: (body) => request(ADMIN_PATHS.passwordResetConfirm, {
627
+ method: "POST",
628
+ body
629
+ })
630
+ },
631
+ sessions: {
632
+ list: () => request(ADMIN_PATHS.sessions),
633
+ revoke: (id) => request(withId(ADMIN_PATHS.sessions, id), { method: "DELETE" })
634
+ },
635
+ users: {
636
+ list: () => request(ADMIN_PATHS.users),
637
+ get: (id) => request(withId(ADMIN_PATHS.users, id)),
638
+ update: (id, body) => request(withId(ADMIN_PATHS.users, id), {
639
+ method: "PATCH",
640
+ body
641
+ }),
642
+ remove: (id) => request(withId(ADMIN_PATHS.users, id), { method: "DELETE" }),
643
+ revokeSessions: (id) => request(`${withId(ADMIN_PATHS.users, id)}/sessions`, { method: "DELETE" })
644
+ },
645
+ invitations: {
646
+ list: () => request(ADMIN_PATHS.invitations),
647
+ create: (body) => request(ADMIN_PATHS.invitations, {
648
+ method: "POST",
649
+ body
650
+ }),
651
+ revoke: (id) => request(withId(ADMIN_PATHS.invitations, id), { method: "DELETE" }),
652
+ /** A fresh link to send by hand; any earlier link for this invitation stops working. */
653
+ link: (id) => request(`${withId(ADMIN_PATHS.invitations, id)}/link`, { method: "POST" }),
654
+ /** Public: the token travels in the body, never in a URL the server logs. */
655
+ inspect: (token) => request(ADMIN_PATHS.inspectInvitation, {
656
+ method: "POST",
657
+ body: { token }
658
+ }),
659
+ accept: (body) => request(ADMIN_PATHS.acceptInvitation, {
660
+ method: "POST",
661
+ body
662
+ })
663
+ },
664
+ roles: {
665
+ list: () => request(ADMIN_PATHS.roles),
666
+ get: (id) => request(withId(ADMIN_PATHS.roles, id)),
667
+ create: (body) => request(ADMIN_PATHS.roles, {
668
+ method: "POST",
669
+ body
670
+ }),
671
+ update: (id, body) => request(withId(ADMIN_PATHS.roles, id), {
672
+ method: "PATCH",
673
+ body
674
+ }),
675
+ remove: (id) => request(withId(ADMIN_PATHS.roles, id), { method: "DELETE" })
676
+ },
677
+ tokens: {
678
+ list: () => request(ADMIN_PATHS.tokens),
679
+ create: (body) => request(ADMIN_PATHS.tokens, {
680
+ method: "POST",
681
+ body
682
+ }),
683
+ revoke: (id) => request(withId(ADMIN_PATHS.tokens, id), { method: "DELETE" })
684
+ },
685
+ audit: { list: (query = {}) => request(`${ADMIN_PATHS.audit}${toQueryString(query)}`) },
686
+ ...createSchemaApi(request),
687
+ ...createMediaApi(request),
688
+ ...createContentApi(request),
689
+ ...createEditingApi(request),
690
+ ...createPublishingApi(request),
691
+ ...createAppUsersApi(request),
692
+ ...createChangeSetsApi(request),
693
+ ...createUsageApi(request),
694
+ ...createSitesApi(request),
695
+ ...createAssistApi(request)
696
+ });
697
+ //#endregion
698
+ //#region src/appAuth/index.ts
699
+ const APP_AUTH_PATHS = {
700
+ register: "/api/app-auth/register",
701
+ login: "/api/app-auth/login",
702
+ refresh: "/api/app-auth/refresh",
703
+ logout: "/api/app-auth/logout",
704
+ me: "/api/app-auth/me",
705
+ password: "/api/app-auth/me/password",
706
+ confirmEmail: "/api/app-auth/confirm-email",
707
+ resendConfirmation: "/api/app-auth/confirm-email/resend",
708
+ passwordReset: "/api/app-auth/password-reset",
709
+ passwordResetConfirm: "/api/app-auth/password-reset/confirm",
710
+ providers: "/api/app-auth/providers",
711
+ oauthExchange: "/api/app-auth/oauth/exchange"
712
+ };
713
+ /**
714
+ * App-user endpoints. Calls that act as the signed-in user need a client created with `token: accessToken`.
715
+ * `baseUrl` is the client's base URL, used to build the OAuth start link the browser navigates to.
716
+ */
717
+ const createAppAuthApi = (request, baseUrl) => ({
718
+ register: (body) => request(APP_AUTH_PATHS.register, {
719
+ method: "POST",
720
+ body
721
+ }),
722
+ login: (body) => request(APP_AUTH_PATHS.login, {
723
+ method: "POST",
724
+ body
725
+ }),
726
+ refresh: (refreshToken) => request(APP_AUTH_PATHS.refresh, {
727
+ method: "POST",
728
+ body: { refreshToken }
729
+ }),
730
+ /**
731
+ * Signs the account out everywhere: this login's refresh tokens are revoked, and every access token the
732
+ * account holds (on any device) is refused from the next request. Other logins recover with their own
733
+ * refresh token.
734
+ */
735
+ logout: (refreshToken) => request(APP_AUTH_PATHS.logout, {
736
+ method: "POST",
737
+ body: { refreshToken }
738
+ }),
739
+ /** The signed-in user. Answers 401 once the account logged out anywhere, changed its password or was blocked. */
740
+ me: () => request(APP_AUTH_PATHS.me),
741
+ updateMe: (body) => request(APP_AUTH_PATHS.me, {
742
+ method: "PATCH",
743
+ body
744
+ }),
745
+ /** Returns a new session; every other sign-in of the account is revoked. */
746
+ changePassword: (body) => request(APP_AUTH_PATHS.password, {
747
+ method: "POST",
748
+ body
749
+ }),
750
+ deleteMe: (body = {}) => request(APP_AUTH_PATHS.me, {
751
+ method: "DELETE",
752
+ body
753
+ }),
754
+ /** The token from the `#token=` fragment of the confirmation link. */
755
+ confirmEmail: (token) => request(APP_AUTH_PATHS.confirmEmail, {
756
+ method: "POST",
757
+ body: { token }
758
+ }),
759
+ resendConfirmation: (email) => request(APP_AUTH_PATHS.resendConfirmation, {
760
+ method: "POST",
761
+ body: { email }
762
+ }),
763
+ requestPasswordReset: (email) => request(APP_AUTH_PATHS.passwordReset, {
764
+ method: "POST",
765
+ body: { email }
766
+ }),
767
+ confirmPasswordReset: (body) => request(APP_AUTH_PATHS.passwordResetConfirm, {
768
+ method: "POST",
769
+ body
770
+ }),
771
+ providers: async () => (await request(APP_AUTH_PATHS.providers)).providers,
772
+ /**
773
+ * Where to send the browser to sign in with a provider. `codeChallenge` comes from `createPkcePair()`;
774
+ * keep its `codeVerifier` until the browser returns. It comes back to `redirectTo` (an allowed return URL,
775
+ * by default an origin listed in CORS_ORIGINS) with `?code=` (pass it to `exchangeCode`) or `?error=`.
776
+ */
777
+ oauthStartUrl: (provider, redirectTo, codeChallenge) => `${baseUrl.replace(/\/+$/, "")}/api/app-auth/oauth/${provider}/start?redirectTo=${encodeURIComponent(redirectTo)}&codeChallenge=${encodeURIComponent(codeChallenge)}`,
778
+ /** Trades the one-time code for a session. A wrong verifier spends the code: start the sign-in again. */
779
+ exchangeCode: (code, codeVerifier) => request(APP_AUTH_PATHS.oauthExchange, {
780
+ method: "POST",
781
+ body: {
782
+ code,
783
+ codeVerifier
784
+ }
785
+ })
786
+ });
787
+ //#endregion
788
+ //#region src/delivery.ts
789
+ const DELIVERY_PATH = "/api/content";
790
+ const routePath = (routeKey) => `${DELIVERY_PATH}/${encodeURIComponent(routeKey)}`;
791
+ /** The content query string plus `snapshot`, which only delivery reads take. */
792
+ const deliveryQueryString = ({ snapshot, ...query }) => {
793
+ const base = toContentQueryString(query);
794
+ if (snapshot === void 0) return base;
795
+ const pinned = `snapshot=${encodeURIComponent(String(snapshot))}`;
796
+ return base ? `${base}&${pinned}` : `?${pinned}`;
797
+ };
798
+ /**
799
+ * Published content, typed by the caller (`delivery.list<Article>('articles', { locale: 'fr', snapshot })`).
800
+ * A collection is read with `list` (a page) and `get` (one entry); a singleton with `singleton`.
801
+ */
802
+ const createDeliveryApi = (request) => ({
803
+ list: (routeKey, query = {}) => request(`${routePath(routeKey)}${deliveryQueryString(query)}`),
804
+ singleton: (apiKey, query = {}) => request(`${routePath(apiKey)}${deliveryQueryString(query)}`),
805
+ get: (routeKey, id, query = {}) => request(`${routePath(routeKey)}/${encodeURIComponent(id)}${deliveryQueryString(query)}`)
806
+ });
807
+ //#endregion
808
+ //#region src/snapshots.ts
809
+ const SNAPSHOT_PATHS = {
810
+ current: "/api/snapshots/current",
811
+ changes: "/api/snapshots/changes"
812
+ };
813
+ /** Snapshots and the diff between two of them, for incremental site builds (delivery credentials). */
814
+ const createSnapshotsApi = (request) => {
815
+ const changes = (query) => request(`${SNAPSHOT_PATHS.changes}${toQueryString(query)}`);
816
+ return {
817
+ current: () => request(SNAPSHOT_PATHS.current),
818
+ changes,
819
+ /** Every change between `from` and `to`, following `nextCursor` until the last page. */
820
+ allChanges: async (query) => {
821
+ const items = [];
822
+ let page = await changes(query);
823
+ items.push(...page.items);
824
+ while (page.nextCursor !== null) {
825
+ page = await changes({
826
+ ...query,
827
+ to: page.to,
828
+ after: page.nextCursor
829
+ });
830
+ items.push(...page.items);
831
+ }
832
+ return {
833
+ from: page.from,
834
+ to: page.to,
835
+ schemaVersions: page.schemaVersions,
836
+ items
837
+ };
838
+ }
839
+ };
840
+ };
841
+ //#endregion
842
+ //#region src/site.ts
843
+ /** Delivery reads name their site with `?site=`; everything else with the `Shapio-Site` header. */
844
+ const SITE_QUERY_PARAMETER = "site";
845
+ /** Delivery reads (content and snapshots): a GET there carries the site in its URL. */
846
+ const DELIVERY_READ_PREFIXES = [
847
+ `${DELIVERY_PATH}/`,
848
+ `${DELIVERY_PATH}?`,
849
+ "/api/snapshots/"
850
+ ];
851
+ const isDeliveryRead = (method, path) => method === "GET" && (path === "/api/content" || Object.values(SNAPSHOT_PATHS).some((snapshotPath) => path === snapshotPath) || DELIVERY_READ_PREFIXES.some((prefix) => path.startsWith(prefix)));
852
+ const namesSite = (path) => {
853
+ const index = path.indexOf("?");
854
+ return index !== -1 && new URLSearchParams(path.slice(index + 1)).has("site");
855
+ };
856
+ /**
857
+ * Where a request names the client's site (sites plan §H). Delivery GETs put it in the query string: a
858
+ * simple cross-origin GET needs no CORS preflight, and URL-keyed CDN caches keep sites apart. Every other
859
+ * request sends the `Shapio-Site` header. A path that already names a site is left as it is.
860
+ */
861
+ const applySite = (site, method, path) => {
862
+ if (site === void 0 || namesSite(path)) return {
863
+ path,
864
+ headers: {}
865
+ };
866
+ if (isDeliveryRead(method, path)) {
867
+ const parameter = `${SITE_QUERY_PARAMETER}=${encodeURIComponent(site)}`;
868
+ return {
869
+ path: `${path}${path.includes("?") ? "&" : "?"}${parameter}`,
870
+ headers: {}
871
+ };
872
+ }
873
+ return {
874
+ path,
875
+ headers: { [SITE_HEADER]: site }
876
+ };
877
+ };
878
+ //#endregion
879
+ //#region src/request.ts
880
+ const parseBody = async (response) => {
881
+ const text = await response.text();
882
+ if (text.length === 0) return;
883
+ try {
884
+ return JSON.parse(text);
885
+ } catch {
886
+ return text;
887
+ }
888
+ };
889
+ /** Builds the JSON request function every endpoint group uses. Non-2xx responses throw ShapioApiError. */
890
+ const createRequest = ({ baseUrl, token, fetch, credentials, headers, site }) => {
891
+ const origin = baseUrl.replace(/\/+$/, "");
892
+ return async (requestPath, { method = "GET", body, signal } = {}) => {
893
+ const { path, headers: siteHeaders } = applySite(site, method, requestPath);
894
+ const requestHeaders = {
895
+ accept: "application/json",
896
+ ...siteHeaders,
897
+ ...headers?.()
898
+ };
899
+ if (token) requestHeaders.authorization = `Bearer ${token}`;
900
+ if (body !== void 0) requestHeaders["content-type"] = "application/json";
901
+ const response = await fetch(`${origin}${path}`, {
902
+ method,
903
+ headers: requestHeaders,
904
+ ...body !== void 0 ? { body: JSON.stringify(body) } : {},
905
+ ...signal ? { signal } : {},
906
+ ...credentials ? { credentials } : {}
907
+ });
908
+ const parsed = await parseBody(response);
909
+ if (!response.ok) throw new ShapioApiError(response.status, parsed);
910
+ return parsed;
911
+ };
912
+ };
913
+ //#endregion
914
+ //#region src/client.ts
915
+ const createClient = ({ baseUrl, token, fetch = globalThis.fetch, credentials, headers, site }) => {
916
+ const request = createRequest({
917
+ baseUrl,
918
+ token,
919
+ fetch,
920
+ credentials,
921
+ headers,
922
+ site
923
+ });
924
+ return {
925
+ /**
926
+ * Any Shapio route, e.g. the delivery API. Delivery paths use the model's route key: a collection's
927
+ * plural API ID (`request('/api/content/articles')`, `/api/content/articles/<id>`), a singleton's API ID
928
+ * (`/api/content/homepage`). The admin API (`admin.*`) always uses the singular API ID.
929
+ */
930
+ request,
931
+ system: {
932
+ health: (signal) => request("/api/health", signal ? { signal } : {}),
933
+ ready: (signal) => request("/api/ready", signal ? { signal } : {}),
934
+ version: (signal) => request("/api/version", signal ? { signal } : {})
935
+ },
936
+ /** Published content (the delivery API), typed by the caller. */
937
+ delivery: createDeliveryApi(request),
938
+ admin: createAdminApi(request),
939
+ /** Publication snapshots and the diff between two (incremental builds). */
940
+ snapshots: createSnapshotsApi(request),
941
+ /** End users of your sites and apps: sign-up, sign-in, tokens, OAuth. */
942
+ appAuth: createAppAuthApi(request, baseUrl)
943
+ };
944
+ };
945
+ //#endregion
946
+ //#region src/webhooks.ts
947
+ /**
948
+ * Verifying Shapio's signed webhooks and deploy triggers on the receiving side (a site's revalidate route,
949
+ * a build hook). The scheme is the server's (`apps/api/src/publishing/signature.ts`):
950
+ * `X-Shapio-Signature: v1=<hex HMAC-SHA256(secret, "<timestamp>.<raw body>")>` with
951
+ * `X-Shapio-Timestamp: <unix seconds>`; several comma-separated `v1=` values are accepted (secret rotation).
952
+ * Uses Web Crypto, so it runs in Node, edge runtimes and browsers alike.
953
+ */
954
+ const WEBHOOK_SIGNATURE_HEADER = "x-shapio-signature";
955
+ const WEBHOOK_TIMESTAMP_HEADER = "x-shapio-timestamp";
956
+ const WEBHOOK_EVENT_HEADER = "x-shapio-event";
957
+ /** Requests signed further from now than this are rejected (replay protection). */
958
+ const WEBHOOK_TOLERANCE_SECONDS = 300;
959
+ const SIGNATURE_VERSION = "v1";
960
+ const toHex = (bytes) => Array.from(new Uint8Array(bytes), (byte) => byte.toString(16).padStart(2, "0")).join("");
961
+ /** Compares two strings without returning early on the first difference. */
962
+ const constantTimeEqual = (a, b) => {
963
+ let difference = a.length ^ b.length;
964
+ for (let index = 0; index < Math.max(a.length, b.length); index += 1) difference |= (a.charCodeAt(index) || 0) ^ (b.charCodeAt(index) || 0);
965
+ return difference === 0;
966
+ };
967
+ const expectedSignature = async (secret, timestamp, body) => {
968
+ const encoder = new TextEncoder();
969
+ const key = await globalThis.crypto.subtle.importKey("raw", encoder.encode(secret), {
970
+ name: "HMAC",
971
+ hash: "SHA-256"
972
+ }, false, ["sign"]);
973
+ const mac = await globalThis.crypto.subtle.sign("HMAC", key, encoder.encode(`${timestamp}.${body}`));
974
+ return `${SIGNATURE_VERSION}=${toHex(mac)}`;
975
+ };
976
+ /** Checks a Shapio signature; `{ ok: false, reason }` says why it was refused (never throws for bad input). */
977
+ const verifyWebhookSignature = async ({ secret, signature, timestamp, body, now = /* @__PURE__ */ new Date(), toleranceSeconds = 300 }) => {
978
+ if (!signature || !timestamp || !/^\d{1,12}$/.test(timestamp)) return {
979
+ ok: false,
980
+ reason: "missing signature or timestamp"
981
+ };
982
+ const seconds = Number(timestamp);
983
+ if (Math.abs(now.getTime() / 1e3 - seconds) > toleranceSeconds) return {
984
+ ok: false,
985
+ reason: "timestamp outside the allowed window"
986
+ };
987
+ const expected = await expectedSignature(secret, seconds, body);
988
+ return signature.split(",").map((candidate) => candidate.trim()).some((candidate) => constantTimeEqual(candidate, expected)) ? { ok: true } : {
989
+ ok: false,
990
+ reason: "signature mismatch"
991
+ };
992
+ };
993
+ //#endregion
994
+ //#region src/appAuth/pkce.ts
995
+ const toBase64Url = (bytes) => btoa(String.fromCharCode(...bytes)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
996
+ /** The S256 challenge of a verifier: base64url(SHA-256(verifier)). */
997
+ const codeChallengeOf = async (codeVerifier) => toBase64Url(new Uint8Array(await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(codeVerifier))));
998
+ /** A fresh verifier (32 random bytes, 43 characters) and its S256 challenge. */
999
+ const createPkcePair = async () => {
1000
+ const codeVerifier = toBase64Url(globalThis.crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(32)));
1001
+ return {
1002
+ codeVerifier,
1003
+ codeChallenge: await codeChallengeOf(codeVerifier)
1004
+ };
1005
+ };
1006
+ //#endregion
1007
+ export { ADMIN_PATHS, APP_AUTH_PATHS, APP_CONTENT_ACTIONS, ASSIST_PATHS, BUILT_IN_APP_ROLE_KEYS, CHANGE_SET_STATUSES, CONTENT_ACTIONS, CONTENT_OPS_RULES, DELIVERY_PATH, DEPLOYMENT_PROVIDERS, DEPLOYMENT_RUN_STATUSES, DEPLOYMENT_TRIGGERS, GLOBAL_ACTIONS, HEALTH_RULES, JOB_STATUSES, NETWORK_ACTIONS, NOT_RESTORABLE_REASONS, PUBLICATION_ACTIONS, SCHEDULE_STATUSES, SITES_PATHS, SITE_ACTIONS, SITE_HEADER, SITE_QUERY_PARAMETER, SNAPSHOT_SOURCES, ShapioApiError, WEBHOOK_EVENT_HEADER, WEBHOOK_SIGNATURE_HEADER, WEBHOOK_TIMESTAMP_HEADER, WEBHOOK_TOLERANCE_SECONDS, buildUploadForm, codeChallengeOf, createClient, createPkcePair, resolveUploadUrl, toContentQueryString, verifyWebhookSignature };
1008
+
1009
+ //# sourceMappingURL=index.js.map