@dotcms/client 1.0.6 → 1.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 +279 -8
- package/index.cjs.js +342 -101
- package/index.esm.js +342 -101
- package/internal.cjs.js +3 -4
- package/internal.esm.js +3 -4
- package/package.json +1 -1
- package/src/lib/client/adapters/fetch-http-client.d.ts +66 -0
- package/src/lib/client/client.d.ts +1 -0
- package/src/lib/client/content/builders/collection/collection.d.ts +53 -7
- package/src/lib/client/content/content-api.d.ts +4 -3
- package/src/lib/client/content/shared/types.d.ts +6 -16
- package/src/lib/client/content/shared/utils.d.ts +36 -0
- package/src/lib/client/navigation/navigation-api.d.ts +6 -4
- package/src/lib/client/page/page-api.d.ts +12 -4
- package/src/lib/client/page/utils.d.ts +11 -8
- package/src/lib/utils/graphql/transforms.d.ts +2 -2
package/index.esm.js
CHANGED
|
@@ -1,6 +1,115 @@
|
|
|
1
1
|
import { consola } from 'consola';
|
|
2
|
+
import { BaseHttpClient, DotHttpError, DotErrorContent, DotErrorNavigation, DotErrorPage } from '@dotcms/types';
|
|
2
3
|
import { graphqlToPageEntity } from './internal.esm.js';
|
|
3
4
|
|
|
5
|
+
/**
|
|
6
|
+
* HTTP client implementation using the Fetch API.
|
|
7
|
+
*
|
|
8
|
+
* Extends BaseHttpClient to provide a standard interface for making HTTP requests.
|
|
9
|
+
* This implementation uses the native Fetch API and handles:
|
|
10
|
+
* - JSON and non-JSON response parsing
|
|
11
|
+
* - HTTP error response parsing and conversion to DotHttpError
|
|
12
|
+
* - Network error handling and wrapping
|
|
13
|
+
* - Content-Type detection for proper response handling
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* const client = new FetchHttpClient();
|
|
18
|
+
*
|
|
19
|
+
* // JSON request
|
|
20
|
+
* const data = await client.request<MyType>('/api/data', {
|
|
21
|
+
* method: 'GET',
|
|
22
|
+
* headers: { 'Authorization': 'Bearer token' }
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* // Non-JSON request (e.g., file download)
|
|
26
|
+
* const response = await client.request<Response>('/api/file.pdf', {
|
|
27
|
+
* method: 'GET'
|
|
28
|
+
* });
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
class FetchHttpClient extends BaseHttpClient {
|
|
32
|
+
/**
|
|
33
|
+
* Sends an HTTP request using the Fetch API.
|
|
34
|
+
*
|
|
35
|
+
* Implements the abstract request method from BaseHttpClient using the native Fetch API.
|
|
36
|
+
* Automatically handles response parsing based on Content-Type headers and converts
|
|
37
|
+
* HTTP errors to standardized DotHttpError instances.
|
|
38
|
+
*
|
|
39
|
+
* @template T - The expected response type. For JSON responses, T should be the parsed object type.
|
|
40
|
+
* For non-JSON responses, T should be Response or the expected response type.
|
|
41
|
+
* @param url - The URL to send the request to.
|
|
42
|
+
* @param options - Optional fetch options including method, headers, body, etc.
|
|
43
|
+
* @returns Promise that resolves with the parsed response data or the Response object for non-JSON.
|
|
44
|
+
* @throws {DotHttpError} - Throws DotHttpError for HTTP errors (4xx/5xx status codes).
|
|
45
|
+
* @throws {DotHttpError} - Throws DotHttpError for network errors (connection issues, timeouts).
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* // JSON API request
|
|
50
|
+
* const user = await client.request<User>('/api/users/123', {
|
|
51
|
+
* method: 'GET',
|
|
52
|
+
* headers: { 'Accept': 'application/json' }
|
|
53
|
+
* });
|
|
54
|
+
*
|
|
55
|
+
* // POST request with JSON body
|
|
56
|
+
* const result = await client.request<CreateResult>('/api/users', {
|
|
57
|
+
* method: 'POST',
|
|
58
|
+
* headers: { 'Content-Type': 'application/json' },
|
|
59
|
+
* body: JSON.stringify({ name: 'John', email: 'john@example.com' })
|
|
60
|
+
* });
|
|
61
|
+
*
|
|
62
|
+
* // File download (non-JSON response)
|
|
63
|
+
* const response = await client.request<Response>('/api/files/document.pdf', {
|
|
64
|
+
* method: 'GET'
|
|
65
|
+
* });
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
async request(url, options) {
|
|
69
|
+
try {
|
|
70
|
+
// Use native fetch API - no additional configuration needed
|
|
71
|
+
const response = await fetch(url, options);
|
|
72
|
+
if (!response.ok) {
|
|
73
|
+
// Parse response body for error context
|
|
74
|
+
let errorBody;
|
|
75
|
+
try {
|
|
76
|
+
const contentType = response.headers.get('content-type');
|
|
77
|
+
if (contentType?.includes('application/json')) {
|
|
78
|
+
errorBody = await response.json();
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
errorBody = await response.text();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
errorBody = response.statusText;
|
|
86
|
+
}
|
|
87
|
+
// Convert headers to plain object
|
|
88
|
+
const headers = {};
|
|
89
|
+
response.headers.forEach((value, key) => {
|
|
90
|
+
headers[key] = value;
|
|
91
|
+
});
|
|
92
|
+
throw this.createHttpError(response.status, response.statusText, headers, errorBody);
|
|
93
|
+
}
|
|
94
|
+
// Handle different response types
|
|
95
|
+
const contentType = response.headers.get('content-type');
|
|
96
|
+
if (contentType?.includes('application/json')) {
|
|
97
|
+
return response.json();
|
|
98
|
+
}
|
|
99
|
+
// For non-JSON responses, return the response object
|
|
100
|
+
// Sub-clients can handle specific response types as needed
|
|
101
|
+
return response;
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
// Handle network errors (fetch throws TypeError for network issues)
|
|
105
|
+
if (error instanceof TypeError) {
|
|
106
|
+
throw this.createNetworkError(error);
|
|
107
|
+
}
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
4
113
|
/******************************************************************************
|
|
5
114
|
Copyright (c) Microsoft Corporation.
|
|
6
115
|
|
|
@@ -43,7 +152,13 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
|
|
|
43
152
|
* Fields that should not be formatted when sanitizing the query.
|
|
44
153
|
* These fields are essential for maintaining the integrity of the content type.
|
|
45
154
|
*/
|
|
46
|
-
const CONTENT_TYPE_MAIN_FIELDS = [
|
|
155
|
+
const CONTENT_TYPE_MAIN_FIELDS = [
|
|
156
|
+
'live',
|
|
157
|
+
'variant',
|
|
158
|
+
'contentType',
|
|
159
|
+
'languageId',
|
|
160
|
+
'conhost'
|
|
161
|
+
];
|
|
47
162
|
/**
|
|
48
163
|
* URL endpoint for the content API search functionality.
|
|
49
164
|
*/
|
|
@@ -75,6 +190,58 @@ function sanitizeQueryForContentType(query, contentType) {
|
|
|
75
190
|
: original; // Return the field if it is a content type field
|
|
76
191
|
});
|
|
77
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* @description
|
|
195
|
+
* Determines whether a site ID constraint should be added to a query based on existing constraints.
|
|
196
|
+
*
|
|
197
|
+
* The site ID constraint is added only when:
|
|
198
|
+
* - Query doesn't already contain a positive site constraint (+conhost)
|
|
199
|
+
* - Query doesn't explicitly exclude the specified site ID (-conhost:siteId)
|
|
200
|
+
* - Site ID is provided and configured
|
|
201
|
+
*
|
|
202
|
+
* @example
|
|
203
|
+
* ```ts
|
|
204
|
+
* const query = '+contentType:Blog +languageId:1';
|
|
205
|
+
* const siteId = '123';
|
|
206
|
+
* const shouldAdd = shouldAddSiteIdConstraint(query, siteId); // true
|
|
207
|
+
* ```
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* ```ts
|
|
211
|
+
* const query = '+contentType:Blog -conhost:123';
|
|
212
|
+
* const siteId = '123';
|
|
213
|
+
* const shouldAdd = shouldAddSiteIdConstraint(query, siteId); // false (explicitly excluded)
|
|
214
|
+
* ```
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* ```ts
|
|
218
|
+
* const query = '+contentType:Blog +conhost:456';
|
|
219
|
+
* const siteId = '123';
|
|
220
|
+
* const shouldAdd = shouldAddSiteIdConstraint(query, siteId); // false (already has constraint)
|
|
221
|
+
* ```
|
|
222
|
+
*
|
|
223
|
+
* @export
|
|
224
|
+
* @param {string} query - The Lucene query string to analyze
|
|
225
|
+
* @param {string | number | null | undefined} siteId - The site ID to check for
|
|
226
|
+
* @returns {boolean} True if site ID constraint should be added, false otherwise
|
|
227
|
+
*/
|
|
228
|
+
function shouldAddSiteIdConstraint(query, siteId) {
|
|
229
|
+
// No site ID configured
|
|
230
|
+
if (!siteId) {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
// Query already contains a positive site constraint
|
|
234
|
+
const hasExistingSiteConstraint = /\+conhost/gi.test(query);
|
|
235
|
+
if (hasExistingSiteConstraint) {
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
// Query explicitly excludes this specific site ID
|
|
239
|
+
const hasThisSiteIdExclusion = new RegExp(`-conhost:${siteId}`, 'gi').test(query);
|
|
240
|
+
if (hasThisSiteIdExclusion) {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
78
245
|
|
|
79
246
|
var _Field_query;
|
|
80
247
|
/**
|
|
@@ -577,7 +744,7 @@ class QueryBuilder {
|
|
|
577
744
|
}
|
|
578
745
|
_QueryBuilder_query = new WeakMap();
|
|
579
746
|
|
|
580
|
-
var _CollectionBuilder_page, _CollectionBuilder_limit, _CollectionBuilder_depth, _CollectionBuilder_render, _CollectionBuilder_sortBy, _CollectionBuilder_contentType, _CollectionBuilder_defaultQuery, _CollectionBuilder_query, _CollectionBuilder_rawQuery, _CollectionBuilder_languageId, _CollectionBuilder_draft,
|
|
747
|
+
var _CollectionBuilder_page, _CollectionBuilder_limit, _CollectionBuilder_depth, _CollectionBuilder_render, _CollectionBuilder_sortBy, _CollectionBuilder_contentType, _CollectionBuilder_defaultQuery, _CollectionBuilder_query, _CollectionBuilder_rawQuery, _CollectionBuilder_languageId, _CollectionBuilder_draft, _CollectionBuilder_requestOptions, _CollectionBuilder_httpClient, _CollectionBuilder_config;
|
|
581
748
|
/**
|
|
582
749
|
* Creates a Builder to filter and fetch content from the content API for a specific content type.
|
|
583
750
|
*
|
|
@@ -589,11 +756,12 @@ class CollectionBuilder {
|
|
|
589
756
|
/**
|
|
590
757
|
* Creates an instance of CollectionBuilder.
|
|
591
758
|
* @param {ClientOptions} requestOptions Options for the client request.
|
|
592
|
-
* @param {
|
|
759
|
+
* @param {DotCMSClientConfig} config The client configuration.
|
|
593
760
|
* @param {string} contentType The content type to fetch.
|
|
761
|
+
* @param {DotHttpClient} httpClient HTTP client for making requests.
|
|
594
762
|
* @memberof CollectionBuilder
|
|
595
763
|
*/
|
|
596
|
-
constructor(requestOptions,
|
|
764
|
+
constructor(requestOptions, config, contentType, httpClient) {
|
|
597
765
|
_CollectionBuilder_page.set(this, 1);
|
|
598
766
|
_CollectionBuilder_limit.set(this, 10);
|
|
599
767
|
_CollectionBuilder_depth.set(this, 0);
|
|
@@ -605,11 +773,14 @@ class CollectionBuilder {
|
|
|
605
773
|
_CollectionBuilder_rawQuery.set(this, void 0);
|
|
606
774
|
_CollectionBuilder_languageId.set(this, 1);
|
|
607
775
|
_CollectionBuilder_draft.set(this, false);
|
|
608
|
-
_CollectionBuilder_serverUrl.set(this, void 0);
|
|
609
776
|
_CollectionBuilder_requestOptions.set(this, void 0);
|
|
777
|
+
_CollectionBuilder_httpClient.set(this, void 0);
|
|
778
|
+
_CollectionBuilder_config.set(this, void 0);
|
|
610
779
|
__classPrivateFieldSet(this, _CollectionBuilder_requestOptions, requestOptions, "f");
|
|
611
|
-
__classPrivateFieldSet(this,
|
|
780
|
+
__classPrivateFieldSet(this, _CollectionBuilder_config, config, "f");
|
|
612
781
|
__classPrivateFieldSet(this, _CollectionBuilder_contentType, contentType, "f");
|
|
782
|
+
__classPrivateFieldSet(this, _CollectionBuilder_httpClient, httpClient, "f");
|
|
783
|
+
__classPrivateFieldSet(this, _CollectionBuilder_config, config, "f");
|
|
613
784
|
// Build the default query with the contentType field
|
|
614
785
|
__classPrivateFieldSet(this, _CollectionBuilder_defaultQuery, new QueryBuilder().field('contentType').equals(__classPrivateFieldGet(this, _CollectionBuilder_contentType, "f")), "f");
|
|
615
786
|
}
|
|
@@ -641,7 +812,17 @@ class CollectionBuilder {
|
|
|
641
812
|
* @memberof CollectionBuilder
|
|
642
813
|
*/
|
|
643
814
|
get url() {
|
|
644
|
-
return `${__classPrivateFieldGet(this,
|
|
815
|
+
return `${__classPrivateFieldGet(this, _CollectionBuilder_config, "f").dotcmsUrl}${CONTENT_API_URL}`;
|
|
816
|
+
}
|
|
817
|
+
/**
|
|
818
|
+
* Returns the site ID from the configuration.
|
|
819
|
+
*
|
|
820
|
+
* @readonly
|
|
821
|
+
* @private
|
|
822
|
+
* @memberof CollectionBuilder
|
|
823
|
+
*/
|
|
824
|
+
get siteId() {
|
|
825
|
+
return __classPrivateFieldGet(this, _CollectionBuilder_config, "f").siteId;
|
|
645
826
|
}
|
|
646
827
|
/**
|
|
647
828
|
* Returns the current query built.
|
|
@@ -826,26 +1007,36 @@ class CollectionBuilder {
|
|
|
826
1007
|
*
|
|
827
1008
|
* @param {OnFullfilled} [onfulfilled] A callback that is called when the fetch is successful.
|
|
828
1009
|
* @param {OnRejected} [onrejected] A callback that is called when the fetch fails.
|
|
829
|
-
* @return {Promise<GetCollectionResponse<T> |
|
|
1010
|
+
* @return {Promise<GetCollectionResponse<T> | DotErrorContent>} A promise that resolves to the content or rejects with an error.
|
|
830
1011
|
* @memberof CollectionBuilder
|
|
831
1012
|
*/
|
|
832
1013
|
then(onfulfilled, onrejected) {
|
|
833
|
-
return this.fetch().then(
|
|
834
|
-
const
|
|
835
|
-
if (
|
|
836
|
-
const
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
1014
|
+
return this.fetch().then((data) => {
|
|
1015
|
+
const formattedResponse = this.formatResponse(data);
|
|
1016
|
+
if (typeof onfulfilled === 'function') {
|
|
1017
|
+
const result = onfulfilled(formattedResponse);
|
|
1018
|
+
// Ensure we always return a value, fallback to formattedResponse if callback returns undefined
|
|
1019
|
+
return result ?? formattedResponse;
|
|
1020
|
+
}
|
|
1021
|
+
return formattedResponse;
|
|
1022
|
+
}, (error) => {
|
|
1023
|
+
// Wrap error in DotCMSContentError
|
|
1024
|
+
let contentError;
|
|
1025
|
+
if (error instanceof DotHttpError) {
|
|
1026
|
+
contentError = new DotErrorContent(`Content API failed for '${__classPrivateFieldGet(this, _CollectionBuilder_contentType, "f")}' (fetch): ${error.message}`, __classPrivateFieldGet(this, _CollectionBuilder_contentType, "f"), 'fetch', error, this.getFinalQuery());
|
|
841
1027
|
}
|
|
842
1028
|
else {
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
...data
|
|
846
|
-
};
|
|
1029
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
1030
|
+
contentError = new DotErrorContent(`Content API failed for '${__classPrivateFieldGet(this, _CollectionBuilder_contentType, "f")}' (fetch): ${errorMessage}`, __classPrivateFieldGet(this, _CollectionBuilder_contentType, "f"), 'fetch', undefined, this.getFinalQuery());
|
|
847
1031
|
}
|
|
848
|
-
|
|
1032
|
+
if (typeof onrejected === 'function') {
|
|
1033
|
+
const result = onrejected(contentError);
|
|
1034
|
+
// Ensure we always return a value, fallback to original error if callback returns undefined
|
|
1035
|
+
return result ?? contentError;
|
|
1036
|
+
}
|
|
1037
|
+
// Throw the wrapped error to trigger .catch()
|
|
1038
|
+
throw contentError;
|
|
1039
|
+
});
|
|
849
1040
|
}
|
|
850
1041
|
/**
|
|
851
1042
|
* Formats the response to the desired format.
|
|
@@ -875,19 +1066,15 @@ class CollectionBuilder {
|
|
|
875
1066
|
* Calls the content API to fetch the content.
|
|
876
1067
|
*
|
|
877
1068
|
* @private
|
|
878
|
-
* @return {Promise<
|
|
1069
|
+
* @return {Promise<GetCollectionRawResponse<T>>} The fetch response data.
|
|
1070
|
+
* @throws {DotHttpError} When the HTTP request fails.
|
|
879
1071
|
* @memberof CollectionBuilder
|
|
880
1072
|
*/
|
|
881
1073
|
fetch() {
|
|
882
|
-
const finalQuery = this.
|
|
883
|
-
.field('languageId')
|
|
884
|
-
.equals(__classPrivateFieldGet(this, _CollectionBuilder_languageId, "f").toString())
|
|
885
|
-
.field('live')
|
|
886
|
-
.equals((!__classPrivateFieldGet(this, _CollectionBuilder_draft, "f")).toString())
|
|
887
|
-
.build();
|
|
1074
|
+
const finalQuery = this.getFinalQuery();
|
|
888
1075
|
const sanitizedQuery = sanitizeQueryForContentType(finalQuery, __classPrivateFieldGet(this, _CollectionBuilder_contentType, "f"));
|
|
889
1076
|
const query = __classPrivateFieldGet(this, _CollectionBuilder_rawQuery, "f") ? `${sanitizedQuery} ${__classPrivateFieldGet(this, _CollectionBuilder_rawQuery, "f")}` : sanitizedQuery;
|
|
890
|
-
return
|
|
1077
|
+
return __classPrivateFieldGet(this, _CollectionBuilder_httpClient, "f").request(this.url, {
|
|
891
1078
|
...__classPrivateFieldGet(this, _CollectionBuilder_requestOptions, "f"),
|
|
892
1079
|
method: 'POST',
|
|
893
1080
|
headers: {
|
|
@@ -906,10 +1093,62 @@ class CollectionBuilder {
|
|
|
906
1093
|
})
|
|
907
1094
|
});
|
|
908
1095
|
}
|
|
1096
|
+
/**
|
|
1097
|
+
* Builds the final Lucene query string by combining the base query with required system constraints.
|
|
1098
|
+
*
|
|
1099
|
+
* This method constructs the complete query by:
|
|
1100
|
+
* 1. Adding language ID filter to ensure content matches the specified language
|
|
1101
|
+
* 2. Adding live/draft status filter based on the draft flag
|
|
1102
|
+
* 3. Optionally adding site ID constraint if conditions are met
|
|
1103
|
+
*
|
|
1104
|
+
* Site ID constraint is added only when:
|
|
1105
|
+
* - Query doesn't already contain a positive site constraint (+conhost)
|
|
1106
|
+
* - Query doesn't explicitly exclude the current site ID (-conhost:currentSiteId)
|
|
1107
|
+
* - Site ID is configured in the system
|
|
1108
|
+
*
|
|
1109
|
+
* @private
|
|
1110
|
+
* @returns {string} The complete Lucene query string ready for the Content API
|
|
1111
|
+
* @memberof CollectionBuilder
|
|
1112
|
+
*
|
|
1113
|
+
* @example
|
|
1114
|
+
* // For live content in language 1 with site ID 123:
|
|
1115
|
+
* // Returns: "+contentType:Blog +languageId:1 +live:true +conhost:123"
|
|
1116
|
+
*
|
|
1117
|
+
* @example
|
|
1118
|
+
* // For draft content without site constraint:
|
|
1119
|
+
* // Returns: "+contentType:Blog +languageId:1 +live:false"
|
|
1120
|
+
*
|
|
1121
|
+
* @example
|
|
1122
|
+
* // For content with explicit exclusion of current site (site ID 123):
|
|
1123
|
+
* // Query: "+contentType:Blog -conhost:123"
|
|
1124
|
+
* // Returns: "+contentType:Blog -conhost:123 +languageId:1 +live:true" (no site ID added)
|
|
1125
|
+
*
|
|
1126
|
+
* @example
|
|
1127
|
+
* // For content with exclusion of different site (site ID 456, current is 123):
|
|
1128
|
+
* // Query: "+contentType:Blog -conhost:456"
|
|
1129
|
+
* // Returns: "+contentType:Blog -conhost:456 +languageId:1 +live:true +conhost:123" (site ID still added)
|
|
1130
|
+
*/
|
|
1131
|
+
getFinalQuery() {
|
|
1132
|
+
// Build base query with language and live/draft constraints
|
|
1133
|
+
const baseQuery = this.currentQuery
|
|
1134
|
+
.field('languageId')
|
|
1135
|
+
.equals(__classPrivateFieldGet(this, _CollectionBuilder_languageId, "f").toString())
|
|
1136
|
+
.field('live')
|
|
1137
|
+
.equals((!__classPrivateFieldGet(this, _CollectionBuilder_draft, "f")).toString())
|
|
1138
|
+
.build();
|
|
1139
|
+
// Check if site ID constraint should be added using utility function
|
|
1140
|
+
const shouldAddSiteId = shouldAddSiteIdConstraint(baseQuery, this.siteId);
|
|
1141
|
+
// Add site ID constraint if needed
|
|
1142
|
+
if (shouldAddSiteId) {
|
|
1143
|
+
const queryWithSiteId = `${baseQuery} +conhost:${this.siteId}`;
|
|
1144
|
+
return sanitizeQuery(queryWithSiteId);
|
|
1145
|
+
}
|
|
1146
|
+
return baseQuery;
|
|
1147
|
+
}
|
|
909
1148
|
}
|
|
910
|
-
_CollectionBuilder_page = new WeakMap(), _CollectionBuilder_limit = new WeakMap(), _CollectionBuilder_depth = new WeakMap(), _CollectionBuilder_render = new WeakMap(), _CollectionBuilder_sortBy = new WeakMap(), _CollectionBuilder_contentType = new WeakMap(), _CollectionBuilder_defaultQuery = new WeakMap(), _CollectionBuilder_query = new WeakMap(), _CollectionBuilder_rawQuery = new WeakMap(), _CollectionBuilder_languageId = new WeakMap(), _CollectionBuilder_draft = new WeakMap(),
|
|
1149
|
+
_CollectionBuilder_page = new WeakMap(), _CollectionBuilder_limit = new WeakMap(), _CollectionBuilder_depth = new WeakMap(), _CollectionBuilder_render = new WeakMap(), _CollectionBuilder_sortBy = new WeakMap(), _CollectionBuilder_contentType = new WeakMap(), _CollectionBuilder_defaultQuery = new WeakMap(), _CollectionBuilder_query = new WeakMap(), _CollectionBuilder_rawQuery = new WeakMap(), _CollectionBuilder_languageId = new WeakMap(), _CollectionBuilder_draft = new WeakMap(), _CollectionBuilder_requestOptions = new WeakMap(), _CollectionBuilder_httpClient = new WeakMap(), _CollectionBuilder_config = new WeakMap();
|
|
911
1150
|
|
|
912
|
-
var _Content_requestOptions,
|
|
1151
|
+
var _Content_requestOptions, _Content_httpClient, _Content_config;
|
|
913
1152
|
/**
|
|
914
1153
|
* Creates a builder to filter and fetch a collection of content items.
|
|
915
1154
|
* @param contentType - The content type to retrieve.
|
|
@@ -962,14 +1201,17 @@ var _Content_requestOptions, _Content_serverUrl;
|
|
|
962
1201
|
class Content {
|
|
963
1202
|
/**
|
|
964
1203
|
* Creates an instance of Content.
|
|
965
|
-
* @param {
|
|
1204
|
+
* @param {DotRequestOptions} requestOptions - The options for the client request.
|
|
966
1205
|
* @param {string} serverUrl - The server URL.
|
|
1206
|
+
* @param {DotHttpClient} httpClient - HTTP client for making requests.
|
|
967
1207
|
*/
|
|
968
|
-
constructor(requestOptions,
|
|
1208
|
+
constructor(config, requestOptions, httpClient) {
|
|
969
1209
|
_Content_requestOptions.set(this, void 0);
|
|
970
|
-
|
|
1210
|
+
_Content_httpClient.set(this, void 0);
|
|
1211
|
+
_Content_config.set(this, void 0);
|
|
971
1212
|
__classPrivateFieldSet(this, _Content_requestOptions, requestOptions, "f");
|
|
972
|
-
__classPrivateFieldSet(this,
|
|
1213
|
+
__classPrivateFieldSet(this, _Content_config, config, "f");
|
|
1214
|
+
__classPrivateFieldSet(this, _Content_httpClient, httpClient, "f");
|
|
973
1215
|
}
|
|
974
1216
|
/**
|
|
975
1217
|
* Takes a content type and returns a builder to filter and fetch the collection.
|
|
@@ -1037,35 +1279,44 @@ class Content {
|
|
|
1037
1279
|
*
|
|
1038
1280
|
*/
|
|
1039
1281
|
getCollection(contentType) {
|
|
1040
|
-
return new CollectionBuilder(__classPrivateFieldGet(this, _Content_requestOptions, "f"), __classPrivateFieldGet(this,
|
|
1282
|
+
return new CollectionBuilder(__classPrivateFieldGet(this, _Content_requestOptions, "f"), __classPrivateFieldGet(this, _Content_config, "f"), contentType, __classPrivateFieldGet(this, _Content_httpClient, "f"));
|
|
1041
1283
|
}
|
|
1042
1284
|
}
|
|
1043
|
-
_Content_requestOptions = new WeakMap(),
|
|
1285
|
+
_Content_requestOptions = new WeakMap(), _Content_httpClient = new WeakMap(), _Content_config = new WeakMap();
|
|
1044
1286
|
|
|
1045
1287
|
class NavigationClient {
|
|
1046
|
-
constructor(config, requestOptions) {
|
|
1288
|
+
constructor(config, requestOptions, httpClient) {
|
|
1047
1289
|
this.requestOptions = requestOptions;
|
|
1048
1290
|
this.BASE_URL = `${config?.dotcmsUrl}/api/v1/nav`;
|
|
1291
|
+
this.httpClient = httpClient;
|
|
1049
1292
|
}
|
|
1050
1293
|
/**
|
|
1051
1294
|
* Retrieves information about the dotCMS file and folder tree.
|
|
1052
|
-
* @param {
|
|
1295
|
+
* @param {string} path - The path to retrieve navigation for.
|
|
1296
|
+
* @param {DotCMSNavigationRequestParams} params - The options for the Navigation API call.
|
|
1053
1297
|
* @returns {Promise<DotCMSNavigationItem[]>} - A Promise that resolves to the response from the DotCMS API.
|
|
1054
|
-
* @throws {
|
|
1298
|
+
* @throws {DotErrorNavigation} - Throws a navigation-specific error if the request fails.
|
|
1055
1299
|
*/
|
|
1056
1300
|
async get(path, params) {
|
|
1057
1301
|
if (!path) {
|
|
1058
|
-
throw new
|
|
1302
|
+
throw new DotErrorNavigation("The 'path' parameter is required for the Navigation API", path);
|
|
1059
1303
|
}
|
|
1060
1304
|
const navParams = params ? this.mapToBackendParams(params) : {};
|
|
1061
1305
|
const urlParams = new URLSearchParams(navParams).toString();
|
|
1062
1306
|
const parsedPath = path.replace(/^\/+/, '/').replace(/\/+$/, '/');
|
|
1063
1307
|
const url = `${this.BASE_URL}${parsedPath}${urlParams ? `?${urlParams}` : ''}`;
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1308
|
+
try {
|
|
1309
|
+
const response = await this.httpClient.request(url, this.requestOptions);
|
|
1310
|
+
return response.entity;
|
|
1311
|
+
}
|
|
1312
|
+
catch (error) {
|
|
1313
|
+
// Handle DotHttpError instances from httpClient.request
|
|
1314
|
+
if (error instanceof DotHttpError) {
|
|
1315
|
+
throw new DotErrorNavigation(`Navigation API failed for path '${parsedPath}': ${error.message}`, parsedPath, error);
|
|
1316
|
+
}
|
|
1317
|
+
// Handle other errors (validation, network, etc.)
|
|
1318
|
+
throw new DotErrorNavigation(`Navigation API failed for path '${parsedPath}': ${error instanceof Error ? error.message : 'Unknown error'}`, parsedPath);
|
|
1067
1319
|
}
|
|
1068
|
-
return response.json().then((data) => data.entity);
|
|
1069
1320
|
}
|
|
1070
1321
|
mapToBackendParams(params) {
|
|
1071
1322
|
const backendParams = {};
|
|
@@ -1079,26 +1330,6 @@ class NavigationClient {
|
|
|
1079
1330
|
}
|
|
1080
1331
|
}
|
|
1081
1332
|
|
|
1082
|
-
/**
|
|
1083
|
-
* A record of HTTP status codes and their corresponding error messages.
|
|
1084
|
-
*
|
|
1085
|
-
* @type {Record<number, string>}
|
|
1086
|
-
* @property {string} 401 - Unauthorized. Check the token and try again.
|
|
1087
|
-
* @property {string} 403 - Forbidden. Check the permissions and try again.
|
|
1088
|
-
* @property {string} 404 - Not Found. Check the URL and try again.
|
|
1089
|
-
* @property {string} 500 - Internal Server Error. Try again later.
|
|
1090
|
-
* @property {string} 502 - Bad Gateway. Try again later.
|
|
1091
|
-
* @property {string} 503 - Service Unavailable. Try again later.
|
|
1092
|
-
*/
|
|
1093
|
-
const ErrorMessages = {
|
|
1094
|
-
401: 'Unauthorized. Check the token and try again.',
|
|
1095
|
-
403: 'Forbidden. Check the permissions and try again.',
|
|
1096
|
-
404: 'Not Found. Check the URL and try again.',
|
|
1097
|
-
500: 'Internal Server Error. Try again later.',
|
|
1098
|
-
502: 'Bad Gateway. Try again later.',
|
|
1099
|
-
503: 'Service Unavailable. Try again later.'
|
|
1100
|
-
};
|
|
1101
|
-
|
|
1102
1333
|
const DEFAULT_PAGE_CONTENTLETS_CONTENT = `
|
|
1103
1334
|
publishDate
|
|
1104
1335
|
inode
|
|
@@ -1314,11 +1545,14 @@ function buildQuery(queryData) {
|
|
|
1314
1545
|
/**
|
|
1315
1546
|
* Filters response data to include only specified keys.
|
|
1316
1547
|
*
|
|
1317
|
-
* @param {Record<string,
|
|
1548
|
+
* @param {Record<string, unknown> | undefined} responseData - Original response data object
|
|
1318
1549
|
* @param {string[]} keys - Array of keys to extract from the response data
|
|
1319
|
-
* @returns {Record<string,
|
|
1550
|
+
* @returns {Record<string, unknown> | undefined} New object containing only the specified keys
|
|
1320
1551
|
*/
|
|
1321
|
-
function
|
|
1552
|
+
function mapContentResponse(responseData, keys) {
|
|
1553
|
+
if (!responseData) {
|
|
1554
|
+
return undefined;
|
|
1555
|
+
}
|
|
1322
1556
|
return keys.reduce((accumulator, key) => {
|
|
1323
1557
|
if (responseData[key] !== undefined) {
|
|
1324
1558
|
accumulator[key] = responseData[key];
|
|
@@ -1332,25 +1566,19 @@ function mapResponseData(responseData, keys) {
|
|
|
1332
1566
|
* @param {Object} options - Options for the fetch request
|
|
1333
1567
|
* @param {string} options.body - GraphQL query string
|
|
1334
1568
|
* @param {Record<string, string>} options.headers - HTTP headers for the request
|
|
1335
|
-
* @
|
|
1336
|
-
* @
|
|
1569
|
+
* @param {DotHttpClient} options.httpClient - HTTP client for making requests
|
|
1570
|
+
* @returns {Promise<DotGraphQLApiResponse>} Parsed JSON response from the GraphQL API
|
|
1571
|
+
* @throws {DotHttpError} If the HTTP request fails (non-2xx status or network error)
|
|
1337
1572
|
*/
|
|
1338
|
-
async function fetchGraphQL({ baseURL, body, headers }) {
|
|
1573
|
+
async function fetchGraphQL({ baseURL, body, headers, httpClient }) {
|
|
1339
1574
|
const url = new URL(baseURL);
|
|
1340
1575
|
url.pathname = '/api/v1/graphql';
|
|
1341
|
-
|
|
1576
|
+
// httpClient.request throws DotHttpError on failure, so we just return the response directly
|
|
1577
|
+
return await httpClient.request(url.toString(), {
|
|
1342
1578
|
method: 'POST',
|
|
1343
1579
|
body,
|
|
1344
1580
|
headers
|
|
1345
1581
|
});
|
|
1346
|
-
if (!response.ok) {
|
|
1347
|
-
const error = {
|
|
1348
|
-
status: response.status,
|
|
1349
|
-
message: ErrorMessages[response.status] || response.statusText
|
|
1350
|
-
};
|
|
1351
|
-
throw error;
|
|
1352
|
-
}
|
|
1353
|
-
return await response.json();
|
|
1354
1582
|
}
|
|
1355
1583
|
|
|
1356
1584
|
/**
|
|
@@ -1362,7 +1590,8 @@ class PageClient {
|
|
|
1362
1590
|
* Creates a new PageClient instance.
|
|
1363
1591
|
*
|
|
1364
1592
|
* @param {DotCMSClientConfig} config - Configuration options for the DotCMS client
|
|
1365
|
-
* @param {
|
|
1593
|
+
* @param {DotRequestOptions} requestOptions - Options for fetch requests including authorization headers
|
|
1594
|
+
* @param {DotHttpClient} httpClient - HTTP client for making requests
|
|
1366
1595
|
* @example
|
|
1367
1596
|
* ```typescript
|
|
1368
1597
|
* const pageClient = new PageClient(
|
|
@@ -1375,14 +1604,16 @@ class PageClient {
|
|
|
1375
1604
|
* headers: {
|
|
1376
1605
|
* Authorization: 'Bearer your-auth-token'
|
|
1377
1606
|
* }
|
|
1378
|
-
* }
|
|
1607
|
+
* },
|
|
1608
|
+
* httpClient
|
|
1379
1609
|
* );
|
|
1380
1610
|
* ```
|
|
1381
1611
|
*/
|
|
1382
|
-
constructor(config, requestOptions) {
|
|
1612
|
+
constructor(config, requestOptions, httpClient) {
|
|
1383
1613
|
this.requestOptions = requestOptions;
|
|
1384
1614
|
this.siteId = config.siteId || '';
|
|
1385
1615
|
this.dotcmsUrl = config.dotcmsUrl;
|
|
1616
|
+
this.httpClient = httpClient;
|
|
1386
1617
|
}
|
|
1387
1618
|
/**
|
|
1388
1619
|
* Retrieves a page from DotCMS using GraphQL.
|
|
@@ -1391,6 +1622,7 @@ class PageClient {
|
|
|
1391
1622
|
* @param {DotCMSPageRequestParams} [options] - Options for the request
|
|
1392
1623
|
* @template T - The type of the page and content, defaults to DotCMSBasicPage and Record<string, unknown> | unknown
|
|
1393
1624
|
* @returns {Promise<DotCMSComposedPageResponse<T>>} A Promise that resolves to the page data
|
|
1625
|
+
* @throws {DotErrorPage} - Throws a page-specific error if the request fails or page is not found
|
|
1394
1626
|
*
|
|
1395
1627
|
* @example Using GraphQL
|
|
1396
1628
|
* ```typescript
|
|
@@ -1458,21 +1690,26 @@ class PageClient {
|
|
|
1458
1690
|
const requestHeaders = this.requestOptions.headers;
|
|
1459
1691
|
const requestBody = JSON.stringify({ query: completeQuery, variables: requestVariables });
|
|
1460
1692
|
try {
|
|
1461
|
-
const
|
|
1693
|
+
const response = await fetchGraphQL({
|
|
1462
1694
|
baseURL: this.dotcmsUrl,
|
|
1463
1695
|
body: requestBody,
|
|
1464
|
-
headers: requestHeaders
|
|
1696
|
+
headers: requestHeaders,
|
|
1697
|
+
httpClient: this.httpClient
|
|
1465
1698
|
});
|
|
1466
|
-
|
|
1467
|
-
|
|
1699
|
+
// The GQL endpoint can return errors and data, we need to handle both
|
|
1700
|
+
if (response.errors) {
|
|
1701
|
+
response.errors.forEach((error) => {
|
|
1468
1702
|
consola.error('[DotCMS GraphQL Error]: ', error.message);
|
|
1469
1703
|
});
|
|
1470
1704
|
}
|
|
1471
|
-
const pageResponse = graphqlToPageEntity(data);
|
|
1705
|
+
const pageResponse = graphqlToPageEntity(response.data.page);
|
|
1472
1706
|
if (!pageResponse) {
|
|
1473
|
-
throw new
|
|
1707
|
+
throw new DotErrorPage(`Page ${url} not found. Check the page URL and permissions.`, undefined, {
|
|
1708
|
+
query: completeQuery,
|
|
1709
|
+
variables: requestVariables
|
|
1710
|
+
});
|
|
1474
1711
|
}
|
|
1475
|
-
const contentResponse =
|
|
1712
|
+
const contentResponse = mapContentResponse(response.data, Object.keys(content));
|
|
1476
1713
|
return {
|
|
1477
1714
|
pageAsset: pageResponse,
|
|
1478
1715
|
content: contentResponse,
|
|
@@ -1483,15 +1720,18 @@ class PageClient {
|
|
|
1483
1720
|
};
|
|
1484
1721
|
}
|
|
1485
1722
|
catch (error) {
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
graphql: {
|
|
1723
|
+
// Handle DotHttpError instances from httpClient.request
|
|
1724
|
+
if (error instanceof DotHttpError) {
|
|
1725
|
+
throw new DotErrorPage(`Page request failed for URL '${url}': ${error.message}`, error, {
|
|
1490
1726
|
query: completeQuery,
|
|
1491
1727
|
variables: requestVariables
|
|
1492
|
-
}
|
|
1493
|
-
}
|
|
1494
|
-
|
|
1728
|
+
});
|
|
1729
|
+
}
|
|
1730
|
+
// Handle other errors (GraphQL errors, validation errors, etc.)
|
|
1731
|
+
throw new DotErrorPage(`Page request failed for URL '${url}': ${error instanceof Error ? error.message : 'Unknown error'}`, undefined, {
|
|
1732
|
+
query: completeQuery,
|
|
1733
|
+
variables: requestVariables
|
|
1734
|
+
});
|
|
1495
1735
|
}
|
|
1496
1736
|
}
|
|
1497
1737
|
}
|
|
@@ -1532,11 +1772,12 @@ class DotCMSClient {
|
|
|
1532
1772
|
*/
|
|
1533
1773
|
constructor(config = defaultConfig) {
|
|
1534
1774
|
this.config = config;
|
|
1775
|
+
this.httpClient = config.httpClient || new FetchHttpClient();
|
|
1535
1776
|
this.requestOptions = this.createAuthenticatedRequestOptions(this.config);
|
|
1536
|
-
// Initialize clients
|
|
1537
|
-
this.page = new PageClient(this.config, this.requestOptions);
|
|
1538
|
-
this.nav = new NavigationClient(this.config, this.requestOptions);
|
|
1539
|
-
this.content = new Content(this.requestOptions, this.
|
|
1777
|
+
// Initialize clients with httpClient
|
|
1778
|
+
this.page = new PageClient(this.config, this.requestOptions, this.httpClient);
|
|
1779
|
+
this.nav = new NavigationClient(this.config, this.requestOptions, this.httpClient);
|
|
1780
|
+
this.content = new Content(this.config, this.requestOptions, this.httpClient);
|
|
1540
1781
|
}
|
|
1541
1782
|
/**
|
|
1542
1783
|
* Creates request options with authentication headers.
|