@headroom-cms/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/README.md +477 -0
- package/blocks/BlockRenderer.astro +61 -0
- package/blocks/BulletList.astro +19 -0
- package/blocks/CheckList.astro +27 -0
- package/blocks/CodeBlock.astro +14 -0
- package/blocks/Fallback.astro +24 -0
- package/blocks/Heading.astro +17 -0
- package/blocks/Image.astro +31 -0
- package/blocks/InlineContent.astro +47 -0
- package/blocks/NumberedList.astro +19 -0
- package/blocks/Paragraph.astro +15 -0
- package/blocks/Table.astro +29 -0
- package/dist/astro.d.ts +49 -0
- package/dist/astro.js +325 -0
- package/dist/codegen.cjs +172 -0
- package/dist/codegen.d.cts +49 -0
- package/dist/codegen.d.ts +49 -0
- package/dist/codegen.js +144 -0
- package/dist/headroom-blocks.css +96 -0
- package/dist/index.cjs +202 -0
- package/dist/index.d.cts +227 -0
- package/dist/index.d.ts +227 -0
- package/dist/index.js +174 -0
- package/dist/react.cjs +323 -0
- package/dist/react.d.cts +105 -0
- package/dist/react.d.ts +105 -0
- package/dist/react.js +275 -0
- package/package.json +72 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
interface TextStyles {
|
|
2
|
+
bold?: boolean;
|
|
3
|
+
italic?: boolean;
|
|
4
|
+
underline?: boolean;
|
|
5
|
+
strikethrough?: boolean;
|
|
6
|
+
code?: boolean;
|
|
7
|
+
}
|
|
8
|
+
interface TextContent {
|
|
9
|
+
type: "text";
|
|
10
|
+
text: string;
|
|
11
|
+
styles?: TextStyles;
|
|
12
|
+
}
|
|
13
|
+
interface LinkContent {
|
|
14
|
+
type: "link";
|
|
15
|
+
href: string;
|
|
16
|
+
content: TextContent[];
|
|
17
|
+
}
|
|
18
|
+
type InlineContent = TextContent | LinkContent;
|
|
19
|
+
interface TableRow {
|
|
20
|
+
cells: InlineContent[][];
|
|
21
|
+
}
|
|
22
|
+
interface TableContent {
|
|
23
|
+
type: "tableContent";
|
|
24
|
+
rows: TableRow[];
|
|
25
|
+
}
|
|
26
|
+
interface Block {
|
|
27
|
+
id: string;
|
|
28
|
+
type: string;
|
|
29
|
+
props?: Record<string, unknown> | null;
|
|
30
|
+
content?: InlineContent[] | TableContent;
|
|
31
|
+
children?: Block[];
|
|
32
|
+
}
|
|
33
|
+
/** A reference to another content item (used in relationships and _refs). */
|
|
34
|
+
interface ContentRef {
|
|
35
|
+
contentId: string;
|
|
36
|
+
collection: string;
|
|
37
|
+
slug: string;
|
|
38
|
+
title: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Resolved content reference in the public API `_refs` map.
|
|
42
|
+
* Includes `published` status so frontends can handle broken links.
|
|
43
|
+
*/
|
|
44
|
+
interface PublicContentRef extends ContentRef {
|
|
45
|
+
published: boolean;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Map of contentId → resolved reference metadata.
|
|
49
|
+
* Returned in `_refs` on single-content API responses.
|
|
50
|
+
* Used by block renderers to resolve `headroom://content/...` links.
|
|
51
|
+
*/
|
|
52
|
+
type RefsMap = Record<string, PublicContentRef>;
|
|
53
|
+
interface ContentMetadata {
|
|
54
|
+
contentId: string;
|
|
55
|
+
collection: string;
|
|
56
|
+
slug: string;
|
|
57
|
+
title: string;
|
|
58
|
+
snippet: string;
|
|
59
|
+
publishedAt: number;
|
|
60
|
+
tags: string[];
|
|
61
|
+
coverUrl?: string;
|
|
62
|
+
coverMediaId?: string;
|
|
63
|
+
lastPublishedAt?: number;
|
|
64
|
+
}
|
|
65
|
+
interface ContentItem extends ContentMetadata {
|
|
66
|
+
body?: {
|
|
67
|
+
content?: Block[];
|
|
68
|
+
[key: string]: unknown;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Resolved content link references. Map of contentId → metadata.
|
|
72
|
+
* Populated on single-content responses (GET by ID, GET singleton).
|
|
73
|
+
* Used by block renderers to resolve `headroom://content/{collection}/{contentId}` links.
|
|
74
|
+
*/
|
|
75
|
+
_refs?: RefsMap;
|
|
76
|
+
/**
|
|
77
|
+
* Content relationships. Map of relationship name → array of referenced content.
|
|
78
|
+
* Populated on single-content responses (GET by ID, GET singleton).
|
|
79
|
+
*/
|
|
80
|
+
relationships?: Record<string, ContentRef[]>;
|
|
81
|
+
}
|
|
82
|
+
interface ContentListResult {
|
|
83
|
+
items: ContentMetadata[];
|
|
84
|
+
cursor?: string;
|
|
85
|
+
hasMore: boolean;
|
|
86
|
+
}
|
|
87
|
+
interface FieldDef {
|
|
88
|
+
name: string;
|
|
89
|
+
type: string;
|
|
90
|
+
label: string;
|
|
91
|
+
options?: Record<string, unknown> | null;
|
|
92
|
+
}
|
|
93
|
+
interface CollectionSummary {
|
|
94
|
+
name: string;
|
|
95
|
+
label: string;
|
|
96
|
+
labelSingular: string;
|
|
97
|
+
slug?: string;
|
|
98
|
+
singleton: boolean;
|
|
99
|
+
}
|
|
100
|
+
/** Defines a belongs-to relationship on a collection. */
|
|
101
|
+
interface RelationshipDef {
|
|
102
|
+
name: string;
|
|
103
|
+
label: string;
|
|
104
|
+
targetCollection: string;
|
|
105
|
+
multiple: boolean;
|
|
106
|
+
}
|
|
107
|
+
interface Collection extends CollectionSummary {
|
|
108
|
+
version: number;
|
|
109
|
+
fields: FieldDef[];
|
|
110
|
+
relationships?: RelationshipDef[];
|
|
111
|
+
}
|
|
112
|
+
interface CollectionListResult {
|
|
113
|
+
items: CollectionSummary[];
|
|
114
|
+
}
|
|
115
|
+
interface BlockTypeDef {
|
|
116
|
+
name: string;
|
|
117
|
+
label: string;
|
|
118
|
+
icon?: string;
|
|
119
|
+
fields?: FieldDef[];
|
|
120
|
+
previewTemplate?: string;
|
|
121
|
+
previewData?: Record<string, unknown>;
|
|
122
|
+
}
|
|
123
|
+
interface BlockTypeListResult {
|
|
124
|
+
items: BlockTypeDef[];
|
|
125
|
+
builtIn: BlockTypeDef[];
|
|
126
|
+
}
|
|
127
|
+
interface BatchContentResult {
|
|
128
|
+
items: ContentItem[];
|
|
129
|
+
missing: string[];
|
|
130
|
+
}
|
|
131
|
+
interface HeadroomConfig {
|
|
132
|
+
/** Base URL of the Headroom API (e.g. "https://api.example.com") */
|
|
133
|
+
apiUrl: string;
|
|
134
|
+
/** Site host identifier (e.g. "mysite.com") */
|
|
135
|
+
site: string;
|
|
136
|
+
/** Public API key for X-Headroom-Key header */
|
|
137
|
+
apiKey: string;
|
|
138
|
+
/** CDN base URL for media (e.g. "https://d123.cloudfront.net"). Required for mediaUrl(). */
|
|
139
|
+
cdnUrl?: string;
|
|
140
|
+
/** HMAC secret for signing image transform URLs. Required for transformUrl(). */
|
|
141
|
+
imageSigningSecret?: string;
|
|
142
|
+
}
|
|
143
|
+
/** Options for image transforms via the Sharp Lambda pipeline. */
|
|
144
|
+
interface TransformOptions {
|
|
145
|
+
/** Target width in pixels */
|
|
146
|
+
width?: number;
|
|
147
|
+
/** Target height in pixels */
|
|
148
|
+
height?: number;
|
|
149
|
+
/** Resize fit mode */
|
|
150
|
+
fit?: "cover" | "contain" | "fill" | "inside" | "outside";
|
|
151
|
+
/** Output format */
|
|
152
|
+
format?: "webp" | "avif" | "jpeg" | "png";
|
|
153
|
+
/** Quality (1-100) */
|
|
154
|
+
quality?: number;
|
|
155
|
+
}
|
|
156
|
+
interface HeadroomErrorBody {
|
|
157
|
+
error: string;
|
|
158
|
+
code?: string;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
declare class HeadroomError extends Error {
|
|
162
|
+
status: number;
|
|
163
|
+
code: string;
|
|
164
|
+
constructor(status: number, code: string, message: string);
|
|
165
|
+
}
|
|
166
|
+
declare class HeadroomClient {
|
|
167
|
+
private config;
|
|
168
|
+
constructor(config: HeadroomConfig);
|
|
169
|
+
/** Build the full URL for a public API path */
|
|
170
|
+
private url;
|
|
171
|
+
/**
|
|
172
|
+
* Construct a full CDN URL from a stored media path.
|
|
173
|
+
* Media paths are stored as `/media/{site}/{mediaId}/original{ext}` in
|
|
174
|
+
* content responses (block props, coverUrl, field values).
|
|
175
|
+
*
|
|
176
|
+
* client.mediaUrl("/media/myblog.com/01ABC/original.jpg")
|
|
177
|
+
* → "https://d123.cloudfront.net/media/myblog.com/01ABC/original.jpg"
|
|
178
|
+
*
|
|
179
|
+
* Returns the path as-is if no cdnUrl is configured (useful for dev).
|
|
180
|
+
*/
|
|
181
|
+
mediaUrl(path: string | undefined): string;
|
|
182
|
+
/**
|
|
183
|
+
* Build a signed image transform URL from a stored media path.
|
|
184
|
+
* Converts the `/media/...` path to `/img/...`, appends transform
|
|
185
|
+
* parameters, and signs with HMAC-SHA256 (matching the Go imagesign).
|
|
186
|
+
*
|
|
187
|
+
* Falls back to `mediaUrl()` when no signing secret is configured
|
|
188
|
+
* or when no transform options are provided.
|
|
189
|
+
*
|
|
190
|
+
* client.transformUrl("/media/myblog.com/01ABC/original.jpg", { width: 800, format: "webp" })
|
|
191
|
+
* → "https://cdn.example.com/img/myblog.com/01ABC/original.jpg?format=webp&w=800&sig=abc123..."
|
|
192
|
+
*/
|
|
193
|
+
transformUrl(path: string | undefined, opts?: TransformOptions): string;
|
|
194
|
+
private fetch;
|
|
195
|
+
private fetchPost;
|
|
196
|
+
listContent(collection: string, opts?: {
|
|
197
|
+
limit?: number;
|
|
198
|
+
cursor?: string;
|
|
199
|
+
before?: number;
|
|
200
|
+
after?: number;
|
|
201
|
+
/** Sort order: "published_desc" (default), "published_asc", "title_asc", "title_desc" */
|
|
202
|
+
sort?: "published_desc" | "published_asc" | "title_asc" | "title_desc";
|
|
203
|
+
/** Find content that has a relationship pointing to this content ID (reverse query) */
|
|
204
|
+
relatedTo?: string;
|
|
205
|
+
/** Filter reverse query to a specific relationship field name */
|
|
206
|
+
relField?: string;
|
|
207
|
+
}): Promise<ContentListResult>;
|
|
208
|
+
getContent(contentId: string): Promise<ContentItem>;
|
|
209
|
+
/**
|
|
210
|
+
* Fetch a single content item by slug. Uses a dedicated server-side
|
|
211
|
+
* endpoint for efficient lookup (no client-side pagination).
|
|
212
|
+
* Returns undefined if no published content matches the slug.
|
|
213
|
+
*/
|
|
214
|
+
getContentBySlug(collection: string, slug: string): Promise<ContentItem | undefined>;
|
|
215
|
+
getSingleton(collection: string): Promise<ContentItem>;
|
|
216
|
+
/**
|
|
217
|
+
* Fetch multiple content items with bodies in a single request.
|
|
218
|
+
* Max 50 IDs per call. Items not found or unpublished are listed in `missing`.
|
|
219
|
+
*/
|
|
220
|
+
getBatchContent(ids: string[]): Promise<BatchContentResult>;
|
|
221
|
+
listCollections(): Promise<CollectionListResult>;
|
|
222
|
+
getCollection(name: string): Promise<Collection>;
|
|
223
|
+
listBlockTypes(): Promise<BlockTypeListResult>;
|
|
224
|
+
getVersion(): Promise<number>;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export { type BatchContentResult, type Block, type BlockTypeDef, type BlockTypeListResult, type Collection, type CollectionListResult, type CollectionSummary, type ContentItem, type ContentListResult, type ContentMetadata, type ContentRef, type FieldDef, HeadroomClient, type HeadroomConfig, HeadroomError, type HeadroomErrorBody, type InlineContent, type LinkContent, type PublicContentRef, type RefsMap, type RelationshipDef, type TableContent, type TableRow, type TextContent, type TextStyles, type TransformOptions };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
interface TextStyles {
|
|
2
|
+
bold?: boolean;
|
|
3
|
+
italic?: boolean;
|
|
4
|
+
underline?: boolean;
|
|
5
|
+
strikethrough?: boolean;
|
|
6
|
+
code?: boolean;
|
|
7
|
+
}
|
|
8
|
+
interface TextContent {
|
|
9
|
+
type: "text";
|
|
10
|
+
text: string;
|
|
11
|
+
styles?: TextStyles;
|
|
12
|
+
}
|
|
13
|
+
interface LinkContent {
|
|
14
|
+
type: "link";
|
|
15
|
+
href: string;
|
|
16
|
+
content: TextContent[];
|
|
17
|
+
}
|
|
18
|
+
type InlineContent = TextContent | LinkContent;
|
|
19
|
+
interface TableRow {
|
|
20
|
+
cells: InlineContent[][];
|
|
21
|
+
}
|
|
22
|
+
interface TableContent {
|
|
23
|
+
type: "tableContent";
|
|
24
|
+
rows: TableRow[];
|
|
25
|
+
}
|
|
26
|
+
interface Block {
|
|
27
|
+
id: string;
|
|
28
|
+
type: string;
|
|
29
|
+
props?: Record<string, unknown> | null;
|
|
30
|
+
content?: InlineContent[] | TableContent;
|
|
31
|
+
children?: Block[];
|
|
32
|
+
}
|
|
33
|
+
/** A reference to another content item (used in relationships and _refs). */
|
|
34
|
+
interface ContentRef {
|
|
35
|
+
contentId: string;
|
|
36
|
+
collection: string;
|
|
37
|
+
slug: string;
|
|
38
|
+
title: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Resolved content reference in the public API `_refs` map.
|
|
42
|
+
* Includes `published` status so frontends can handle broken links.
|
|
43
|
+
*/
|
|
44
|
+
interface PublicContentRef extends ContentRef {
|
|
45
|
+
published: boolean;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Map of contentId → resolved reference metadata.
|
|
49
|
+
* Returned in `_refs` on single-content API responses.
|
|
50
|
+
* Used by block renderers to resolve `headroom://content/...` links.
|
|
51
|
+
*/
|
|
52
|
+
type RefsMap = Record<string, PublicContentRef>;
|
|
53
|
+
interface ContentMetadata {
|
|
54
|
+
contentId: string;
|
|
55
|
+
collection: string;
|
|
56
|
+
slug: string;
|
|
57
|
+
title: string;
|
|
58
|
+
snippet: string;
|
|
59
|
+
publishedAt: number;
|
|
60
|
+
tags: string[];
|
|
61
|
+
coverUrl?: string;
|
|
62
|
+
coverMediaId?: string;
|
|
63
|
+
lastPublishedAt?: number;
|
|
64
|
+
}
|
|
65
|
+
interface ContentItem extends ContentMetadata {
|
|
66
|
+
body?: {
|
|
67
|
+
content?: Block[];
|
|
68
|
+
[key: string]: unknown;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Resolved content link references. Map of contentId → metadata.
|
|
72
|
+
* Populated on single-content responses (GET by ID, GET singleton).
|
|
73
|
+
* Used by block renderers to resolve `headroom://content/{collection}/{contentId}` links.
|
|
74
|
+
*/
|
|
75
|
+
_refs?: RefsMap;
|
|
76
|
+
/**
|
|
77
|
+
* Content relationships. Map of relationship name → array of referenced content.
|
|
78
|
+
* Populated on single-content responses (GET by ID, GET singleton).
|
|
79
|
+
*/
|
|
80
|
+
relationships?: Record<string, ContentRef[]>;
|
|
81
|
+
}
|
|
82
|
+
interface ContentListResult {
|
|
83
|
+
items: ContentMetadata[];
|
|
84
|
+
cursor?: string;
|
|
85
|
+
hasMore: boolean;
|
|
86
|
+
}
|
|
87
|
+
interface FieldDef {
|
|
88
|
+
name: string;
|
|
89
|
+
type: string;
|
|
90
|
+
label: string;
|
|
91
|
+
options?: Record<string, unknown> | null;
|
|
92
|
+
}
|
|
93
|
+
interface CollectionSummary {
|
|
94
|
+
name: string;
|
|
95
|
+
label: string;
|
|
96
|
+
labelSingular: string;
|
|
97
|
+
slug?: string;
|
|
98
|
+
singleton: boolean;
|
|
99
|
+
}
|
|
100
|
+
/** Defines a belongs-to relationship on a collection. */
|
|
101
|
+
interface RelationshipDef {
|
|
102
|
+
name: string;
|
|
103
|
+
label: string;
|
|
104
|
+
targetCollection: string;
|
|
105
|
+
multiple: boolean;
|
|
106
|
+
}
|
|
107
|
+
interface Collection extends CollectionSummary {
|
|
108
|
+
version: number;
|
|
109
|
+
fields: FieldDef[];
|
|
110
|
+
relationships?: RelationshipDef[];
|
|
111
|
+
}
|
|
112
|
+
interface CollectionListResult {
|
|
113
|
+
items: CollectionSummary[];
|
|
114
|
+
}
|
|
115
|
+
interface BlockTypeDef {
|
|
116
|
+
name: string;
|
|
117
|
+
label: string;
|
|
118
|
+
icon?: string;
|
|
119
|
+
fields?: FieldDef[];
|
|
120
|
+
previewTemplate?: string;
|
|
121
|
+
previewData?: Record<string, unknown>;
|
|
122
|
+
}
|
|
123
|
+
interface BlockTypeListResult {
|
|
124
|
+
items: BlockTypeDef[];
|
|
125
|
+
builtIn: BlockTypeDef[];
|
|
126
|
+
}
|
|
127
|
+
interface BatchContentResult {
|
|
128
|
+
items: ContentItem[];
|
|
129
|
+
missing: string[];
|
|
130
|
+
}
|
|
131
|
+
interface HeadroomConfig {
|
|
132
|
+
/** Base URL of the Headroom API (e.g. "https://api.example.com") */
|
|
133
|
+
apiUrl: string;
|
|
134
|
+
/** Site host identifier (e.g. "mysite.com") */
|
|
135
|
+
site: string;
|
|
136
|
+
/** Public API key for X-Headroom-Key header */
|
|
137
|
+
apiKey: string;
|
|
138
|
+
/** CDN base URL for media (e.g. "https://d123.cloudfront.net"). Required for mediaUrl(). */
|
|
139
|
+
cdnUrl?: string;
|
|
140
|
+
/** HMAC secret for signing image transform URLs. Required for transformUrl(). */
|
|
141
|
+
imageSigningSecret?: string;
|
|
142
|
+
}
|
|
143
|
+
/** Options for image transforms via the Sharp Lambda pipeline. */
|
|
144
|
+
interface TransformOptions {
|
|
145
|
+
/** Target width in pixels */
|
|
146
|
+
width?: number;
|
|
147
|
+
/** Target height in pixels */
|
|
148
|
+
height?: number;
|
|
149
|
+
/** Resize fit mode */
|
|
150
|
+
fit?: "cover" | "contain" | "fill" | "inside" | "outside";
|
|
151
|
+
/** Output format */
|
|
152
|
+
format?: "webp" | "avif" | "jpeg" | "png";
|
|
153
|
+
/** Quality (1-100) */
|
|
154
|
+
quality?: number;
|
|
155
|
+
}
|
|
156
|
+
interface HeadroomErrorBody {
|
|
157
|
+
error: string;
|
|
158
|
+
code?: string;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
declare class HeadroomError extends Error {
|
|
162
|
+
status: number;
|
|
163
|
+
code: string;
|
|
164
|
+
constructor(status: number, code: string, message: string);
|
|
165
|
+
}
|
|
166
|
+
declare class HeadroomClient {
|
|
167
|
+
private config;
|
|
168
|
+
constructor(config: HeadroomConfig);
|
|
169
|
+
/** Build the full URL for a public API path */
|
|
170
|
+
private url;
|
|
171
|
+
/**
|
|
172
|
+
* Construct a full CDN URL from a stored media path.
|
|
173
|
+
* Media paths are stored as `/media/{site}/{mediaId}/original{ext}` in
|
|
174
|
+
* content responses (block props, coverUrl, field values).
|
|
175
|
+
*
|
|
176
|
+
* client.mediaUrl("/media/myblog.com/01ABC/original.jpg")
|
|
177
|
+
* → "https://d123.cloudfront.net/media/myblog.com/01ABC/original.jpg"
|
|
178
|
+
*
|
|
179
|
+
* Returns the path as-is if no cdnUrl is configured (useful for dev).
|
|
180
|
+
*/
|
|
181
|
+
mediaUrl(path: string | undefined): string;
|
|
182
|
+
/**
|
|
183
|
+
* Build a signed image transform URL from a stored media path.
|
|
184
|
+
* Converts the `/media/...` path to `/img/...`, appends transform
|
|
185
|
+
* parameters, and signs with HMAC-SHA256 (matching the Go imagesign).
|
|
186
|
+
*
|
|
187
|
+
* Falls back to `mediaUrl()` when no signing secret is configured
|
|
188
|
+
* or when no transform options are provided.
|
|
189
|
+
*
|
|
190
|
+
* client.transformUrl("/media/myblog.com/01ABC/original.jpg", { width: 800, format: "webp" })
|
|
191
|
+
* → "https://cdn.example.com/img/myblog.com/01ABC/original.jpg?format=webp&w=800&sig=abc123..."
|
|
192
|
+
*/
|
|
193
|
+
transformUrl(path: string | undefined, opts?: TransformOptions): string;
|
|
194
|
+
private fetch;
|
|
195
|
+
private fetchPost;
|
|
196
|
+
listContent(collection: string, opts?: {
|
|
197
|
+
limit?: number;
|
|
198
|
+
cursor?: string;
|
|
199
|
+
before?: number;
|
|
200
|
+
after?: number;
|
|
201
|
+
/** Sort order: "published_desc" (default), "published_asc", "title_asc", "title_desc" */
|
|
202
|
+
sort?: "published_desc" | "published_asc" | "title_asc" | "title_desc";
|
|
203
|
+
/** Find content that has a relationship pointing to this content ID (reverse query) */
|
|
204
|
+
relatedTo?: string;
|
|
205
|
+
/** Filter reverse query to a specific relationship field name */
|
|
206
|
+
relField?: string;
|
|
207
|
+
}): Promise<ContentListResult>;
|
|
208
|
+
getContent(contentId: string): Promise<ContentItem>;
|
|
209
|
+
/**
|
|
210
|
+
* Fetch a single content item by slug. Uses a dedicated server-side
|
|
211
|
+
* endpoint for efficient lookup (no client-side pagination).
|
|
212
|
+
* Returns undefined if no published content matches the slug.
|
|
213
|
+
*/
|
|
214
|
+
getContentBySlug(collection: string, slug: string): Promise<ContentItem | undefined>;
|
|
215
|
+
getSingleton(collection: string): Promise<ContentItem>;
|
|
216
|
+
/**
|
|
217
|
+
* Fetch multiple content items with bodies in a single request.
|
|
218
|
+
* Max 50 IDs per call. Items not found or unpublished are listed in `missing`.
|
|
219
|
+
*/
|
|
220
|
+
getBatchContent(ids: string[]): Promise<BatchContentResult>;
|
|
221
|
+
listCollections(): Promise<CollectionListResult>;
|
|
222
|
+
getCollection(name: string): Promise<Collection>;
|
|
223
|
+
listBlockTypes(): Promise<BlockTypeListResult>;
|
|
224
|
+
getVersion(): Promise<number>;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export { type BatchContentResult, type Block, type BlockTypeDef, type BlockTypeListResult, type Collection, type CollectionListResult, type CollectionSummary, type ContentItem, type ContentListResult, type ContentMetadata, type ContentRef, type FieldDef, HeadroomClient, type HeadroomConfig, HeadroomError, type HeadroomErrorBody, type InlineContent, type LinkContent, type PublicContentRef, type RefsMap, type RelationshipDef, type TableContent, type TableRow, type TextContent, type TextStyles, type TransformOptions };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// src/client.ts
|
|
2
|
+
import { createHmac } from "crypto";
|
|
3
|
+
var HeadroomError = class extends Error {
|
|
4
|
+
status;
|
|
5
|
+
code;
|
|
6
|
+
constructor(status, code, message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "HeadroomError";
|
|
9
|
+
this.status = status;
|
|
10
|
+
this.code = code;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
var HeadroomClient = class {
|
|
14
|
+
config;
|
|
15
|
+
constructor(config) {
|
|
16
|
+
this.config = {
|
|
17
|
+
...config,
|
|
18
|
+
apiUrl: config.apiUrl.replace(/\/+$/, ""),
|
|
19
|
+
cdnUrl: config.cdnUrl?.replace(/\/+$/, "")
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** Build the full URL for a public API path */
|
|
23
|
+
url(path) {
|
|
24
|
+
return `${this.config.apiUrl}/v1/${this.config.site}${path}`;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Construct a full CDN URL from a stored media path.
|
|
28
|
+
* Media paths are stored as `/media/{site}/{mediaId}/original{ext}` in
|
|
29
|
+
* content responses (block props, coverUrl, field values).
|
|
30
|
+
*
|
|
31
|
+
* client.mediaUrl("/media/myblog.com/01ABC/original.jpg")
|
|
32
|
+
* → "https://d123.cloudfront.net/media/myblog.com/01ABC/original.jpg"
|
|
33
|
+
*
|
|
34
|
+
* Returns the path as-is if no cdnUrl is configured (useful for dev).
|
|
35
|
+
*/
|
|
36
|
+
mediaUrl(path) {
|
|
37
|
+
if (!path) return "";
|
|
38
|
+
return this.config.cdnUrl ? `${this.config.cdnUrl}${path}` : path;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Build a signed image transform URL from a stored media path.
|
|
42
|
+
* Converts the `/media/...` path to `/img/...`, appends transform
|
|
43
|
+
* parameters, and signs with HMAC-SHA256 (matching the Go imagesign).
|
|
44
|
+
*
|
|
45
|
+
* Falls back to `mediaUrl()` when no signing secret is configured
|
|
46
|
+
* or when no transform options are provided.
|
|
47
|
+
*
|
|
48
|
+
* client.transformUrl("/media/myblog.com/01ABC/original.jpg", { width: 800, format: "webp" })
|
|
49
|
+
* → "https://cdn.example.com/img/myblog.com/01ABC/original.jpg?format=webp&w=800&sig=abc123..."
|
|
50
|
+
*/
|
|
51
|
+
transformUrl(path, opts) {
|
|
52
|
+
if (!path) return "";
|
|
53
|
+
if (!this.config.imageSigningSecret || !opts || Object.keys(opts).length === 0) {
|
|
54
|
+
return this.mediaUrl(path);
|
|
55
|
+
}
|
|
56
|
+
const imgPath = path.replace(/^\/media\//, "/img/");
|
|
57
|
+
const params = new URLSearchParams();
|
|
58
|
+
if (opts.width) params.set("w", String(opts.width));
|
|
59
|
+
if (opts.height) params.set("h", String(opts.height));
|
|
60
|
+
if (opts.fit) params.set("fit", opts.fit);
|
|
61
|
+
if (opts.format) params.set("format", opts.format);
|
|
62
|
+
if (opts.quality) params.set("q", String(opts.quality));
|
|
63
|
+
params.sort();
|
|
64
|
+
const encoded = params.toString();
|
|
65
|
+
const canonical = encoded ? `${imgPath}?${encoded}` : imgPath;
|
|
66
|
+
const mac = createHmac("sha256", this.config.imageSigningSecret).update(canonical).digest("hex").slice(0, 32);
|
|
67
|
+
params.set("sig", mac);
|
|
68
|
+
const cdnBase = this.config.cdnUrl || "";
|
|
69
|
+
return `${cdnBase}${imgPath}?${params.toString()}`;
|
|
70
|
+
}
|
|
71
|
+
async fetch(path, params) {
|
|
72
|
+
const base = this.url(path);
|
|
73
|
+
const url = params?.toString() ? `${base}?${params}` : base;
|
|
74
|
+
const res = await fetch(url, {
|
|
75
|
+
headers: { "X-Headroom-Key": this.config.apiKey }
|
|
76
|
+
});
|
|
77
|
+
if (!res.ok) {
|
|
78
|
+
let code = "UNKNOWN";
|
|
79
|
+
let message = `HTTP ${res.status}`;
|
|
80
|
+
try {
|
|
81
|
+
const body = await res.json();
|
|
82
|
+
code = body.code || code;
|
|
83
|
+
message = body.error || message;
|
|
84
|
+
} catch {
|
|
85
|
+
}
|
|
86
|
+
throw new HeadroomError(res.status, code, message);
|
|
87
|
+
}
|
|
88
|
+
return res.json();
|
|
89
|
+
}
|
|
90
|
+
async fetchPost(path, body) {
|
|
91
|
+
const res = await fetch(this.url(path), {
|
|
92
|
+
method: "POST",
|
|
93
|
+
headers: {
|
|
94
|
+
"X-Headroom-Key": this.config.apiKey,
|
|
95
|
+
"Content-Type": "application/json"
|
|
96
|
+
},
|
|
97
|
+
body: JSON.stringify(body)
|
|
98
|
+
});
|
|
99
|
+
if (!res.ok) {
|
|
100
|
+
let code = "UNKNOWN";
|
|
101
|
+
let message = `HTTP ${res.status}`;
|
|
102
|
+
try {
|
|
103
|
+
const errBody = await res.json();
|
|
104
|
+
code = errBody.code || code;
|
|
105
|
+
message = errBody.error || message;
|
|
106
|
+
} catch {
|
|
107
|
+
}
|
|
108
|
+
throw new HeadroomError(res.status, code, message);
|
|
109
|
+
}
|
|
110
|
+
return res.json();
|
|
111
|
+
}
|
|
112
|
+
// --- Content ---
|
|
113
|
+
async listContent(collection, opts) {
|
|
114
|
+
const params = new URLSearchParams({ collection });
|
|
115
|
+
if (opts?.limit) params.set("limit", String(opts.limit));
|
|
116
|
+
if (opts?.cursor) params.set("cursor", opts.cursor);
|
|
117
|
+
if (opts?.before) params.set("before", String(opts.before));
|
|
118
|
+
if (opts?.after) params.set("after", String(opts.after));
|
|
119
|
+
if (opts?.sort) params.set("sort", opts.sort);
|
|
120
|
+
if (opts?.relatedTo) params.set("relatedTo", opts.relatedTo);
|
|
121
|
+
if (opts?.relField) params.set("relField", opts.relField);
|
|
122
|
+
return this.fetch("/content", params);
|
|
123
|
+
}
|
|
124
|
+
async getContent(contentId) {
|
|
125
|
+
return this.fetch(`/content/${contentId}`);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Fetch a single content item by slug. Uses a dedicated server-side
|
|
129
|
+
* endpoint for efficient lookup (no client-side pagination).
|
|
130
|
+
* Returns undefined if no published content matches the slug.
|
|
131
|
+
*/
|
|
132
|
+
async getContentBySlug(collection, slug) {
|
|
133
|
+
try {
|
|
134
|
+
return await this.fetch(
|
|
135
|
+
`/content/by-slug/${encodeURIComponent(collection)}/${encodeURIComponent(slug)}`
|
|
136
|
+
);
|
|
137
|
+
} catch (e) {
|
|
138
|
+
if (e instanceof HeadroomError && e.status === 404) {
|
|
139
|
+
return void 0;
|
|
140
|
+
}
|
|
141
|
+
throw e;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async getSingleton(collection) {
|
|
145
|
+
return this.fetch(`/content/singleton/${collection}`);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Fetch multiple content items with bodies in a single request.
|
|
149
|
+
* Max 50 IDs per call. Items not found or unpublished are listed in `missing`.
|
|
150
|
+
*/
|
|
151
|
+
async getBatchContent(ids) {
|
|
152
|
+
return this.fetchPost("/content/batch", { ids });
|
|
153
|
+
}
|
|
154
|
+
// --- Collections ---
|
|
155
|
+
async listCollections() {
|
|
156
|
+
return this.fetch("/collections");
|
|
157
|
+
}
|
|
158
|
+
async getCollection(name) {
|
|
159
|
+
return this.fetch(`/collections/${name}`);
|
|
160
|
+
}
|
|
161
|
+
// --- Block Types ---
|
|
162
|
+
async listBlockTypes() {
|
|
163
|
+
return this.fetch("/block-types");
|
|
164
|
+
}
|
|
165
|
+
// --- Version ---
|
|
166
|
+
async getVersion() {
|
|
167
|
+
const result = await this.fetch("/version");
|
|
168
|
+
return result.contentVersion;
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
export {
|
|
172
|
+
HeadroomClient,
|
|
173
|
+
HeadroomError
|
|
174
|
+
};
|