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