@dotcms/client 1.5.6 → 1.6.0-next.36

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
@@ -84,7 +84,7 @@ The `@dotcms/client` is a powerful JavaScript/TypeScript SDK designed to simplif
84
84
  **For Local Development:**
85
85
 
86
86
  - 🐳 [Docker setup guide](https://github.com/dotCMS/core/tree/main/docker/docker-compose-examples/single-node-demo-site)
87
- - 💻 [Local installation guide](https://dev.dotcms.com/docs/quick-start-guide)
87
+ - 💻 [Local installation guide](https://dev.dotcms.com/getting-started/setup/run-locally)
88
88
 
89
89
  #### Create a dotCMS API Key
90
90
 
@@ -100,7 +100,7 @@ This integration requires an API Key with read-only permissions for security bes
100
100
 
101
101
  For detailed instructions, please refer to the [dotCMS API Documentation - Read-only token](https://dev.dotcms.com/docs/rest-api-authentication#ReadOnlyToken).
102
102
 
103
- #### Installation
103
+ ### Installation
104
104
 
105
105
  Install the SDK and required dependencies:
106
106
 
@@ -121,7 +121,7 @@ import { createDotCMSClient } from '@dotcms/client';
121
121
  // Create a client instance
122
122
  const client = createDotCMSClient({
123
123
  dotcmsUrl: 'https://your-dotcms-instance.com',
124
- authToken: 'your-auth-token', // Optional for public content
124
+ authToken: 'your-auth-token',
125
125
  siteId: 'your-site-id' // Optional site identifier
126
126
  });
127
127
 
@@ -361,7 +361,7 @@ const response = await client.ai.search(
361
361
  );
362
362
 
363
363
  // Access results with match scores
364
- resp[onse].results.forEach(result => {
364
+ response.results.forEach(result => {
365
365
  console.log(result.title);
366
366
  console.log('Matches:', result.matches); // Distance and extracted text
367
367
  });
@@ -457,11 +457,8 @@ response.contentlets.forEach(post => {
457
457
  #### Typing AI Search Results
458
458
 
459
459
  ```typescript
460
- import type {
461
- DotCMSAISearchResponse,
462
- DotCMSBasicContentlet,
463
- DISTANCE_FUNCTIONS
464
- } from '@dotcms/types';
460
+ import { DISTANCE_FUNCTIONS } from '@dotcms/types';
461
+ import type { DotCMSAISearchResponse, DotCMSBasicContentlet } from '@dotcms/types';
465
462
 
466
463
  // Define your content type
467
464
  interface Article extends DotCMSBasicContentlet {
@@ -688,6 +685,69 @@ const response = await client.page.get('/about-us', {
688
685
  });
689
686
  ```
690
687
 
688
+ ### How to Enable Page Editing
689
+
690
+ The `@dotcms/client` SDK is responsible for **fetching** your page, while a framework SDK ([`@dotcms/react`](https://www.npmjs.com/package/@dotcms/react) or [`@dotcms/angular`](https://www.npmjs.com/package/@dotcms/angular)) makes that page **editable** inside the [Universal Visual Editor (UVE)](https://dev.dotcms.com/docs/uve-headless-config).
691
+
692
+ The flow is always the same three steps:
693
+
694
+ 1. **Fetch the page** on the server with `client.page.get()`.
695
+ 2. **Connect the page to the editor** with the framework hook/service (`useEditableDotCMSPage` in React, `DotCMSEditablePageService` in Angular).
696
+ 3. **Render the layout** with `DotCMSLayoutBody`, mapping your content types to components.
697
+
698
+ #### 1. Fetch the page with `client.page.get()`
699
+
700
+ Fetch the full page response on the server. The complete response object — not just `pageAsset` — must be forwarded to the editor layer, because it carries the data UVE needs to track changes.
701
+
702
+ ```typescript
703
+ // server-side, e.g. a Next.js Server Component
704
+ import { createDotCMSClient } from '@dotcms/client';
705
+
706
+ const client = createDotCMSClient({
707
+ dotcmsUrl: 'https://your-dotcms-instance.com',
708
+ authToken: 'your-auth-token',
709
+ siteId: 'your-site-id'
710
+ });
711
+
712
+ // Return the whole response so the framework SDK can make it editable
713
+ export async function getPage(path: string) {
714
+ return await client.page.get(path);
715
+ }
716
+ ```
717
+
718
+ #### 2. Make the page editable (React example)
719
+
720
+ Pass the full page response into `useEditableDotCMSPage`. The hook keeps the page in sync with UVE while editing and returns the same `pageAsset` / `content` shape you get from `client.page.get()`, so the component works identically in and out of the editor.
721
+
722
+ ```tsx
723
+ 'use client';
724
+
725
+ import { DotCMSLayoutBody, useEditableDotCMSPage } from '@dotcms/react';
726
+ import { pageComponents } from '@/components/content-types';
727
+
728
+ export function Page({ pageContent }) {
729
+ // `pageContent` is the full response from client.page.get()
730
+ const { pageAsset } = useEditableDotCMSPage(pageContent);
731
+
732
+ return (
733
+ <DotCMSLayoutBody
734
+ page={pageAsset}
735
+ components={pageComponents}
736
+ mode={process.env.NEXT_PUBLIC_DOTCMS_MODE}
737
+ />
738
+ );
739
+ }
740
+ ```
741
+
742
+ > 💡 Using Angular? Use [`DotCMSEditablePageService`](https://www.npmjs.com/package/@dotcms/angular) together with `DotCMSLayoutBody` — the same fetch → make-editable → render flow applies.
743
+
744
+ #### 3. Render the layout with `DotCMSLayoutBody`
745
+
746
+ `DotCMSLayoutBody` renders the page's rows, columns, and containers, and maps each contentlet to one of your components via the `components` prop. When loaded inside UVE it automatically applies the `data-dot-*` attributes that make the page editable — no extra wiring required.
747
+
748
+ #### Working example
749
+
750
+ See the page-editing flow end to end in the official Next.js example — [`examples/nextjs`](https://github.com/dotCMS/core/tree/main/examples/nextjs). In particular, [`src/views/Page.tsx`](https://github.com/dotCMS/core/blob/main/examples/nextjs/src/views/Page.tsx) uses `useEditableDotCMSPage` and `DotCMSLayoutBody` exactly as shown above.
691
751
 
692
752
  ## API Reference
693
753
 
@@ -816,14 +876,15 @@ getCollection<T = DotCMSBasicContentlet>(
816
876
 
817
877
  #### Builder Methods
818
878
 
819
- | Method | Arguments | Description |
820
- | ------------ | ----------------------------- | ---------------------------------------- |
821
- | `query()` | `string` \| `BuildQuery` | Filter content using query builder |
822
- | `limit()` | `number` | Set number of items to return |
823
- | `page()` | `number` | Set which page of results to fetch |
824
- | `sortBy()` | `SortBy[]` | Sort by one or more fields |
825
- | `language()` | `number \| string` | Set content language |
826
- | `depth()` | `number` | Set depth of related content |
879
+ | Method | Arguments | Description |
880
+ | --------------------- | ----------------------------- | ------------------------------------------------------------------ |
881
+ | `query()` | `string` \| `BuildQuery` | Filter content using query builder |
882
+ | `limit()` | `number` | Set number of items to return |
883
+ | `page()` | `number` | Set which page of results to fetch |
884
+ | `sortBy()` | `SortBy[]` | Sort by one or more fields |
885
+ | `language()` | `number \| string` | Set content language |
886
+ | `depth()` | `number` | Set depth of related content |
887
+ | `includeSystemHost()` | - | Include content from the System Host alongside the configured site |
827
888
 
828
889
  #### Example
829
890
  ```typescript
@@ -1137,12 +1198,13 @@ DotHttpError: "Network request failed"
1137
1198
 
1138
1199
  ### Choosing the Right Method
1139
1200
 
1140
- The dotCMS Client SDK provides four core methods for fetching data. Use this quick guide to decide which one is best for your use case:
1201
+ The dotCMS Client SDK provides five core methods for fetching data. Use this quick guide to decide which one is best for your use case:
1141
1202
 
1142
1203
  | Method | Use When You Need... | Best For |
1143
1204
  | -------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
1144
1205
  | `client.page.get()` | A full page with layout, containers, and related content | **Rendering entire pages** with a single request. Ideal for headless setups, SSR/SSG frameworks, and cases where you want everything—page structure, content, and navigation—tied to a URL path. |
1145
- | `client.content.getCollection()` | A filtered list of content items from a specific content type | Populating dynamic blocks, lists, search results, widgets, or reusable components. |
1206
+ | `client.content.getCollection()` | A filtered list of content items from a specific content type | Populating dynamic blocks, lists, search results, widgets, or reusable components using the fluent query builder. |
1207
+ | `client.content.query()` | Full control over a raw Lucene query string | Advanced search scenarios where you need direct Lucene syntax without the `getCollection()` query-builder DSL or automatic `contentType.` field prefixing. |
1146
1208
  | `client.ai.search()` | Semantic/AI-powered content discovery based on natural language | **Intelligent search experiences** where users describe what they're looking for in natural language. Great for search features, content recommendations, and finding relevant content by meaning rather than exact keywords. ⚠️ **Experimental API** |
1147
1209
  | `client.navigation.get()` | Only the site's navigation structure (folders and links) | Standalone menus or use cases where navigation is needed outside of page context. |
1148
1210
 
@@ -1156,7 +1218,7 @@ For most use cases, `client.page.get()` is all you need. It lets you retrieve:
1156
1218
 
1157
1219
  All in a single request using GraphQL.
1158
1220
 
1159
- Only use `content.getCollection()` or `navigation.get()` if you have advanced needs, like real-time data fetching or building custom dynamic components.
1221
+ Only use `content.getCollection()`, `content.query()`, or `navigation.get()` if you have advanced needs, like real-time data fetching or building custom dynamic components.
1160
1222
 
1161
1223
  > 🔍 **For comprehensive examples of advanced GraphQL querying including relationships and custom fields,** see the [How to Work with GraphQL](#how-to-work-with-graphql) section.
1162
1224
 
@@ -1165,7 +1227,7 @@ Only use `content.getCollection()` or `navigation.get()` if you have advanced ne
1165
1227
  The SDK follows a client-builder pattern with four main APIs:
1166
1228
 
1167
1229
  - **Page API** (`client.page.get()`) - Fetches complete page content with layout and containers
1168
- - **Content API** (`client.content.getCollection()`) - Builder pattern for querying content collections
1230
+ - **Content API** (`client.content.getCollection()`, `client.content.query()`) - Builder pattern for querying content collections or raw Lucene queries
1169
1231
  - **AI API** (`client.ai.search()`) - AI-powered semantic search using embeddings and vector similarity ⚠️ **Experimental**
1170
1232
  - **Navigation API** (`client.navigation.get()`) - Fetches site navigation structure
1171
1233
 
@@ -1182,7 +1244,7 @@ We offer multiple channels to get help with the dotCMS Client SDK:
1182
1244
  - **GitHub Issues**: For bug reports and feature requests, please [open an issue](https://github.com/dotCMS/core/issues/new/choose) in the GitHub repository.
1183
1245
  - **Community Forum**: Join our [community discussions](https://community.dotcms.com/) to ask questions and share solutions.
1184
1246
  - **Stack Overflow**: Use the tag `dotcms-client` when posting questions.
1185
- - **Enterprise Support**: Enterprise customers can access premium support through the [dotCMS Support Portal](https://helpdesk.dotcms.com/support/).
1247
+ - **Enterprise Support**: Enterprise customers can access premium support through the [dotCMS Support Portal](https://www.dotcms.com/support).
1186
1248
 
1187
1249
  When reporting issues, please include:
1188
1250
 
@@ -1203,6 +1265,14 @@ GitHub pull requests are the preferred method to contribute code to dotCMS. We w
1203
1265
 
1204
1266
  Please ensure your code follows the existing style and includes appropriate tests.
1205
1267
 
1268
+ ## Licensing
1269
+
1270
+ dotCMS is available under either the [Business Source License 1.1 (BSL)](https://www.dotcms.com/bsl) or a commercial license.
1271
+
1272
+ Under the BSL, dotCMS can be used at no cost by individual developers, small businesses or agencies under $5M in total finances, and by larger organizations in non-production environments. Every BSL release automatically converts to GPL v3 four years after its release date. For full terms and FAQs, visit [dotcms.com/bsl](https://www.dotcms.com/bsl) and [dotcms.com/bsl-faq](https://www.dotcms.com/bsl-faq).
1273
+
1274
+ Production use in larger organizations, along with access to managed cloud, SLAs, support, and enterprise capabilities, is available under a commercial license from dotCMS. For details on commercial plans, features, and support options, see [dotcms.com/pricing](https://www.dotcms.com/pricing).
1275
+
1206
1276
  ## Changelog
1207
1277
 
1208
1278
  ### v1.3.0
@@ -1362,11 +1432,3 @@ import { RequestOptions } from '@dotcms/types';
1362
1432
  // After
1363
1433
  import { DotRequestOptions } from '@dotcms/types';
1364
1434
  ```
1365
-
1366
- ## Licensing
1367
-
1368
- dotCMS is available under either the [Business Source License 1.1 (BSL)](https://www.dotcms.com/bsl) or a commercial license.
1369
-
1370
- Under the BSL, dotCMS can be used at no cost by individual developers, small businesses or agencies under $5M in total finances, and by larger organizations in non-production environments. Every BSL release automatically converts to GPL v3 four years after its release date. For full terms and FAQs, visit [dotcms.com/bsl](https://www.dotcms.com/bsl) and [dotcms.com/bsl-faq](https://www.dotcms.com/bsl-faq).
1371
-
1372
- Production use in larger organizations, along with access to managed cloud, SLAs, support, and enterprise capabilities, is available under a commercial license from dotCMS. For details on commercial plans, features, and support options, see [dotcms.com/pricing](https://www.dotcms.com/pricing).
package/index.cjs.js CHANGED
@@ -112,7 +112,7 @@ class FetchHttpClient extends types.BaseHttpClient {
112
112
  }
113
113
  }
114
114
 
115
- /******************************************************************************
115
+ /*! *****************************************************************************
116
116
  Copyright (c) Microsoft Corporation.
117
117
 
118
118
  Permission to use, copy, modify, and/or distribute this software for any
@@ -126,7 +126,7 @@ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
126
126
  OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
127
127
  PERFORMANCE OF THIS SOFTWARE.
128
128
  ***************************************************************************** */
129
- /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
129
+ /* global Reflect, Promise */
130
130
 
131
131
 
132
132
  function __classPrivateFieldGet(receiver, state, kind, f) {
@@ -140,12 +140,7 @@ function __classPrivateFieldSet(receiver, state, value, kind, f) {
140
140
  if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
141
141
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
142
142
  return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
143
- }
144
-
145
- typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
146
- var e = new Error(message);
147
- return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
148
- };
143
+ }
149
144
 
150
145
  /**
151
146
  * Utility functions for AI search parameter mapping and processing
@@ -2310,6 +2305,19 @@ function mapContentResponse(responseData, keys) {
2310
2305
  return accumulator;
2311
2306
  }, {});
2312
2307
  }
2308
+ /**
2309
+ * Returns a shallow copy of the object with every key whose value is `undefined` removed.
2310
+ *
2311
+ * `undefined` is not valid JSON, so keeping such keys breaks consumers that serialize the value
2312
+ * (e.g. Next.js Pages Router `getServerSideProps`/`getStaticProps`). `null` and other falsy values
2313
+ * are preserved since they serialize fine.
2314
+ *
2315
+ * @param {Record<string, unknown>} object - Source object to clean
2316
+ * @returns {Record<string, unknown>} New object without `undefined` values
2317
+ */
2318
+ function removeUndefinedValues(object) {
2319
+ return Object.fromEntries(Object.entries(object).filter(([, value]) => value !== undefined));
2320
+ }
2313
2321
  /**
2314
2322
  * Executes a GraphQL query against the DotCMS API.
2315
2323
  *
@@ -2429,12 +2437,10 @@ class PageClient extends BaseApiClient {
2429
2437
  additionalQueries: contentQuery,
2430
2438
  verbose
2431
2439
  });
2432
- const normalizedUrl = url.startsWith('/') ? url : `/${url}`;
2433
- const requestVariables = {
2434
- // The url is expected to have a leading slash to comply on VanityURL Matching, some frameworks like Angular will not add the leading slash
2435
- url: normalizedUrl,
2436
- // Translate the UVE_MODE key ('EDIT' | 'PREVIEW' | ...) to the value the backend PageMode enum expects ('EDIT_MODE' | 'PREVIEW_MODE' | ...)
2437
- mode: types.UVE_MODE[mode],
2440
+ const newURL = url.startsWith('/') ? url : `/${url}`;
2441
+ const requestVariables = removeUndefinedValues({
2442
+ url: newURL,
2443
+ mode: types.UVE_MODE[mode], // Translate the UVE_MODE key ('EDIT' | 'PREVIEW' | ...) to the value the backend PageMode enum expects ('EDIT_MODE' | 'PREVIEW_MODE' | ...)
2438
2444
  languageId,
2439
2445
  personaId,
2440
2446
  fireRules,
@@ -2442,7 +2448,7 @@ class PageClient extends BaseApiClient {
2442
2448
  siteId,
2443
2449
  variantName,
2444
2450
  ...variables
2445
- };
2451
+ });
2446
2452
  const requestHeaders = this.requestOptions.headers;
2447
2453
  const requestBody = JSON.stringify({ query: completeQuery, variables: requestVariables });
2448
2454
  try {
@@ -2458,12 +2464,12 @@ class PageClient extends BaseApiClient {
2458
2464
  .filter((error) => !error.extensions?.code)
2459
2465
  .forEach((error) => {
2460
2466
  if (verbose) {
2461
- logVerboseError(normalizedUrl, error.message, {
2467
+ logVerboseError(newURL, error.message, {
2462
2468
  variables: requestVariables
2463
2469
  });
2464
2470
  }
2465
2471
  else {
2466
- consola.consola.error(`[DotCMS GraphQL Error] ${normalizedUrl}: `, error.message);
2472
+ consola.consola.error(`[DotCMS GraphQL Error] ${newURL}: `, error.message);
2467
2473
  }
2468
2474
  });
2469
2475
  }
@@ -2489,19 +2495,19 @@ class PageClient extends BaseApiClient {
2489
2495
  const status = structuredError.extensions?.status ??
2490
2496
  (code === 'NOT_FOUND' ? 404 : code === 'PERMISSION_DENIED' ? 403 : 400);
2491
2497
  const message = code === 'NOT_FOUND'
2492
- ? `Page '${normalizedUrl}' was not found`
2498
+ ? `Page '${newURL}' was not found`
2493
2499
  : code === 'PERMISSION_DENIED'
2494
- ? `Permission denied: you do not have access to page '${normalizedUrl}'. Verify the page permissions in dotCMS and that the auth token has sufficient access.`
2495
- : `Page '${normalizedUrl}' could not be loaded (${code})`;
2500
+ ? `Permission denied: you do not have access to page '${newURL}'. Verify the page permissions in dotCMS and that the auth token has sufficient access.`
2501
+ : `Page '${newURL}' could not be loaded (${code})`;
2496
2502
  if (verbose) {
2497
- logVerboseError(normalizedUrl, message, {
2503
+ logVerboseError(newURL, message, {
2498
2504
  status,
2499
2505
  code,
2500
2506
  variables: requestVariables
2501
2507
  });
2502
2508
  }
2503
2509
  else {
2504
- consola.consola.error(`[DotCMS GraphQL Error] ${normalizedUrl}: `, message);
2510
+ consola.consola.error(`[DotCMS GraphQL Error] ${newURL}: `, message);
2505
2511
  }
2506
2512
  throw new types.DotErrorPage(message, status, code, undefined, {
2507
2513
  query: completeQuery,
@@ -2515,10 +2521,10 @@ class PageClient extends BaseApiClient {
2515
2521
  : null;
2516
2522
  const styleEditorSchemas = pageResponse ? pageResponse.page.styleEditorSchemas : [];
2517
2523
  if (!pageResponse) {
2518
- throw new types.DotErrorPage(`Page '${normalizedUrl}' was not found`, 404, 'NOT_FOUND', new types.DotHttpError({
2524
+ throw new types.DotErrorPage(`Page '${newURL}' was not found`, 404, 'NOT_FOUND', new types.DotHttpError({
2519
2525
  status: 404,
2520
2526
  statusText: 'Not Found',
2521
- message: `Page '${normalizedUrl}' was not found`,
2527
+ message: `Page '${newURL}' was not found`,
2522
2528
  data: response.errors
2523
2529
  }), { query: completeQuery, variables: requestVariables });
2524
2530
  }
@@ -2531,7 +2537,9 @@ class PageClient extends BaseApiClient {
2531
2537
  query: completeQuery,
2532
2538
  variables: requestVariables
2533
2539
  },
2534
- errors: response.errors?.length ? response.errors : undefined,
2540
+ // Always return an array (never `undefined`) so the response stays JSON-serializable
2541
+ // for consumers like Next.js Pages Router (getServerSideProps/getStaticProps throw on undefined).
2542
+ errors: response.errors?.length ? response.errors : [],
2535
2543
  ...(styleEditorSchemas?.length && { styleEditorSchemas })
2536
2544
  };
2537
2545
  }
@@ -2540,9 +2548,9 @@ class PageClient extends BaseApiClient {
2540
2548
  throw error;
2541
2549
  }
2542
2550
  if (error instanceof types.DotHttpError) {
2543
- throw new types.DotErrorPage(`Page request failed for URL '${normalizedUrl}': ${error.message}`, error.status, 'UNKNOWN', error, { query: completeQuery, variables: requestVariables });
2551
+ throw new types.DotErrorPage(`Page request failed for URL '${newURL}': ${error.message}`, error.status, 'UNKNOWN', error, { query: completeQuery, variables: requestVariables });
2544
2552
  }
2545
- throw new types.DotErrorPage(`Page request failed for URL '${normalizedUrl}': ${error instanceof Error ? error.message : 'Unknown error'}`, 500, 'UNKNOWN', undefined, { query: completeQuery, variables: requestVariables });
2553
+ throw new types.DotErrorPage(`Page request failed for URL '${newURL}': ${error instanceof Error ? error.message : 'Unknown error'}`, 500, 'UNKNOWN', undefined, { query: completeQuery, variables: requestVariables });
2546
2554
  }
2547
2555
  }
2548
2556
  }
package/index.esm.js CHANGED
@@ -110,7 +110,7 @@ class FetchHttpClient extends BaseHttpClient {
110
110
  }
111
111
  }
112
112
 
113
- /******************************************************************************
113
+ /*! *****************************************************************************
114
114
  Copyright (c) Microsoft Corporation.
115
115
 
116
116
  Permission to use, copy, modify, and/or distribute this software for any
@@ -124,7 +124,7 @@ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
124
124
  OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
125
125
  PERFORMANCE OF THIS SOFTWARE.
126
126
  ***************************************************************************** */
127
- /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
127
+ /* global Reflect, Promise */
128
128
 
129
129
 
130
130
  function __classPrivateFieldGet(receiver, state, kind, f) {
@@ -138,12 +138,7 @@ function __classPrivateFieldSet(receiver, state, value, kind, f) {
138
138
  if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
139
139
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
140
140
  return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
141
- }
142
-
143
- typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
144
- var e = new Error(message);
145
- return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
146
- };
141
+ }
147
142
 
148
143
  /**
149
144
  * Utility functions for AI search parameter mapping and processing
@@ -2308,6 +2303,19 @@ function mapContentResponse(responseData, keys) {
2308
2303
  return accumulator;
2309
2304
  }, {});
2310
2305
  }
2306
+ /**
2307
+ * Returns a shallow copy of the object with every key whose value is `undefined` removed.
2308
+ *
2309
+ * `undefined` is not valid JSON, so keeping such keys breaks consumers that serialize the value
2310
+ * (e.g. Next.js Pages Router `getServerSideProps`/`getStaticProps`). `null` and other falsy values
2311
+ * are preserved since they serialize fine.
2312
+ *
2313
+ * @param {Record<string, unknown>} object - Source object to clean
2314
+ * @returns {Record<string, unknown>} New object without `undefined` values
2315
+ */
2316
+ function removeUndefinedValues(object) {
2317
+ return Object.fromEntries(Object.entries(object).filter(([, value]) => value !== undefined));
2318
+ }
2311
2319
  /**
2312
2320
  * Executes a GraphQL query against the DotCMS API.
2313
2321
  *
@@ -2427,12 +2435,10 @@ class PageClient extends BaseApiClient {
2427
2435
  additionalQueries: contentQuery,
2428
2436
  verbose
2429
2437
  });
2430
- const normalizedUrl = url.startsWith('/') ? url : `/${url}`;
2431
- const requestVariables = {
2432
- // The url is expected to have a leading slash to comply on VanityURL Matching, some frameworks like Angular will not add the leading slash
2433
- url: normalizedUrl,
2434
- // Translate the UVE_MODE key ('EDIT' | 'PREVIEW' | ...) to the value the backend PageMode enum expects ('EDIT_MODE' | 'PREVIEW_MODE' | ...)
2435
- mode: UVE_MODE[mode],
2438
+ const newURL = url.startsWith('/') ? url : `/${url}`;
2439
+ const requestVariables = removeUndefinedValues({
2440
+ url: newURL,
2441
+ mode: UVE_MODE[mode], // Translate the UVE_MODE key ('EDIT' | 'PREVIEW' | ...) to the value the backend PageMode enum expects ('EDIT_MODE' | 'PREVIEW_MODE' | ...)
2436
2442
  languageId,
2437
2443
  personaId,
2438
2444
  fireRules,
@@ -2440,7 +2446,7 @@ class PageClient extends BaseApiClient {
2440
2446
  siteId,
2441
2447
  variantName,
2442
2448
  ...variables
2443
- };
2449
+ });
2444
2450
  const requestHeaders = this.requestOptions.headers;
2445
2451
  const requestBody = JSON.stringify({ query: completeQuery, variables: requestVariables });
2446
2452
  try {
@@ -2456,12 +2462,12 @@ class PageClient extends BaseApiClient {
2456
2462
  .filter((error) => !error.extensions?.code)
2457
2463
  .forEach((error) => {
2458
2464
  if (verbose) {
2459
- logVerboseError(normalizedUrl, error.message, {
2465
+ logVerboseError(newURL, error.message, {
2460
2466
  variables: requestVariables
2461
2467
  });
2462
2468
  }
2463
2469
  else {
2464
- consola.error(`[DotCMS GraphQL Error] ${normalizedUrl}: `, error.message);
2470
+ consola.error(`[DotCMS GraphQL Error] ${newURL}: `, error.message);
2465
2471
  }
2466
2472
  });
2467
2473
  }
@@ -2487,19 +2493,19 @@ class PageClient extends BaseApiClient {
2487
2493
  const status = structuredError.extensions?.status ??
2488
2494
  (code === 'NOT_FOUND' ? 404 : code === 'PERMISSION_DENIED' ? 403 : 400);
2489
2495
  const message = code === 'NOT_FOUND'
2490
- ? `Page '${normalizedUrl}' was not found`
2496
+ ? `Page '${newURL}' was not found`
2491
2497
  : code === 'PERMISSION_DENIED'
2492
- ? `Permission denied: you do not have access to page '${normalizedUrl}'. Verify the page permissions in dotCMS and that the auth token has sufficient access.`
2493
- : `Page '${normalizedUrl}' could not be loaded (${code})`;
2498
+ ? `Permission denied: you do not have access to page '${newURL}'. Verify the page permissions in dotCMS and that the auth token has sufficient access.`
2499
+ : `Page '${newURL}' could not be loaded (${code})`;
2494
2500
  if (verbose) {
2495
- logVerboseError(normalizedUrl, message, {
2501
+ logVerboseError(newURL, message, {
2496
2502
  status,
2497
2503
  code,
2498
2504
  variables: requestVariables
2499
2505
  });
2500
2506
  }
2501
2507
  else {
2502
- consola.error(`[DotCMS GraphQL Error] ${normalizedUrl}: `, message);
2508
+ consola.error(`[DotCMS GraphQL Error] ${newURL}: `, message);
2503
2509
  }
2504
2510
  throw new DotErrorPage(message, status, code, undefined, {
2505
2511
  query: completeQuery,
@@ -2513,10 +2519,10 @@ class PageClient extends BaseApiClient {
2513
2519
  : null;
2514
2520
  const styleEditorSchemas = pageResponse ? pageResponse.page.styleEditorSchemas : [];
2515
2521
  if (!pageResponse) {
2516
- throw new DotErrorPage(`Page '${normalizedUrl}' was not found`, 404, 'NOT_FOUND', new DotHttpError({
2522
+ throw new DotErrorPage(`Page '${newURL}' was not found`, 404, 'NOT_FOUND', new DotHttpError({
2517
2523
  status: 404,
2518
2524
  statusText: 'Not Found',
2519
- message: `Page '${normalizedUrl}' was not found`,
2525
+ message: `Page '${newURL}' was not found`,
2520
2526
  data: response.errors
2521
2527
  }), { query: completeQuery, variables: requestVariables });
2522
2528
  }
@@ -2529,7 +2535,9 @@ class PageClient extends BaseApiClient {
2529
2535
  query: completeQuery,
2530
2536
  variables: requestVariables
2531
2537
  },
2532
- errors: response.errors?.length ? response.errors : undefined,
2538
+ // Always return an array (never `undefined`) so the response stays JSON-serializable
2539
+ // for consumers like Next.js Pages Router (getServerSideProps/getStaticProps throw on undefined).
2540
+ errors: response.errors?.length ? response.errors : [],
2533
2541
  ...(styleEditorSchemas?.length && { styleEditorSchemas })
2534
2542
  };
2535
2543
  }
@@ -2538,9 +2546,9 @@ class PageClient extends BaseApiClient {
2538
2546
  throw error;
2539
2547
  }
2540
2548
  if (error instanceof DotHttpError) {
2541
- throw new DotErrorPage(`Page request failed for URL '${normalizedUrl}': ${error.message}`, error.status, 'UNKNOWN', error, { query: completeQuery, variables: requestVariables });
2549
+ throw new DotErrorPage(`Page request failed for URL '${newURL}': ${error.message}`, error.status, 'UNKNOWN', error, { query: completeQuery, variables: requestVariables });
2542
2550
  }
2543
- throw new DotErrorPage(`Page request failed for URL '${normalizedUrl}': ${error instanceof Error ? error.message : 'Unknown error'}`, 500, 'UNKNOWN', undefined, { query: completeQuery, variables: requestVariables });
2551
+ throw new DotErrorPage(`Page request failed for URL '${newURL}': ${error instanceof Error ? error.message : 'Unknown error'}`, 500, 'UNKNOWN', undefined, { query: completeQuery, variables: requestVariables });
2544
2552
  }
2545
2553
  }
2546
2554
  }
package/internal.cjs.js CHANGED
@@ -1,6 +1,5 @@
1
1
  'use strict';
2
2
 
3
- /* eslint-disable @typescript-eslint/no-explicit-any */
4
3
  /**
5
4
  * Transforms a GraphQL Page response to a Page Entity.
6
5
  *
@@ -19,7 +18,12 @@ const graphqlToPageEntity = (page) => {
19
18
  }
20
19
  const { layout, template, containers, urlContentMap, viewAs, host, vanityUrl, runningExperimentId, numberContents, _map, ...pageAsset } = page;
21
20
  const data = (_map || {});
22
- const typedPageAsset = pageAsset;
21
+ // styleEditorSchemas comes back as null from GraphQL outside EDIT_MODE. Separate it from the
22
+ // rest of the page fields so it can be omitted entirely when it has no value. Emitting
23
+ // `undefined` (the previous behaviour) breaks JSON serialization for consumers like Next.js
24
+ // Pages Router (getServerSideProps/getStaticProps), while omitting the key keeps the optional
25
+ // DotCMSPage.styleEditorSchemas type accurate.
26
+ const { styleEditorSchemas, ...typedPageAsset } = pageAsset;
23
27
  // Merge all urlContentMap keys into _map, except _map itself
24
28
  const mergedUrlContentMap = {
25
29
  ...(urlContentMap?._map || {}),
@@ -43,9 +47,8 @@ const graphqlToPageEntity = (page) => {
43
47
  page: {
44
48
  ...data,
45
49
  ...typedPageAsset,
46
- // GQL returns null when not in EDIT_MODE; normalize to undefined so the
47
- // DotCMSPage type (StyleEditorFormSchema[], non-null) remains accurate.
48
- styleEditorSchemas: typedPageAsset.styleEditorSchemas ?? undefined
50
+ // Only re-add styleEditorSchemas when it actually has a value (see destructure above).
51
+ ...(styleEditorSchemas ? { styleEditorSchemas } : {})
49
52
  }
50
53
  };
51
54
  };
package/internal.esm.js CHANGED
@@ -1,4 +1,3 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
1
  /**
3
2
  * Transforms a GraphQL Page response to a Page Entity.
4
3
  *
@@ -17,7 +16,12 @@ const graphqlToPageEntity = (page) => {
17
16
  }
18
17
  const { layout, template, containers, urlContentMap, viewAs, host, vanityUrl, runningExperimentId, numberContents, _map, ...pageAsset } = page;
19
18
  const data = (_map || {});
20
- const typedPageAsset = pageAsset;
19
+ // styleEditorSchemas comes back as null from GraphQL outside EDIT_MODE. Separate it from the
20
+ // rest of the page fields so it can be omitted entirely when it has no value. Emitting
21
+ // `undefined` (the previous behaviour) breaks JSON serialization for consumers like Next.js
22
+ // Pages Router (getServerSideProps/getStaticProps), while omitting the key keeps the optional
23
+ // DotCMSPage.styleEditorSchemas type accurate.
24
+ const { styleEditorSchemas, ...typedPageAsset } = pageAsset;
21
25
  // Merge all urlContentMap keys into _map, except _map itself
22
26
  const mergedUrlContentMap = {
23
27
  ...(urlContentMap?._map || {}),
@@ -41,9 +45,8 @@ const graphqlToPageEntity = (page) => {
41
45
  page: {
42
46
  ...data,
43
47
  ...typedPageAsset,
44
- // GQL returns null when not in EDIT_MODE; normalize to undefined so the
45
- // DotCMSPage type (StyleEditorFormSchema[], non-null) remains accurate.
46
- styleEditorSchemas: typedPageAsset.styleEditorSchemas ?? undefined
48
+ // Only re-add styleEditorSchemas when it actually has a value (see destructure above).
49
+ ...(styleEditorSchemas ? { styleEditorSchemas } : {})
47
50
  }
48
51
  };
49
52
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotcms/client",
3
- "version": "1.5.6",
3
+ "version": "1.6.0-next.36",
4
4
  "description": "Official JavaScript library for interacting with DotCMS REST APIs.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -12,9 +12,6 @@
12
12
  "devDependencies": {
13
13
  "@dotcms/types": "latest"
14
14
  },
15
- "scripts": {
16
- "build": "nx run sdk-client:build:js; cd ../../../../dotCMS/src/main/webapp/html/js/editor-js; rm -rf src package.json *.esm.d.ts"
17
- },
18
15
  "keywords": [
19
16
  "dotCMS",
20
17
  "CMS",
@@ -56,4 +53,4 @@
56
53
  "module": "./index.esm.js",
57
54
  "main": "./index.cjs.js",
58
55
  "types": "./index.d.ts"
59
- }
56
+ }
@@ -27,6 +27,17 @@ export declare function buildQuery(queryData: Record<string, string>): string;
27
27
  * @returns {Record<string, unknown> | undefined} New object containing only the specified keys
28
28
  */
29
29
  export declare function mapContentResponse(responseData: Record<string, unknown> | undefined, keys: string[]): Record<string, unknown> | undefined;
30
+ /**
31
+ * Returns a shallow copy of the object with every key whose value is `undefined` removed.
32
+ *
33
+ * `undefined` is not valid JSON, so keeping such keys breaks consumers that serialize the value
34
+ * (e.g. Next.js Pages Router `getServerSideProps`/`getStaticProps`). `null` and other falsy values
35
+ * are preserved since they serialize fine.
36
+ *
37
+ * @param {Record<string, unknown>} object - Source object to clean
38
+ * @returns {Record<string, unknown>} New object without `undefined` values
39
+ */
40
+ export declare function removeUndefinedValues(object: Record<string, unknown>): Record<string, unknown>;
30
41
  /**
31
42
  * Executes a GraphQL query against the DotCMS API.
32
43
  *