@stackbit/cms-core 0.1.19 → 0.1.20-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,
@@ -1179,6 +1180,8 @@ export class ContentStore {
1179
1180
  srcDocumentId,
1180
1181
  fieldPath,
1181
1182
  modelName,
1183
+ refSrcType,
1184
+ refProjectId,
1182
1185
  object,
1183
1186
  index,
1184
1187
  locale,
@@ -1189,12 +1192,14 @@ export class ContentStore {
1189
1192
  srcDocumentId: string;
1190
1193
  fieldPath: (string | number)[];
1191
1194
  modelName?: string;
1195
+ refSrcType?: string;
1196
+ refProjectId?: string;
1192
1197
  object?: Record<string, any>;
1193
1198
  index?: number;
1194
1199
  locale?: string;
1195
1200
  user?: ContentStoreTypes.User;
1196
- }): Promise<{ srcDocumentId: string, createdDocumentId: string }> {
1197
- this.logger.debug('createAndLinkDocument', { srcType, srcProjectId, srcDocumentId, fieldPath, modelName, index, locale });
1201
+ }): Promise<{ srcDocumentId: string; createdDocumentId: string }> {
1202
+ this.logger.debug('createAndLinkDocument', { srcType, srcProjectId, srcDocumentId, fieldPath, modelName, refSrcType, refProjectId, index, locale });
1198
1203
 
1199
1204
  const contentSourceId = getContentSourceId(srcType, srcProjectId);
1200
1205
  const contentSourceData = this.getContentSourceDataByIdOrThrow(contentSourceId);
@@ -1208,36 +1213,51 @@ export class ContentStore {
1208
1213
 
1209
1214
  // get the document model
1210
1215
  const documentModelName = document.srcModelName;
1216
+ const modelMap = contentSourceData.modelMap;
1217
+ const model = modelMap[documentModelName];
1211
1218
  const csiModelMap = contentSourceData.csiModelMap;
1212
1219
  const csiModel = csiModelMap[documentModelName];
1213
- if (!csiModel) {
1220
+ if (!model || !csiModel) {
1214
1221
  throw new Error(`error updating document, could not find document model: '${documentModelName}'`);
1215
1222
  }
1216
1223
 
1217
1224
  // get the 'reference' model field in the updated document that will be used to link the new document
1218
1225
  locale = locale ?? contentSourceData.defaultLocaleCode;
1226
+ const modelField = getModelFieldForFieldAtPath(document, model, fieldPath, modelMap, locale);
1219
1227
  const csiModelField = getModelFieldForFieldAtPath(document, csiModel, fieldPath, csiModelMap, locale);
1220
- if (!csiModelField) {
1228
+ if (!modelField || !csiModelField) {
1221
1229
  throw Error(`the "fieldPath" points to non existing model field: ${fieldPath.join('.')}`);
1222
1230
  }
1223
- const fieldProps = csiModelField.type === 'list' ? csiModelField.items! : csiModelField;
1224
- if (fieldProps.type !== 'reference') {
1231
+ const fieldProps = modelField.type === 'list' ? modelField.items! : modelField;
1232
+ const csiFieldProps = csiModelField.type === 'list' ? csiModelField.items! : csiModelField;
1233
+ if (fieldProps.type !== 'reference' && fieldProps.type !== 'cross-reference') {
1225
1234
  throw Error(`error in "createAndLinkDocument", this operation can only be used on reference field: ${fieldPath.join('.')}`);
1226
1235
  }
1227
1236
 
1228
1237
  // get the model name for the new document
1229
1238
  if (!modelName && fieldProps.models.length === 1) {
1230
- modelName = fieldProps.models[0];
1239
+ if (fieldProps.type === 'reference') {
1240
+ modelName = fieldProps.models[0];
1241
+ } else if (fieldProps.type === 'cross-reference') {
1242
+ modelName = fieldProps.models[0]!.modelName;
1243
+ }
1231
1244
  }
1232
1245
  if (!modelName) {
1233
1246
  throw Error(`error in "createAndLinkDocument", missing "modelName": ${fieldPath.join('.')}`);
1234
1247
  }
1235
1248
 
1249
+ if (fieldProps.type === 'reference') {
1250
+ refSrcType = srcType;
1251
+ refProjectId = srcProjectId;
1252
+ } else if (!refSrcType || !refProjectId) {
1253
+ throw Error(`the "refSrcType" and "refProjectId" must be specified when linking a cross-reference field: ${fieldPath.join('.')}`);
1254
+ }
1255
+
1236
1256
  // create the new document
1237
1257
  const result = await this.createDocument({
1238
1258
  object: object,
1239
- srcProjectId: srcProjectId,
1240
- srcType: srcType,
1259
+ srcProjectId: refProjectId,
1260
+ srcType: refSrcType,
1241
1261
  modelName: modelName,
1242
1262
  locale: locale,
1243
1263
  user: user
@@ -1245,17 +1265,29 @@ export class ContentStore {
1245
1265
 
1246
1266
  // update the document by linking the field to the created document
1247
1267
  const userContext = getUserContextForSrcType(srcType, user);
1248
- const field = {
1249
- type: 'reference',
1250
- refType: 'document',
1251
- refId: result.srcDocumentId
1252
- } as const;
1268
+ let field: CSITypes.UpdateOperationField;
1269
+ if (fieldProps.type === 'reference') {
1270
+ field = {
1271
+ type: 'reference',
1272
+ refType: 'document',
1273
+ refId: result.srcDocumentId
1274
+ } as CSITypes.UpdateOperationReferenceField;
1275
+ } else {
1276
+ if (!['string', 'text', 'json'].includes(csiFieldProps.type)) {
1277
+ throw new Error(`The 'cross-reference' field can be only applied on string, text and json fields: ${fieldPath.join('.')}`);
1278
+ }
1279
+ field = updateOperationValueFieldWithCrossReference(csiFieldProps.type as 'string' | 'text' | 'json', {
1280
+ refId: result.srcDocumentId,
1281
+ refSrcType: refSrcType,
1282
+ refProjectId: refProjectId
1283
+ });
1284
+ }
1253
1285
  const updatedDocument = await contentSourceData.instance.updateDocument({
1254
1286
  document: csiDocument,
1255
1287
  modelMap: csiModelMap,
1256
1288
  userContext: userContext,
1257
1289
  operations: [
1258
- csiModelField.type === 'list'
1290
+ modelField.type === 'list'
1259
1291
  ? {
1260
1292
  opType: 'insert',
1261
1293
  fieldPath: fieldPath,
@@ -1431,7 +1463,7 @@ export class ContentStore {
1431
1463
  object,
1432
1464
  locale,
1433
1465
  defaultLocaleDocumentId,
1434
- user,
1466
+ user
1435
1467
  }: {
1436
1468
  srcType: string;
1437
1469
  srcProjectId: string;
@@ -1445,22 +1477,17 @@ export class ContentStore {
1445
1477
 
1446
1478
  const contentSourceId = getContentSourceId(srcType, srcProjectId);
1447
1479
  const contentSourceData = this.getContentSourceDataByIdOrThrow(contentSourceId);
1448
- const modelMap = contentSourceData.modelMap;
1449
- const csiModelMap = contentSourceData.csiModelMap;
1450
- const userContext = getUserContextForSrcType(srcType, user);
1451
1480
  const resolvedLocale = locale ?? contentSourceData.defaultLocaleCode;
1452
1481
 
1453
1482
  const result = await createDocumentRecursively({
1454
1483
  object,
1455
1484
  modelName,
1456
- modelMap,
1457
- csiModelMap,
1485
+ contentSourceId,
1486
+ contentSourceDataById: this.contentSourceDataById,
1458
1487
  createDocument: getCreateDocumentThunk({
1459
1488
  locale: resolvedLocale,
1460
- csiModelMap,
1461
- userContext,
1462
1489
  defaultLocaleDocumentId,
1463
- contentSourceInstance: contentSourceData.instance
1490
+ user
1464
1491
  })
1465
1492
  });
1466
1493
 
@@ -1523,11 +1550,11 @@ export class ContentStore {
1523
1550
  csiModelField,
1524
1551
  modelMap,
1525
1552
  csiModelMap,
1553
+ contentSourceId,
1554
+ contentSourceDataById: this.contentSourceDataById,
1526
1555
  createDocument: getCreateDocumentThunk({
1527
1556
  locale: updateOperation.locale,
1528
- csiModelMap,
1529
- userContext,
1530
- contentSourceInstance: contentSourceData.instance
1557
+ user
1531
1558
  })
1532
1559
  });
1533
1560
  return {
@@ -1551,11 +1578,11 @@ export class ContentStore {
1551
1578
  csiModelField: csiModelField.items,
1552
1579
  modelMap,
1553
1580
  csiModelMap,
1581
+ contentSourceId,
1582
+ contentSourceDataById: this.contentSourceDataById,
1554
1583
  createDocument: getCreateDocumentThunk({
1555
1584
  locale: updateOperation.locale,
1556
- csiModelMap,
1557
- userContext,
1558
- contentSourceInstance: contentSourceData.instance
1585
+ user
1559
1586
  })
1560
1587
  });
1561
1588
  return {
@@ -1622,7 +1649,6 @@ export class ContentStore {
1622
1649
  throw new Error(`no model with name '${document.srcModelName}' was found`);
1623
1650
  }
1624
1651
 
1625
- const userContext = getUserContextForSrcType(srcType, user);
1626
1652
  const resolvedLocale = locale ?? contentSourceData.defaultLocaleCode;
1627
1653
 
1628
1654
  const extendedObject = mergeObjectWithDocument({
@@ -1638,13 +1664,11 @@ export class ContentStore {
1638
1664
  const result = await createDocumentRecursively({
1639
1665
  object: extendedObject,
1640
1666
  modelName: model.name,
1641
- modelMap,
1642
- csiModelMap,
1667
+ contentSourceId,
1668
+ contentSourceDataById: this.contentSourceDataById,
1643
1669
  createDocument: getCreateDocumentThunk({
1644
1670
  locale: resolvedLocale,
1645
- csiModelMap,
1646
- userContext,
1647
- contentSourceInstance: contentSourceData.instance
1671
+ user
1648
1672
  })
1649
1673
  });
1650
1674
 
@@ -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) {