@stackbit/cms-core 0.1.20 → 0.1.21-cross-references.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 (42) 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 +57 -36
  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.js +69 -1
  16. package/dist/utils/csi-to-store-docs-converter.js.map +1 -1
  17. package/dist/utils/duplicate-document.js +3 -0
  18. package/dist/utils/duplicate-document.js.map +1 -1
  19. package/dist/utils/model-utils.d.ts +4 -4
  20. package/dist/utils/model-utils.d.ts.map +1 -1
  21. package/dist/utils/model-utils.js +67 -2
  22. package/dist/utils/model-utils.js.map +1 -1
  23. package/dist/utils/store-to-api-docs-converter.d.ts.map +1 -1
  24. package/dist/utils/store-to-api-docs-converter.js +38 -1
  25. package/dist/utils/store-to-api-docs-converter.js.map +1 -1
  26. package/dist/utils/store-to-csi-docs-converter.js +30 -0
  27. package/dist/utils/store-to-csi-docs-converter.js.map +1 -1
  28. package/package.json +4 -4
  29. package/src/content-store-types.ts +41 -3
  30. package/src/content-store-utils.ts +9 -1
  31. package/src/content-store.ts +61 -37
  32. package/src/utils/create-update-csi-docs.ts +109 -18
  33. package/src/utils/csi-to-store-docs-converter.ts +91 -17
  34. package/src/utils/duplicate-document.ts +3 -0
  35. package/src/utils/model-utils.ts +95 -7
  36. package/src/utils/store-to-api-docs-converter.ts +38 -1
  37. package/src/utils/store-to-csi-docs-converter.ts +30 -0
  38. package/dist/utils/schema-utils.d.ts +0 -87
  39. package/dist/utils/schema-utils.d.ts.map +0 -1
  40. package/dist/utils/schema-utils.js +0 -195
  41. package/dist/utils/schema-utils.js.map +0 -1
  42. package/src/utils/schema-utils.js +0 -212
@@ -32,7 +32,8 @@ import {
32
32
  getModelFieldForFieldAtPath,
33
33
  getUserContextForSrcType,
34
34
  groupDocumentsByContentSource,
35
- groupModelsByContentSource
35
+ groupModelsByContentSource,
36
+ updateOperationValueFieldWithCrossReference
36
37
  } from './content-store-utils';
