@sprintup-cms/sdk 1.0.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.
@@ -0,0 +1,190 @@
1
+ /**
2
+ * @sprintup-cms/sdk — Core Client
3
+ *
4
+ * Zero-dependency, framework-agnostic typed API client for SprintUp Forge CMS.
5
+ *
6
+ * @example
7
+ * import { cmsClient } from '@sprintup-cms/sdk'
8
+ * const page = await cmsClient.getPage('about')
9
+ *
10
+ * @example Custom instance
11
+ * import { createCMSClient } from '@sprintup-cms/sdk'
12
+ * const cms = createCMSClient({ baseUrl: '...', apiKey: '...', appId: '...' })
13
+ */
14
+ interface CMSBlock {
15
+ id: string;
16
+ type: string;
17
+ label?: string;
18
+ locked?: boolean;
19
+ data?: Record<string, any>;
20
+ /** Legacy field — blocks created before v1.1 used `content` instead of `data` */
21
+ content?: Record<string, any>;
22
+ order?: number;
23
+ }
24
+ interface CMSPage {
25
+ _id?: string;
26
+ slug: string;
27
+ title: string;
28
+ description?: string;
29
+ pageType?: string;
30
+ pageTypeId?: string;
31
+ variant?: string;
32
+ status: 'draft' | 'published' | 'archived';
33
+ visibility?: 'public' | 'private' | 'password';
34
+ blocks: CMSBlock[];
35
+ publishedAt?: string;
36
+ updatedAt?: string;
37
+ seo?: {
38
+ title?: string;
39
+ description?: string;
40
+ keywords?: string[];
41
+ ogImage?: string;
42
+ noIndex?: boolean;
43
+ };
44
+ }
45
+ interface CMSPageTypeField {
46
+ id: string;
47
+ name: string;
48
+ label: string;
49
+ fieldType: 'text' | 'textarea' | 'richtext' | 'image' | 'url' | 'number' | 'boolean' | 'date' | 'select' | 'relation' | 'email' | 'phone' | 'slug' | 'color' | 'embed' | 'multi-select' | 'repeater' | 'file' | 'code';
50
+ required?: boolean;
51
+ options?: string[];
52
+ description?: string;
53
+ maxLength?: number;
54
+ multiple?: boolean;
55
+ targetPageTypeKey?: string;
56
+ }
57
+ interface CMSPageTypeSection {
58
+ id: string;
59
+ name: string;
60
+ label: string;
61
+ order: number;
62
+ locked?: boolean;
63
+ fields: CMSPageTypeField[];
64
+ }
65
+ interface CMSPageTypeVariant {
66
+ key: string;
67
+ label: string;
68
+ description?: string;
69
+ visibleSections?: string[];
70
+ }
71
+ interface CMSPageType {
72
+ _id: string;
73
+ name: string;
74
+ key: string;
75
+ description?: string;
76
+ icon?: string;
77
+ category: 'singleton' | 'collection' | 'global';
78
+ contentCategory: 'singleton' | 'collection' | 'global';
79
+ allowedRoles?: string[];
80
+ variants: CMSPageTypeVariant[];
81
+ sections: CMSPageTypeSection[];
82
+ }
83
+ type CMSMenuItemType = 'page' | 'url' | 'dynamic';
84
+ interface CMSMenuItem {
85
+ id: string;
86
+ type: CMSMenuItemType;
87
+ label: string;
88
+ contentId?: string;
89
+ url?: string;
90
+ /** Resolved href — ready to pass to <a href> or <Link href>. Never null, falls back to "#". */
91
+ href: string;
92
+ locked: boolean;
93
+ openInNewTab: boolean;
94
+ children: CMSMenuItem[];
95
+ page?: {
96
+ title?: string;
97
+ slug?: string;
98
+ seoTitle?: string;
99
+ seoDescription?: string;
100
+ };
101
+ }
102
+ interface CMSPageTreeNode {
103
+ id: string;
104
+ contentId: string;
105
+ parentId: string | null;
106
+ order: number;
107
+ locked: boolean;
108
+ visible?: boolean;
109
+ page?: {
110
+ title?: string;
111
+ slug?: string;
112
+ seoTitle?: string;
113
+ seoDescription?: string;
114
+ };
115
+ }
116
+ interface CMSFooterGroup {
117
+ id: string;
118
+ heading: string;
119
+ headingUrl?: string;
120
+ locked: boolean;
121
+ links: CMSMenuItem[];
122
+ }
123
+ interface CMSSiteStructure {
124
+ appId: string;
125
+ pageTree: CMSPageTreeNode[];
126
+ menus: {
127
+ header: CMSMenuItem[];
128
+ footer: CMSFooterGroup[];
129
+ footerBottom: CMSMenuItem[];
130
+ sidebar: CMSMenuItem[];
131
+ [slot: string]: CMSMenuItem[] | CMSFooterGroup[];
132
+ };
133
+ updatedAt?: string;
134
+ }
135
+ interface CMSListMeta {
136
+ total: number;
137
+ page: number;
138
+ perPage: number;
139
+ totalPages: number;
140
+ }
141
+ interface CMSListResponse<T = CMSPage> {
142
+ data: T[];
143
+ meta: CMSListMeta;
144
+ appId: string;
145
+ }
146
+ interface CMSSingleResponse<T = CMSPage> {
147
+ data: T;
148
+ }
149
+ interface CMSClientOptions {
150
+ /** Base URL of your Forge CMS instance, e.g. https://cms.yourschool.io */
151
+ baseUrl?: string;
152
+ /** API key generated in CMS Admin → API Keys. Server-side only. */
153
+ apiKey?: string;
154
+ /** App ID from CMS Admin → Apps, e.g. "school-website" */
155
+ appId?: string;
156
+ }
157
+ interface CMSGetPagesOptions {
158
+ type?: string;
159
+ group?: string;
160
+ page?: number;
161
+ perPage?: number;
162
+ status?: 'published' | 'draft' | 'archived';
163
+ }
164
+ declare function createCMSClient(options?: CMSClientOptions): {
165
+ getPages: (params?: CMSGetPagesOptions) => Promise<CMSPage[]>;
166
+ getPage: (slug: string) => Promise<CMSPage | null>;
167
+ getBlogPosts: () => Promise<CMSPage[]>;
168
+ getEvents: () => Promise<CMSPage[]>;
169
+ getAnnouncements: () => Promise<CMSPage[]>;
170
+ getPreviewPage: (token: string) => Promise<CMSPage | null>;
171
+ getPageWithPreview: (slug: string, previewToken?: string | null) => Promise<CMSPage | null>;
172
+ getPageType: (pageTypeId: string) => Promise<CMSPageType | null>;
173
+ getPageTypes: () => Promise<CMSPageType[]>;
174
+ getSiteStructure: () => Promise<CMSSiteStructure | null>;
175
+ };
176
+ /** Pre-configured singleton. Reads env vars lazily at request time. */
177
+ declare const cmsClient: {
178
+ getPages: (params?: CMSGetPagesOptions) => Promise<CMSPage[]>;
179
+ getPage: (slug: string) => Promise<CMSPage | null>;
180
+ getBlogPosts: () => Promise<CMSPage[]>;
181
+ getEvents: () => Promise<CMSPage[]>;
182
+ getAnnouncements: () => Promise<CMSPage[]>;
183
+ getPreviewPage: (token: string) => Promise<CMSPage | null>;
184
+ getPageWithPreview: (slug: string, previewToken?: string | null) => Promise<CMSPage | null>;
185
+ getPageType: (pageTypeId: string) => Promise<CMSPageType | null>;
186
+ getPageTypes: () => Promise<CMSPageType[]>;
187
+ getSiteStructure: () => Promise<CMSSiteStructure | null>;
188
+ };
189
+
190
+ export { type CMSBlock, type CMSClientOptions, type CMSFooterGroup, type CMSGetPagesOptions, type CMSListMeta, type CMSListResponse, type CMSMenuItem, type CMSMenuItemType, type CMSPage, type CMSPageTreeNode, type CMSPageType, type CMSPageTypeField, type CMSPageTypeSection, type CMSPageTypeVariant, type CMSSingleResponse, type CMSSiteStructure, cmsClient, createCMSClient };
package/dist/client.js ADDED
@@ -0,0 +1,161 @@
1
+ /* @sprintup-cms/sdk — https://forgecms.io */
2
+
3
+ // src/client.ts
4
+ function createCMSClient(options) {
5
+ function cfg() {
6
+ return {
7
+ baseUrl: (options?.baseUrl ?? process.env.NEXT_PUBLIC_CMS_URL ?? process.env.CMS_BASE_URL ?? "").replace(/\/$/, ""),
8
+ apiKey: options?.apiKey ?? process.env.CMS_API_KEY ?? "",
9
+ appId: options?.appId ?? process.env.CMS_APP_ID ?? ""
10
+ };
11
+ }
12
+ function headers() {
13
+ return { "X-CMS-API-Key": cfg().apiKey, "Content-Type": "application/json" };
14
+ }
15
+ async function getPages(params) {
16
+ const { baseUrl, apiKey, appId } = cfg();
17
+ if (!baseUrl || !apiKey || !appId) {
18
+ console.warn("[sprintup-cms] Missing CMS_BASE_URL / CMS_API_KEY / CMS_APP_ID \u2014 returning []");
19
+ return [];
20
+ }
21
+ try {
22
+ const qs = new URLSearchParams();
23
+ if (params?.type) qs.set("type", params.type);
24
+ if (params?.group) qs.set("group", params.group);
25
+ if (params?.page) qs.set("page", String(params.page));
26
+ if (params?.perPage) qs.set("perPage", String(params.perPage));
27
+ const url = `${baseUrl}/api/v1/${appId}/pages${qs.size ? `?${qs}` : ""}`;
28
+ const res = await fetch(url, {
29
+ headers: headers(),
30
+ next: { revalidate: 60, tags: [`cms-pages-${appId}`] }
31
+ });
32
+ if (!res.ok) {
33
+ console.error(`[sprintup-cms] getPages (${res.status})`);
34
+ return [];
35
+ }
36
+ const json = await res.json();
37
+ return json.data ?? [];
38
+ } catch (err) {
39
+ console.error("[sprintup-cms] getPages error:", err);
40
+ return [];
41
+ }
42
+ }
43
+ async function getPage(slug) {
44
+ const { baseUrl, apiKey, appId } = cfg();
45
+ if (!baseUrl || !apiKey || !appId) {
46
+ console.warn("[sprintup-cms] Missing config \u2014 returning null");
47
+ return null;
48
+ }
49
+ try {
50
+ const res = await fetch(`${baseUrl}/api/v1/${appId}/pages/${slug}`, {
51
+ headers: headers(),
52
+ next: { revalidate: 60, tags: [`cms-page-${slug}`, `cms-pages-${appId}`] }
53
+ });
54
+ if (res.status === 404) return null;
55
+ if (!res.ok) {
56
+ console.error(`[sprintup-cms] getPage "${slug}" (${res.status})`);
57
+ return null;
58
+ }
59
+ const json = await res.json();
60
+ return json.data ?? null;
61
+ } catch (err) {
62
+ console.error(`[sprintup-cms] getPage "${slug}" error:`, err);
63
+ return null;
64
+ }
65
+ }
66
+ async function getBlogPosts() {
67
+ return getPages({ type: "blog-post" });
68
+ }
69
+ async function getEvents() {
70
+ return getPages({ type: "event-page" });
71
+ }
72
+ async function getAnnouncements() {
73
+ return getPages({ type: "announcement-page" });
74
+ }
75
+ async function getPreviewPage(token) {
76
+ const { baseUrl, appId } = cfg();
77
+ if (!baseUrl || !appId) return null;
78
+ try {
79
+ const res = await fetch(`${baseUrl}/api/v1/${appId}/preview?token=${encodeURIComponent(token)}`, {
80
+ cache: "no-store"
81
+ });
82
+ if (!res.ok) return null;
83
+ const json = await res.json();
84
+ return json.data ?? null;
85
+ } catch {
86
+ return null;
87
+ }
88
+ }
89
+ async function getPageWithPreview(slug, previewToken) {
90
+ if (previewToken) {
91
+ const preview = await getPreviewPage(previewToken);
92
+ if (preview?.slug === slug) return preview;
93
+ }
94
+ return getPage(slug);
95
+ }
96
+ async function getPageType(pageTypeId) {
97
+ const { baseUrl, apiKey, appId } = cfg();
98
+ if (!baseUrl || !apiKey || !appId || !pageTypeId) return null;
99
+ try {
100
+ const res = await fetch(`${baseUrl}/api/v1/${appId}/page-types/${pageTypeId}`, {
101
+ headers: headers(),
102
+ next: { revalidate: 3600, tags: [`cms-page-type-${pageTypeId}`] }
103
+ });
104
+ if (!res.ok) return null;
105
+ const json = await res.json();
106
+ return json.data ?? null;
107
+ } catch {
108
+ return null;
109
+ }
110
+ }
111
+ async function getPageTypes() {
112
+ const { baseUrl, apiKey, appId } = cfg();
113
+ if (!baseUrl || !apiKey || !appId) return [];
114
+ try {
115
+ const res = await fetch(`${baseUrl}/api/v1/${appId}/page-types`, {
116
+ headers: headers(),
117
+ next: { revalidate: 3600, tags: [`cms-page-types-${appId}`] }
118
+ });
119
+ if (!res.ok) return [];
120
+ const json = await res.json();
121
+ return json.data ?? [];
122
+ } catch {
123
+ return [];
124
+ }
125
+ }
126
+ async function getSiteStructure() {
127
+ const { baseUrl, apiKey, appId } = cfg();
128
+ if (!baseUrl || !apiKey || !appId) return null;
129
+ try {
130
+ const res = await fetch(`${baseUrl}/api/v1/${appId}/site-structure`, {
131
+ headers: headers(),
132
+ next: {
133
+ revalidate: 300,
134
+ tags: [`site-structure-${appId}`]
135
+ }
136
+ });
137
+ if (!res.ok) return null;
138
+ const json = await res.json();
139
+ return json.data ?? null;
140
+ } catch {
141
+ return null;
142
+ }
143
+ }
144
+ return {
145
+ getPages,
146
+ getPage,
147
+ getBlogPosts,
148
+ getEvents,
149
+ getAnnouncements,
150
+ getPreviewPage,
151
+ getPageWithPreview,
152
+ getPageType,
153
+ getPageTypes,
154
+ getSiteStructure
155
+ };
156
+ }
157
+ var cmsClient = createCMSClient();
158
+
159
+ export { cmsClient, createCMSClient };
160
+ //# sourceMappingURL=client.js.map
161
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client.ts"],"names":[],"mappings":"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","file":"client.js","sourcesContent":["/**\n * @sprintup-cms/sdk — Core Client\n *\n * Zero-dependency, framework-agnostic typed API client for SprintUp Forge CMS.\n *\n * @example\n * import { cmsClient } from '@sprintup-cms/sdk'\n * const page = await cmsClient.getPage('about')\n *\n * @example Custom instance\n * import { createCMSClient } from '@sprintup-cms/sdk'\n * const cms = createCMSClient({ baseUrl: '...', apiKey: '...', appId: '...' })\n */\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\nexport interface CMSBlock {\n id: string\n type: string\n label?: string\n locked?: boolean\n data?: Record<string, any>\n /** Legacy field — blocks created before v1.1 used `content` instead of `data` */\n content?: Record<string, any>\n order?: number\n}\n\nexport interface CMSPage {\n _id?: string\n slug: string\n title: string\n description?: string\n pageType?: string\n pageTypeId?: string\n variant?: string\n status: 'draft' | 'published' | 'archived'\n visibility?: 'public' | 'private' | 'password'\n blocks: CMSBlock[]\n publishedAt?: string\n updatedAt?: string\n seo?: {\n title?: string\n description?: string\n keywords?: string[]\n ogImage?: string\n noIndex?: boolean\n }\n}\n\nexport interface CMSPageTypeField {\n id: string\n name: string\n label: string\n fieldType:\n | 'text' | 'textarea' | 'richtext' | 'image' | 'url'\n | 'number' | 'boolean' | 'date' | 'select' | 'relation'\n | 'email' | 'phone' | 'slug' | 'color' | 'embed'\n | 'multi-select' | 'repeater' | 'file' | 'code'\n required?: boolean\n options?: string[]\n description?: string\n maxLength?: number\n multiple?: boolean\n targetPageTypeKey?: string\n}\n\nexport interface CMSPageTypeSection {\n id: string\n name: string\n label: string\n order: number\n locked?: boolean\n fields: CMSPageTypeField[]\n}\n\nexport interface CMSPageTypeVariant {\n key: string\n label: string\n description?: string\n visibleSections?: string[]\n}\n\nexport interface CMSPageType {\n _id: string\n name: string\n key: string\n description?: string\n icon?: string\n category: 'singleton' | 'collection' | 'global'\n contentCategory: 'singleton' | 'collection' | 'global'\n allowedRoles?: string[]\n variants: CMSPageTypeVariant[]\n sections: CMSPageTypeSection[]\n}\n\n// ── Site Structure ─────────────────────────────────────────────────────────────\n\nexport type CMSMenuItemType = 'page' | 'url' | 'dynamic'\n\nexport interface CMSMenuItem {\n id: string\n type: CMSMenuItemType\n label: string\n contentId?: string\n url?: string\n /** Resolved href — ready to pass to <a href> or <Link href>. Never null, falls back to \"#\". */\n href: string\n locked: boolean\n openInNewTab: boolean\n children: CMSMenuItem[]\n page?: {\n title?: string\n slug?: string\n seoTitle?: string\n seoDescription?: string\n }\n}\n\nexport interface CMSPageTreeNode {\n id: string\n contentId: string\n parentId: string | null\n order: number\n locked: boolean\n visible?: boolean\n page?: {\n title?: string\n slug?: string\n seoTitle?: string\n seoDescription?: string\n }\n}\n\nexport interface CMSFooterGroup {\n id: string\n heading: string\n headingUrl?: string\n locked: boolean\n links: CMSMenuItem[]\n}\n\nexport interface CMSSiteStructure {\n appId: string\n pageTree: CMSPageTreeNode[]\n menus: {\n header: CMSMenuItem[]\n footer: CMSFooterGroup[]\n footerBottom: CMSMenuItem[]\n sidebar: CMSMenuItem[]\n [slot: string]: CMSMenuItem[] | CMSFooterGroup[]\n }\n updatedAt?: string\n}\n\n// ── Pagination & Responses ─────────────────────────────────────────────────────\n\nexport interface CMSListMeta {\n total: number\n page: number\n perPage: number\n totalPages: number\n}\n\nexport interface CMSListResponse<T = CMSPage> {\n data: T[]\n meta: CMSListMeta\n appId: string\n}\n\nexport interface CMSSingleResponse<T = CMSPage> {\n data: T\n}\n\n// ── Client Options ─────────────────────────────────────────────────────────────\n\nexport interface CMSClientOptions {\n /** Base URL of your Forge CMS instance, e.g. https://cms.yourschool.io */\n baseUrl?: string\n /** API key generated in CMS Admin → API Keys. Server-side only. */\n apiKey?: string\n /** App ID from CMS Admin → Apps, e.g. \"school-website\" */\n appId?: string\n}\n\nexport interface CMSGetPagesOptions {\n type?: string\n group?: string\n page?: number\n perPage?: number\n status?: 'published' | 'draft' | 'archived'\n}\n\n// ── Factory ───────────────────────────────────────────────────────────────────\n\nexport function createCMSClient(options?: CMSClientOptions) {\n /**\n * Resolve config lazily at request time — NOT at module/build time.\n * This prevents static prerendering crashes when env vars are absent during build.\n */\n function cfg() {\n return {\n baseUrl: (options?.baseUrl ?? process.env.NEXT_PUBLIC_CMS_URL ?? process.env.CMS_BASE_URL ?? '').replace(/\\/$/, ''),\n apiKey: options?.apiKey ?? process.env.CMS_API_KEY ?? '',\n appId: options?.appId ?? process.env.CMS_APP_ID ?? '',\n }\n }\n\n function headers() {\n return { 'X-CMS-API-Key': cfg().apiKey, 'Content-Type': 'application/json' }\n }\n\n // ── Pages ──────────────────────────────────────────────────────────────────\n\n async function getPages(params?: CMSGetPagesOptions): Promise<CMSPage[]> {\n const { baseUrl, apiKey, appId } = cfg()\n if (!baseUrl || !apiKey || !appId) {\n console.warn('[sprintup-cms] Missing CMS_BASE_URL / CMS_API_KEY / CMS_APP_ID — returning []')\n return []\n }\n try {\n const qs = new URLSearchParams()\n if (params?.type) qs.set('type', params.type)\n if (params?.group) qs.set('group', params.group)\n if (params?.page) qs.set('page', String(params.page))\n if (params?.perPage) qs.set('perPage', String(params.perPage))\n const url = `${baseUrl}/api/v1/${appId}/pages${qs.size ? `?${qs}` : ''}`\n const res = await fetch(url, {\n headers: headers(),\n next: { revalidate: 60, tags: [`cms-pages-${appId}`] },\n } as RequestInit)\n if (!res.ok) { console.error(`[sprintup-cms] getPages (${res.status})`); return [] }\n const json: CMSListResponse = await res.json()\n return json.data ?? []\n } catch (err) {\n console.error('[sprintup-cms] getPages error:', err)\n return []\n }\n }\n\n async function getPage(slug: string): Promise<CMSPage | null> {\n const { baseUrl, apiKey, appId } = cfg()\n if (!baseUrl || !apiKey || !appId) {\n console.warn('[sprintup-cms] Missing config — returning null')\n return null\n }\n try {\n const res = await fetch(`${baseUrl}/api/v1/${appId}/pages/${slug}`, {\n headers: headers(),\n next: { revalidate: 60, tags: [`cms-page-${slug}`, `cms-pages-${appId}`] },\n } as RequestInit)\n if (res.status === 404) return null\n if (!res.ok) { console.error(`[sprintup-cms] getPage \"${slug}\" (${res.status})`); return null }\n const json: CMSSingleResponse = await res.json()\n return json.data ?? null\n } catch (err) {\n console.error(`[sprintup-cms] getPage \"${slug}\" error:`, err)\n return null\n }\n }\n\n async function getBlogPosts(): Promise<CMSPage[]> {\n return getPages({ type: 'blog-post' })\n }\n\n async function getEvents(): Promise<CMSPage[]> {\n return getPages({ type: 'event-page' })\n }\n\n async function getAnnouncements(): Promise<CMSPage[]> {\n return getPages({ type: 'announcement-page' })\n }\n\n // ── Preview ────────────────────────────────────────────────────────────────\n\n async function getPreviewPage(token: string): Promise<CMSPage | null> {\n const { baseUrl, appId } = cfg()\n if (!baseUrl || !appId) return null\n try {\n const res = await fetch(`${baseUrl}/api/v1/${appId}/preview?token=${encodeURIComponent(token)}`, {\n cache: 'no-store',\n })\n if (!res.ok) return null\n const json = await res.json()\n return json.data ?? null\n } catch {\n return null\n }\n }\n\n async function getPageWithPreview(slug: string, previewToken?: string | null): Promise<CMSPage | null> {\n if (previewToken) {\n const preview = await getPreviewPage(previewToken)\n if (preview?.slug === slug) return preview\n }\n return getPage(slug)\n }\n\n // ── Page Types ─────────────────────────────────────────────────────────────\n\n async function getPageType(pageTypeId: string): Promise<CMSPageType | null> {\n const { baseUrl, apiKey, appId } = cfg()\n if (!baseUrl || !apiKey || !appId || !pageTypeId) return null\n try {\n const res = await fetch(`${baseUrl}/api/v1/${appId}/page-types/${pageTypeId}`, {\n headers: headers(),\n next: { revalidate: 3600, tags: [`cms-page-type-${pageTypeId}`] },\n } as RequestInit)\n if (!res.ok) return null\n const json = await res.json()\n return json.data ?? null\n } catch {\n return null\n }\n }\n\n async function getPageTypes(): Promise<CMSPageType[]> {\n const { baseUrl, apiKey, appId } = cfg()\n if (!baseUrl || !apiKey || !appId) return []\n try {\n const res = await fetch(`${baseUrl}/api/v1/${appId}/page-types`, {\n headers: headers(),\n next: { revalidate: 3600, tags: [`cms-page-types-${appId}`] },\n } as RequestInit)\n if (!res.ok) return []\n const json = await res.json()\n return json.data ?? []\n } catch {\n return []\n }\n }\n\n // ── Site Structure ─────────────────────────────────────────────────────────\n\n async function getSiteStructure(): Promise<CMSSiteStructure | null> {\n const { baseUrl, apiKey, appId } = cfg()\n if (!baseUrl || !apiKey || !appId) return null\n try {\n const res = await fetch(`${baseUrl}/api/v1/${appId}/site-structure`, {\n headers: headers(),\n next: {\n revalidate: 300,\n tags: [`site-structure-${appId}`],\n },\n } as RequestInit)\n if (!res.ok) return null\n const json = await res.json()\n return json.data ?? null\n } catch {\n return null\n }\n }\n\n return {\n getPages,\n getPage,\n getBlogPosts,\n getEvents,\n getAnnouncements,\n getPreviewPage,\n getPageWithPreview,\n getPageType,\n getPageTypes,\n getSiteStructure,\n }\n}\n\n// ── Default singleton ─────────────────────────────────────────────────────────\n\n/** Pre-configured singleton. Reads env vars lazily at request time. */\nexport const cmsClient = createCMSClient()\n"]}