@stackbit/cms-core 0.1.21 → 0.1.22-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/content-store-types.d.ts +34 -8
  2. package/dist/content-store-types.d.ts.map +1 -1
  3. package/dist/content-store-utils.d.ts +3 -1
  4. package/dist/content-store-utils.d.ts.map +1 -1
  5. package/dist/content-store-utils.js +8 -1
  6. package/dist/content-store-utils.js.map +1 -1
  7. package/dist/content-store.d.ts +4 -2
  8. package/dist/content-store.d.ts.map +1 -1
  9. package/dist/content-store.js +59 -37
  10. package/dist/content-store.js.map +1 -1
  11. package/dist/utils/create-update-csi-docs.d.ts +11 -10
  12. package/dist/utils/create-update-csi-docs.d.ts.map +1 -1
  13. package/dist/utils/create-update-csi-docs.js +92 -13
  14. package/dist/utils/create-update-csi-docs.js.map +1 -1
  15. package/dist/utils/csi-to-store-docs-converter.d.ts.map +1 -1
  16. package/dist/utils/csi-to-store-docs-converter.js +74 -5
  17. package/dist/utils/csi-to-store-docs-converter.js.map +1 -1
  18. package/dist/utils/duplicate-document.d.ts +3 -2
  19. package/dist/utils/duplicate-document.d.ts.map +1 -1
  20. package/dist/utils/duplicate-document.js +86 -6
  21. package/dist/utils/duplicate-document.js.map +1 -1
  22. package/dist/utils/model-utils.d.ts +4 -4
  23. package/dist/utils/model-utils.d.ts.map +1 -1
  24. package/dist/utils/model-utils.js +67 -2
  25. package/dist/utils/model-utils.js.map +1 -1
  26. package/dist/utils/store-to-api-docs-converter.d.ts.map +1 -1
  27. package/dist/utils/store-to-api-docs-converter.js +53 -8
  28. package/dist/utils/store-to-api-docs-converter.js.map +1 -1
  29. package/dist/utils/store-to-csi-docs-converter.js +30 -0
  30. package/dist/utils/store-to-csi-docs-converter.js.map +1 -1
  31. package/package.json +5 -5
  32. package/src/content-store-types.ts +41 -3
  33. package/src/content-store-utils.ts +9 -1
  34. package/src/content-store.ts +63 -38
  35. package/src/utils/create-update-csi-docs.ts +109 -18
  36. package/src/utils/csi-to-store-docs-converter.ts +96 -21
  37. package/src/utils/duplicate-document.ts +98 -15
  38. package/src/utils/model-utils.ts +95 -7
  39. package/src/utils/store-to-api-docs-converter.ts +50 -6
  40. package/src/utils/store-to-csi-docs-converter.ts +30 -0
  41. package/dist/utils/schema-utils.d.ts +0 -87
  42. package/dist/utils/schema-utils.d.ts.map +0 -1
  43. package/dist/utils/schema-utils.js +0 -195
  44. package/dist/utils/schema-utils.js.map +0 -1
  45. package/src/utils/schema-utils.js +0 -212
@@ -7,41 +7,41 @@ import { fieldPathToString, mapPromise } from '@stackbit/utils';
7
7
  import * as CSITypes from '@stackbit/types';
8
8
 
9
9
  import * as ContentStoreTypes from '../content-store-types';
10
+ import { getContentSourceId, getUserContextForSrcType, updateOperationValueFieldWithCrossReference } from '../content-store-utils';
10
11
 
11
12
  export type CreateDocumentCallback = ({
12
13
  updateOperationFields,
14
+ contentSourceData,
13
15
  modelName
14
16
  }: {
15
17
  updateOperationFields: Record<string, CSITypes.UpdateOperationField>;
18
+ contentSourceData: ContentStoreTypes.ContentSourceData;
16
19
  modelName: string;
17
20
  }) => Promise<CSITypes.Document>;
18
21
 
