@utilsy/cms-nextjs 0.1.0 → 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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @utilsy/cms-nextjs
2
2
 
3
- Headless Next.js SDK for [Utilsy gateway-cms](https://github.com/utilsy/utilsy-cms-nextjs-sdk) public blog APIs. Provides a typed HTTP client, domain mappers, server-friendly fetch helpers, and React hooks—bring your own UI.
3
+ Headless Next.js SDK for [Utilsy gateway-cms](https://github.com/utilsy/utilsy-cms-nextjs-sdk) public CMS APIs: blog (`/public/blog`) and dynamic content types (`/public/api`). Provides a typed HTTP client, domain mappers, server-friendly fetch helpers, and React data hooks—bring your own UI.
4
4
 
5
5
  ## Install
6
6
 
@@ -25,7 +25,7 @@ export const cms = createCmsClient({
25
25
  });
26
26
  ```
27
27
 
28
- **Main API gateway** (prefix before `/public/blog`):
28
+ **Main API gateway** (prefix before `/public/blog` and `/public/api`):
29
29
 
30
30
  ```ts
31
31
  export const cms = createCmsClient({
@@ -62,7 +62,35 @@ export default async function BlogPage() {
62
62
  }
63
63
  ```
64
64
 
65
- ### 3. Client engagement (likes & comments)
65
+ ### 3. Server Component (filtered content by type)
66
+
67
+ ```tsx
68
+ import { cms } from "@/lib/cms";
69
+
70
+ export default async function ServicesPage() {
71
+ const result = await cms.content.listMappedEntries(
72
+ "services",
73
+ { page: 1, limit: 12, filters: { featured: true } },
74
+ { next: { revalidate: 60, tags: ["cms:services"] } },
75
+ );
76
+
77
+ if (!result?.entries.length) {
78
+ return <p>No services yet.</p>;
79
+ }
80
+
81
+ return (
82
+ <ul>
83
+ {result.entries.map((entry) => (
84
+ <li key={entry.id}>
85
+ <a href={`/services/${entry.data.slug}`}>{String(entry.data.title ?? "")}</a>
86
+ </li>
87
+ ))}
88
+ </ul>
89
+ );
90
+ }
91
+ ```
92
+
93
+ ### 4. Client engagement (likes & comments)
66
94
 
67
95
  ```tsx
68
96
  "use client";
@@ -97,6 +125,41 @@ function Engagement({ postId }: { postId: string }) {
97
125
  }
98
126
  ```
99
127
 
128
+ ### 5. Client content list (hooks)
129
+
130
+ ```tsx
131
+ "use client";
132
+
133
+ import { CmsProvider, useContentEntries } from "@utilsy/cms-nextjs/react";
134
+ import { cms } from "@/lib/cms";
135
+
136
+ export function ServicesList() {
137
+ return (
138
+ <CmsProvider client={cms}>
139
+ <ServicesListInner />
140
+ </CmsProvider>
141
+ );
142
+ }
143
+
144
+ function ServicesListInner() {
145
+ const { entries, loading, error } = useContentEntries("services", {
146
+ page: 1,
147
+ limit: 20,
148
+ });
149
+
150
+ if (loading) return <p>Loading…</p>;
151
+ if (error) return <p>Error: {error.message}</p>;
152
+
153
+ return (
154
+ <ul>
155
+ {entries.map((e) => (
156
+ <li key={e.id}>{String(e.data.title ?? "")}</li>
157
+ ))}
158
+ </ul>
159
+ );
160
+ }
161
+ ```
162
+
100
163
  ## Environment variables
101
164
 
102
165
  | Variable | Description |
@@ -114,10 +177,19 @@ The CMS resolves the site from:
114
177
 
115
178
  For local dev or gateway-only deployments, always set `siteId` in the client config.
116
179
 
180
+ ## Content types and filters
181
+
182
+ - Endpoint: `GET /public/api/{contentTypeApiId}` (published entries only).
183
+ - Content types must have `apiAccess: PUBLIC`.
184
+ - `filters` are exact matches on `data.<field>` (JSON query param). Example: `{ category: "news" }`.
185
+ - Use `contentTypeApiId` (kebab-case slug from CMS), not the Mongo content type id.
186
+
117
187
  ## API reference
118
188
 
119
189
  ### Client (`createCmsClient`)
120
190
 
191
+ #### Blog
192
+
121
193
  | Method | Description |
122
194
  |--------|-------------|
123
195
  | `blog.listPosts(query?, init?)` | Paginated list (summary fields only) |
@@ -132,6 +204,19 @@ For local dev or gateway-only deployments, always set `siteId` in the client con
132
204
  | `blog.listMappedCategories(...)` | Mapped categories |
133
205
  | `blog.listMappedComments(...)` | Mapped comments |
134
206
 
207
+ #### Content
208
+
209
+ | Method | Description |
210
+ |--------|-------------|
211
+ | `content.listEntries(contentTypeApiId, query?, init?)` | Paginated raw DTOs |
212
+ | `content.getEntry(contentTypeApiId, idOrSlug, init?)` | Single raw DTO |
213
+ | `content.listMappedEntries(...)` | List + `mapCmsEntryToContentEntry` |
214
+ | `content.getMappedEntry(...)` | Single mapped `ContentEntry` |
215
+
216
+ Query params for `listEntries` / `listMappedEntries`: `page`, `limit`, `search`, `sort`, `order`, `filters` (object).
217
+
218
+ Helpers: `serializeContentFilters`, `mapCmsEntryToContentEntry`.
219
+
135
220
  ### React (`@utilsy/cms-nextjs/react`)
136
221
 
137
222
  | Export | Description |
@@ -142,10 +227,12 @@ For local dev or gateway-only deployments, always set `siteId` in the client con
142
227
  | `useBlogEngagement` | Load engagement stats |
143
228
  | `useBlogLike` | Like toggle + counts |
144
229
  | `useBlogComments` | List + submit comments |
230
+ | `useContentEntries` | List mapped entries by content type |
231
+ | `useContentEntry` | Single mapped entry by id or slug |
145
232
 
146
233
  ## CORS
147
234
 
148
- Public blog reads from the browser require either:
235
+ Public CMS reads from the browser require either:
149
236
 
150
237
  - Same-origin proxy (e.g. Next.js Route Handler forwarding to gateway), or
151
238
  - Server Components / Route Handlers calling the SDK server-side.
@@ -11,6 +11,13 @@ type FetchRequestInit = RequestInit & {
11
11
  cache?: RequestCache;
12
12
  };
13
13
 
14
+ type RequestContext = {
15
+ baseUrl: string;
16
+ siteId?: string;
17
+ fetchFn: typeof fetch;
18
+ defaultHeaders: Record<string, string>;
19
+ };
20
+
14
21
  /** Wire DTO from CMS public blog API */
15
22
  type CmsBlogPostDto = {
16
23
  id: string;
@@ -131,12 +138,8 @@ type ListBlogPostsQuery = {
131
138
  tag?: string;
132
139
  };
133
140
 
134
- type BlogRequestContext = {
135
- baseUrl: string;
141
+ type BlogRequestContext = RequestContext & {
136
142
  blogBasePath: string;
137
- siteId?: string;
138
- fetchFn: typeof fetch;
139
- defaultHeaders: Record<string, string>;
140
143
  };
141
144
  declare function createBlogApi(ctx: BlogRequestContext): {
142
145
  listPosts(query?: ListBlogPostsQuery, init?: FetchRequestInit): Promise<CmsBlogPostsPage | null>;
@@ -162,14 +165,81 @@ declare function createBlogApi(ctx: BlogRequestContext): {
162
165
  };
163
166
  type BlogApi = ReturnType<typeof createBlogApi>;
164
167
 
168
+ type ContentFilters = Record<string, unknown>;
169
+ declare function serializeContentFilters(filters: ContentFilters): string;
170
+
171
+ type ListContentEntriesQuery = {
172
+ page?: number;
173
+ limit?: number;
174
+ search?: string;
175
+ sort?: string;
176
+ order?: "asc" | "desc";
177
+ filters?: ContentFilters;
178
+ };
179
+ type CmsContentTypeDto = {
180
+ _id?: string;
181
+ id?: string;
182
+ name?: string;
183
+ apiId?: string;
184
+ fields?: unknown[];
185
+ status?: string;
186
+ };
187
+ type CmsContentEntryDto = {
188
+ _id?: string;
189
+ id?: string;
190
+ contentTypeId?: string;
191
+ contentType?: CmsContentTypeDto;
192
+ siteId?: string;
193
+ status?: string;
194
+ data?: Record<string, unknown>;
195
+ createdAt?: string;
196
+ updatedAt?: string;
197
+ };
198
+ type CmsContentEntriesPage = {
199
+ message?: string;
200
+ docs: CmsContentEntryDto[];
201
+ totalDocs: number;
202
+ };
203
+ type ContentTypeMeta = {
204
+ id: string;
205
+ name: string;
206
+ apiId: string;
207
+ fields?: unknown[];
208
+ status?: string;
209
+ };
210
+ type ContentEntry = {
211
+ id: string;
212
+ contentTypeId: string;
213
+ contentType?: ContentTypeMeta;
214
+ status: string;
215
+ data: Record<string, unknown>;
216
+ createdAt?: string;
217
+ updatedAt?: string;
218
+ };
219
+ type MappedContentEntriesPage = {
220
+ entries: ContentEntry[];
221
+ totalDocs: number;
222
+ };
223
+
224
+ type ContentRequestContext = RequestContext & {
225
+ contentBasePath: string;
226
+ };
227
+ declare function createContentApi(ctx: ContentRequestContext): {
228
+ listEntries(contentTypeApiId: string, query?: ListContentEntriesQuery, init?: FetchRequestInit): Promise<CmsContentEntriesPage | null>;
229
+ getEntry(contentTypeApiId: string, idOrSlug: string, init?: FetchRequestInit): Promise<CmsContentEntryDto | null>;
230
+ listMappedEntries(contentTypeApiId: string, query?: ListContentEntriesQuery, init?: FetchRequestInit): Promise<MappedContentEntriesPage | null>;
231
+ getMappedEntry(contentTypeApiId: string, idOrSlug: string, init?: FetchRequestInit): Promise<ContentEntry | null>;
232
+ };
233
+ type ContentApi = ReturnType<typeof createContentApi>;
234
+
165
235
  type CmsClientConfig = {
166
236
  /** Base URL, e.g. https://cms-gateway.example.com */
167
237
  baseUrl: string;
168
- /** CMS site Mongo id — appended as ?siteId= on every blog request */
238
+ /** CMS site Mongo id — appended as ?siteId= on every public request */
169
239
  siteId?: string;
170
240
  /**
171
- * Path prefix before /public/blog.
172
- * Default '' for gateway-cms direct (/public/blog/...).
241
+ * Path prefix before /public/blog and /public/api.
242
+ * Default '' for gateway-cms direct (/public/blog/..., /public/api/...).
173
243
  * Use '/api/backend/cms' when routing through the main API gateway.
174
244
  */
175
245
  pathPrefix?: string;
@@ -178,7 +248,8 @@ type CmsClientConfig = {
178
248
  };
179
249
  type CmsClient = {
180
250
  blog: BlogApi;
251
+ content: ContentApi;
181
252
  };
182
253
  declare function createCmsClient(config: CmsClientConfig): CmsClient;
183
254
 
184
- export { type BlogApi as B, type CmsApiResponse as C, type FetchRequestInit as F, type ListBlogPostsQuery as L, type BlogAuthor as a, type BlogCategory as b, type BlogComment as c, type BlogCommentReply as d, type BlogEngagement as e, type BlogLikeResult as f, type BlogPost as g, type CmsBlogCategoryDto as h, type CmsBlogCommentDto as i, type CmsBlogPostDto as j, type CmsBlogPostsPage as k, type CmsClient as l, type CmsClientConfig as m, type CreateBlogCommentInput as n, type CreateBlogCommentResult as o, createCmsClient as p };
255
+ export { type BlogApi as B, type CmsApiResponse as C, type FetchRequestInit as F, type ListBlogPostsQuery as L, type MappedContentEntriesPage as M, type BlogAuthor as a, type BlogCategory as b, type BlogComment as c, type BlogCommentReply as d, type BlogEngagement as e, type BlogLikeResult as f, type BlogPost as g, type CmsBlogCategoryDto as h, type CmsBlogCommentDto as i, type CmsBlogPostDto as j, type CmsBlogPostsPage as k, type CmsClient as l, type CmsClientConfig as m, type CmsContentEntriesPage as n, type CmsContentEntryDto as o, type CmsContentTypeDto as p, type ContentApi as q, type ContentEntry as r, type ContentFilters as s, type ContentTypeMeta as t, type CreateBlogCommentInput as u, type CreateBlogCommentResult as v, type ListContentEntriesQuery as w, createCmsClient as x, serializeContentFilters as y };
@@ -11,6 +11,13 @@ type FetchRequestInit = RequestInit & {
11
11
  cache?: RequestCache;
12
12
  };
13
13
 
14
+ type RequestContext = {
15
+ baseUrl: string;
16
+ siteId?: string;
17
+ fetchFn: typeof fetch;
18
+ defaultHeaders: Record<string, string>;
19
+ };
20
+
14
21
  /** Wire DTO from CMS public blog API */
15
22
  type CmsBlogPostDto = {
16
23
  id: string;
@@ -131,12 +138,8 @@ type ListBlogPostsQuery = {
131
138
  tag?: string;
132
139
  };
133
140
 
134
- type BlogRequestContext = {
135
- baseUrl: string;
141
+ type BlogRequestContext = RequestContext & {
136
142
  blogBasePath: string;
137
- siteId?: string;
138
- fetchFn: typeof fetch;
139
- defaultHeaders: Record<string, string>;
140
143
  };
141
144
  declare function createBlogApi(ctx: BlogRequestContext): {
142
145
  listPosts(query?: ListBlogPostsQuery, init?: FetchRequestInit): Promise<CmsBlogPostsPage | null>;
@@ -162,14 +165,81 @@ declare function createBlogApi(ctx: BlogRequestContext): {
162
165
  };
163
166
  type BlogApi = ReturnType<typeof createBlogApi>;
164
167
 
168
+ type ContentFilters = Record<string, unknown>;
169
+ declare function serializeContentFilters(filters: ContentFilters): string;
170
+
171
+ type ListContentEntriesQuery = {
172
+ page?: number;
173
+ limit?: number;
174
+ search?: string;
175
+ sort?: string;
176
+ order?: "asc" | "desc";
177
+ filters?: ContentFilters;
178
+ };
179
+ type CmsContentTypeDto = {
180
+ _id?: string;
181
+ id?: string;
182
+ name?: string;
183
+ apiId?: string;
184
+ fields?: unknown[];
185
+ status?: string;
186
+ };
187
+ type CmsContentEntryDto = {
188
+ _id?: string;
189
+ id?: string;
190
+ contentTypeId?: string;
191
+ contentType?: CmsContentTypeDto;
192
+ siteId?: string;
193
+ status?: string;
194
+ data?: Record<string, unknown>;
195
+ createdAt?: string;
196
+ updatedAt?: string;
197
+ };
198
+ type CmsContentEntriesPage = {
199
+ message?: string;
200
+ docs: CmsContentEntryDto[];
201
+ totalDocs: number;
202
+ };
203
+ type ContentTypeMeta = {
204
+ id: string;
205
+ name: string;
206
+ apiId: string;
207
+ fields?: unknown[];
208
+ status?: string;
209
+ };
210
+ type ContentEntry = {
211
+ id: string;
212
+ contentTypeId: string;
213
+ contentType?: ContentTypeMeta;
214
+ status: string;
215
+ data: Record<string, unknown>;
216
+ createdAt?: string;
217
+ updatedAt?: string;
218
+ };
219
+ type MappedContentEntriesPage = {
220
+ entries: ContentEntry[];
221
+ totalDocs: number;
222
+ };
223
+
224
+ type ContentRequestContext = RequestContext & {
225
+ contentBasePath: string;
226
+ };
227
+ declare function createContentApi(ctx: ContentRequestContext): {
228
+ listEntries(contentTypeApiId: string, query?: ListContentEntriesQuery, init?: FetchRequestInit): Promise<CmsContentEntriesPage | null>;
229
+ getEntry(contentTypeApiId: string, idOrSlug: string, init?: FetchRequestInit): Promise<CmsContentEntryDto | null>;
230
+ listMappedEntries(contentTypeApiId: string, query?: ListContentEntriesQuery, init?: FetchRequestInit): Promise<MappedContentEntriesPage | null>;
231
+ getMappedEntry(contentTypeApiId: string, idOrSlug: string, init?: FetchRequestInit): Promise<ContentEntry | null>;
232
+ };
233
+ type ContentApi = ReturnType<typeof createContentApi>;
234
+
165
235
  type CmsClientConfig = {
166
236
  /** Base URL, e.g. https://cms-gateway.example.com */
167
237
  baseUrl: string;
168
- /** CMS site Mongo id — appended as ?siteId= on every blog request */
238
+ /** CMS site Mongo id — appended as ?siteId= on every public request */
169
239
  siteId?: string;
170
240
  /**
171
- * Path prefix before /public/blog.
172
- * Default '' for gateway-cms direct (/public/blog/...).
241
+ * Path prefix before /public/blog and /public/api.
242
+ * Default '' for gateway-cms direct (/public/blog/..., /public/api/...).
173
243
  * Use '/api/backend/cms' when routing through the main API gateway.
174
244
  */
175
245
  pathPrefix?: string;
@@ -178,7 +248,8 @@ type CmsClientConfig = {
178
248
  };
179
249
  type CmsClient = {
180
250
  blog: BlogApi;
251
+ content: ContentApi;
181
252
  };
182
253
  declare function createCmsClient(config: CmsClientConfig): CmsClient;
183
254
 
184
- export { type BlogApi as B, type CmsApiResponse as C, type FetchRequestInit as F, type ListBlogPostsQuery as L, type BlogAuthor as a, type BlogCategory as b, type BlogComment as c, type BlogCommentReply as d, type BlogEngagement as e, type BlogLikeResult as f, type BlogPost as g, type CmsBlogCategoryDto as h, type CmsBlogCommentDto as i, type CmsBlogPostDto as j, type CmsBlogPostsPage as k, type CmsClient as l, type CmsClientConfig as m, type CreateBlogCommentInput as n, type CreateBlogCommentResult as o, createCmsClient as p };
255
+ export { type BlogApi as B, type CmsApiResponse as C, type FetchRequestInit as F, type ListBlogPostsQuery as L, type MappedContentEntriesPage as M, type BlogAuthor as a, type BlogCategory as b, type BlogComment as c, type BlogCommentReply as d, type BlogEngagement as e, type BlogLikeResult as f, type BlogPost as g, type CmsBlogCategoryDto as h, type CmsBlogCommentDto as i, type CmsBlogPostDto as j, type CmsBlogPostsPage as k, type CmsClient as l, type CmsClientConfig as m, type CmsContentEntriesPage as n, type CmsContentEntryDto as o, type CmsContentTypeDto as p, type ContentApi as q, type ContentEntry as r, type ContentFilters as s, type ContentTypeMeta as t, type CreateBlogCommentInput as u, type CreateBlogCommentResult as v, type ListContentEntriesQuery as w, createCmsClient as x, serializeContentFilters as y };
package/dist/index.cjs CHANGED
@@ -1,5 +1,47 @@
1
1
  'use strict';
2
2
 
3
+ // src/http.ts
4
+ function appendSiteId(path, siteId) {
5
+ if (!siteId) return path;
6
+ const sep = path.includes("?") ? "&" : "?";
7
+ return `${path}${sep}siteId=${encodeURIComponent(siteId)}`;
8
+ }
9
+ function buildPublicUrl(ctx, path) {
10
+ return `${ctx.baseUrl}${appendSiteId(path, ctx.siteId)}`;
11
+ }
12
+ async function unwrapResponse(res) {
13
+ const json = await res.json();
14
+ if (!res.ok) {
15
+ const message = json.message ?? `Request failed (${res.status})`;
16
+ throw new Error(message);
17
+ }
18
+ const hasEntryIdentity = "_id" in json || "id" in json;
19
+ const hasListDocs = "docs" in json && Array.isArray(json.docs);
20
+ const isDataEnvelope = json.data !== void 0 && "message" in json && !hasListDocs && !hasEntryIdentity;
21
+ if (isDataEnvelope) {
22
+ return json.data;
23
+ }
24
+ return json;
25
+ }
26
+ async function publicGet(ctx, path, init) {
27
+ const res = await ctx.fetchFn(buildPublicUrl(ctx, path), {
28
+ ...init,
29
+ headers: {
30
+ "Content-Type": "application/json",
31
+ ...ctx.defaultHeaders,
32
+ ...init?.headers
33
+ }
34
+ });
35
+ return unwrapResponse(res);
36
+ }
37
+ async function publicGetNullable(ctx, path, init) {
38
+ try {
39
+ return await publicGet(ctx, path, init);
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
44
+
3
45
  // src/blog/mappers.ts
4
46
  function mediaUrl(value) {
5
47
  if (!value) return void 0;
@@ -68,42 +110,6 @@ function mapCmsCategoryToBlogCategory(dto) {
68
110
  }
69
111
 
70
112
  // src/blog/endpoints.ts
71
- function appendSiteId(path, siteId) {
72
- if (!siteId) return path;
73
- const sep = path.includes("?") ? "&" : "?";
74
- return `${path}${sep}siteId=${encodeURIComponent(siteId)}`;
75
- }
76
- async function unwrap(res) {
77
- const json = await res.json();
78
- if (!res.ok) {
79
- const message = json.message ?? `Request failed (${res.status})`;
80
- throw new Error(message);
81
- }
82
- const data = json.data ?? json.data;
83
- if (data === void 0) {
84
- throw new Error("Response missing data");
85
- }
86
- return data;
87
- }
88
- async function publicGet(ctx, path, init) {
89
- const url = `${ctx.baseUrl}${appendSiteId(path, ctx.siteId)}`;
90
- const res = await ctx.fetchFn(url, {
91
- ...init,
92
- headers: {
93
- "Content-Type": "application/json",
94
- ...ctx.defaultHeaders,
95
- ...init?.headers
96
- }
97
- });
98
- return unwrap(res);
99
- }
100
- async function publicGetNullable(ctx, path, init) {
101
- try {
102
- return await publicGet(ctx, path, init);
103
- } catch {
104
- return null;
105
- }
106
- }
107
113
  function createBlogApi(ctx) {
108
114
  const prefix = ctx.blogBasePath;
109
115
  return {
@@ -135,10 +141,10 @@ function createBlogApi(ctx) {
135
141
  );
136
142
  },
137
143
  async createComment(postId, body) {
138
- const url = `${ctx.baseUrl}${appendSiteId(
139
- `${prefix}/posts/${encodeURIComponent(postId)}/comments`,
140
- ctx.siteId
141
- )}`;
144
+ const url = buildPublicUrl(
145
+ ctx,
146
+ `${prefix}/posts/${encodeURIComponent(postId)}/comments`
147
+ );
142
148
  const res = await ctx.fetchFn(url, {
143
149
  method: "POST",
144
150
  headers: {
@@ -147,7 +153,7 @@ function createBlogApi(ctx) {
147
153
  },
148
154
  body: JSON.stringify(body)
149
155
  });
150
- return unwrap(res);
156
+ return unwrapResponse(res);
151
157
  },
152
158
  getEngagement(postId, options, init) {
153
159
  return publicGetNullable(
@@ -163,10 +169,7 @@ function createBlogApi(ctx) {
163
169
  );
164
170
  },
165
171
  async toggleLike(postId, options) {
166
- const url = `${ctx.baseUrl}${appendSiteId(
167
- `${prefix}/posts/${encodeURIComponent(postId)}/like`,
168
- ctx.siteId
169
- )}`;
172
+ const url = buildPublicUrl(ctx, `${prefix}/posts/${encodeURIComponent(postId)}/like`);
170
173
  const res = await ctx.fetchFn(url, {
171
174
  method: "POST",
172
175
  headers: {
@@ -175,7 +178,7 @@ function createBlogApi(ctx) {
175
178
  ...options?.visitorId ? { "x-visitor-id": options.visitorId } : {}
176
179
  }
177
180
  });
178
- return unwrap(res);
181
+ return unwrapResponse(res);
179
182
  },
180
183
  async listMappedPosts(query, init) {
181
184
  const page = await this.listPosts(query, init);
@@ -205,6 +208,85 @@ function createBlogApi(ctx) {
205
208
  };
206
209
  }
207
210
 
211
+ // src/content/filters.ts
212
+ function serializeContentFilters(filters) {
213
+ return JSON.stringify(filters);
214
+ }
215
+
216
+ // src/content/mappers.ts
217
+ function resolveId(dto) {
218
+ if (dto.id) return String(dto.id);
219
+ if (dto._id) return String(dto._id);
220
+ return "";
221
+ }
222
+ function mapContentType(dto) {
223
+ if (!dto) return void 0;
224
+ const id = resolveId(dto);
225
+ return {
226
+ id,
227
+ name: dto.name ?? "",
228
+ apiId: dto.apiId ?? "",
229
+ fields: dto.fields,
230
+ status: dto.status
231
+ };
232
+ }
233
+ function mapCmsEntryToContentEntry(dto) {
234
+ const contentTypeId = dto.contentTypeId != null ? String(dto.contentTypeId) : dto.contentType ? resolveId(dto.contentType) : "";
235
+ return {
236
+ id: resolveId(dto),
237
+ contentTypeId,
238
+ contentType: mapContentType(dto.contentType),
239
+ status: dto.status ?? "PUBLISHED",
240
+ data: dto.data ?? {},
241
+ createdAt: dto.createdAt,
242
+ updatedAt: dto.updatedAt
243
+ };
244
+ }
245
+
246
+ // src/content/endpoints.ts
247
+ function buildListQueryString(query) {
248
+ const qs = new URLSearchParams();
249
+ if (query?.page != null) qs.set("page", String(query.page));
250
+ if (query?.limit != null) qs.set("limit", String(query.limit));
251
+ if (query?.search) qs.set("search", query.search);
252
+ if (query?.sort) qs.set("sort", query.sort);
253
+ if (query?.order) qs.set("order", query.order);
254
+ if (query?.filters && Object.keys(query.filters).length > 0) {
255
+ qs.set("filters", serializeContentFilters(query.filters));
256
+ }
257
+ return qs.toString();
258
+ }
259
+ function createContentApi(ctx) {
260
+ const prefix = ctx.contentBasePath;
261
+ return {
262
+ listEntries(contentTypeApiId, query, init) {
263
+ const queryStr = buildListQueryString(query);
264
+ const path = `${prefix}/${encodeURIComponent(contentTypeApiId)}${queryStr ? `?${queryStr}` : ""}`;
265
+ return publicGetNullable(ctx, path, init);
266
+ },
267
+ getEntry(contentTypeApiId, idOrSlug, init) {
268
+ return publicGetNullable(
269
+ ctx,
270
+ `${prefix}/${encodeURIComponent(contentTypeApiId)}/${encodeURIComponent(idOrSlug)}`,
271
+ init
272
+ );
273
+ },
274
+ async listMappedEntries(contentTypeApiId, query, init) {
275
+ const page = await this.listEntries(contentTypeApiId, query, init);
276
+ if (!page?.docs) return null;
277
+ return {
278
+ entries: page.docs.map(mapCmsEntryToContentEntry),
279
+ totalDocs: page.totalDocs
280
+ };
281
+ },
282
+ async getMappedEntry(contentTypeApiId, idOrSlug, init) {
283
+ const dto = await this.getEntry(contentTypeApiId, idOrSlug, init);
284
+ if (!dto) return null;
285
+ return mapCmsEntryToContentEntry(dto);
286
+ }
287
+ };
288
+ }
289
+
208
290
  // src/client.ts
209
291
  function normalizeBaseUrl(url) {
210
292
  return url.replace(/\/$/, "");
@@ -218,22 +300,25 @@ function createCmsClient(config) {
218
300
  const baseUrl = normalizeBaseUrl(config.baseUrl);
219
301
  const pathPrefix = normalizePathPrefix(config.pathPrefix ?? "");
220
302
  const blogBasePath = `${pathPrefix}/public/blog`.replace(/\/+/g, "/");
303
+ const contentBasePath = `${pathPrefix}/public/api`.replace(/\/+/g, "/");
221
304
  const ctx = {
222
305
  baseUrl,
223
- blogBasePath,
224
306
  siteId: config.siteId,
225
307
  fetchFn: config.fetch ?? globalThis.fetch.bind(globalThis),
226
308
  defaultHeaders: config.defaultHeaders ?? {}
227
309
  };
228
310
  return {
229
- blog: createBlogApi(ctx)
311
+ blog: createBlogApi({ ...ctx, blogBasePath }),
312
+ content: createContentApi({ ...ctx, contentBasePath })
230
313
  };
231
314
  }
232
315
 
233
316
  exports.createCmsClient = createCmsClient;
234
317
  exports.mapCmsCategoryToBlogCategory = mapCmsCategoryToBlogCategory;
235
318
  exports.mapCmsCommentToBlogComment = mapCmsCommentToBlogComment;
319
+ exports.mapCmsEntryToContentEntry = mapCmsEntryToContentEntry;
236
320
  exports.mapCmsPostToBlogPost = mapCmsPostToBlogPost;
237
321
  exports.mediaUrl = mediaUrl;
322
+ exports.serializeContentFilters = serializeContentFilters;
238
323
  //# sourceMappingURL=index.cjs.map
239
324
  //# sourceMappingURL=index.cjs.map