@stackbit/cms-core 0.0.19-alpha.0 → 0.0.19-alpha.3

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.
@@ -17,13 +17,11 @@ import {
17
17
  } from '@stackbit/sdk';
18
18
  import { mapPromise, omitByNil } from '@stackbit/utils';
19
19
  import * as CSITypes from './content-source-interface';
20
- import {
21
- DocumentObjectField,
22
- isLocalizedField
23
- } from './content-source-interface';
20
+ import { isLocalizedField } from './content-source-interface';
24
21
  import * as ContentStoreTypes from './content-store-types';
25
22
  import { IMAGE_MODEL } from './common/common-schema';
26
23
  import { Timer } from './utils/timer';
24
+ import { UserCommandSpawner } from './common/common-types';
27
25
 
28
26
  export interface ContentSourceOptions {
29
27
  logger: ContentStoreTypes.Logger;
@@ -31,6 +29,7 @@ export interface ContentSourceOptions {
31
29
  localDev: boolean;
32
30
  stackbitYamlDir: string;
33
31
  contentSources: CSITypes.ContentSourceInterface[];
32
+ userCommandSpawner?: UserCommandSpawner;
34
33
  onSchemaChangeCallback: () => void;
35
34
  onContentChangeCallback: (contentChanges: ContentStoreTypes.ContentChangeResult) => void;
36
35
  handleConfigAssets: (config: Config) => Promise<Config>;
@@ -59,6 +58,7 @@ export class ContentStore {
59
58
  private readonly contentSources: CSITypes.ContentSourceInterface[];
60
59
  private readonly logger: ContentStoreTypes.Logger;
61
60
  private readonly userLogger: ContentStoreTypes.Logger;
61
+ private readonly userCommandSpawner?: UserCommandSpawner;
62
62
  private readonly localDev: boolean;
63
63
  private readonly stackbitYamlDir: string; // TODO: remove stackbitYamlDir, ContentStore should not be aware of filesystem, instead pass autoreloading Config object
64
64
  private readonly onSchemaChangeCallback: () => void;
@@ -73,6 +73,7 @@ export class ContentStore {
73
73
  this.logger = options.logger.createLogger({ label: 'content-store' });
74
74
  this.userLogger = options.userLogger.createLogger({ label: 'content-store' });
75
75
  this.localDev = options.localDev;
76
+ this.userCommandSpawner = options.userCommandSpawner;
76
77
  this.stackbitYamlDir = options.stackbitYamlDir; // TODO: remove stackbitYamlDir, ContentStore should not be aware of filesystem, instead pass autoreloading Config object
77
78
  this.contentSources = options.contentSources;
78
79
  this.onSchemaChangeCallback = options.onSchemaChangeCallback;
@@ -226,6 +227,7 @@ export class ContentStore {
226
227
  await contentSourceInstance.init({
227
228
  logger: this.logger,
228
229
  userLogger: this.userLogger,
230
+ userCommandSpawner: this.userCommandSpawner,
229
231
  localDev: this.localDev
230
232
  });
231
233
  } else {
@@ -411,7 +413,7 @@ export class ContentStore {
411
413
  const dataIndex = contentSourceData.documents.findIndex((existingDoc) => existingDoc.srcObjectId === document.srcObjectId);
412
414
  if (dataIndex === -1) {
413
415
  contentSourceData.documents.push(document);
414
- contentSourceData.csiDocuments.push(csiDocument)
416
+ contentSourceData.csiDocuments.push(csiDocument);
415
417
  } else {
416
418
  // the indexes of documents and csiDocuments are always the same as they are always updated at the same time
417
419
  contentSourceData.documents.splice(dataIndex, 1, document);
@@ -1283,30 +1285,34 @@ function mapCSIDocumentToStoreDocument({
1283
1285
  fields: mapCSIFieldsToStoreFields({
1284
1286
  csiDocumentFields: csiDocument.fields,
1285
1287
  modelFields: model.fields ?? [],
1286
- modelMap,
1287
- defaultLocaleCode
1288
+ context: {
1289
+ modelMap,
1290
+ defaultLocaleCode
1291
+ }
1288
1292
  })
1289
1293
  };
1290
1294
  }
1291
1295
 
1296
+ type MapContext = {
1297
+ modelMap: Record<string, Model>;
1298
+ defaultLocaleCode?: string;
1299
+ };
1300
+
1292
1301
  function mapCSIFieldsToStoreFields({
1293
1302
  csiDocumentFields,
1294
1303
  modelFields,
1295
- modelMap,
1296
- defaultLocaleCode
1304
+ context
1297
1305
  }: {
1298
1306
  csiDocumentFields: Record<string, CSITypes.DocumentField>;
1299
1307
  modelFields: Field[];
1300
- modelMap: Record<string, Model>;
1301
- defaultLocaleCode?: string;
1308
+ context: MapContext;
1302
1309
  }): Record<string, ContentStoreTypes.DocumentField> {
1303
1310
  return modelFields.reduce((result: Record<string, ContentStoreTypes.DocumentField>, modelField) => {
1304
1311
  const csiDocumentField = csiDocumentFields[modelField.name];
1305
- const docField = mapSourceFieldToStoreField({
1312
+ const docField = mapCSIFieldToStoreField({
1306
1313
  csiDocumentField,
1307
1314
  modelField,
1308
- modelMap,
1309
- defaultLocaleCode
1315
+ context
1310
1316
  });
1311
1317
  docField.label = modelField.label;
1312
1318
  result[modelField.name] = docField;
@@ -1314,16 +1320,14 @@ function mapCSIFieldsToStoreFields({
1314
1320
  }, {});
1315
1321
  }
1316
1322
 
1317
- function mapSourceFieldToStoreField({
1323
+ function mapCSIFieldToStoreField({
1318
1324
  csiDocumentField,
1319
1325
  modelField,
1320
- modelMap,
1321
- defaultLocaleCode
1326
+ context
1322
1327
  }: {
1323
1328
  csiDocumentField: CSITypes.DocumentField | undefined;
1324
1329
  modelField: FieldSpecificProps;
1325
- modelMap: Record<string, Model>;
1326
- defaultLocaleCode?: string;
1330
+ context: MapContext;
1327
1331
  }): ContentStoreTypes.DocumentField {
1328
1332
  if (!csiDocumentField) {
1329
1333
  const isUnset = ['object', 'model', 'reference', 'richText', 'markdown', 'image', 'file', 'json'].includes(modelField.type);
@@ -1336,11 +1340,11 @@ function mapSourceFieldToStoreField({
1336
1340
  // TODO: check if need to add "options" to "enum" and subtype/min/max to "number"
1337
1341
  switch (modelField.type) {
1338
1342
  case 'object':
1339
- return mapObjectField(csiDocumentField as CSITypes.DocumentObjectField, modelField, modelMap, defaultLocaleCode);
1343
+ return mapObjectField(csiDocumentField as CSITypes.DocumentObjectField, modelField, context);
1340
1344
  case 'model':
1341
- return mapModelField(csiDocumentField as CSITypes.DocumentModelField, modelField, modelMap, defaultLocaleCode);
1345
+ return mapModelField(csiDocumentField as CSITypes.DocumentModelField, modelField, context);
1342
1346
  case 'list':
1343
- return mapListField(csiDocumentField as CSITypes.DocumentListField, modelField, modelMap, defaultLocaleCode);
1347
+ return mapListField(csiDocumentField as CSITypes.DocumentListField, modelField, context);
1344
1348
  case 'richText':
1345
1349
  return mapRichTextField(csiDocumentField as CSITypes.DocumentRichTextField);
1346
1350
  case 'markdown':
@@ -1353,18 +1357,16 @@ function mapSourceFieldToStoreField({
1353
1357
  function mapObjectField(
1354
1358
  csiDocumentField: CSITypes.DocumentObjectField,
1355
1359
  modelField: FieldObjectProps,
1356
- modelMap: Record<string, Model>,
1357
- defaultLocaleCode?: string
1360
+ context: MapContext
1358
1361
  ): ContentStoreTypes.DocumentObjectField {
1359
1362
  if (!isLocalizedField(csiDocumentField)) {
1360
1363
  return {
1361
1364
  type: csiDocumentField.type,
1362
- srcObjectLabel: getObjectLabel(csiDocumentField.fields ?? {}, modelField ?? [], defaultLocaleCode),
1365
+ srcObjectLabel: getObjectLabel(csiDocumentField.fields ?? {}, modelField ?? [], context.defaultLocaleCode),
1363
1366
  fields: mapCSIFieldsToStoreFields({
1364
1367
  csiDocumentFields: csiDocumentField.fields ?? {},
1365
1368
  modelFields: modelField.fields ?? [],
1366
- modelMap,
1367
- defaultLocaleCode
1369
+ context
1368
1370
  })
1369
1371
  };
1370
1372
  }
@@ -1378,32 +1380,25 @@ function mapObjectField(
1378
1380
  fields: mapCSIFieldsToStoreFields({
1379
1381
  csiDocumentFields: locale.fields ?? {},
1380
1382
  modelFields: modelField.fields ?? [],
1381
- modelMap,
1382
- defaultLocaleCode
1383
+ context
1383
1384
  })
1384
1385
  };
1385
1386
  })
1386
1387
  };
1387
1388
  }
1388
1389
 
1389
- function mapModelField(
1390
- csiDocumentField: CSITypes.DocumentModelField,
1391
- modelField: FieldModelProps,
1392
- modelMap: Record<string, Model>,
1393
- defaultLocaleCode?: string
1394
- ): ContentStoreTypes.DocumentModelField {
1390
+ function mapModelField(csiDocumentField: CSITypes.DocumentModelField, modelField: FieldModelProps, context: MapContext): ContentStoreTypes.DocumentModelField {
1395
1391
  if (!isLocalizedField(csiDocumentField)) {
1396
- const model = modelMap[csiDocumentField.modelName]!;
1392
+ const model = context.modelMap[csiDocumentField.modelName]!;
1397
1393
  return {
1398
1394
  type: csiDocumentField.type,
1399
- srcObjectLabel: getObjectLabel(csiDocumentField.fields ?? {}, model, defaultLocaleCode),
1395
+ srcObjectLabel: getObjectLabel(csiDocumentField.fields ?? {}, model, context.defaultLocaleCode),
1400
1396
  srcModelName: csiDocumentField.modelName,
1401
1397
  srcModelLabel: model.label ?? _.startCase(model.name),
1402
1398
  fields: mapCSIFieldsToStoreFields({
1403
1399
  csiDocumentFields: csiDocumentField.fields ?? {},
1404
1400
  modelFields: model.fields ?? [],
1405
- modelMap,
1406
- defaultLocaleCode
1401
+ context
1407
1402
  })
1408
1403
  };
1409
1404
  }
@@ -1411,7 +1406,7 @@ function mapModelField(
1411
1406
  type: csiDocumentField.type,
1412
1407
  localized: true,
1413
1408
  locales: _.mapValues(csiDocumentField.locales, (locale) => {
1414
- const model = modelMap[locale.modelName]!;
1409
+ const model = context.modelMap[locale.modelName]!;
1415
1410
  return {
1416
1411
  locale: locale.locale,
1417
1412
  srcObjectLabel: getObjectLabel(locale.fields ?? {}, model, locale.locale),
@@ -1420,29 +1415,22 @@ function mapModelField(
1420
1415
  fields: mapCSIFieldsToStoreFields({
1421
1416
  csiDocumentFields: locale.fields ?? {},
1422
1417
  modelFields: model.fields ?? [],
1423
- modelMap,
1424
- defaultLocaleCode
1418
+ context
1425
1419
  })
1426
1420
  };
1427
1421
  })
1428
1422
  };
1429
1423
  }
1430
1424
 
1431
- function mapListField(
1432
- csiDocumentField: CSITypes.DocumentListField,
1433
- modelField: FieldListProps,
1434
- modelMap: Record<string, Model>,
1435
- defaultLocaleCode?: string
1436
- ): ContentStoreTypes.DocumentListField {
1425
+ function mapListField(csiDocumentField: CSITypes.DocumentListField, modelField: FieldListProps, context: MapContext): ContentStoreTypes.DocumentListField {
1437
1426
  if (!isLocalizedField(csiDocumentField)) {
1438
1427
  return {
1439
1428
  type: csiDocumentField.type,
1440
1429
  items: csiDocumentField.items.map((item) =>
1441
- mapSourceFieldToStoreField({
1430
+ mapCSIFieldToStoreField({
1442
1431
  csiDocumentField: item,
1443
1432
  modelField: modelField.items ?? { type: 'string' },
1444
- modelMap,
1445
- defaultLocaleCode
1433
+ context
1446
1434
  })
1447
1435
  )
1448
1436
  };
@@ -1454,11 +1442,10 @@ function mapListField(
1454
1442
  return {
1455
1443
  locale: locale.locale,
1456
1444
  items: (locale.items ?? []).map((item) =>
1457
- mapSourceFieldToStoreField({
1445
+ mapCSIFieldToStoreField({
1458
1446
  csiDocumentField: item,
1459
1447
  modelField: modelField.items ?? { type: 'string' },
1460
- modelMap,
1461
- defaultLocaleCode
1448
+ context
1462
1449
  })
1463
1450
  )
1464
1451
  };
@@ -1603,6 +1590,7 @@ function toLocalizedAPIField(docField: ContentStoreTypes.DocumentField, locale?:
1603
1590
  if (docFieldLocalized.type === 'object' || docFieldLocalized.type === 'model') {
1604
1591
  return {
1605
1592
  ...docFieldLocalized,
1593
+ type: 'object',
1606
1594
  ...commonProps,
1607
1595
  ...(docFieldLocalized.isUnset
1608
1596
  ? null
@@ -1743,7 +1731,7 @@ async function createDocumentRecursively({
1743
1731
  contentSourceInstance
1744
1732
  });
1745
1733
  const document = await contentSourceInstance.createDocument({
1746
- documentFields: nestedResult.fields,
1734
+ documentFields: nestedResult.fields as Record<string, CSITypes.DocumentField>,
1747
1735
  model,
1748
1736
  modelMap,
1749
1737
  locale,
@@ -1772,166 +1760,12 @@ async function createNestedObjectRecursively({
1772
1760
  userContext: unknown;
1773
1761
  contentSourceInstance: CSITypes.ContentSourceInterface;
1774
1762
  }): Promise<{
1775
- fields: Record<string, CSITypes.DocumentFieldNonLocalized>;
1763
+ fields: Record<string, CSITypes.UpdateOperationField>;
1776
1764
  newRefDocuments: CSITypes.Document[];
1777
1765
  }> {
1778
- const createNestedField = async ({
1779
- value,
1780
- modelField,
1781
- fieldPath
1782
- }: {
1783
- value: any;
1784
- modelField: FieldSpecificProps;
1785
- fieldPath: (string | number)[];
1786
- }): Promise<{ field: CSITypes.DocumentFieldNonLocalized; newRefDocuments: CSITypes.Document[] }> => {
1787
- if (modelField.type === 'object') {
1788
- const result = await createNestedObjectRecursively({
1789
- object: value,
1790
- modelFields: modelField.fields,
1791
- fieldPath,
1792
- modelMap,
1793
- locale,
1794
- userContext,
1795
- contentSourceInstance
1796
- });
1797
- return {
1798
- field: {
1799
- type: 'object',
1800
- fields: result.fields
1801
- },
1802
- newRefDocuments: result.newRefDocuments
1803
- };
1804
- } else if (modelField.type === 'model') {
1805
- let { $$type, ...rest } = value;
1806
- const modelNames = modelField.models;
1807
- // for backward compatibility check if the object has 'type' instead of '$$type' because older projects use
1808
- // the 'type' property in default values
1809
- if (!$$type && 'type' in rest) {
1810
- $$type = rest.type;
1811
- rest = _.omit(rest, 'type');
1812
- }
1813
- const modelName = $$type ?? (modelNames.length === 1 ? modelNames[0] : null);
1814
- if (!modelName) {
1815
- throw new Error(`no $$type was specified for nested model`);
1816
- }
1817
- const model = modelMap[modelName];
1818
- if (!model) {
1819
- throw new Error(`no model with name '${modelName}' was found`);
1820
- }
1821
- const result = await createNestedObjectRecursively({
1822
- object: rest,
1823
- modelFields: model.fields ?? [],
1824
- fieldPath,
1825
- modelMap,
1826
- locale,
1827
- userContext,
1828
- contentSourceInstance
1829
- });
1830
- return {
1831
- field: {
1832
- type: 'model',
1833
- modelName: modelName,
1834
- fields: result.fields
1835
- },
1836
- newRefDocuments: result.newRefDocuments
1837
- };
1838
- } else if (modelField.type === 'image') {
1839
- let refId: string | undefined;
1840
- if (_.isPlainObject(value)) {
1841
- refId = value.$$ref;
1842
- } else {
1843
- refId = value;
1844
- }
1845
- if (!refId) {
1846
- throw new Error(`reference field must specify a value`);
1847
- }
1848
- return {
1849
- field: {
1850
- type: 'reference',
1851
- refType: 'asset',
1852
- refId: refId
1853
- },
1854
- newRefDocuments: []
1855
- };
1856
- } else if (modelField.type === 'reference') {
1857
- let { $$ref: refId = null, $$type: modelName = null, ...rest } = _.isPlainObject(value) ? value : { $$ref: value };
1858
- if (refId) {
1859
- return {
1860
- field: {
1861
- type: 'reference',
1862
- refType: 'document',
1863
- refId: refId
1864
- },
1865
- newRefDocuments: []
1866
- };
1867
- } else {
1868
- const modelNames = modelField.models;
1869
- if (!modelName) {
1870
- // for backward compatibility check if the object has 'type' instead of '$$type' because older projects use
1871
- // the 'type' property in default values
1872
- if ('type' in rest) {
1873
- modelName = rest.type;
1874
- rest = _.omit(rest, 'type');
1875
- } else if (modelNames.length === 1) {
1876
- modelName = modelNames[0];
1877
- }
1878
- }
1879
- const model = modelMap[modelName];
1880
- if (!model) {
1881
- throw new Error(`no model with name '${modelName}' was found`);
1882
- }
1883
- const { document, newRefDocuments } = await createDocumentRecursively({
1884
- object: rest,
1885
- model: model,
1886
- modelMap,
1887
- locale,
1888
- userContext,
1889
- contentSourceInstance
1890
- });
1891
- return {
1892
- field: {
1893
- type: 'reference',
1894
- refType: 'document',
1895
- refId: document.id
1896
- },
1897
- newRefDocuments: [document, ...newRefDocuments]
1898
- };
1899
- }
1900
- } else if (modelField.type === 'list') {
1901
- if (!Array.isArray(value)) {
1902
- throw new Error(`value for list field must be array`);
1903
- }
1904
- const itemsField = modelField.items;
1905
- if (!itemsField) {
1906
- throw new Error(`list field does not define items`);
1907
- }
1908
- const arrayResult = await mapPromise(value, async (item, index) => {
1909
- return createNestedField({
1910
- value: item,
1911
- modelField: itemsField,
1912
- fieldPath: fieldPath.concat(index)
1913
- });
1914
- });
1915
- return {
1916
- field: {
1917
- type: 'list',
1918
- items: arrayResult.map((result) => result.field)
1919
- },
1920
- newRefDocuments: arrayResult.reduce((result: CSITypes.Document[], { newRefDocuments }) => result.concat(newRefDocuments), [])
1921
- };
1922
- }
1923
- return {
1924
- field: {
1925
- type: modelField.type,
1926
- value: value
1927
- } as CSITypes.DocumentFieldNonLocalized,
1928
- newRefDocuments: []
1929
- };
1930
- };
1931
-
1932
1766
  object = object ?? {};
1933
1767
  const result: {
1934
- fields: Record<string, CSITypes.DocumentFieldNonLocalized>;
1768
+ fields: Record<string, CSITypes.UpdateOperationField>;
1935
1769
  newRefDocuments: CSITypes.Document[];
1936
1770
  } = {
1937
1771
  fields: {},
@@ -1953,7 +1787,11 @@ async function createNestedObjectRecursively({
1953
1787
  const fieldResult = await createNestedField({
1954
1788
  value,
1955
1789
  modelField,
1956
- fieldPath: fieldPath.concat(fieldName)
1790
+ fieldPath: fieldPath.concat(fieldName),
1791
+ modelMap,
1792
+ locale,
1793
+ userContext,
1794
+ contentSourceInstance
1957
1795
  });
1958
1796
  result.fields[fieldName] = fieldResult.field;
1959
1797
  result.newRefDocuments = result.newRefDocuments.concat(fieldResult.newRefDocuments);
@@ -1966,6 +1804,172 @@ async function createNestedObjectRecursively({
1966
1804
  return result;
1967
1805
  }
1968
1806
 
1807
+ async function createNestedField({
1808
+ value,
1809
+ modelField,
1810
+ fieldPath,
1811
+ modelMap,
1812
+ locale,
1813
+ userContext,
1814
+ contentSourceInstance
1815
+ }: {
1816
+ value: any;
1817
+ modelField: FieldSpecificProps;
1818
+ fieldPath: (string | number)[];
1819
+ modelMap: Record<string, Model>;
1820
+ locale?: string;
1821
+ userContext: unknown;
1822
+ contentSourceInstance: CSITypes.ContentSourceInterface;
1823
+ }): Promise<{ field: CSITypes.UpdateOperationField; newRefDocuments: CSITypes.Document[] }> {
1824
+ if (modelField.type === 'object') {
1825
+ const result = await createNestedObjectRecursively({
1826
+ object: value,
1827
+ modelFields: modelField.fields,
1828
+ fieldPath,
1829
+ modelMap,
1830
+ locale,
1831
+ userContext,
1832
+ contentSourceInstance
1833
+ });
1834
+ return {
1835
+ field: {
1836
+ type: 'object',
1837
+ fields: result.fields
1838
+ },
1839
+ newRefDocuments: result.newRefDocuments
1840
+ };
1841
+ } else if (modelField.type === 'model') {
1842
+ let { $$type, ...rest } = value;
1843
+ const modelNames = modelField.models;
1844
+ // for backward compatibility check if the object has 'type' instead of '$$type' because older projects use
1845
+ // the 'type' property in default values
1846
+ if (!$$type && 'type' in rest) {
1847
+ $$type = rest.type;
1848
+ rest = _.omit(rest, 'type');
1849
+ }
1850
+ const modelName = $$type ?? (modelNames.length === 1 ? modelNames[0] : null);
1851
+ if (!modelName) {
1852
+ throw new Error(`no $$type was specified for nested model`);
1853
+ }
1854
+ const model = modelMap[modelName];
1855
+ if (!model) {
1856
+ throw new Error(`no model with name '${modelName}' was found`);
1857
+ }
1858
+ const result = await createNestedObjectRecursively({
1859
+ object: rest,
1860
+ modelFields: model.fields ?? [],
1861
+ fieldPath,
1862
+ modelMap,
1863
+ locale,
1864
+ userContext,
1865
+ contentSourceInstance
1866
+ });
1867
+ return {
1868
+ field: {
1869
+ type: 'model',
1870
+ modelName: modelName,
1871
+ fields: result.fields
1872
+ },
1873
+ newRefDocuments: result.newRefDocuments
1874
+ };
1875
+ } else if (modelField.type === 'image') {
1876
+ let refId: string | undefined;
1877
+ if (_.isPlainObject(value)) {
1878
+ refId = value.$$ref;
1879
+ } else {
1880
+ refId = value;
1881
+ }
1882
+ if (!refId) {
1883
+ throw new Error(`reference field must specify a value`);
1884
+ }
1885
+ return {
1886
+ field: {
1887
+ type: 'reference',
1888
+ refType: 'asset',
1889
+ refId: refId
1890
+ },
1891
+ newRefDocuments: []
1892
+ };
1893
+ } else if (modelField.type === 'reference') {
1894
+ let { $$ref: refId = null, $$type: modelName = null, ...rest } = _.isPlainObject(value) ? value : { $$ref: value };
1895
+ if (refId) {
1896
+ return {
1897
+ field: {
1898
+ type: 'reference',
1899
+ refType: 'document',
1900
+ refId: refId
1901
+ },
1902
+ newRefDocuments: []
1903
+ };
1904
+ } else {
1905
+ const modelNames = modelField.models;
1906
+ if (!modelName) {
1907
+ // for backward compatibility check if the object has 'type' instead of '$$type' because older projects use
1908
+ // the 'type' property in default values
1909
+ if ('type' in rest) {
1910
+ modelName = rest.type;
1911
+ rest = _.omit(rest, 'type');
1912
+ } else if (modelNames.length === 1) {
1913
+ modelName = modelNames[0];
1914
+ }
1915
+ }
1916
+ const model = modelMap[modelName];
1917
+ if (!model) {
1918
+ throw new Error(`no model with name '${modelName}' was found`);
1919
+ }
1920
+ const { document, newRefDocuments } = await createDocumentRecursively({
1921
+ object: rest,
1922
+ model: model,
1923
+ modelMap,
1924
+ locale,
1925
+ userContext,
1926
+ contentSourceInstance
1927
+ });
1928
+ return {
1929
+ field: {
1930
+ type: 'reference',
1931
+ refType: 'document',
1932
+ refId: document.id
1933
+ },
1934
+ newRefDocuments: [document, ...newRefDocuments]
1935
+ };
1936
+ }
1937
+ } else if (modelField.type === 'list') {
1938
+ if (!Array.isArray(value)) {
1939
+ throw new Error(`value for list field must be array`);
1940
+ }
1941
+ const itemsField = modelField.items;
1942
+ if (!itemsField) {
1943
+ throw new Error(`list field does not define items`);
1944
+ }
1945
+ const arrayResult = await mapPromise(value, async (item, index) => {
1946
+ return createNestedField({
1947
+ value: item,
1948
+ modelField: itemsField,
1949
+ fieldPath: fieldPath.concat(index),
1950
+ modelMap,
1951
+ locale,
1952
+ userContext,
1953
+ contentSourceInstance
1954
+ });
1955
+ });
1956
+ return {
1957
+ field: {
1958
+ type: 'list',
1959
+ items: arrayResult.map((result) => result.field)
1960
+ },
1961
+ newRefDocuments: arrayResult.reduce((result: CSITypes.Document[], { newRefDocuments }) => result.concat(newRefDocuments), [])
1962
+ };
1963
+ }
1964
+ return {
1965
+ field: {
1966
+ type: modelField.type,
1967
+ value: value
1968
+ },
1969
+ newRefDocuments: []
1970
+ };
1971
+ }
1972
+
1969
1973
  function getModelFieldForFieldAtPath(
1970
1974
  document: ContentStoreTypes.Document,
1971
1975
  model: Model,
@@ -2093,12 +2097,13 @@ async function convertOperationField({
2093
2097
  userContext: unknown;
2094
2098
  contentSourceInstance: CSITypes.ContentSourceInterface;
2095
2099
  }): Promise<CSITypes.UpdateOperationField> {
2096
- let result;
2100
+ // for insert operations, the modelField will be of the list, so get the modelField of the list items
2101
+ const modelFieldOrListItems: FieldSpecificProps = modelField.type === 'list' ? modelField.items! : modelField;
2097
2102
  switch (operationField.type) {
2098
- case 'object':
2099
- result = await createNestedObjectRecursively({
2103
+ case 'object': {
2104
+ const result = await createNestedObjectRecursively({
2100
2105
  object: operationField.object,
2101
- modelFields: (modelField as FieldObjectProps).fields,
2106
+ modelFields: (modelFieldOrListItems as FieldObjectProps).fields,
2102
2107
  fieldPath: fieldPath,
2103
2108
  modelMap,
2104
2109
  locale,
@@ -2109,12 +2114,13 @@ async function convertOperationField({
2109
2114
  type: operationField.type,
2110
2115
  fields: result.fields
2111
2116
  };
2112
- case 'model':
2117
+ }
2118
+ case 'model': {
2113
2119
  const model = modelMap[operationField.modelName];
2114
2120
  if (!model) {
2115
2121
  throw new Error(`error updating document, could not find document model: '${operationField.modelName}'`);
2116
2122
  }
2117
- result = await createNestedObjectRecursively({
2123
+ const result = await createNestedObjectRecursively({
2118
2124
  object: operationField.object,
2119
2125
  modelFields: model.fields!,
2120
2126
  fieldPath,
@@ -2128,6 +2134,51 @@ async function convertOperationField({
2128
2134
  modelName: operationField.modelName,
2129
2135
  fields: result.fields
2130
2136
  };
2137
+ }
2138
+ case 'list': {
2139
+ if (modelField.type !== 'list') {
2140
+ throw new Error(`'the operation field type '${operationField.type}' does not match the model field type '${modelField.type}'`);
2141
+ }
2142
+ const result = await mapPromise(operationField.items, async (item, index) => {
2143
+ const result = await createNestedField({
2144
+ value: item,
2145
+ modelField: modelField.items!,
2146
+ fieldPath,
2147
+ modelMap,
2148
+ locale,
2149
+ userContext,
2150
+ contentSourceInstance
2151
+ });
2152
+ return result.field;
2153
+ });
2154
+ return {
2155
+ type: operationField.type,
2156
+ items: result
2157
+ };
2158
+ }
2159
+ case 'string':
2160
+ if (typeof operationField.value !== 'string') {
2161
+ return {
2162
+ type: operationField.type,
2163
+ value: ''
2164
+ };
2165
+ }
2166
+ return operationField as CSITypes.UpdateOperationField;
2167
+ case 'enum':
2168
+ if (typeof operationField.value !== 'string') {
2169
+ if (modelFieldOrListItems.type !== 'enum') {
2170
+ throw new Error(`'the operation field type 'enum' does not match the model field type '${modelFieldOrListItems.type}'`);
2171
+ }
2172
+ const option = modelFieldOrListItems.options[0]!;
2173
+ const optionValue = typeof option === 'object' ? option.value : option;
2174
+ return {
2175
+ type: operationField.type,
2176
+ value: optionValue
2177
+ };
2178
+ }
2179
+ return operationField as CSITypes.UpdateOperationField;
2180
+ case 'image':
2181
+ return operationField as CSITypes.UpdateOperationField;
2131
2182
  default:
2132
2183
  return operationField as CSITypes.UpdateOperationField;
2133
2184
  }