37
38
  import {
38
39
  getSiteMapEntriesFromStackbitConfig,
@@ -1174,6 +1175,8 @@ export class ContentStore {
1174
1175
  srcDocumentId,
1175
1176
  fieldPath,
1176
1177
  modelName,
1178
+ refSrcType,
1179
+ refProjectId,
1177
1180
  object,
1178
1181
  index,
1179
1182
  locale,
@@ -1184,12 +1187,14 @@ export class ContentStore {
1184
1187
  srcDocumentId: string;
1185
1188
  fieldPath: (string | number)[];
1186
1189
  modelName?: string;
1190
+ refSrcType?: string;
1191
+ refProjectId?: string;
1187
1192
  object?: Record<string, any>;
1188
1193
  index?: number;
1189
1194
  locale?: string;
1190
1195
  user?: ContentStoreTypes.User;
1191
- }): Promise<{ srcDocumentId: string, createdDocumentId: string }> {
1192
- this.logger.debug('createAndLinkDocument', { srcType, srcProjectId, srcDocumentId, fieldPath, modelName, index, locale });
1196
+ }): Promise<{ srcDocumentId: string; createdDocumentId: string }> {
1197
+ this.logger.debug('createAndLinkDocument', { srcType, srcProjectId, srcDocumentId, fieldPath, modelName, refSrcType, refProjectId, index, locale });
1193
1198
 
1194
1199
  const contentSourceId = getContentSourceId(srcType, srcProjectId);
1195
1200
  const contentSourceData = this.getContentSourceDataByIdOrThrow(contentSourceId);
@@ -1203,36 +1208,51 @@ export class ContentStore {
1203
1208
 
1204
1209
  // get the document model
1205
1210
  const documentModelName = document.srcModelName;
1211
+ const modelMap = contentSourceData.modelMap;
1212
+ const model = modelMap[documentModelName];
1206
1213
  const csiModelMap = contentSourceData.csiModelMap;
1207
1214
  const csiModel = csiModelMap[documentModelName];
1208
- if (!csiModel) {
1215
+ if (!model || !csiModel) {
1209
1216
  throw new Error(`error updating document, could not find document model: '${documentModelName}'`);
1210
1217
  }
1211
1218
 
1212
1219
  // get the 'reference' model field in the updated document that will be used to link the new document
1213
1220
  locale = locale ?? contentSourceData.defaultLocaleCode;
1221
+ const modelField = getModelFieldForFieldAtPath(document, model, fieldPath, modelMap, locale);
1214
1222
  const csiModelField = getModelFieldForFieldAtPath(document, csiModel, fieldPath, csiModelMap, locale);
1215
- if (!csiModelField) {
1223
+ if (!modelField || !csiModelField) {
1216
1224
  throw Error(`the "fieldPath" points to non existing model field: ${fieldPath.join('.')}`);
1217
1225
  }
1218
- const fieldProps = csiModelField.type === 'list' ? csiModelField.items! : csiModelField;
1219
- if (fieldProps.type !== 'reference') {
1226
+ const fieldProps = modelField.type === 'list' ? modelField.items! : modelField;
1227
+ const csiFieldProps = csiModelField.type === 'list' ? csiModelField.items! : csiModelField;
1228
+ if (fieldProps.type !== 'reference' && fieldProps.type !== 'cross-reference') {
1220
1229
  throw Error(`error in "createAndLinkDocument", this operation can only be used on reference field: ${fieldPath.join('.')}`);
1221
1230
  }
1222
1231
 
1223
1232
  // get the model name for the new document
1224
1233
  if (!modelName && fieldProps.models.length === 1) {
1225
- modelName = fieldProps.models[0];
1234
+ if (fieldProps.type === 'reference') {
1235
+ modelName = fieldProps.models[0];
1236
+ } else if (fieldProps.type === 'cross-reference') {
1237
+ modelName = fieldProps.models[0]!.modelName;
1238
+ }
1226
1239
  }
1227
1240
  if (!modelName) {
1228
1241
  throw Error(`error in "createAndLinkDocument", missing "modelName": ${fieldPath.join('.')}`);
1229
1242
  }
1230
1243
 
1244
+ if (fieldProps.type === 'reference') {
1245
+ refSrcType = srcType;
1246
+ refProjectId = srcProjectId;
1247
+ } else if (!refSrcType || !refProjectId) {
1248
+ throw Error(`the "refSrcType" and "refProjectId" must be specified when linking a cross-reference field: ${fieldPath.join('.')}`);
1249
+ }
1250
+
1231
1251
  // create the new document
1232
1252
  const result = await this.createDocument({
1233
1253
  object: object,
1234
- srcProjectId: srcProjectId,
1235
- srcType: srcType,
1254
+ srcProjectId: refProjectId,
1255
+ srcType: refSrcType,
1236
1256
  modelName: modelName,
1237
1257
  locale: locale,
1238
1258
  user: user
@@ -1240,17 +1260,29 @@ export class ContentStore {
1240
1260
 
1241
1261
  // update the document by linking the field to the created document
1242
1262
  const userContext = getUserContextForSrcType(srcType, user);
1243
- const field = {
1244
- type: 'reference',
1245
- refType: 'document',
1246
- refId: result.srcDocumentId
1247
- } as const;
1263
+ let field: CSITypes.UpdateOperationField;
1264
+ if (fieldProps.type === 'reference') {
1265
+ field = {
1266
+ type: 'reference',
1267
+ refType: 'document',
1268
+ refId: result.srcDocumentId
1269
+ } as CSITypes.UpdateOperationReferenceField;
1270
+ } else {
1271
+ if (!['string', 'text', 'json'].includes(csiFieldProps.type)) {
1272
+ throw new Error(`The 'cross-reference' field can be only applied on string, text and json fields: ${fieldPath.join('.')}`);
1273
+ }
1274
+ field = updateOperationValueFieldWithCrossReference(csiFieldProps.type as 'string' | 'text' | 'json', {
1275
+ refId: result.srcDocumentId,
1276
+ refSrcType: refSrcType,
1277
+ refProjectId: refProjectId
1278
+ });
1279
+ }
1248
1280
  const updatedDocument = await contentSourceData.instance.updateDocument({
1249
1281
  document: csiDocument,
1250
1282
  modelMap: csiModelMap,
1251
1283
  userContext: userContext,
1252
1284
  operations: [
1253
- csiModelField.type === 'list'
1285
+ modelField.type === 'list'
1254
1286
  ? {
1255
1287
  opType: 'insert',
1256
1288
  fieldPath: fieldPath,
@@ -1426,7 +1458,7 @@ export class ContentStore {
1426
1458
  object,
1427
1459
  locale,
1428
1460
  defaultLocaleDocumentId,
1429
- user,
1461
+ user
1430
1462
  }: {
1431
1463
  srcType: string;
1432
1464
  srcProjectId: string;
@@ -1440,22 +1472,17 @@ export class ContentStore {
1440
1472
 
1441
1473
  const contentSourceId = getContentSourceId(srcType, srcProjectId);
1442
1474
  const contentSourceData = this.getContentSourceDataByIdOrThrow(contentSourceId);
1443
- const modelMap = contentSourceData.modelMap;
1444
- const csiModelMap = contentSourceData.csiModelMap;
1445
- const userContext = getUserContextForSrcType(srcType, user);
1446
1475
  const resolvedLocale = locale ?? contentSourceData.defaultLocaleCode;
1447
1476
 
1448
1477
  const result = await createDocumentRecursively({
1449
1478
  object,
1450
1479
  modelName,
1451
- modelMap,
1452
- csiModelMap,
1480
+ contentSourceId,
1481
+ contentSourceDataById: this.contentSourceDataById,
1453
1482
  createDocument: getCreateDocumentThunk({
1454
1483
  locale: resolvedLocale,
1455
- csiModelMap,
1456
- userContext,
1457
1484
  defaultLocaleDocumentId,
1458
- contentSourceInstance: contentSourceData.instance
1485
+ user
1459
1486
  })
1460
1487
  });
1461
1488
 
@@ -1518,11 +1545,11 @@ export class ContentStore {
1518
1545
  csiModelField,
1519
1546
  modelMap,
1520
1547
  csiModelMap,
1548
+ contentSourceId,
1549
+ contentSourceDataById: this.contentSourceDataById,
1521
1550
  createDocument: getCreateDocumentThunk({
1522
1551
  locale: updateOperation.locale,
1523
- csiModelMap,
1524
- userContext,
1525
- contentSourceInstance: contentSourceData.instance
1552
+ user
1526
1553
  })
1527
1554
  });
1528
1555
  return {
@@ -1546,11 +1573,11 @@ export class ContentStore {
1546
1573
  csiModelField: csiModelField.items,
1547
1574
  modelMap,
1548
1575
  csiModelMap,
1576
+ contentSourceId,
1577
+ contentSourceDataById: this.contentSourceDataById,
1549
1578
  createDocument: getCreateDocumentThunk({
1550
1579
  locale: updateOperation.locale,
1551
- csiModelMap,
1552
- userContext,
1553
- contentSourceInstance: contentSourceData.instance
1580
+ user
1554
1581
  })
1555
1582
  });
1556
1583
  return {
@@ -1617,7 +1644,6 @@ export class ContentStore {
1617
1644
  throw new Error(`no model with name '${document.srcModelName}' was found`);
1618
1645
  }
1619
1646
 
1620
- const userContext = getUserContextForSrcType(srcType, user);
1621
1647
  const resolvedLocale = locale ?? contentSourceData.defaultLocaleCode;
1622
1648
 
1623
1649
  const extendedObject = mergeObjectWithDocument({
@@ -1633,13 +1659,11 @@ export class ContentStore {
1633
1659
  const result = await createDocumentRecursively({
1634
1660
  object: extendedObject,
1635
1661
  modelName: model.name,
1636
- modelMap,
1637
- csiModelMap,
1662
+ contentSourceId,
1663
+ contentSourceDataById: this.contentSourceDataById,
1638
1664
  createDocument: getCreateDocumentThunk({
1639
1665
  locale: resolvedLocale,
1640
- csiModelMap,
1641
- userContext,
1642
- contentSourceInstance: contentSourceData.instance
1666
+ user
1643
1667
  })
1644
1668
  });
1645
1669
 
@@ -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;
@@ -184,7 +184,7 @@ function mapCSIFieldToStoreField({
184
184
  context: MapContext;
185
185
  }): ContentStoreTypes.DocumentField {
186
186
  if (!csiDocumentField) {
187
- const isUnset = ['object', 'model', 'reference', 'richText', 'markdown', 'image', 'file', 'json'].includes(modelField.type);
187
+ const isUnset = ['object', 'model', 'reference', 'cross-reference', 'richText', 'markdown', 'image', 'file', 'json'].includes(modelField.type);
188
188
  return {
189
189
  type: modelField.type,
190
190
  ...(localized
@@ -223,6 +223,8 @@ function mapCSIFieldToStoreField({
223
223
  case 'file':
224
224
  case 'reference':
225
225
  return csiDocumentField as ContentStoreTypes.DocumentField;
226
+ case 'cross-reference':
227
+ return mapReferenceField(csiDocumentField)
226
228
  case 'object':
227
229
  return mapObjectField(csiDocumentField as CSITypes.DocumentObjectField, modelField, context);
228
230
  case 'model':
@@ -241,6 +243,76 @@ function mapCSIFieldToStoreField({
241
243
  }
242
244
  }
243
245
 
246
+ function mapReferenceField(csiDocumentField: CSITypes.DocumentField): ContentStoreTypes.DocumentCrossReferenceField {
247
+ const unlocalizedUnset = {
248
+ type: 'cross-reference',
249
+ refType: 'document',
250
+ isUnset: true
251
+ } as const;
252
+ if (csiDocumentField.type !== 'string' && csiDocumentField.type !== 'text' && csiDocumentField.type !== 'json') {
253
+ if (isLocalizedField(csiDocumentField)) {
254
+ return {
255
+ type: 'cross-reference',
256
+ refType: 'document',
257
+ localized: true,
258
+ locales: {}
259
+ };
260
+ }
261
+ return unlocalizedUnset;
262
+ }
263
+ const parseRefObject = (value: any): { refId: string; refSrcType: string; refProjectId: string } | null => {
264
+ if (typeof value === 'string') {
265
+ try {
266
+ value = JSON.parse(value);
267
+ } catch (error) {
268
+ return null;
269
+ }
270
+ }
271
+ if (_.isPlainObject(value) && 'refId' in value && 'refSrcType' in value && 'refProjectId' in value) {
272
+ return {
273
+ refId: value.refId,
274
+ refSrcType: value.refSrcType,
275
+ refProjectId: value.refProjectId
276
+ };
277
+ }
278
+ return null;
279
+ };
280
+ if (isLocalizedField(csiDocumentField)) {
281
+ csiDocumentField.locales;
282
+ return {
283
+ type: 'cross-reference',
284
+ refType: 'document',
285
+ localized: true,
286
+ locales: _.reduce(
287
+ csiDocumentField.locales,
288
+ (accum: Record<string, { locale: string; refId: string; refSrcType: string; refProjectId: string }>, locale, localeKey) => {
289
+ const refObject = parseRefObject(locale.value);
290
+ if (refObject) {
291
+ accum[localeKey] = {
292
+ locale: locale.locale,
293
+ ...refObject
294
+ };
295
+ }
296
+ return accum;
297
+ },
298
+ {}
299
+ )
300
+ };
301
+ }
302
+ if (!('value' in csiDocumentField)) {
303
+ return unlocalizedUnset;
304
+ }
305
+ const refObject = parseRefObject(csiDocumentField.value);
306
+ if (!refObject) {
307
+ return unlocalizedUnset;
308
+ }
309
+ return {
310
+ type: 'cross-reference',
311
+ refType: 'document',
312
+ ...refObject
313
+ };
314
+ }
315
+
244
316
  function mapObjectField(
245
317
  csiDocumentField: CSITypes.DocumentObjectField,
246
318
  modelField: FieldObjectProps,
@@ -313,14 +385,15 @@ function mapListField(csiDocumentField: CSITypes.DocumentListField, modelField:
313
385
  if (!isLocalizedField(csiDocumentField)) {
314
386
  return {
315
387
  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
388
+ items: csiDocumentField.items.map(
389
+ (item) =>
390
+ mapCSIFieldToStoreField({
391
+ csiDocumentField: item,
392
+ modelField: modelField.items ?? { type: 'string' },
393
+ // list items can not be localized, only the list itself can be localized
394
+ localized: false,
395
+ context
396
+ }) as ContentStoreTypes.DocumentListFieldItems
324
397
  )
325
398
  };
326
399
  }
@@ -330,14 +403,15 @@ function mapListField(csiDocumentField: CSITypes.DocumentListField, modelField:
330
403
  locales: _.mapValues(csiDocumentField.locales, (locale) => {
331
404
  return {
332
405
  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
406
+ items: (locale.items ?? []).map(
407
+ (item) =>
408
+ mapCSIFieldToStoreField({
409
+ csiDocumentField: item,
410
+ modelField: modelField.items ?? { type: 'string' },
411
+ // list items can not be localized, only the list itself can be localized
412
+ localized: false,
413
+ context
414
+ }) as ContentStoreTypes.DocumentListFieldItems
341
415
  )
342
416
  };
343
417
  })
@@ -202,6 +202,9 @@ function mergeObjectWithDocumentField({
202
202
  }
203
203
  break;
204
204
  }
205
+ case 'cross-reference':
206
+ // TODO: implement duplicating documents with cross-references
207
+ break;
205
208
  case 'list': {
206
209
  const localizedField = getDocumentFieldForLocale(documentField, locale);
207
210
  if (value) {