@enonic/nextjs-adapter 0.0.1-alpha.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.
Files changed (46) hide show
  1. package/ComponentRegistry.d.ts +61 -0
  2. package/ComponentRegistry.js +115 -0
  3. package/README.md +39 -0
  4. package/UrlProcessor.d.ts +21 -0
  5. package/UrlProcessor.js +54 -0
  6. package/baseMappings.d.ts +1 -0
  7. package/baseMappings.js +38 -0
  8. package/guillotine/fetchContent.d.ts +82 -0
  9. package/guillotine/fetchContent.js +644 -0
  10. package/guillotine/getMetaData.d.ts +91 -0
  11. package/guillotine/getMetaData.js +128 -0
  12. package/index.d.ts +2 -0
  13. package/index.js +2 -0
  14. package/package.json +33 -0
  15. package/utils.d.ts +64 -0
  16. package/utils.js +146 -0
  17. package/views/BaseComponent.d.ts +25 -0
  18. package/views/BaseComponent.jsx +114 -0
  19. package/views/BaseContent.d.ts +4 -0
  20. package/views/BaseContent.jsx +41 -0
  21. package/views/BaseLayout.d.ts +17 -0
  22. package/views/BaseLayout.jsx +24 -0
  23. package/views/BaseMacro.d.ts +16 -0
  24. package/views/BaseMacro.jsx +85 -0
  25. package/views/BasePage.d.ts +19 -0
  26. package/views/BasePage.jsx +34 -0
  27. package/views/BasePart.d.ts +19 -0
  28. package/views/BasePart.jsx +24 -0
  29. package/views/Empty.d.ts +3 -0
  30. package/views/Empty.jsx +3 -0
  31. package/views/Fragment.d.ts +10 -0
  32. package/views/Fragment.jsx +25 -0
  33. package/views/MainView.d.ts +4 -0
  34. package/views/MainView.jsx +14 -0
  35. package/views/Region.d.ts +20 -0
  36. package/views/Region.jsx +57 -0
  37. package/views/RichTextView.d.ts +15 -0
  38. package/views/RichTextView.jsx +55 -0
  39. package/views/SingleComponent.d.ts +5 -0
  40. package/views/SingleComponent.jsx +11 -0
  41. package/views/Text.d.ts +9 -0
  42. package/views/Text.jsx +4 -0
  43. package/views/macros/DefaultMacro.d.ts +4 -0
  44. package/views/macros/DefaultMacro.jsx +3 -0
  45. package/views/macros/DisableMacro.d.ts +3 -0
  46. package/views/macros/DisableMacro.jsx +2 -0
