@headroom-cms/admin-api 0.1.1

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/node.d.ts ADDED
@@ -0,0 +1,644 @@
1
+ /**
2
+ * Provides JWT tokens for API authentication.
3
+ * Implementations handle token acquisition, caching, and refresh.
4
+ */
5
+ interface TokenProvider {
6
+ /** Return a valid JWT access token. May refresh if expired. */
7
+ getToken(): Promise<string>;
8
+ /** Called when the API returns 401. Return a new token to retry, or throw. */
9
+ onUnauthorized?(): Promise<string>;
10
+ }
11
+
12
+ interface components {
13
+ schemas: {
14
+ APIKeyCreateResult: {
15
+ /** Format: int64 */
16
+ createdAt: number;
17
+ key: string;
18
+ keyId: string;
19
+ label: string;
20
+ };
21
+ APIKeyList: {
22
+ keys?: components["schemas"]["APIKeyMetadata"][];
23
+ };
24
+ APIKeyMetadata: {
25
+ /** Format: int64 */
26
+ createdAt: number;
27
+ keyId: string;
28
+ label: string;
29
+ /** Format: int64 */
30
+ lastUsedAt?: number | null;
31
+ };
32
+ AdminBlockTypeList: {
33
+ blockTypes?: components["schemas"]["BlockType"][];
34
+ };
35
+ AdminCollectionList: {
36
+ collections?: components["schemas"]["Collection"][];
37
+ };
38
+ AdminContentList: {
39
+ cursor?: string;
40
+ hasMore: boolean;
41
+ items: components["schemas"]["ContentMetadata"][];
42
+ };
43
+ AuditEvent: {
44
+ action: string;
45
+ adminId: string;
46
+ /** Format: int64 */
47
+ createdAt: number;
48
+ details?: string;
49
+ errorMsg?: string;
50
+ eventId: string;
51
+ host?: string;
52
+ status: string;
53
+ /** Format: int64 */
54
+ updatedAt?: number;
55
+ };
56
+ AuditEventList: {
57
+ hasMore: boolean;
58
+ items: components["schemas"]["AuditEvent"][];
59
+ };
60
+ BlockType: {
61
+ fields?: components["schemas"]["FieldDef"][];
62
+ icon?: string;
63
+ label: string;
64
+ name: string;
65
+ previewTemplate?: string;
66
+ };
67
+ Collection: {
68
+ fields?: components["schemas"]["FieldDef"][];
69
+ label: string;
70
+ labelSingular: string;
71
+ name: string;
72
+ singleton?: boolean;
73
+ slug?: string;
74
+ version?: number;
75
+ };
76
+ CompleteUploadRequest: {
77
+ alt?: string;
78
+ caption?: string;
79
+ filename: string;
80
+ };
81
+ Content: {
82
+ body?: {
83
+ [key: string]: unknown;
84
+ };
85
+ collection: string;
86
+ contentId: string;
87
+ coverUrl?: string;
88
+ /** Format: int64 */
89
+ lastPublishedAt?: number;
90
+ /** Format: int64 */
91
+ publishedAt?: number;
92
+ slug?: string;
93
+ snippet?: string;
94
+ tags?: string[];
95
+ title: string;
96
+ };
97
+ ContentDetail: {
98
+ content: components["schemas"]["Content"];
99
+ draft?: components["schemas"]["DraftContent"];
100
+ publishedBlockId?: string;
101
+ };
102
+ ContentMetadata: {
103
+ collection: string;
104
+ contentId: string;
105
+ coverUrl?: string;
106
+ /** Format: int64 */
107
+ lastDraftAt?: number;
108
+ lastDraftBlockId?: string;
109
+ /** Format: int64 */
110
+ lastPublishedAt?: number;
111
+ /** Format: int64 */
112
+ publishedAt?: number;
113
+ publishedBlockId?: string;
114
+ slug?: string;
115
+ snippet?: string;
116
+ tags?: string[];
117
+ title: string;
118
+ };
119
+ CreateAPIKeyRequest: {
120
+ label: string;
121
+ };
122
+ CreateContentRequest: {
123
+ collection: string;
124
+ params: components["schemas"]["SaveDraftParams"];
125
+ };
126
+ CreateSiteRequest: {
127
+ host: string;
128
+ name: string;
129
+ };
130
+ CreateWebhookRequest: {
131
+ description?: string;
132
+ enabled?: boolean;
133
+ events: string[];
134
+ url: string;
135
+ };
136
+ DraftContent: {
137
+ blockId: string;
138
+ body?: {
139
+ [key: string]: unknown;
140
+ };
141
+ collection: string;
142
+ contentId: string;
143
+ coverUrl?: string;
144
+ /** Format: int64 */
145
+ createdAt: number;
146
+ createdBy: string;
147
+ slug?: string;
148
+ snippet?: string;
149
+ tags?: string[];
150
+ title?: string;
151
+ };
152
+ DraftVersion: {
153
+ /** Format: int64 */
154
+ createdAt: number;
155
+ createdBy: string;
156
+ title?: string;
157
+ };
158
+ DraftVersionList: {
159
+ versions?: components["schemas"]["DraftVersion"][];
160
+ };
161
+ Error: {
162
+ code: string;
163
+ error: string;
164
+ };
165
+ FieldDef: {
166
+ label: string;
167
+ name: string;
168
+ options?: {
169
+ [key: string]: unknown;
170
+ };
171
+ type: string;
172
+ };
173
+ Media: {
174
+ alt?: string;
175
+ caption?: string;
176
+ filename: string;
177
+ height?: number;
178
+ mediaId: string;
179
+ mimeType: string;
180
+ /** Format: int64 */
181
+ size: number;
182
+ /** Format: int64 */
183
+ uploadedAt: number;
184
+ url: string;
185
+ width?: number;
186
+ };
187
+ MediaList: {
188
+ cursor?: string;
189
+ hasMore: boolean;
190
+ items: components["schemas"]["Media"][];
191
+ };
192
+ RetryDeliveryResult: {
193
+ deliveryId: string;
194
+ };
195
+ RotateSecretResult: {
196
+ secret: string;
197
+ };
198
+ SaveDraftParams: {
199
+ body: {
200
+ [key: string]: unknown;
201
+ };
202
+ coverUrl?: string;
203
+ slug?: string;
204
+ snippet?: string;
205
+ tags?: string[];
206
+ title?: string;
207
+ };
208
+ SaveDraftResult: {
209
+ blockId: string;
210
+ contentId: string;
211
+ /** Format: int64 */
212
+ createdAt: number;
213
+ };
214
+ Site: {
215
+ admins?: components["schemas"]["SiteAdmin"][];
216
+ /** Format: int64 */
217
+ createdAt: number;
218
+ host: string;
219
+ name: string;
220
+ schemaVersion: number;
221
+ status: string;
222
+ /** Format: int64 */
223
+ updatedAt: number;
224
+ };
225
+ SiteAdmin: {
226
+ id: string;
227
+ /** @enum {string} */
228
+ role: "admin" | "editor";
229
+ };
230
+ SiteList: {
231
+ items?: components["schemas"]["Site"][];
232
+ };
233
+ UpdateAPIKeyRequest: {
234
+ label: string;
235
+ };
236
+ UpdateAdminsRequest: {
237
+ admins: components["schemas"]["SiteAdmin"][];
238
+ };
239
+ UpdateMediaRequest: {
240
+ alt?: string;
241
+ caption?: string;
242
+ };
243
+ UpdateSiteRequest: {
244
+ name?: string;
245
+ status?: string;
246
+ };
247
+ UpdateWebhookRequest: {
248
+ description?: string;
249
+ enabled?: boolean;
250
+ events?: string[];
251
+ url?: string;
252
+ };
253
+ UploadURLRequest: {
254
+ contentType: string;
255
+ filename: string;
256
+ /** Format: int64 */
257
+ size: number;
258
+ };
259
+ UploadURLResult: {
260
+ /** Format: int64 */
261
+ expiresAt: number;
262
+ mediaId: string;
263
+ uploadUrl: string;
264
+ };
265
+ Webhook: {
266
+ /** Format: int64 */
267
+ createdAt: number;
268
+ description?: string;
269
+ enabled: boolean;
270
+ events: string[];
271
+ host?: string;
272
+ secret?: string;
273
+ /** Format: int64 */
274
+ updatedAt?: number;
275
+ url: string;
276
+ webhookId: string;
277
+ };
278
+ WebhookDelivery: {
279
+ attempts?: number;
280
+ /** Format: int64 */
281
+ createdAt: number;
282
+ deliveryId: string;
283
+ /** Format: int64 */
284
+ duration?: number;
285
+ error?: string;
286
+ event: string;
287
+ payload?: string;
288
+ responseBody?: string;
289
+ responseStatus?: number;
290
+ status: string;
291
+ webhookId: string;
292
+ };
293
+ WebhookDeliveryList: {
294
+ deliveries: components["schemas"]["WebhookDelivery"][];
295
+ hasMore: boolean;
296
+ };
297
+ WebhookList: {
298
+ webhooks?: components["schemas"]["Webhook"][];
299
+ };
300
+ WebhookTestResult: {
301
+ /** Format: int64 */
302
+ durationMs: number;
303
+ error?: string;
304
+ requestPayload: string;
305
+ responseBody?: string;
306
+ statusCode?: number;
307
+ success: boolean;
308
+ };
309
+ };
310
+ responses: never;
311
+ parameters: never;
312
+ requestBodies: never;
313
+ headers: never;
314
+ pathItems: never;
315
+ }
316
+
317
+ type Site = components["schemas"]["Site"];
318
+ type SiteAdmin = components["schemas"]["SiteAdmin"];
319
+ type Collection = components["schemas"]["Collection"] & {
320
+ relationships?: RelationshipDef[];
321
+ singletonContentId?: string;
322
+ };
323
+ type FieldDefinition = components["schemas"]["FieldDef"];
324
+ type BlockType = components["schemas"]["BlockType"] & {
325
+ previewData?: Record<string, unknown>;
326
+ };
327
+ type ContentItem = components["schemas"]["ContentMetadata"] & {
328
+ coverMediaId?: string;
329
+ relationships?: Record<string, ContentRef[]>;
330
+ };
331
+ type ContentDetail = components["schemas"]["ContentDetail"];
332
+ type SaveDraftResult = components["schemas"]["SaveDraftResult"];
333
+ type UploadURLResult = components["schemas"]["UploadURLResult"];
334
+ type ApiKeyItem = components["schemas"]["APIKeyMetadata"];
335
+ type ApiKeyCreateResponse = components["schemas"]["APIKeyCreateResult"];
336
+ type Webhook = components["schemas"]["Webhook"];
337
+ type WebhookDelivery = components["schemas"]["WebhookDelivery"];
338
+ type AuditEvent = components["schemas"]["AuditEvent"];
339
+ interface AdminMediaItem {
340
+ mediaId: string;
341
+ filename: string;
342
+ mimeType: string;
343
+ size: number;
344
+ width?: number;
345
+ height?: number;
346
+ uploadedAt: number;
347
+ url: string;
348
+ alt?: string;
349
+ caption?: string;
350
+ tags: string[];
351
+ userTags: string[];
352
+ folderId?: string;
353
+ urls?: Record<string, string>;
354
+ }
355
+ interface MediaFolder {
356
+ folderId: string;
357
+ name: string;
358
+ createdAt: number;
359
+ count: number;
360
+ }
361
+ interface RelationshipDef {
362
+ name: string;
363
+ label: string;
364
+ targetCollection: string;
365
+ multiple: boolean;
366
+ }
367
+ interface ContentRef {
368
+ contentId: string;
369
+ collection: string;
370
+ slug: string;
371
+ title: string;
372
+ }
373
+ interface ReferencesResult {
374
+ count: number;
375
+ items: {
376
+ contentId: string;
377
+ collection: string;
378
+ title: string;
379
+ fieldName: string;
380
+ type?: string;
381
+ }[];
382
+ }
383
+ interface BulkMediaRequest {
384
+ mediaIds: string[];
385
+ action: "delete" | "move" | "addTags" | "removeTags";
386
+ folderId?: string;
387
+ tags?: string[];
388
+ }
389
+ interface BulkMediaResult {
390
+ succeeded: string[];
391
+ failed: {
392
+ mediaId: string;
393
+ error: string;
394
+ }[];
395
+ }
396
+ interface MediaUsageResponse {
397
+ references: {
398
+ contentId: string;
399
+ collection: string;
400
+ title: string;
401
+ }[];
402
+ note: string;
403
+ }
404
+ interface DuplicateCheckResponse {
405
+ duplicates: AdminMediaItem[];
406
+ }
407
+ interface CollectionInput {
408
+ name: string;
409
+ label: string;
410
+ labelSingular?: string;
411
+ slug?: string;
412
+ singleton?: boolean;
413
+ fields: FieldDefinition[];
414
+ relationships?: RelationshipDef[];
415
+ }
416
+ interface BlockTypeInput {
417
+ name: string;
418
+ label: string;
419
+ icon?: string;
420
+ fields: FieldDefinition[];
421
+ }
422
+ interface CreateContentParams {
423
+ collection: string;
424
+ params: SaveDraftParams;
425
+ }
426
+ interface SaveDraftParams {
427
+ title?: string;
428
+ slug?: string;
429
+ snippet?: string;
430
+ tags?: string[];
431
+ coverUrl?: string;
432
+ coverMediaId?: string;
433
+ coverWidth?: number;
434
+ coverHeight?: number;
435
+ body?: Record<string, unknown>;
436
+ createVersion?: boolean;
437
+ relationships?: Record<string, string[]>;
438
+ }
439
+ interface ContentListParams {
440
+ collection?: string;
441
+ q?: string;
442
+ search?: "prefix" | "full";
443
+ tag?: string;
444
+ status?: "draft" | "published" | "changed" | "unpublished";
445
+ sort?: string;
446
+ relatedTo?: string;
447
+ limit?: number;
448
+ cursor?: string;
449
+ }
450
+ interface MediaListParams {
451
+ tag?: string;
452
+ folderId?: string;
453
+ q?: string;
454
+ sort?: string;
455
+ limit?: number;
456
+ cursor?: string;
457
+ }
458
+ interface MediaTransformParams {
459
+ w?: number;
460
+ h?: number;
461
+ fit?: string;
462
+ format?: string;
463
+ q?: number;
464
+ cx?: number;
465
+ cy?: number;
466
+ cw?: number;
467
+ ch?: number;
468
+ }
469
+ interface SaveTransformRequest {
470
+ width?: number;
471
+ height?: number;
472
+ fit?: string;
473
+ format?: string;
474
+ quality?: number;
475
+ cropX?: number;
476
+ cropY?: number;
477
+ cropW?: number;
478
+ cropH?: number;
479
+ filename?: string;
480
+ }
481
+ interface AuditListParams {
482
+ action?: string;
483
+ before?: number;
484
+ }
485
+ interface PaginatedResponse<T> {
486
+ items: T[];
487
+ cursor: string | null;
488
+ hasMore: boolean;
489
+ }
490
+
491
+ declare class HeadroomAdminClient {
492
+ private baseUrl;
493
+ private tokenProvider;
494
+ constructor(baseUrl: string, tokenProvider: TokenProvider);
495
+ /**
496
+ * Low-level fetch — public so admin UI hooks can migrate incrementally.
497
+ * Prefer typed methods (listSites, createContent, etc.) in new code.
498
+ */
499
+ apiFetch<T>(path: string, options?: RequestInit): Promise<T>;
500
+ private handleResponse;
501
+ listSites(includeArchived?: boolean): Promise<Site[]>;
502
+ createSite(host: string, name: string): Promise<Site>;
503
+ getSite(host: string): Promise<Site>;
504
+ updateSite(host: string, data: {
505
+ name?: string;
506
+ status?: string;
507
+ }): Promise<Site>;
508
+ deleteSite(host: string): Promise<void>;
509
+ updateSiteAdmins(host: string, admins: SiteAdmin[]): Promise<void>;
510
+ purgeSite(host: string): Promise<void>;
511
+ inviteToSite(host: string, email: string, adminUrl: string): Promise<{
512
+ email: string;
513
+ sub: string;
514
+ existed: boolean;
515
+ }>;
516
+ listCollections(host: string): Promise<Collection[]>;
517
+ createCollection(host: string, input: CollectionInput): Promise<Collection>;
518
+ getCollection(host: string, name: string): Promise<Collection>;
519
+ updateCollection(host: string, name: string, input: CollectionInput): Promise<Collection>;
520
+ deleteCollection(host: string, name: string): Promise<void>;
521
+ listBlockTypes(host: string): Promise<BlockType[]>;
522
+ createBlockType(host: string, input: BlockTypeInput): Promise<BlockType>;
523
+ getBlockType(host: string, name: string): Promise<BlockType>;
524
+ updateBlockType(host: string, name: string, input: BlockTypeInput): Promise<BlockType>;
525
+ deleteBlockType(host: string, name: string): Promise<void>;
526
+ listContent(host: string, params?: ContentListParams): Promise<PaginatedResponse<ContentItem>>;
527
+ createContent(host: string, params: CreateContentParams): Promise<SaveDraftResult>;
528
+ getContent(host: string, contentId: string): Promise<ContentDetail>;
529
+ deleteContent(host: string, contentId: string): Promise<void>;
530
+ saveDraft(host: string, contentId: string, draft: SaveDraftParams): Promise<SaveDraftResult>;
531
+ publishContent(host: string, contentId: string): Promise<ContentItem>;
532
+ unpublishContent(host: string, contentId: string): Promise<void>;
533
+ discardDraft(host: string, contentId: string): Promise<void>;
534
+ getPublishedContent(host: string, contentId: string): Promise<ContentItem & {
535
+ body?: Record<string, unknown>;
536
+ }>;
537
+ listVersions(host: string, contentId: string): Promise<{
538
+ versions: {
539
+ blockId: string;
540
+ createdAt: number;
541
+ createdBy: string;
542
+ }[];
543
+ }>;
544
+ getVersion(host: string, contentId: string, blockId: string): Promise<{
545
+ body: Record<string, unknown>;
546
+ }>;
547
+ getReferences(host: string, contentId: string): Promise<ReferencesResult>;
548
+ listMedia(host: string, params?: MediaListParams): Promise<PaginatedResponse<AdminMediaItem>>;
549
+ getMedia(host: string, mediaId: string): Promise<AdminMediaItem>;
550
+ updateMedia(host: string, mediaId: string, data: {
551
+ alt?: string;
552
+ caption?: string;
553
+ userTags?: string[];
554
+ folderId?: string | null;
555
+ }): Promise<AdminMediaItem>;
556
+ deleteMedia(host: string, mediaId: string): Promise<void>;
557
+ getUploadUrl(host: string, params: {
558
+ filename: string;
559
+ contentType: string;
560
+ size: number;
561
+ }): Promise<UploadURLResult>;
562
+ completeUpload(host: string, mediaId: string, params: {
563
+ filename: string;
564
+ alt?: string;
565
+ caption?: string;
566
+ folderId?: string;
567
+ }): Promise<AdminMediaItem>;
568
+ /**
569
+ * Convenience: download from URL and upload through the media pipeline.
570
+ * Works in both Node.js and browsers (uses fetch + Uint8Array, no Node APIs).
571
+ */
572
+ uploadFromUrl(host: string, url: string, filename: string, alt?: string): Promise<AdminMediaItem>;
573
+ listMediaFolders(host: string): Promise<MediaFolder[]>;
574
+ createMediaFolder(host: string, name: string): Promise<MediaFolder>;
575
+ updateMediaFolder(host: string, folderId: string, name: string): Promise<MediaFolder>;
576
+ deleteMediaFolder(host: string, folderId: string): Promise<void>;
577
+ bulkMediaOperation(host: string, params: BulkMediaRequest): Promise<BulkMediaResult>;
578
+ checkDuplicate(host: string, filename: string, size: number): Promise<DuplicateCheckResponse>;
579
+ getTransformUrl(host: string, mediaId: string, params: MediaTransformParams): Promise<{
580
+ url: string;
581
+ }>;
582
+ saveTransform(host: string, mediaId: string, params: SaveTransformRequest): Promise<AdminMediaItem>;
583
+ getMediaUsage(host: string, mediaId: string): Promise<MediaUsageResponse>;
584
+ listApiKeys(host: string): Promise<ApiKeyItem[]>;
585
+ createApiKey(host: string, label: string): Promise<ApiKeyCreateResponse>;
586
+ updateApiKey(host: string, keyId: string, label: string): Promise<ApiKeyItem>;
587
+ deleteApiKey(host: string, keyId: string): Promise<void>;
588
+ listWebhooks(host: string): Promise<Webhook[]>;
589
+ createWebhook(host: string, data: {
590
+ url: string;
591
+ events: string[];
592
+ }): Promise<Webhook>;
593
+ getWebhook(host: string, webhookId: string): Promise<Webhook>;
594
+ updateWebhook(host: string, webhookId: string, data: {
595
+ url?: string;
596
+ events?: string[];
597
+ }): Promise<Webhook>;
598
+ deleteWebhook(host: string, webhookId: string): Promise<void>;
599
+ testWebhook(host: string, webhookId: string): Promise<void>;
600
+ rotateWebhookSecret(host: string, webhookId: string): Promise<{
601
+ secret: string;
602
+ }>;
603
+ listWebhookDeliveries(host: string, webhookId: string): Promise<PaginatedResponse<WebhookDelivery>>;
604
+ getWebhookDelivery(host: string, webhookId: string, deliveryId: string): Promise<WebhookDelivery>;
605
+ retryDelivery(host: string, webhookId: string, deliveryId: string): Promise<void>;
606
+ listTags(host: string): Promise<{
607
+ tags: {
608
+ tag: string;
609
+ count: number;
610
+ }[];
611
+ }>;
612
+ createTag(host: string, tag: string): Promise<void>;
613
+ deleteTag(host: string, tag: string): Promise<void>;
614
+ listAuditEvents(host: string, params?: AuditListParams): Promise<PaginatedResponse<AuditEvent>>;
615
+ listUsers(): Promise<{
616
+ users: {
617
+ sub: string;
618
+ email: string;
619
+ status: string;
620
+ createdAt: number;
621
+ mfaEnabled: boolean;
622
+ }[];
623
+ }>;
624
+ deleteUser(sub: string): Promise<void>;
625
+ disableUserMfa(sub: string): Promise<void>;
626
+ resolveAdmins(subs: string[]): Promise<{
627
+ users: Record<string, {
628
+ sub: string;
629
+ email: string;
630
+ } | null>;
631
+ }>;
632
+ listSuperAdmins(): Promise<{
633
+ superAdminIds: string[];
634
+ }>;
635
+ updateSuperAdmins(superAdminIds: string[]): Promise<void>;
636
+ }
637
+
638
+ /**
639
+ * Upload a file from the local filesystem.
640
+ * Node.js only — uses fs/promises.
641
+ */
642
+ declare function uploadFile(client: HeadroomAdminClient, host: string, filePath: string, alt?: string): Promise<AdminMediaItem>;
643
+
644
+ export { uploadFile };
package/dist/node.js ADDED
@@ -0,0 +1,41 @@
1
+ // src/mime.ts
2
+ var MIME_MAP = {
3
+ ".jpg": "image/jpeg",
4
+ ".jpeg": "image/jpeg",
5
+ ".png": "image/png",
6
+ ".gif": "image/gif",
7
+ ".webp": "image/webp",
8
+ ".svg": "image/svg+xml",
9
+ ".mp4": "video/mp4",
10
+ ".pdf": "application/pdf"
11
+ };
12
+ function mimeFromFilename(filename) {
13
+ const ext = filename.includes(".") ? "." + filename.split(".").pop().toLowerCase() : "";
14
+ return MIME_MAP[ext] || "application/octet-stream";
15
+ }
16
+
17
+ // src/node.ts
18
+ async function uploadFile(client, host, filePath, alt) {
19
+ const fs = await import("fs/promises");
20
+ const path = await import("path");
21
+ const data = await fs.readFile(filePath);
22
+ const filename = path.basename(filePath);
23
+ const contentType = mimeFromFilename(filename);
24
+ const { mediaId, uploadUrl } = await client.getUploadUrl(host, {
25
+ filename,
26
+ contentType,
27
+ size: data.length
28
+ });
29
+ const uploadRes = await fetch(uploadUrl, {
30
+ method: "PUT",
31
+ body: data,
32
+ headers: { "Content-Type": contentType }
33
+ });
34
+ if (!uploadRes.ok) {
35
+ throw new Error(`S3 upload failed: ${uploadRes.status}`);
36
+ }
37
+ return client.completeUpload(host, mediaId, { filename, alt });
38
+ }
39
+ export {
40
+ uploadFile
41
+ };