19
22
  export function getCreateDocumentThunk({
20
- csiModelMap,
21
23
  locale,
22
24
  defaultLocaleDocumentId,
23
- userContext,
24
- contentSourceInstance
25
+ user
25
26
  }: {
26
- csiModelMap: Record<string, CSIModel>;
27
27
  locale?: string;
28
28
  defaultLocaleDocumentId?: string;
29
- userContext: unknown;
30
- contentSourceInstance: CSITypes.ContentSourceInterface;
29
+ user?: ContentStoreTypes.User;
31
30
  }): CreateDocumentCallback {
32
- return async ({ updateOperationFields, modelName }) => {
31
+ return async ({ updateOperationFields, modelName, contentSourceData }) => {
33
32
  // When passing model and modelMap to contentSourceInstance, we have to pass
34
33
  // the original models (i.e., csiModel and csiModelMap) that we've received
35
34
  // from that contentSourceInstance. We can't pass internal models as they
36
35
  // might
37
- const csiModel = csiModelMap[modelName];
36
+ const csiModel = contentSourceData.csiModelMap[modelName];
38
37
  if (!csiModel) {
39
38
  throw new Error(`no model with name '${modelName}' was found`);
40
39
  }
41
- return await contentSourceInstance.createDocument({
40
+ const userContext = getUserContextForSrcType(contentSourceData.srcType, user);
41
+ return await contentSourceData.instance.createDocument({
42
42
  updateOperationFields: updateOperationFields,
43
43
  model: csiModel,
44
- modelMap: csiModelMap,
44
+ modelMap: contentSourceData.csiModelMap,
45
45
  locale,
46
46
  defaultLocaleDocumentId,
47
47
  userContext
@@ -90,18 +90,24 @@ export function getCreateDocumentThunk({
90
90
  export async function createDocumentRecursively({
91
91
  object,
92
92
  modelName,
93
- modelMap,
94
- csiModelMap,
93
+ contentSourceId,
94
+ contentSourceDataById,
95
95
  createDocument
96
96
  }: {
97
97
  object?: Record<string, any>;
98
98
  modelName: string;
99
- modelMap: Record<string, SDKModel>;
100
- csiModelMap: Record<string, CSIModel>;
99
+ contentSourceId: string;
100
+ contentSourceDataById: Record<string, ContentStoreTypes.ContentSourceData>;
101
101
  createDocument: CreateDocumentCallback;
102
102
  }): Promise<{ document: CSITypes.Document; newRefDocuments: CSITypes.Document[] }> {
103
+ const contentSourceData = contentSourceDataById[contentSourceId];
104
+ if (!contentSourceData) {
105
+ throw new Error(`no content source data for id '${contentSourceId}'`);
106
+ }
107
+ const modelMap = contentSourceData.modelMap;
108
+ const csiModelMap = contentSourceData.csiModelMap;
103
109
  const model = modelMap[modelName];
104
- const csiModel = csiModelMap[modelName];
110
+ const csiModel = contentSourceData.csiModelMap[modelName];
105
111
  if (!model || !csiModel) {
106
112
  throw new Error(`no model with name '${modelName}' was found`);
107
113
  }
@@ -121,11 +127,14 @@ export async function createDocumentRecursively({
121
127
  fieldPath: [modelName],
122
128
  modelMap,
123
129
  csiModelMap,
130
+ contentSourceId,
131
+ contentSourceDataById,
124
132
  createDocument
125
133
  });
126
134
 
127
135
  const document = await createDocument({
128
136
  updateOperationFields: nestedResult.fields,
137
+ contentSourceData: contentSourceData,
129
138
  modelName: modelName
130
139
  });
131
140
  return {
@@ -152,6 +161,8 @@ async function createObjectRecursively({
152
161
  fieldPath,
153
162
  modelMap,
154
163
  csiModelMap,
164
+ contentSourceId,
165
+ contentSourceDataById,
155
166
  createDocument
156
167
  }: {
157
168
  object?: Record<string, any>;
@@ -160,6 +171,8 @@ async function createObjectRecursively({
160
171
  fieldPath: (string | number)[];
161
172
  modelMap: Record<string, SDKModel>;
162
173
  csiModelMap: Record<string, CSIModel>;
174
+ contentSourceId: string;
175
+ contentSourceDataById: Record<string, ContentStoreTypes.ContentSourceData>;
163
176
  createDocument: CreateDocumentCallback;
164
177
  }): Promise<{
165
178
  fields: Record<string, CSITypes.UpdateOperationField>;
@@ -206,6 +219,8 @@ async function createObjectRecursively({
206
219
  fieldPath: fieldPath.concat(fieldName),
207
220
  modelMap,
208
221
  csiModelMap,
222
+ contentSourceId,
223
+ contentSourceDataById,
209
224
  createDocument
210
225
  });
211
226
  result.fields[fieldName] = fieldResult.field;
@@ -226,6 +241,8 @@ async function createUpdateOperationFieldRecursively({
226
241
  fieldPath,
227
242
  modelMap,
228
243
  csiModelMap,
244
+ contentSourceId,
245
+ contentSourceDataById,
229
246
  createDocument
230
247
  }: {
231
248
  value: any;
@@ -234,6 +251,8 @@ async function createUpdateOperationFieldRecursively({
234
251
  fieldPath: (string | number)[];
235
252
  modelMap: Record<string, SDKModel>;
236
253
  csiModelMap: Record<string, CSIModel>;
254
+ contentSourceId: string;
255
+ contentSourceDataById: Record<string, ContentStoreTypes.ContentSourceData>;
237
256
  createDocument: CreateDocumentCallback;
238
257
  }): Promise<{ field: CSITypes.UpdateOperationField; newRefDocuments: CSITypes.Document[] }> {
239
258
  if (csiModelField.type === 'object') {
@@ -247,6 +266,8 @@ async function createUpdateOperationFieldRecursively({
247
266
  fieldPath,
248
267
  modelMap,
249
268
  csiModelMap,
269
+ contentSourceId,
270
+ contentSourceDataById,
250
271
  createDocument
251
272
  });
252
273
  return {
@@ -284,6 +305,8 @@ async function createUpdateOperationFieldRecursively({
284
305
  fieldPath,
285
306
  modelMap,
286
307
  csiModelMap,
308
+ contentSourceId,
309
+ contentSourceDataById,
287
310
  createDocument
288
311
  });
289
312
  return {
@@ -340,11 +363,14 @@ async function createUpdateOperationFieldRecursively({
340
363
  modelName = modelNames[0];
341
364
  }
342
365
  }
366
+ if (!modelName) {
367
+ throw new Error('reference field type must have $$type or $$ref properties when creating new documents');
368
+ }
343
369
  const { document, newRefDocuments } = await createDocumentRecursively({
344
370
  object: rest,
345
371
  modelName,
346
- modelMap,
347
- csiModelMap,
372
+ contentSourceId,
373
+ contentSourceDataById,
348
374
  createDocument
349
375
  });
350
376
  return {
@@ -356,6 +382,39 @@ async function createUpdateOperationFieldRecursively({
356
382
  newRefDocuments: [document, ...newRefDocuments]
357
383
  };
358
384
  }
385
+ } else if (['string', 'text', 'json'].includes(csiModelField.type) && modelField.type === 'cross-reference') {
386
+ const fieldType = csiModelField.type as 'string' | 'text' | 'json';
387
+ let { $$ref: refId = null, $$type: modelName = null, $$refSrcType: refSrcType = null, $$refProjectId: refProjectId = null, ...rest } = value;
388
+ let refObject;
389
+ const newRefDocuments: CSITypes.Document[] = [];
390
+ if (refId && refSrcType && refProjectId) {
391
+ refObject = { refId, refSrcType, refProjectId };
392
+ } else {
393
+ if (!modelName || !refSrcType || !refProjectId) {
394
+ const models = modelField.models;
395
+ if (models && models.length === 1) {
396
+ modelName = models[0]!.modelName;
397
+ refSrcType = models[0]!.srcType;
398
+ refProjectId = models[0]!.srcProjectId;
399
+ }
400
+ }
401
+ if (!modelName || !refSrcType || !refProjectId) {
402
+ throw new Error('reference field type must have $$type or $$ref properties when creating new documents');
403
+ }
404
+ const { document, newRefDocuments } = await createDocumentRecursively({
405
+ object: rest,
406
+ modelName,
407
+ contentSourceId: getContentSourceId(refSrcType, refProjectId),
408
+ contentSourceDataById,
409
+ createDocument
410
+ });
411
+ newRefDocuments.push(document, ...newRefDocuments);
412
+ refObject = { refId: document.id, refSrcType, refProjectId };
413
+ }
414
+ return {
415
+ field: updateOperationValueFieldWithCrossReference(fieldType, refObject),
416
+ newRefDocuments
417
+ };
359
418
  } else if (csiModelField.type === 'list') {
360
419
  if (modelField.type !== 'list') {
361
420
  throw new Error(`field type mismatch between external and internal models at field path ${fieldPathToString(fieldPath)}`);
@@ -378,6 +437,8 @@ async function createUpdateOperationFieldRecursively({
378
437
  fieldPath: fieldPath.concat(index),
379
438
  modelMap,
380
439
  csiModelMap,
440
+ contentSourceId,
441
+ contentSourceDataById,
381
442
  createDocument
382
443
  });
383
444
  if (result.field.type === 'list') {
@@ -425,6 +486,8 @@ export async function convertOperationField({
425
486
  csiModelField,
426
487
  modelMap,
427
488
  csiModelMap,
489
+ contentSourceId,
490
+ contentSourceDataById,
428
491
  createDocument
429
492
  }: {
430
493
  operationField: ContentStoreTypes.UpdateOperationField;
@@ -433,6 +496,8 @@ export async function convertOperationField({
433
496
  csiModelField: FieldSpecificProps;
434
497
  modelMap: Record<string, SDKModel>;
435
498
  csiModelMap: Record<string, SDKModel>;
499
+ contentSourceId: string;
500
+ contentSourceDataById: Record<string, ContentStoreTypes.ContentSourceData>;
436
501
  createDocument: CreateDocumentCallback;
437
502
  }): Promise<CSITypes.UpdateOperationField> {
438
503
  switch (operationField.type) {
@@ -447,6 +512,8 @@ export async function convertOperationField({
447
512
  fieldPath,
448
513
  modelMap,
449
514
  csiModelMap,
515
+ contentSourceId,
516
+ contentSourceDataById,
450
517
  createDocument
451
518
  });
452
519
  return {
@@ -467,6 +534,8 @@ export async function convertOperationField({
467
534
  fieldPath,
468
535
  modelMap,
469
536
  csiModelMap,
537
+ contentSourceId,
538
+ contentSourceDataById,
470
539
  createDocument
471
540
  });
472
541
  return {
@@ -476,7 +545,23 @@ export async function convertOperationField({
476
545
  };
477
546
  }
478
547
  case 'reference':
548
+ // ContentStore and CSI 'reference' operation field have the same format
479
549
  return operationField;
550
+ case 'cross-reference':
551
+ if (csiModelField.type !== 'string' && csiModelField.type !== 'text' && csiModelField.type !== 'json') {
552
+ throw new Error(
553
+ `update operation with with 'cross-reference' field can be performed on 'string', 'text' and 'json' content-source field types only, got '${csiModelField.type}'`
554
+ );
555
+ }
556
+ const refObject = {
557
+ refId: operationField.refId,
558
+ refSrcType: operationField.refSrcType,
559
+ refProjectId: operationField.refProjectId
560
+ };
561
+ return {
562
+ type: csiModelField.type,
563
+ value: csiModelField.type === 'json' ? refObject : JSON.stringify(refObject)
564
+ };
480
565
  case 'list': {
481
566
  if (modelField.type !== 'list' || csiModelField.type !== 'list') {
482
567
  throw new Error(`the operation field type '${operationField.type}' does not match the model field type '${modelField.type}'`);
@@ -491,6 +576,8 @@ export async function convertOperationField({
491
576
  fieldPath: fieldPath.concat(index),
492
577
  modelMap,
493
578
  csiModelMap,
579
+ contentSourceId,
580
+ contentSourceDataById,
494
581
  createDocument
495
582
  });
496
583
  if (result.field.type === 'list') {
@@ -506,7 +593,9 @@ export async function convertOperationField({
506
593
  }
507
594
  case 'enum':
508
595
  if (csiModelField.type !== 'enum' && csiModelField.type !== 'string') {
509
- throw new Error(`the operation field type 'enum' can be performed on 'string' and 'enum' content-source field types '${csiModelField.type}'`);
596
+ throw new Error(
597
+ `update operation with 'enum' field can be performed on 'string' and 'enum' content-source field types only, got '${csiModelField.type}'`
598
+ );
510
599
  }
511
600
  // When inserting new enum value into a list, the client does not
512
601
  // send value. Set first option as the value.
@@ -543,6 +632,8 @@ export async function convertOperationField({
543
632
  fieldPath,
544
633
  modelMap,
545
634
  csiModelMap,
635
+ contentSourceId,
636
+ contentSourceDataById,
546
637
  createDocument
547
638
  });
548
639
  return result.field;
@@ -5,6 +5,7 @@ import * as CSITypes from '@stackbit/types';
5
5
 
6
6
  import * as ContentStoreTypes from '../content-store-types';
7
7
  import { IMAGE_MODEL } from '../common/common-schema';
8
+ import { omitByNil } from '@stackbit/utils';
8
9
 
9
10
  export function mapCSIAssetsToStoreAssets({
10
11
  csiAssets,
@@ -34,7 +35,7 @@ function sourceAssetToStoreAsset({
34
35
  defaultLocaleCode?: string;
35
36
  extra: { srcType: string; srcProjectId: string; srcProjectUrl: string; srcEnvironment: string };
36
37
  }): ContentStoreTypes.Asset {
37
- return {
38
+ return omitByNil({
38
39
  type: 'asset',
39
40
  ...extra,
40
41
  srcObjectId: csiAsset.id,
@@ -59,7 +60,7 @@ function sourceAssetToStoreAsset({
59
60
  ...csiAsset.fields.file
60
61
  }
61
62
  }
62
- };
63
+ });
63
64
  }
64
65
 
65
66
  /**
@@ -118,7 +119,7 @@ function mapCSIDocumentToStoreDocument({
118
119
  defaultLocaleCode?: string;
119
120
  extra: { srcType: string; srcProjectId: string; srcProjectUrl: string; srcEnvironment: string };
120
121
  }): ContentStoreTypes.Document {
121
- return {
122
+ return omitByNil({
122
123
  type: 'document',
123
124
  ...extra,
124
125
  srcObjectId: csiDocument.id,
@@ -141,7 +142,7 @@ function mapCSIDocumentToStoreDocument({
141
142
  defaultLocaleCode
142
143
  }
143
144
  })
144
- };
145
+ });
145
146
  }
146
147
 
147
148
  type MapContext = {
@@ -184,7 +185,7 @@ function mapCSIFieldToStoreField({
184
185
  context: MapContext;
185
186
  }): ContentStoreTypes.DocumentField {
186
187
  if (!csiDocumentField) {
187
- const isUnset = ['object', 'model', 'reference', 'richText', 'markdown', 'image', 'file', 'json'].includes(modelField.type);
188
+ const isUnset = ['object', 'model', 'reference', 'cross-reference', 'richText', 'markdown', 'image', 'file', 'json'].includes(modelField.type);
188
189
  return {
189
190
  type: modelField.type,
190
191
  ...(localized
@@ -223,6 +224,8 @@ function mapCSIFieldToStoreField({
223
224
  case 'file':
224
225
  case 'reference':
225
226
  return csiDocumentField as ContentStoreTypes.DocumentField;
227
+ case 'cross-reference':
228
+ return mapReferenceField(csiDocumentField)
226
229
  case 'object':
227
230
  return mapObjectField(csiDocumentField as CSITypes.DocumentObjectField, modelField, context);
228
231
  case 'model':
@@ -241,6 +244,76 @@ function mapCSIFieldToStoreField({
241
244
  }
242
245
  }
243
246
 
247
+ function mapReferenceField(csiDocumentField: CSITypes.DocumentField): ContentStoreTypes.DocumentCrossReferenceField {
248
+ const unlocalizedUnset = {
249
+ type: 'cross-reference',
250
+ refType: 'document',
251
+ isUnset: true
252
+ } as const;
253
+ if (csiDocumentField.type !== 'string' && csiDocumentField.type !== 'text' && csiDocumentField.type !== 'json') {
254
+ if (isLocalizedField(csiDocumentField)) {
255
+ return {
256
+ type: 'cross-reference',
257
+ refType: 'document',
258
+ localized: true,
259
+ locales: {}
260
+ };
261
+ }
262
+ return unlocalizedUnset;
263
+ }
264
+ const parseRefObject = (value: any): { refId: string; refSrcType: string; refProjectId: string } | null => {
265
+ if (typeof value === 'string') {
266
+ try {
267
+ value = JSON.parse(value);
268
+ } catch (error) {
269
+ return null;
270
+ }
271
+ }
272
+ if (_.isPlainObject(value) && 'refId' in value && 'refSrcType' in value && 'refProjectId' in value) {
273
+ return {
274
+ refId: value.refId,
275
+ refSrcType: value.refSrcType,
276
+ refProjectId: value.refProjectId
277
+ };
278
+ }
279
+ return null;
280
+ };
281
+ if (isLocalizedField(csiDocumentField)) {
282
+ csiDocumentField.locales;
283
+ return {
284
+ type: 'cross-reference',
285
+ refType: 'document',
286
+ localized: true,
287
+ locales: _.reduce(
288
+ csiDocumentField.locales,
289
+ (accum: Record<string, { locale: string; refId: string; refSrcType: string; refProjectId: string }>, locale, localeKey) => {
290
+ const refObject = parseRefObject(locale.value);
291
+ if (refObject) {
292
+ accum[localeKey] = {
293
+ locale: locale.locale,
294
+ ...refObject
295
+ };
296
+ }
297
+ return accum;
298
+ },
299
+ {}
300
+ )
301
+ };
302
+ }
303
+ if (!('value' in csiDocumentField)) {
304
+ return unlocalizedUnset;
305
+ }
306
+ const refObject = parseRefObject(csiDocumentField.value);
307
+ if (!refObject) {
308
+ return unlocalizedUnset;
309
+ }
310
+ return {
311
+ type: 'cross-reference',
312
+ refType: 'document',
313
+ ...refObject
314
+ };
315
+ }
316
+
244
317
  function mapObjectField(
245
318
  csiDocumentField: CSITypes.DocumentObjectField,
246
319
  modelField: FieldObjectProps,
@@ -313,14 +386,15 @@ function mapListField(csiDocumentField: CSITypes.DocumentListField, modelField:
313
386
  if (!isLocalizedField(csiDocumentField)) {
314
387
  return {
315
388
  type: csiDocumentField.type,
316
- items: csiDocumentField.items.map((item) =>
317
- mapCSIFieldToStoreField({
318
- csiDocumentField: item,
319
- modelField: modelField.items ?? { type: 'string' },
320
- // list items can not be localized, only the list itself can be localized
321
- localized: false,
322
- context
323
- }) as ContentStoreTypes.DocumentListFieldItems
389
+ items: csiDocumentField.items.map(
390
+ (item) =>
391
+ mapCSIFieldToStoreField({
392
+ csiDocumentField: item,
393
+ modelField: modelField.items ?? { type: 'string' },
394
+ // list items can not be localized, only the list itself can be localized
395
+ localized: false,
396
+ context
397
+ }) as ContentStoreTypes.DocumentListFieldItems
324
398
  )
325
399
  };
326
400
  }
@@ -330,14 +404,15 @@ function mapListField(csiDocumentField: CSITypes.DocumentListField, modelField:
330
404
  locales: _.mapValues(csiDocumentField.locales, (locale) => {
331
405
  return {
332
406
  locale: locale.locale,
333
- items: (locale.items ?? []).map((item) =>
334
- mapCSIFieldToStoreField({
335
- csiDocumentField: item,
336
- modelField: modelField.items ?? { type: 'string' },
337
- // list items can not be localized, only the list itself can be localized
338
- localized: false,
339
- context
340
- }) as ContentStoreTypes.DocumentListFieldItems
407
+ items: (locale.items ?? []).map(
408
+ (item) =>
409
+ mapCSIFieldToStoreField({
410
+ csiDocumentField: item,
411
+ modelField: modelField.items ?? { type: 'string' },
412
+ // list items can not be localized, only the list itself can be localized
413
+ localized: false,
414
+ context
415
+ }) as ContentStoreTypes.DocumentListFieldItems
341
416
  )
342
417
  };
343
418
  })
@@ -1,14 +1,15 @@
1
1
  import _ from 'lodash';
2
2
 
3
3
  import * as ContentStoreTypes from '../content-store-types';
4
- import { getDocumentFieldForLocale } from '../content-store-utils';
4
+ import { getContentSourceId, getDocumentFieldForLocale } from '../content-store-utils';
5
5
  import { IMAGE_MODEL } from '../common/common-schema';
6
6
 
7
7
  export function mergeObjectWithDocument({
8
8
  object,
9
9
  document,
10
10
  locale,
11
- documentMap,
11
+ contentSourceId,
12
+ contentSourceDataById,
12
13
  seenReferences = [],
13
14
  referenceBehavior,
14
15
  duplicatableModels,
@@ -17,7 +18,8 @@ export function mergeObjectWithDocument({
17
18
  object: Record<string, unknown> | undefined;
18
19
  document: ContentStoreTypes.Document;
19
20
  locale?: string;
20
- documentMap: Record<string, ContentStoreTypes.Document>;
21
+ contentSourceId: string;
22
+ contentSourceDataById: Record<string, ContentStoreTypes.ContentSourceData>;
21
23
  seenReferences?: string[];
22
24
  referenceBehavior?: 'copyReference' | 'duplicateContents';
23
25
  duplicatableModels?: string[];
@@ -27,7 +29,8 @@ export function mergeObjectWithDocument({
27
29
  object,
28
30
  documentFields: document.fields,
29
31
  locale,
30
- documentMap,
32
+ contentSourceId,
33
+ contentSourceDataById,
31
34
  seenReferences: seenReferences.concat(document.srcObjectId),
32
35
  referenceBehavior,
33
36
  duplicatableModels,
@@ -37,7 +40,8 @@ export function mergeObjectWithDocument({
37
40
 
38
41
  type Context = {
39
42
  locale?: string;
40
- documentMap: Record<string, ContentStoreTypes.Document>;
43
+ contentSourceId: string;
44
+ contentSourceDataById: Record<string, ContentStoreTypes.ContentSourceData>;
41
45
  seenReferences: string[];
42
46
  referenceBehavior?: 'copyReference' | 'duplicateContents';
43
47
  duplicatableModels?: string[];
@@ -156,11 +160,16 @@ function mergeObjectWithDocumentField({
156
160
  break;
157
161
  }
158
162
  if (localizedField.refType === 'asset') {
163
+ // assets always duplicated by reference
159
164
  return {
160
165
  $$ref: localizedField.refId
161
166
  };
162
167
  } else {
163
- const document = context.documentMap[localizedField.refId];
168
+ const contentSourceData = context.contentSourceDataById[context.contentSourceId];
169
+ if (!contentSourceData) {
170
+ throw new Error(`no content source data for id '${context.contentSourceId}'`);
171
+ }
172
+ const document = contentSourceData.documentMap[localizedField.refId];
164
173
  if (!document) {
165
174
  break;
166
175
  }
@@ -186,7 +195,8 @@ function mergeObjectWithDocumentField({
186
195
  object: value,
187
196
  document,
188
197
  locale: context.locale,
189
- documentMap: context.documentMap,
198
+ contentSourceId: context.contentSourceId,
199
+ contentSourceDataById: context.contentSourceDataById,
190
200
  seenReferences: context.seenReferences.concat(document.srcObjectId),
191
201
  referenceBehavior: context.referenceBehavior,
192
202
  duplicatableModels: context.duplicatableModels,
@@ -202,6 +212,77 @@ function mergeObjectWithDocumentField({
202
212
  }
203
213
  break;
204
214
  }
215
+ case 'cross-reference':
216
+ const localizedField = getDocumentFieldForLocale(documentField, locale);
217
+ if (localizedField && !localizedField.isUnset && isPlainObjectOrUndefined(value)) {
218
+ if (value && value.$$ref) {
219
+ // if the override object has $$ref, use it
220
+ break;
221
+ }
222
+ if (localizedField.refType === 'asset') {
223
+ // assets always duplicated by reference
224
+ return {
225
+ $$ref: localizedField.refId,
226
+ $$refSrcType: localizedField.refSrcType,
227
+ $$refProjectId: localizedField.refProjectId
228
+ };
229
+ } else {
230
+ const contentSourceId = getContentSourceId(localizedField.refSrcType, localizedField.refProjectId);
231
+ const contentSourceData = context.contentSourceDataById[contentSourceId];
232
+ if (!contentSourceData) {
233
+ throw new Error(`no content source data for id '${context.contentSourceId}'`);
234
+ }
235
+ const document = contentSourceData.documentMap[localizedField.refId];
236
+ if (!document) {
237
+ break;
238
+ }
239
+ const shouldDuplicateDocument = shouldDuplicate({
240
+ referenceField: localizedField,
241
+ modelName: document.srcModelName,
242
+ seenReferences: context.seenReferences,
243
+ referenceBehavior: context.referenceBehavior,
244
+ duplicatableModels: context.duplicatableModels,
245
+ nonDuplicatableModels: context.nonDuplicatableModels
246
+ });
247
+ if (shouldDuplicateDocument || (value && value.$$type)) {
248
+ if (
249
+ value &&
250
+ (value.$$type !== document.srcModelName ||
251
+ value.$$refSrcType !== document.srcType ||
252
+ value.$$refProjectId !== document.srcProjectId)
253
+ ) {
254
+ // if the override object has $$type different from
255
+ // the type of the document that is currently
256
+ // referenced in the field, then create and link a
257
+ // new document according to the provided object
258
+ break;
259
+ }
260
+ return {
261
+ $$type: document.srcModelName,
262
+ $$refSrcType: document.srcType,
263
+ $$refProjectId: document.srcProjectId,
264
+ ...mergeObjectWithDocument({
265
+ object: value,
266
+ document,
267
+ locale: context.locale,
268
+ contentSourceId: context.contentSourceId,
269
+ contentSourceDataById: context.contentSourceDataById,
270
+ seenReferences: context.seenReferences.concat(document.srcObjectId),
271
+ referenceBehavior: context.referenceBehavior,
272
+ duplicatableModels: context.duplicatableModels,
273
+ nonDuplicatableModels: context.nonDuplicatableModels
274
+ })
275
+ };
276
+ } else {
277
+ return {
278
+ $$ref: localizedField.refId,
279
+ $$refSrcType: localizedField.refSrcType,
280
+ $$refProjectId: localizedField.refProjectId
281
+ };
282
+ }
283
+ }
284
+ }
285
+ break;
205
286
  case 'list': {
206
287
  const localizedField = getDocumentFieldForLocale(documentField, locale);
207
288
  if (value) {
@@ -210,13 +291,15 @@ function mergeObjectWithDocumentField({
210
291
  break;
211
292
  }
212
293
  if (localizedField) {
213
- return (localizedField.items ?? []).map((field) =>
214
- mergeObjectWithDocumentField({
215
- value: undefined,
216
- documentField: field,
217
- ...context
218
- })
219
- ).filter((value) => typeof value !== 'undefined') // if locale passed, it may return undefined for items localized to a different locale;
294
+ return (localizedField.items ?? [])
295
+ .map((field) =>
296
+ mergeObjectWithDocumentField({
297
+ value: undefined,
298
+ documentField: field,
299
+ ...context
300
+ })
301
+ )
302
+ .filter((value) => typeof value !== 'undefined'); // if locale passed, it may return undefined for items localized to a different locale;
220
303
  }
221
304
  break;
222
305
  }
@@ -242,7 +325,7 @@ function shouldDuplicate({
242
325
  nonDuplicatableModels,
243
326
  duplicatableModels
244
327
  }: {
245
- referenceField: ContentStoreTypes.DocumentReferenceFieldNonLocalized & { isUnset?: false };
328
+ referenceField: { refId: string; isUnset?: false };
246
329
  modelName: string;
247
330
  seenReferences: string[];
248
331
  referenceBehavior?: 'copyReference' | 'duplicateContents';