@@ -0,0 +1,644 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { getMetaQuery, pageFragmentQuery, } from './getMetaData';
11
+ import adapterConstants, { APP_NAME, APP_NAME_DASHED, FRAGMENT_CONTENTTYPE_NAME, FRAGMENT_DEFAULT_REGION_NAME, getContentApiUrl, getXpBaseUrl, IS_DEV_MODE, PAGE_TEMPLATE_CONTENTTYPE_NAME, PAGE_TEMPLATE_FOLDER, RENDER_MODE, sanitizeGraphqlName, SITE_KEY, XP_COMPONENT_TYPE, XP_REQUEST_TYPE, } from '../utils';
12
+ import { ComponentRegistry } from '../ComponentRegistry';
13
+ const NO_PROPS_PROCESSOR = (props) => __awaiter(void 0, void 0, void 0, function* () { var _a; return (_a = yield props) !== null && _a !== void 0 ? _a : {}; });
14
+ const ALIAS_PREFIX = 'request';
15
+ const GUILLOTINE_QUERY_REGEXP = /^\s*query\s*(?:\((.*)*\))?\s*{\s*guillotine\s*{((?:.|\s)+)}\s*}\s*$/;
16
+ const GRAPHQL_FRAGMENTS_REGEXP = /fragment\s+.+\s+on\s+.+\s*{[\s\w{}().,:"'`]+}/;
17
+ /** Generic fetch */
18
+ export const fetchFromApi = (apiUrl, body, method = 'POST') => __awaiter(void 0, void 0, void 0, function* () {
19
+ const options = {
20
+ method,
21
+ headers: {
22
+ 'Accept': 'application/json',
23
+ 'Content-Type': 'application/json',
24
+ 'X-Guillotine-SiteKey': SITE_KEY,
25
+ },
26
+ body: JSON.stringify(body),
27
+ };
28
+ let res;
29
+ try {
30
+ res = yield fetch(apiUrl, options);
31
+ }
32
+ catch (e) {
33
+ console.warn(apiUrl, e);
34
+ throw new Error(JSON.stringify({
35
+ code: 'API',
36
+ message: e.message,
37
+ }));
38
+ }
39
+ if (!res.ok) {
40
+ throw new Error(JSON.stringify({
41
+ code: res.status,
42
+ message: `Data fetching failed (message: '${yield res.text()}')`,
43
+ }));
44
+ }
45
+ let json;
46
+ try {
47
+ json = yield res.json();
48
+ }
49
+ catch (e) {
50
+ throw new Error(JSON.stringify({
51
+ code: 500,
52
+ message: `API call completed but with non-JSON data: ${JSON.stringify(yield res.text())}`,
53
+ }));
54
+ }
55
+ if (!json) {
56
+ throw new Error(JSON.stringify({
57
+ code: 500,
58
+ message: `API call completed but with unexpectedly empty data: ${JSON.stringify(yield res.text())}`,
59
+ }));
60
+ }
61
+ return json;
62
+ });
63
+ /** Guillotine-specialized fetch, using the generic fetch above */
64
+ export const fetchGuillotine = (contentApiUrl, body, xpContentPath) => __awaiter(void 0, void 0, void 0, function* () {
65
+ if (typeof body.query !== 'string' || !body.query.trim()) {
66
+ return {
67
+ error: {
68
+ code: '400',
69
+ message: `Invalid or missing query. JSON.stringify(query) = ${JSON.stringify(body.query)}`,
70
+ },
71
+ };
72
+ }
73
+ const result = yield fetchFromApi(contentApiUrl, body)
74
+ .then(json => {
75
+ let errors = (json || {}).errors;
76
+ if (errors) {
77
+ if (!Array.isArray(errors)) {
78
+ errors = [errors];
79
+ }
80
+ console.warn(`${errors.length} error(s) when trying to fetch data (path = ${JSON.stringify(xpContentPath)}):`);
81
+ errors.forEach(error => {
82
+ console.error(error);
83
+ });
84
+ console.warn(`Query:\n${body.query}`);
85
+ console.warn(`Variables: ${JSON.stringify(body.variables, null, 2)}`);
86
+ return {
87
+ error: {
88
+ code: '500',
89
+ message: `Server responded with ${errors.length} error(s), probably from guillotine - see log.`,
90
+ },
91
+ };
92
+ }
93
+ return json.data;
94
+ })
95
+ .catch((err) => {
96
+ console.warn(`Client-side error when trying to fetch data (path = ${JSON.stringify(xpContentPath)})`, err);
97
+ try {
98
+ return { error: JSON.parse(err.message) };
99
+ }
100
+ catch (e2) {
101
+ return { error: { code: 'Client-side error', message: err.message } };
102
+ }
103
+ });
104
+ return result;
105
+ });
106
+ // /////////////////////////////////////////////////////////////////////////////// Specific fetch wrappers:
107
+ const fetchMetaData = (contentApiUrl, xpContentPath) => __awaiter(void 0, void 0, void 0, function* () {
108
+ var _b;
109
+ const body = {
110
+ query: getMetaQuery(pageFragmentQuery()),
111
+ variables: {
112
+ path: xpContentPath,
113
+ },
114
+ };
115
+ const metaResult = yield fetchGuillotine(contentApiUrl, body, xpContentPath);
116
+ if (metaResult.error) {
117
+ return metaResult;
118
+ }
119
+ else {
120
+ return {
121
+ meta: (_b = metaResult === null || metaResult === void 0 ? void 0 : metaResult.guillotine) === null || _b === void 0 ? void 0 : _b.get,
122
+ };
123
+ }
124
+ });
125
+ const fetchContentData = (contentApiUrl, xpContentPath, query, variables) => __awaiter(void 0, void 0, void 0, function* () {
126
+ const body = { query };
127
+ if (variables && Object.keys(variables).length > 0) {
128
+ body.variables = variables;
129
+ }
130
+ const contentResults = yield fetchGuillotine(contentApiUrl, body, xpContentPath);
131
+ if (contentResults.error) {
132
+ return contentResults;
133
+ }
134
+ else {
135
+ return {
136
+ // omit the aliases and return values
137
+ contents: Object.values(contentResults),
138
+ };
139
+ }
140
+ });
141
+ // /////////////////////////////////////////////////////////////////////////////// Error checking:
142
+ /** Checks a site-relative contentPath as a slash-delimited string or a string array, and returns a pure site-relative path string (no double slashes, starts with a slash, does not end with one). */
143
+ const getCleanContentPathArrayOrThrow400 = (contentPath) => {
144
+ if (contentPath === undefined) {
145
+ return '';
146
+ }
147
+ const isArray = Array.isArray(contentPath);
148
+ if (!isArray) {
149
+ if (typeof contentPath !== 'string') {
150
+ throw Error(JSON.stringify({
151
+ code: 400,
152
+ message: `Unexpected target content _path: contentPath must be a string or pure string array (contentPath=${JSON.stringify(contentPath)})`,
153
+ }));
154
+ }
155
+ return contentPath;
156
+ }
157
+ else {
158
+ return (contentPath).join('/');
159
+ }
160
+ };
161
+ function parseComponentPath(contentType, path) {
162
+ const matches = [];
163
+ let match;
164
+ const myRegexp = /(?:(\w+)\/(\d+))+/g;
165
+ while ((match = myRegexp.exec(path)) !== null) {
166
+ matches.push({
167
+ region: match[1],
168
+ index: +match[2],
169
+ });
170
+ }
171
+ if (contentType === FRAGMENT_CONTENTTYPE_NAME) {
172
+ // there is no main region in fragment content and the root component has '/' path
173
+ // prepend FRAGMENT_DEFAULT_REGION_NAME to path to conform to page structure
174
+ matches.unshift({
175
+ region: FRAGMENT_DEFAULT_REGION_NAME,
176
+ index: 0,
177
+ });
178
+ }
179
+ return matches;
180
+ }
181
+ function getParentRegion(source, contentType, cmpPath, components = [], createMissing) {
182
+ let currentTree = source;
183
+ let currentRegion;
184
+ let parentPath = '';
185
+ for (let i = 0; i < cmpPath.length; i++) {
186
+ const pathFragment = cmpPath[i];
187
+ const regionName = pathFragment.region;
188
+ parentPath += `/${pathFragment.region}/${pathFragment.index}`;
189
+ currentRegion = currentTree[regionName];
190
+ if (!currentRegion) {
191
+ if (createMissing) {
192
+ currentRegion = {
193
+ name: regionName,
194
+ components: [],
195
+ };
196
+ currentTree[regionName] = currentRegion;
197
+ }
198
+ else {
199
+ throw `Region [${regionName}] was not found`;
200
+ }
201
+ }
202
+ if (i < cmpPath.length - 1) {
203
+ // look for layouts inside if this is not the last path fragment
204
+ const layout = components.find((cmp) => {
205
+ return cmp.type === XP_COMPONENT_TYPE.LAYOUT && prefixLayoutPath(contentType, cmp.path) === parentPath;
206
+ });
207
+ if (!layout) {
208
+ throw `Layout [${parentPath}] not found among components, but needed for component [${JSON.stringify(cmpPath, null, 2)}]`;
209
+ }
210
+ if (!layout.regions) {
211
+ layout.regions = {};
212
+ }
213
+ currentTree = layout.regions;
214
+ }
215
+ }
216
+ return currentRegion;
217
+ }
218
+ function prefixLayoutPath(contentType, path) {
219
+ if (contentType !== FRAGMENT_CONTENTTYPE_NAME) {
220
+ return path;
221
+ }
222
+ else {
223
+ // prepend FRAGMENT_DEFAULT_REGION_NAME to path to conform to page structure
224
+ // so that component with path '/' becomes /FRAGMENT_DEFAULT_REGION_NAME/0
225
+ // path /left/1 becomes /FRAGMENT_DEFAULT_REGION_NAME/0/left/1
226
+ return `/${FRAGMENT_DEFAULT_REGION_NAME}/0${path === '/' ? '' : path}`;
227
+ }
228
+ }
229
+ function buildPage(contentType, comps = []) {
230
+ let page = {
231
+ type: XP_COMPONENT_TYPE.PAGE,
232
+ path: '/',
233
+ };
234
+ const tree = {};
235
+ if (contentType === FRAGMENT_CONTENTTYPE_NAME) {
236
+ page.regions = tree;
237
+ }
238
+ comps.forEach(cmp => {
239
+ var _a;
240
+ const cmpPath = parseComponentPath(contentType, cmp.path);
241
+ let region;
242
+ if (cmp.path === '/' && cmp.type === XP_COMPONENT_TYPE.PAGE) {
243
+ // add page values to page object
244
+ page = Object.assign(page, cmp);
245
+ page.page.regions = tree;
246
+ return;
247
+ }
248
+ else {
249
+ region = getParentRegion(tree, contentType, cmpPath, comps, true);
250
+ }
251
+ if (region) {
252
+ // getting the index of component from string like '/main/0/left/11'
253
+ const cmpIndex = (_a = cmpPath[cmpPath.length - 1]) === null || _a === void 0 ? void 0 : _a.index;
254
+ if (cmpIndex >= 0) {
255
+ region.components.splice(cmpIndex, 0, cmp);
256
+ }
257
+ else {
258
+ throw Error(`Could not find [${cmp.type}] component index at ${JSON.stringify(cmpPath, null, 2)}, rendering not possible.`);
259
+ }
260
+ }
261
+ });
262
+ return page;
263
+ }
264
+ function combineMultipleQueries(queriesWithVars) {
265
+ const queries = [];
266
+ const fragments = [];
267
+ const superVars = {};
268
+ const superParams = [];
269
+ queriesWithVars.forEach((componentDescriptor, index) => {
270
+ const queryAndVars = componentDescriptor.queryAndVariables;
271
+ if (!queryAndVars) {
272
+ return;
273
+ }
274
+ // Extract fragments first if exist
275
+ let q = queryAndVars.query;
276
+ let match = q.match(GRAPHQL_FRAGMENTS_REGEXP);
277
+ if ((match === null || match === void 0 ? void 0 : match.length) === 1) {
278
+ // extract a fragment to put it at root level
279
+ fragments.push(match[0]);
280
+ // remove it from query because queries are going to get wrapped
281
+ q = q.replace(match[0], '');
282
+ }
283
+ // Extract graphql query and its params and add prefixes to exclude collisions with other queries
284
+ match = q.match(GUILLOTINE_QUERY_REGEXP);
285
+ let query = '';
286
+ if (match && match.length === 2) {
287
+ // no params, just query
288
+ query = match[1];
289
+ }
290
+ else if (match && match.length === 3) {
291
+ // both query and params are present
292
+ query = match[2];
293
+ // process args
294
+ const args = match[1];
295
+ if (args) {
296
+ args.split(',').forEach(originalParamString => {
297
+ const [originalKey, originalVal] = originalParamString.trim().split(':');
298
+ const [prefixedKey, prefixedVal] = [`$${ALIAS_PREFIX}${index}_${originalKey.substr(1)}`, originalVal];
299
+ superParams.push(`${prefixedKey}:${prefixedVal}`);
300
+ // also update param references in query itself !
301
+ // query = query.replaceAll(originalKey, prefixedKey);
302
+ // replaceAll is not supported in older nodejs versions
303
+ const origKeyPattern = new RegExp(originalKey.replace(/\$/g, '\\$'), 'g');
304
+ query = query.replace(origKeyPattern, prefixedKey);
305
+ });
306
+ }
307
+ }
308
+ if (query.length) {
309
+ queries.push(`${ALIAS_PREFIX}${index}:guillotine {${query}}`);
310
+ }
311
+ // Update variables with the same prefixes
312
+ Object.entries(queryAndVars.variables || {}).forEach(entry => {
313
+ superVars[`${ALIAS_PREFIX}${index}_${entry[0]}`] = entry[1];
314
+ });
315
+ });
316
+ // Compose the super query
317
+ const superQuery = `query ${superParams.length ? `(${superParams.join(', ')})` : ''} {
318
+ ${queries.join('\n')}
319
+ }
320
+ ${fragments.join('\n')}
321
+ `;
322
+ return {
323
+ query: superQuery,
324
+ variables: superVars,
325
+ };
326
+ }
327
+ function applyProcessors(componentDescriptors, contentResults, context) {
328
+ return __awaiter(this, void 0, void 0, function* () {
329
+ let dataCounter = 0;
330
+ const processorPromises = componentDescriptors.map((desc) => __awaiter(this, void 0, void 0, function* () {
331
+ var _a;
332
+ // we're iterating component descriptors here
333
+ // some of them might not have provided graphql requests
334
+ // but we still need to run props processor for them
335
+ // in case they want to fetch their data from elsewhere
336
+ const propsProcessor = ((_a = desc.type) === null || _a === void 0 ? void 0 : _a.processor) || NO_PROPS_PROCESSOR;
337
+ let data;
338
+ if (desc.queryAndVariables) {
339
+ // if there is a query then there must be a result for it
340
+ data = contentResults.contents[dataCounter++];
341
+ }
342
+ return yield propsProcessor(data, context);
343
+ }));
344
+ return Promise.allSettled(processorPromises);
345
+ });
346
+ }
347
+ function collectComponentDescriptors(components, componentRegistry, requestedComponentPath, xpContentPath, context) {
348
+ const descriptors = [];
349
+ for (const cmp of (components || [])) {
350
+ processComponentConfig(APP_NAME, APP_NAME_DASHED, cmp);
351
+ // only look for parts
352
+ // look for single part if it is a single component request
353
+ if (XP_COMPONENT_TYPE.FRAGMENT !== cmp.type) {
354
+ const cmpDef = ComponentRegistry.getByComponent(cmp);
355
+ if (cmpDef) {
356
+ // const partPath = `${xpContentPath}/_component${cmp.path}`;
357
+ const cmpData = cmp[cmp.type];
358
+ const config = cmpData && 'config' in cmpData ? cmpData.config : undefined;
359
+ const queryAndVariables = getQueryAndVariables(cmp.type, xpContentPath, cmpDef.query, context, config);
360
+ if (queryAndVariables) {
361
+ descriptors.push({
362
+ component: cmp,
363
+ type: cmpDef,
364
+ queryAndVariables: queryAndVariables,
365
+ });
366
+ }
367
+ }
368
+ }
369
+ else {
370
+ // look for parts inside fragments
371
+ const fragPartDescs = collectComponentDescriptors(cmp.fragment.fragment.components, componentRegistry, requestedComponentPath, xpContentPath, context);
372
+ if (fragPartDescs.length) {
373
+ descriptors.push(...fragPartDescs);
374
+ }
375
+ }
376
+ }
377
+ return descriptors;
378
+ }
379
+ function processComponentConfig(myAppName, myAppNameDashed, cmp) {
380
+ const cmpData = cmp[cmp.type];
381
+ if (cmpData && 'descriptor' in cmpData && cmpData.descriptor) {
382
+ const [appName, cmpName] = cmpData.descriptor.split(':');
383
+ if (appName !== myAppName) {
384
+ return;
385
+ }
386
+ let config;
387
+ const configArray = [];
388
+ if ('configAsJson' in cmpData && cmpData.configAsJson && cmpData.configAsJson[myAppNameDashed]) {
389
+ config = cmpData.configAsJson[myAppNameDashed][cmpName];
390
+ if (config) {
391
+ configArray.push(config);
392
+ delete cmpData.configAsJson;
393
+ }
394
+ }
395
+ const sanitizedAppName = sanitizeGraphqlName(appName);
396
+ if ('config' in cmpData && cmpData.config && cmpData.config[sanitizedAppName]) {
397
+ config = cmpData.config[sanitizedAppName][sanitizeGraphqlName(cmpName)];
398
+ if (config) {
399
+ configArray.push(config);
400
+ }
401
+ }
402
+ if (configArray.length) {
403
+ cmpData.config = Object.assign({}, ...configArray);
404
+ }
405
+ }
406
+ }
407
+ function getQueryAndVariables(type, path, selectedQuery, context, config) {
408
+ let query, getVariables;
409
+ if (typeof selectedQuery === 'string') {
410
+ query = selectedQuery;
411
+ }
412
+ else if (Array.isArray(selectedQuery)) {
413
+ query = selectedQuery[0];
414
+ getVariables = selectedQuery[1];
415
+ }
416
+ else if (typeof selectedQuery === 'object') {
417
+ query = selectedQuery.query;
418
+ getVariables = selectedQuery.variables;
419
+ }
420
+ if (getVariables && typeof getVariables !== 'function') {
421
+ throw Error(`getVariables for content type ${type} should be a function, not: ${typeof getVariables}`);
422
+ }
423
+ if (query && typeof query !== 'string') {
424
+ throw Error(`Query for content type ${type} should be a string, not: ${typeof query}`);
425
+ }
426
+ if (query) {
427
+ return {
428
+ query: query,
429
+ variables: getVariables ? getVariables(path, context, config) : { path },
430
+ };
431
+ }
432
+ }
433
+ function createPageData(contentType, components) {
434
+ let page;
435
+ if (components) {
436
+ page = buildPage(contentType, components);
437
+ }
438
+ return page;
439
+ }
440
+ function createMetaData(contentType, contentPath, requestType, renderMode, apiUrl, baseUrl, requestedComponentPath, pageCmp, components = []) {
441
+ var _a;
442
+ // .meta will be visible in final rendered inline props.
443
+ // Only adding some .meta attributes here on certain conditions
444
+ // (instead of always adding them and letting them be visible as false/undefined etc)
445
+ const meta = {
446
+ type: contentType,
447
+ path: contentPath,
448
+ requestType: requestType,
449
+ renderMode: renderMode,
450
+ canRender: false,
451
+ catchAll: false,
452
+ apiUrl,
453
+ baseUrl,
454
+ };
455
+ if (requestedComponentPath) {
456
+ meta.requestedComponent = components.find(cmp => cmp.path === requestedComponentPath);
457
+ }
458
+ const pageDesc = (_a = pageCmp === null || pageCmp === void 0 ? void 0 : pageCmp.page) === null || _a === void 0 ? void 0 : _a.descriptor;
459
+ const typeDef = ComponentRegistry.getContentType(contentType);
460
+ if ((typeDef === null || typeDef === void 0 ? void 0 : typeDef.view) && !typeDef.catchAll) {
461
+ meta.canRender = true;
462
+ }
463
+ else if (pageDesc) {
464
+ // always render a page if there is a descriptor (show missing in case it's not implemented)
465
+ meta.canRender = true;
466
+ meta.catchAll = false; // catchAll only refers to content type catch-all
467
+ }
468
+ else if (typeDef === null || typeDef === void 0 ? void 0 : typeDef.view) {
469
+ meta.canRender = true;
470
+ meta.catchAll = true;
471
+ }
472
+ return meta;
473
+ }
474
+ function errorResponse(code = '500', message = 'Unknown error', requestType, renderMode, apiUrl, baseUrl, contentPath) {
475
+ return {
476
+ error: {
477
+ code,
478
+ message,
479
+ },
480
+ page: null,
481
+ common: null,
482
+ data: null,
483
+ meta: {
484
+ type: '',
485
+ requestType: requestType,
486
+ renderMode: renderMode,
487
+ path: contentPath || '',
488
+ canRender: false,
489
+ catchAll: false,
490
+ apiUrl,
491
+ baseUrl,
492
+ },
493
+ };
494
+ }
495
+ // ///////////////////////////// ENTRY 1 - THE BUILDER:
496
+ /**
497
+ * Configures, builds and returns a general fetchContent function.
498
+ * @param adapterConstants Object containing attributes imported from enonic-connecion-config.js: constants and function concerned with connection to the XP backend. Easiest: caller imports enonic-connection-config and just passes that entire object here as adapterConstants.
499
+ * @param componentRegistry ComponentRegistry object from ComponentRegistry.ts, holding user type mappings that are set in typesRegistration.ts file
500
+ * @returns ContentFetcher
501
+ */
502
+ export const buildContentFetcher = (config) => {
503
+ const { APP_NAME, APP_NAME_DASHED, getXPRequestType, getRenderMode, getSingleComponentPath, componentRegistry, } = config;
504
+ return (contentPathOrArray, context) => __awaiter(void 0, void 0, void 0, function* () {
505
+ var _a, _b;
506
+ const xpBaseUrl = getXpBaseUrl(context);
507
+ const contentApiUrl = getContentApiUrl(context);
508
+ const requestType = getXPRequestType(context);
509
+ const renderMode = getRenderMode(context);
510
+ let contentPath;
511
+ try {
512
+ const siteRelativeContentPath = getCleanContentPathArrayOrThrow400(contentPathOrArray);
513
+ let requestedComponentPath;
514
+ if (requestType === XP_REQUEST_TYPE.COMPONENT) {
515
+ requestedComponentPath = getSingleComponentPath(context);
516
+ }
517
+ // ///////////// FIRST GUILLOTINE CALL FOR METADATA /////////////////
518
+ const metaResult = yield fetchMetaData(contentApiUrl, '${site}/' + siteRelativeContentPath);
519
+ // ///////////////////////////////////////////////////////////////////////
520
+ const { type, components, _path } = metaResult.meta || {};
521
+ contentPath = _path || '';
522
+ if (metaResult.error) {
523
+ return errorResponse(metaResult.error.code, metaResult.error.message, requestType, renderMode, contentApiUrl, xpBaseUrl, contentPath);
524
+ }
525
+ if (!metaResult.meta) {
526
+ return errorResponse('404', 'No meta data found for content, most likely content does not exist', requestType, renderMode, contentApiUrl, xpBaseUrl, contentPath);
527
+ }
528
+ else if (!type) {
529
+ return errorResponse('500', "Server responded with incomplete meta data: missing content 'type' attribute.", requestType, renderMode, contentApiUrl, xpBaseUrl, contentPath);
530
+ }
531
+ else if (renderMode === RENDER_MODE.NEXT && !IS_DEV_MODE &&
532
+ (type === FRAGMENT_CONTENTTYPE_NAME ||
533
+ type === PAGE_TEMPLATE_CONTENTTYPE_NAME ||
534
+ type === PAGE_TEMPLATE_FOLDER)) {
535
+ return errorResponse('404', `Content type [${type}] is not accessible in ${renderMode} mode`, requestType, renderMode, contentApiUrl, xpBaseUrl, contentPath);
536
+ }
537
+ // ////////////////////////////////////////////////// Content type established. Proceed to data call:
538
+ const allDescriptors = [];
539
+ // Add the content type query at all cases
540
+ const contentTypeDef = componentRegistry === null || componentRegistry === void 0 ? void 0 : componentRegistry.getContentType(type);
541
+ const pageCmp = (components || []).find(cmp => cmp.type === XP_COMPONENT_TYPE.PAGE);
542
+ if (pageCmp) {
543
+ processComponentConfig(APP_NAME, APP_NAME_DASHED, pageCmp);
544
+ }
545
+ const contentQueryAndVars = getQueryAndVariables(type, contentPath, contentTypeDef === null || contentTypeDef === void 0 ? void 0 : contentTypeDef.query, context, (_a = pageCmp === null || pageCmp === void 0 ? void 0 : pageCmp.page) === null || _a === void 0 ? void 0 : _a.config);
546
+ if (contentQueryAndVars) {
547
+ allDescriptors.push({
548
+ type: contentTypeDef,
549
+ queryAndVariables: contentQueryAndVars,
550
+ });
551
+ }
552
+ const commonQueryAndVars = getQueryAndVariables(type, contentPath, componentRegistry.getCommonQuery(), context, (_b = pageCmp === null || pageCmp === void 0 ? void 0 : pageCmp.page) === null || _b === void 0 ? void 0 : _b.config);
553
+ if (commonQueryAndVars) {
554
+ allDescriptors.push({
555
+ type: contentTypeDef,
556
+ queryAndVariables: commonQueryAndVars,
557
+ });
558
+ }
559
+ if ((components === null || components === void 0 ? void 0 : components.length) && componentRegistry) {
560
+ for (const cmp of (components || [])) {
561
+ processComponentConfig(APP_NAME, APP_NAME_DASHED, cmp);
562
+ }
563
+ // Collect component queries if defined
564
+ const componentDescriptors = collectComponentDescriptors(components, componentRegistry, requestedComponentPath, contentPath, context);
565
+ if (componentDescriptors.length) {
566
+ allDescriptors.push(...componentDescriptors);
567
+ }
568
+ }
569
+ const { query, variables } = combineMultipleQueries(allDescriptors);
570
+ if (!query.trim()) {
571
+ return errorResponse('400', `Missing or empty query override for content type ${type}`, requestType, renderMode, contentApiUrl, xpBaseUrl, contentPath);
572
+ }
573
+ // /////////////// SECOND GUILLOTINE CALL FOR DATA //////////////////////
574
+ const contentResults = yield fetchContentData(contentApiUrl, contentPath, query, variables);
575
+ // ///////////////////////////////////////////////////////////////////////////
576
+ // Apply processors to every component
577
+ const datas = yield applyProcessors(allDescriptors, contentResults, context);
578
+ // Unwind the data back to components
579
+ let contentData = null, common = null;
580
+ let startFrom = 0;
581
+ if (contentQueryAndVars) {
582
+ const item = datas[startFrom];
583
+ contentData = item.status === 'fulfilled' ? item.value : item.reason;
584
+ startFrom++;
585
+ }
586
+ if (commonQueryAndVars) {
587
+ const item = datas[startFrom];
588
+ common = item.status === 'fulfilled' ? item.value : item.reason;
589
+ startFrom++;
590
+ }
591
+ for (let i = startFrom; i < datas.length; i++) {
592
+ // component descriptors hold references to components
593
+ // that will later be used for creating page regions
594
+ const datum = datas[i];
595
+ if (datum.status === 'rejected') {
596
+ let reason = datum.reason;
597
+ if (reason instanceof Error) {
598
+ reason = reason.message;
599
+ }
600
+ else if (typeof reason !== 'string') {
601
+ reason = String(reason);
602
+ }
603
+ allDescriptors[i].component.error = reason;
604
+ }
605
+ else {
606
+ allDescriptors[i].component.data = datum.value;
607
+ }
608
+ }
609
+ const page = createPageData(type, components);
610
+ const meta = createMetaData(type, siteRelativeContentPath, requestType, renderMode, contentApiUrl, xpBaseUrl, requestedComponentPath, page, components);
611
+ return {
612
+ data: contentData,
613
+ common,
614
+ meta,
615
+ page: page || null,
616
+ };
617
+ // /////////////////////////////////////////////////////////// Catch
618
+ }
619
+ catch (e) {
620
+ console.error(e);
621
+ let error;
622
+ try {
623
+ error = JSON.parse(e.message);
624
+ }
625
+ catch (e2) {
626
+ error = {
627
+ code: 'Local',
628
+ message: e.message,
629
+ };
630
+ }
631
+ return errorResponse(error.code, error.message, requestType, renderMode, contentApiUrl, xpBaseUrl, contentPath);
632
+ }
633
+ });
634
+ };
635
+ // //////////////////////////// ENTRY 2: ready-to-use fetchContent function
636
+ /**
637
+ * Default fetchContent function, built with params from imports.
638
+ * It runs custom content-type-specific guillotine calls against an XP guillotine endpoint, returns content data, error and some meta data
639
+ * Sends one query to the guillotine API and asks for content type, then uses the type to select a second query and variables, which is sent to the API and fetches content data.
640
+ * @param contentPath string or string array: local (site-relative) path to a content available on the API (by XP _path - obtainable by running contentPath through getXpPath). Pre-split into string array, or already a slash-delimited string.
641
+ * @param context object from Next, contains .query info
642
+ * @returns FetchContentResult object: {data?: T, error?: {code, message}}
643
+ */
644
+ export const fetchContent = buildContentFetcher(Object.assign(Object.assign({}, adapterConstants), { componentRegistry: ComponentRegistry }));