@stackbit/cms-core 0.1.6 → 0.1.7

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.
@@ -16,11 +16,16 @@ const csi_to_store_docs_converter_1 = require("./utils/csi-to-store-docs-convert
16
16
  const content_store_utils_1 = require("./content-store-utils");
17
17
  const store_to_api_docs_converter_1 = require("./utils/store-to-api-docs-converter");
18
18
  const create_update_csi_docs_1 = require("./utils/create-update-csi-docs");
19
+ const model_utils_1 = require("./utils/model-utils");
20
+ const common_schema_1 = require("./common/common-schema");
19
21
  class ContentStore {
20
22
  constructor(options) {
21
23
  this.contentSources = [];
22
24
  this.contentSourceDataById = {};
23
- this.rawStackbitConfig = null;
25
+ this.stackbitConfig = null;
26
+ this.yamlModels = [];
27
+ this.configModels = [];
28
+ this.presets = {};
24
29
  this.logger = options.logger.createLogger({ label: 'content-store' });
25
30
  this.userLogger = options.userLogger.createLogger({ label: 'content-store' });
26
31
  this.localDev = options.localDev;
@@ -63,6 +68,10 @@ class ContentStore {
63
68
  }
64
69
  async init({ stackbitConfig }) {
65
70
  this.logger.debug('init');
71
+ if (stackbitConfig) {
72
+ this.yamlModels = await this.loadYamlModels({ stackbitConfig });
73
+ this.presets = await this.loadPresets({ stackbitConfig });
74
+ }
66
75
  await this.setStackbitConfig({ stackbitConfig, init: true });
67
76
  }
68
77
  async onStackbitConfigChange({ stackbitConfig }) {
@@ -70,38 +79,17 @@ class ContentStore {
70
79
  await this.setStackbitConfig({ stackbitConfig, init: true });
71
80
  }
72
81
  async setStackbitConfig({ stackbitConfig, init }) {
73
- // TODO: Use stackbitConfig instead of loading rawStackbitConfig here
74
- // by splitting config loader into independent phases:
75
- // 1. load and validate only the root config props
76
- // 2. load content-source models
77
- // 3. merge content-source models with config.models or via config.mapModels, sanitize and validate
78
- // 4. load presets, adjust presets to have srcType and srcProjectId
79
- if (!stackbitConfig) {
80
- this.rawStackbitConfig = null;
81
- }
82
- else {
83
- const rawConfigResult = await (0, sdk_1.loadConfigFromDir)({
84
- dirPath: stackbitConfig.dirPath,
85
- logger: this.logger
86
- });
87
- for (const error of rawConfigResult.errors) {
88
- this.userLogger.warn(error.message);
89
- }
90
- if (rawConfigResult.config) {
91
- this.rawStackbitConfig = {
92
- ...rawConfigResult.config,
93
- contentSources: stackbitConfig.contentSources
94
- };
95
- }
96
- else {
97
- this.rawStackbitConfig = null;
98
- }
99
- }
100
- await this.loadContentSources({ init });
82
+ this.stackbitConfig = stackbitConfig;
83
+ this.configModels = this.mergeConfigModels(this.stackbitConfig, this.yamlModels);
84
+ await this.loadAllContentSourcesAndProcessData({ init });
101
85
  }
102
86
  /**
103
87
  * This method is called when contentUpdatesWatchTimer receives timeout.
104
- * It then notifies all content sources to stop watching for content changes.
88
+ * This happens when the user is not using the Stackbit app for some time
89
+ * but container is not hibernated.
90
+ * It then notifies all content sources to stop watching for content
91
+ * changes, which in turn stops polling CMS for content changes and helps
92
+ * reducing the CMS API usage.
105
93
  */
106
94
  handleTimerTimeout() {
107
95
  for (const contentSourceInstance of this.contentSources) {
@@ -119,7 +107,7 @@ class ContentStore {
119
107
  return;
120
108
  }
121
109
  this.logger.debug('keepAlive => contentUpdatesWatchTimer is not running => load content source data');
122
- await this.loadContentSources({ init: false });
110
+ await this.loadAllContentSourcesAndProcessData({ init: false });
123
111
  }
124
112
  /**
125
113
  * This method is called when a content source notifies Stackbit of models
@@ -132,65 +120,155 @@ class ContentStore {
132
120
  */
133
121
  async onContentSourceSchemaChange({ contentSourceId }) {
134
122
  this.logger.debug('onContentSourceSchemaChange', { contentSourceId });
135
- const contentSourceData = this.getContentSourceDataByIdOrThrow(contentSourceId);
136
- this.contentSourceDataById[contentSourceId] = await this.loadContentSourceData({
137
- contentSourceInstance: contentSourceData.instance,
138
- init: false
139
- });
140
- this.onSchemaChangeCallback();
123
+ await this.reloadContentSourcesByIdAndProcessData({ contentSourceIds: [contentSourceId] });
141
124
  }
142
125
  async onFilesChange(updatedFiles) {
143
126
  var _a, _b;
144
127
  this.logger.debug('onFilesChange');
145
- let someContentSourceSchemaUpdated = false;
146
- const contentChanges = {
147
- updatedDocuments: [],
148
- updatedAssets: [],
149
- deletedDocuments: [],
150
- deletedAssets: []
151
- };
128
+ let schemaChanged = false;
129
+ if (this.stackbitConfig) {
130
+ // Check if any of the yaml models files were changed. If yaml model files were changed,
131
+ // reload them and merge them with models defined in stackbit config.
132
+ const modelDirs = (0, sdk_1.getYamlModelDirs)(this.stackbitConfig);
133
+ const yamlModelsChanged = updatedFiles.find((updatedFile) => lodash_1.default.some(modelDirs, (modelDir) => updatedFile.startsWith(modelDir)));
134
+ if (yamlModelsChanged) {
135
+ this.logger.debug('identified change in stackbit model files');
136
+ schemaChanged = true;
137
+ this.yamlModels = await this.loadYamlModels({ stackbitConfig: this.stackbitConfig });
138
+ this.configModels = this.mergeConfigModels(this.stackbitConfig, this.yamlModels);
139
+ }
140
+ // Check if any of the preset files were changed. If presets were changed, reload them.
141
+ const presetDirs = (0, sdk_1.getPresetDirs)(this.stackbitConfig);
142
+ const presetsChanged = updatedFiles.find((updatedFile) => lodash_1.default.some(presetDirs, (presetDir) => updatedFile.startsWith(presetDir)));
143
+ if (presetsChanged) {
144
+ this.logger.debug('identified change in stackbit preset files');
145
+ schemaChanged = true;
146
+ this.presets = await this.loadPresets({ stackbitConfig: this.stackbitConfig });
147
+ }
148
+ }
149
+ const contentSourceIdsWithChangedSchema = [];
150
+ const contentChangeEvents = [];
152
151
  for (const contentSourceInstance of this.contentSources) {
153
152
  const contentSourceId = (0, content_store_utils_1.getContentSourceIdForContentSource)(contentSourceInstance);
154
153
  this.logger.debug(`call onFilesChange for contentSource: ${contentSourceId}`);
155
- const { schemaChanged, contentChangeEvent } = (_b = (await ((_a = contentSourceInstance.onFilesChange) === null || _a === void 0 ? void 0 : _a.call(contentSourceInstance, { updatedFiles: updatedFiles })))) !== null && _b !== void 0 ? _b : {};
156
- this.logger.debug(`schemaChanged: ${schemaChanged}, has contentChangeEvent: ${!!contentChangeEvent}`);
154
+ const onFilesChangeResult = (_b = (await ((_a = contentSourceInstance.onFilesChange) === null || _a === void 0 ? void 0 : _a.call(contentSourceInstance, { updatedFiles: updatedFiles })))) !== null && _b !== void 0 ? _b : {};
155
+ this.logger.debug(`schemaChanged: ${onFilesChangeResult.schemaChanged}, has contentChangeEvent: ${!!onFilesChangeResult.contentChangeEvent}`);
157
156
  // if schema is changed, there is no need to return contentChanges
158
157
  // because schema changes reloads everything and implies content changes
159
- if (schemaChanged) {
160
- someContentSourceSchemaUpdated = true;
161
- this.contentSourceDataById[contentSourceId] = await this.loadContentSourceData({ contentSourceInstance, init: false });
158
+ if (onFilesChangeResult.schemaChanged) {
159
+ schemaChanged = true;
160
+ contentSourceIdsWithChangedSchema.push(contentSourceId);
161
+ }
162
+ else if (onFilesChangeResult.contentChangeEvent) {
163
+ contentChangeEvents.push({ contentSourceId, contentChangeEvent: onFilesChangeResult.contentChangeEvent });
162
164
  }
163
- else if (contentChangeEvent) {
165
+ }
166
+ const contentChanges = {
167
+ updatedDocuments: [],
168
+ updatedAssets: [],
169
+ deletedDocuments: [],
170
+ deletedAssets: []
171
+ };
172
+ // If the schema was changed, there is no need to accumulate or notify about content changes.
173
+ // The processData will update the store with the latest data. And once the Studio receives
174
+ // the schemaChanged notification it will reload all the models and the documents with their latest state.
175
+ if (schemaChanged) {
176
+ await this.reloadContentSourcesByIdAndProcessData({ contentSourceIds: contentSourceIdsWithChangedSchema });
177
+ }
178
+ else {
179
+ contentChangeEvents.reduce((contentChanges, { contentSourceId, contentChangeEvent }) => {
164
180
  const contentChangeResult = this.onContentChange(contentSourceId, contentChangeEvent);
165
181
  contentChanges.updatedDocuments = contentChanges.updatedDocuments.concat(contentChangeResult.updatedDocuments);
166
182
  contentChanges.updatedAssets = contentChanges.updatedAssets.concat(contentChangeResult.updatedAssets);
167
183
  contentChanges.deletedDocuments = contentChanges.deletedDocuments.concat(contentChangeResult.deletedDocuments);
168
184
  contentChanges.deletedAssets = contentChanges.deletedAssets.concat(contentChangeResult.deletedAssets);
169
- }
185
+ return contentChanges;
186
+ }, contentChanges);
170
187
  }
171
188
  return {
172
- schemaChanged: someContentSourceSchemaUpdated,
189
+ schemaChanged: schemaChanged,
173
190
  contentChanges: contentChanges
174
191
  };
175
192
  }
176
- async loadContentSources({ init }) {
193
+ async loadYamlModels({ stackbitConfig }) {
194
+ const yamlModelsResult = await (0, sdk_1.loadYamlModelsFromFiles)(stackbitConfig);
195
+ for (const error of yamlModelsResult.errors) {
196
+ this.userLogger.warn(error.message);
197
+ }
198
+ return yamlModelsResult.models;
199
+ }
200
+ mergeConfigModels(stackbitConfig, modelsFromFiles) {
201
+ var _a;
202
+ const configModelsResult = (0, sdk_1.mergeConfigModelsWithModelsFromFiles)((_a = stackbitConfig === null || stackbitConfig === void 0 ? void 0 : stackbitConfig.models) !== null && _a !== void 0 ? _a : [], modelsFromFiles);
203
+ for (const error of configModelsResult.errors) {
204
+ this.userLogger.warn(error.message);
205
+ }
206
+ return configModelsResult.models;
207
+ }
208
+ async loadPresets({ stackbitConfig }) {
209
+ var _a;
210
+ const contentSources = (_a = stackbitConfig === null || stackbitConfig === void 0 ? void 0 : stackbitConfig.contentSources) !== null && _a !== void 0 ? _a : [];
211
+ const singleContentSource = contentSources.length === 1 ? contentSources[0] : null;
212
+ const presetResult = await (0, sdk_1.loadPresets)({
213
+ config: stackbitConfig,
214
+ ...(singleContentSource
215
+ ? {
216
+ fallbackSrcType: singleContentSource.getContentSourceType(),
217
+ fallbackSrcProjectId: singleContentSource.getProjectId()
218
+ }
219
+ : null)
220
+ });
221
+ for (const error of presetResult.errors) {
222
+ this.userLogger.warn(error.message);
223
+ }
224
+ const { presets } = await this.handleConfigAssets({ presets: presetResult.presets });
225
+ return presets;
226
+ }
227
+ async loadAllContentSourcesAndProcessData({ init }) {
177
228
  var _a, _b;
178
- this.logger.debug('loadContentSources', { init });
229
+ this.logger.debug('loadAllContentSourcesAndProcessData', { init });
179
230
  this.contentUpdatesWatchTimer.stopTimer();
180
- // TODO: move CSITypes to separate package so Config will be able to use them
181
- const contentSources = ((_b = (_a = this.rawStackbitConfig) === null || _a === void 0 ? void 0 : _a.contentSources) !== null && _b !== void 0 ? _b : []);
231
+ const contentSources = (_b = (_a = this.stackbitConfig) === null || _a === void 0 ? void 0 : _a.contentSources) !== null && _b !== void 0 ? _b : [];
182
232
  const promises = contentSources.map((contentSourceInstance) => {
183
233
  return this.loadContentSourceData({ contentSourceInstance, init });
184
234
  });
185
- const contentSourceDataArr = await Promise.all(promises);
186
- const contentSourceDataById = lodash_1.default.keyBy(contentSourceDataArr, 'id');
235
+ const contentSourceRawDataArr = await Promise.all(promises);
187
236
  // update all content sources at once to prevent race conditions
237
+ this.contentSourceDataById = await this.processData({
238
+ stackbitConfig: this.stackbitConfig,
239
+ configModels: this.configModels,
240
+ presets: this.presets,
241
+ contentSourceRawDataArr: contentSourceRawDataArr
242
+ });
188
243
  this.contentSources = contentSources;
189
- this.contentSourceDataById = contentSourceDataById;
244
+ this.contentUpdatesWatchTimer.startTimer();
245
+ }
246
+ async reloadContentSourcesByIdAndProcessData({ contentSourceIds }) {
247
+ this.logger.debug('reloadContentSourcesByIdAndProcessData', { contentSourceIds });
248
+ this.contentUpdatesWatchTimer.stopTimer();
249
+ const promises = this.contentSources.map((contentSourceInstance) => {
250
+ const contentSourceId = (0, content_store_utils_1.getContentSourceIdForContentSource)(contentSourceInstance);
251
+ if (contentSourceIds.includes(contentSourceId)) {
252
+ return this.loadContentSourceData({
253
+ contentSourceInstance: contentSourceInstance,
254
+ init: false
255
+ });
256
+ }
257
+ else {
258
+ return Promise.resolve(lodash_1.default.omit(this.contentSourceDataById[contentSourceId], ['models', 'modelMap', 'documents', 'documentMap']));
259
+ }
260
+ });
261
+ const contentSourceRawDataArr = await Promise.all(promises);
262
+ this.contentSourceDataById = await this.processData({
263
+ stackbitConfig: this.stackbitConfig,
264
+ configModels: this.configModels,
265
+ presets: this.presets,
266
+ contentSourceRawDataArr: contentSourceRawDataArr
267
+ });
190
268
  this.contentUpdatesWatchTimer.startTimer();
191
269
  }
192
270
  async loadContentSourceData({ contentSourceInstance, init }) {
193
- var _a, _b, _c, _d, _e;
271
+ var _a;
194
272
  const contentSourceId = (0, content_store_utils_1.getContentSourceIdForContentSource)(contentSourceInstance);
195
273
  this.logger.debug('loadContentSourceData', { contentSourceId, init });
196
274
  if (init) {
@@ -207,87 +285,27 @@ class ContentStore {
207
285
  contentSourceInstance.stopWatchingContentUpdates();
208
286
  await contentSourceInstance.reset();
209
287
  }
210
- // TODO: introduce optimization: don't fetch content source models,
211
- // documents, assets if only stackbitConfig was changed
212
288
  const csiModels = await contentSourceInstance.getModels();
213
- const locales = await (contentSourceInstance === null || contentSourceInstance === void 0 ? void 0 : contentSourceInstance.getLocales());
214
- const defaultLocaleCode = (_a = locales === null || locales === void 0 ? void 0 : locales.find((locale) => locale.default)) === null || _a === void 0 ? void 0 : _a.code;
215
- // for older versions of stackbit, it uses models to extend content source models
216
- let modelsNoImage = [];
217
- let imageModel;
218
- if (this.rawStackbitConfig) {
219
- const result = await (0, sdk_1.extendConfig)({
220
- config: this.rawStackbitConfig,
221
- externalModels: csiModels
222
- });
223
- for (const error of (_b = result === null || result === void 0 ? void 0 : result.errors) !== null && _b !== void 0 ? _b : []) {
224
- this.userLogger.warn(error.message);
225
- }
226
- const config = await this.handleConfigAssets(result.config);
227
- const modelsWithImageModel = (_c = config === null || config === void 0 ? void 0 : config.models) !== null && _c !== void 0 ? _c : [];
228
- const imageModelIndex = modelsWithImageModel.findIndex((model) => (0, sdk_1.isImageModel)(model));
229
- if (imageModelIndex > -1) {
230
- imageModel = modelsWithImageModel[imageModelIndex];
231
- modelsWithImageModel.splice(imageModelIndex, 1);
232
- }
233
- modelsNoImage = modelsWithImageModel;
234
- // TODO: load presets externally from config, and create additional map
235
- // that maps presetIds by model name instead of storing that map inside every model
236
- // Augment presets with srcType and srcProjectId if they don't exist
237
- this.presets = lodash_1.default.reduce(Object.keys((_d = config === null || config === void 0 ? void 0 : config.presets) !== null && _d !== void 0 ? _d : {}), (accum, presetId) => {
238
- var _a, _b, _c;
239
- const preset = (_a = config === null || config === void 0 ? void 0 : config.presets) === null || _a === void 0 ? void 0 : _a[presetId];
240
- lodash_1.default.set(accum, [presetId], {
241
- ...preset,
242
- srcType: (_b = preset === null || preset === void 0 ? void 0 : preset.srcType) !== null && _b !== void 0 ? _b : contentSourceInstance.getContentSourceType(),
243
- srcProjectId: (_c = preset === null || preset === void 0 ? void 0 : preset.srcProjectId) !== null && _c !== void 0 ? _c : contentSourceInstance.getProjectId()
244
- });
245
- return accum;
246
- }, {});
247
- }
248
- if ((_e = this.rawStackbitConfig) === null || _e === void 0 ? void 0 : _e.mapModels) {
249
- const srcType = contentSourceInstance.getContentSourceType();
250
- const srcProjectId = contentSourceInstance.getProjectId();
251
- const modelsWithSource = modelsNoImage.map((model) => ({
252
- srcType,
253
- srcProjectId,
254
- ...model
255
- }));
256
- const mappedModels = this.rawStackbitConfig.mapModels({
257
- models: modelsWithSource
258
- });
259
- modelsNoImage = mappedModels.map((model) => {
260
- const { srcType, srcProjectId, ...rest } = model;
261
- return rest;
262
- });
263
- }
264
- const models = imageModel ? [...modelsNoImage, imageModel] : modelsNoImage;
265
- const modelMap = lodash_1.default.keyBy(models, 'name');
266
289
  const csiModelMap = lodash_1.default.keyBy(csiModels, 'name');
290
+ const locales = await contentSourceInstance.getLocales();
291
+ const defaultLocaleCode = (_a = locales === null || locales === void 0 ? void 0 : locales.find((locale) => locale.default)) === null || _a === void 0 ? void 0 : _a.code;
267
292
  const csiDocuments = await contentSourceInstance.getDocuments({ modelMap: csiModelMap });
268
293
  const csiAssets = await contentSourceInstance.getAssets();
269
294
  const csiDocumentMap = lodash_1.default.keyBy(csiDocuments, 'id');
270
295
  const csiAssetMap = lodash_1.default.keyBy(csiAssets, 'id');
271
- const contentStoreDocuments = (0, csi_to_store_docs_converter_1.mapCSIDocumentsToStoreDocuments)({
272
- csiDocuments,
273
- contentSourceInstance,
274
- modelMap,
275
- defaultLocaleCode
276
- });
277
296
  const contentStoreAssets = (0, csi_to_store_docs_converter_1.mapCSIAssetsToStoreAssets)({
278
297
  csiAssets,
279
298
  contentSourceInstance,
280
299
  defaultLocaleCode
281
300
  });
282
- const documentMap = lodash_1.default.keyBy(contentStoreDocuments, 'srcObjectId');
283
301
  const assetMap = lodash_1.default.keyBy(contentStoreAssets, 'srcObjectId');
284
302
  this.logger.debug('loaded content source data', {
285
303
  contentSourceId,
286
- locales,
287
304
  defaultLocaleCode,
288
- modelCount: models.length,
289
- documentCount: contentStoreDocuments.length,
290
- assetCount: contentStoreAssets.length
305
+ localesCount: locales.length,
306
+ modelCount: csiModels.length,
307
+ documentCount: csiDocuments.length,
308
+ assetCount: csiAssets.length
291
309
  });
292
310
  contentSourceInstance.startWatchingContentUpdates({
293
311
  getModelMap: () => {
@@ -306,27 +324,20 @@ class ContentStore {
306
324
  },
307
325
  onSchemaChange: async () => {
308
326
  this.logger.debug('content source called onSchemaChange', { contentSourceId });
309
- this.contentSourceDataById[contentSourceId] = await this.loadContentSourceData({
310
- contentSourceInstance: contentSourceInstance,
311
- init: false
312
- });
313
- this.onSchemaChangeCallback();
327
+ await this.reloadContentSourcesByIdAndProcessData({ contentSourceIds: [contentSourceId] });
314
328
  }
315
329
  });
316
330
  return {
317
331
  id: contentSourceId,
318
- type: contentSourceInstance.getContentSourceType(),
319
- projectId: contentSourceInstance.getProjectId(),
332
+ srcType: contentSourceInstance.getContentSourceType(),
333
+ srcProjectId: contentSourceInstance.getProjectId(),
320
334
  instance: contentSourceInstance,
321
335
  locales: locales,
322
336
  defaultLocaleCode: defaultLocaleCode,
323
- models: models,
324
- modelMap: modelMap,
337
+ csiModels: csiModels,
325
338
  csiModelMap: csiModelMap,
326
339
  csiDocuments: csiDocuments,
327
340
  csiDocumentMap: csiDocumentMap,
328
- documents: contentStoreDocuments,
329
- documentMap: documentMap,
330
341
  csiAssets: csiAssets,
331
342
  csiAssetMap: csiAssetMap,
332
343
  assets: contentStoreAssets,
@@ -335,6 +346,7 @@ class ContentStore {
335
346
  }
336
347
  onContentChange(contentSourceId, contentChangeEvent) {
337
348
  // TODO: prevent content change process for contentSourceId if loading content is in progress
349
+ var _a, _b, _c, _d;
338
350
  this.logger.debug('onContentChange', {
339
351
  contentSourceId,
340
352
  documentCount: contentChangeEvent.documents.length,
@@ -362,8 +374,8 @@ class ContentStore {
362
374
  contentSourceData.csiDocuments.splice(index, 1);
363
375
  }
364
376
  result.deletedDocuments.push({
365
- srcType: contentSourceData.type,
366
- srcProjectId: contentSourceData.projectId,
377
+ srcType: contentSourceData.srcType,
378
+ srcProjectId: contentSourceData.srcProjectId,
367
379
  srcObjectId: docId
368
380
  });
369
381
  });
@@ -380,14 +392,35 @@ class ContentStore {
380
392
  contentSourceData.csiAssets.splice(index, 1);
381
393
  }
382
394
  result.deletedAssets.push({
383
- srcType: contentSourceData.type,
384
- srcProjectId: contentSourceData.projectId,
395
+ srcType: contentSourceData.srcType,
396
+ srcProjectId: contentSourceData.srcProjectId,
385
397
  srcObjectId: assetId
386
398
  });
387
399
  });
400
+ // map csi documents through stackbitConfig.mapDocuments
401
+ let mappedDocs = contentChangeEvent.documents;
402
+ if ((_a = this.stackbitConfig) === null || _a === void 0 ? void 0 : _a.mapDocuments) {
403
+ const csiDocumentsWithSource = contentChangeEvent.documents.map((csiDocument) => ({
404
+ srcType: contentSourceData.srcType,
405
+ srcProjectId: contentSourceData.srcProjectId,
406
+ ...csiDocument
407
+ }));
408
+ const modelsWithSource = contentSourceData.models.map((model) => {
409
+ return {
410
+ srcType: contentSourceData.srcType,
411
+ srcProjectId: contentSourceData.srcProjectId,
412
+ ...model
413
+ };
414
+ });
415
+ mappedDocs =
416
+ (_d = (_c = (_b = this.stackbitConfig) === null || _b === void 0 ? void 0 : _b.mapDocuments) === null || _c === void 0 ? void 0 : _c.call(_b, {
417
+ documents: lodash_1.default.cloneDeep(csiDocumentsWithSource),
418
+ models: lodash_1.default.cloneDeep(modelsWithSource)
419
+ })) !== null && _d !== void 0 ? _d : csiDocumentsWithSource;
420
+ }
388
421
  // map csi documents and assets to content store documents and assets
389
422
  const documents = (0, csi_to_store_docs_converter_1.mapCSIDocumentsToStoreDocuments)({
390
- csiDocuments: contentChangeEvent.documents,
423
+ csiDocuments: mappedDocs,
391
424
  contentSourceInstance: contentSourceData.instance,
392
425
  modelMap: contentSourceData.modelMap,
393
426
  defaultLocaleCode: contentSourceData.defaultLocaleCode
@@ -399,7 +432,7 @@ class ContentStore {
399
432
  });
400
433
  // update contentSourceData with new or updated documents and assets
401
434
  Object.assign(contentSourceData.csiDocumentMap, lodash_1.default.keyBy(contentChangeEvent.documents, 'id'));
402
- Object.assign(contentSourceData.csiAssets, lodash_1.default.keyBy(contentChangeEvent.assets, 'id'));
435
+ Object.assign(contentSourceData.csiAssetMap, lodash_1.default.keyBy(contentChangeEvent.assets, 'id'));
403
436
  Object.assign(contentSourceData.documentMap, lodash_1.default.keyBy(documents, 'srcObjectId'));
404
437
  Object.assign(contentSourceData.assetMap, lodash_1.default.keyBy(assets, 'srcObjectId'));
405
438
  for (let idx = 0; idx < documents.length; idx++) {
@@ -417,8 +450,8 @@ class ContentStore {
417
450
  contentSourceData.csiDocuments.splice(dataIndex, 1, csiDocument);
418
451
  }
419
452
  result.updatedDocuments.push({
420
- srcType: contentSourceData.type,
421
- srcProjectId: contentSourceData.projectId,
453
+ srcType: contentSourceData.srcType,
454
+ srcProjectId: contentSourceData.srcProjectId,
422
455
  srcObjectId: document.srcObjectId
423
456
  });
424
457
  }
@@ -437,18 +470,77 @@ class ContentStore {
437
470
  contentSourceData.csiAssets.splice(index, 1, csiAsset);
438
471
  }
439
472
  result.updatedAssets.push({
440
- srcType: contentSourceData.type,
441
- srcProjectId: contentSourceData.projectId,
473
+ srcType: contentSourceData.srcType,
474
+ srcProjectId: contentSourceData.srcProjectId,
442
475
  srcObjectId: asset.srcObjectId
443
476
  });
444
477
  }
445
478
  return result;
446
479
  }
480
+ async processData({ stackbitConfig, configModels, presets, contentSourceRawDataArr }) {
481
+ var _a, _b, _c, _d;
482
+ const modelsWithSource = contentSourceRawDataArr.reduce((accum, csData) => {
483
+ const mergedModels = (0, sdk_1.mergeConfigModelsWithExternalModels)({
484
+ configModels: configModels,
485
+ externalModels: csData.csiModels
486
+ });
487
+ const modelsWithSource = mergedModels.map((model) => {
488
+ return {
489
+ srcType: csData.srcType,
490
+ srcProjectId: csData.id,
491
+ ...model
492
+ };
493
+ });
494
+ return accum.concat(modelsWithSource);
495
+ }, []);
496
+ // TODO: Is there a better way than deep cloning objects before passing them to user methods?
497
+ // Not cloning mutable objects will break the internal state if user mutates the objects.
498
+ const mappedModels = (_b = (_a = stackbitConfig === null || stackbitConfig === void 0 ? void 0 : stackbitConfig.mapModels) === null || _a === void 0 ? void 0 : _a.call(stackbitConfig, { models: lodash_1.default.cloneDeep(modelsWithSource) })) !== null && _b !== void 0 ? _b : modelsWithSource;
499
+ const normalizedModels = (0, model_utils_1.normalizeModels)({ models: mappedModels, logger: this.userLogger });
500
+ const validatedModels = (0, model_utils_1.validateModels)({ models: normalizedModels, logger: this.userLogger });
501
+ const modelsWithPresetsIds = (0, sdk_1.extendModelsWithPresetsIds)({ models: validatedModels, presets });
502
+ const { models } = await this.handleConfigAssets({ models: modelsWithPresetsIds });
503
+ const csiDocumentsWithSource = contentSourceRawDataArr.reduce((accum, csData) => {
504
+ const csiDocumentsWithSource = csData.csiDocuments.map((csiDocument) => ({
505
+ srcType: csData.srcType,
506
+ srcProjectId: csData.id,
507
+ ...csiDocument
508
+ }));
509
+ return accum.concat(csiDocumentsWithSource);
510
+ }, []);
511
+ // TODO: Is there a better way than deep cloning objects before passing them to user methods?
512
+ // Not cloning mutable objects will break the internal state if user mutates the objects.
513
+ const mappedDocs = (_d = (_c = stackbitConfig === null || stackbitConfig === void 0 ? void 0 : stackbitConfig.mapDocuments) === null || _c === void 0 ? void 0 : _c.call(stackbitConfig, {
514
+ documents: lodash_1.default.cloneDeep(csiDocumentsWithSource),
515
+ models: lodash_1.default.cloneDeep(models)
516
+ })) !== null && _d !== void 0 ? _d : csiDocumentsWithSource;
517
+ const modelMapByContentSource = (0, content_store_utils_1.groupModelsByContentSource)({ models: models });
518
+ const documentMapByContentSource = (0, content_store_utils_1.groupDocumentsByContentSource)({ documents: mappedDocs });
519
+ const contentSourceDataArr = contentSourceRawDataArr.map((csData) => {
520
+ const modelMap = lodash_1.default.get(modelMapByContentSource, [csData.srcType, csData.id], {});
521
+ const mappedCSIDocuments = lodash_1.default.get(documentMapByContentSource, [csData.srcType, csData.id]);
522
+ const documents = (0, csi_to_store_docs_converter_1.mapCSIDocumentsToStoreDocuments)({
523
+ csiDocuments: mappedCSIDocuments,
524
+ contentSourceInstance: csData.instance,
525
+ defaultLocaleCode: csData.defaultLocaleCode,
526
+ modelMap: modelMap
527
+ });
528
+ return {
529
+ ...csData,
530
+ models: Object.values(modelMap),
531
+ modelMap,
532
+ documents,
533
+ documentMap: lodash_1.default.keyBy(documents, 'srcObjectId')
534
+ };
535
+ });
536
+ return lodash_1.default.keyBy(contentSourceDataArr, 'id');
537
+ }
447
538
  getModels() {
448
539
  return lodash_1.default.reduce(this.contentSourceDataById, (result, contentSourceData) => {
449
540
  const contentSourceType = contentSourceData.instance.getContentSourceType();
450
541
  const srcProjectId = contentSourceData.instance.getProjectId();
451
542
  lodash_1.default.set(result, [contentSourceType, srcProjectId], contentSourceData.modelMap);
543
+ lodash_1.default.set(result, [contentSourceType, srcProjectId, '__image_model'], common_schema_1.IMAGE_MODEL);
452
544
  return result;
453
545
  }, {});
454
546
  }
@@ -479,8 +571,8 @@ class ContentStore {
479
571
  contentSourceDataArr = Object.values(this.contentSourceDataById);
480
572
  }
481
573
  return (0, utils_1.reducePromise)(contentSourceDataArr, async (accum, contentSourceData) => {
482
- const srcType = contentSourceData.type;
483
- const srcProjectId = contentSourceData.projectId;
574
+ const srcType = contentSourceData.srcType;
575
+ const srcProjectId = contentSourceData.srcProjectId;
484
576
  const userContext = (0, content_store_utils_1.getUserContextForSrcType)(srcType, user);
485
577
  let result = await contentSourceData.instance.hasAccess({ userContext });
486
578
  // backwards compatibility with older CSI version
@@ -949,13 +1041,13 @@ class ContentStore {
949
1041
  const contentSourceData = this.getContentSourceDataByIdOrThrow(contentSourceId);
950
1042
  locale = locale !== null && locale !== void 0 ? locale : contentSourceData.defaultLocaleCode;
951
1043
  const { documents, assets } = getCSIDocumentsAndAssetsFromContentSourceDataByIds(contentSourceData, contentSourceObjects);
952
- const userContext = (0, content_store_utils_1.getUserContextForSrcType)(contentSourceData.type, user);
1044
+ const userContext = (0, content_store_utils_1.getUserContextForSrcType)(contentSourceData.srcType, user);
953
1045
  const internalValidationErrors = internalValidateContent(documents, assets, contentSourceData);
954
1046
  const validationResult = await contentSourceData.instance.validateDocuments({ documents, assets, locale, userContext });
955
1047
  errors = errors.concat(internalValidationErrors, validationResult.errors.map((validationError) => ({
956
1048
  message: validationError.message,
957
- srcType: contentSourceData.type,
958
- srcProjectId: contentSourceData.projectId,
1049
+ srcType: contentSourceData.srcType,
1050
+ srcProjectId: contentSourceData.srcProjectId,
959
1051
  srcObjectType: validationError.objectType,
960
1052
  srcObjectId: validationError.objectId,
961
1053
  fieldPath: validationError.fieldPath,
@@ -986,7 +1078,7 @@ class ContentStore {
986
1078
  Object.keys(objectsBySourceId).forEach((contentSourceId) => {
987
1079
  const contentSourceData = this.getContentSourceDataByIdOrThrow(contentSourceId);
988
1080
  documents.push(...contentSourceData.documents);
989
- lodash_1.default.set(schema, [contentSourceData.type, contentSourceData.projectId], contentSourceData.modelMap);
1081
+ lodash_1.default.set(schema, [contentSourceData.srcType, contentSourceData.srcProjectId], contentSourceData.modelMap);
990
1082
  });
991
1083
  return (0, search_utils_1.searchDocuments)({
992
1084
  ...data,
@@ -999,7 +1091,7 @@ class ContentStore {
999
1091
  const objectsBySourceId = lodash_1.default.groupBy(objects, (object) => (0, content_store_utils_1.getContentSourceId)(object.srcType, object.srcProjectId));
1000
1092
  for (const [contentSourceId, contentSourceObjects] of Object.entries(objectsBySourceId)) {
1001
1093
  const contentSourceData = this.getContentSourceDataByIdOrThrow(contentSourceId);
1002
- const userContext = (0, content_store_utils_1.getUserContextForSrcType)(contentSourceData.type, user);
1094
+ const userContext = (0, content_store_utils_1.getUserContextForSrcType)(contentSourceData.srcType, user);
1003
1095
  const { documents, assets } = getCSIDocumentsAndAssetsFromContentSourceDataByIds(contentSourceData, contentSourceObjects);
1004
1096
  await contentSourceData.instance.publishDocuments({ documents, assets, userContext });
1005
1097
  }
@@ -1074,8 +1166,8 @@ function validateDocumentFields(document, documentField, fieldPath, contentSourc
1074
1166
  if (!objRef) {
1075
1167
  errors.push({
1076
1168
  fieldPath,
1077
- srcType: contentSourceData.type,
1078
- srcProjectId: contentSourceData.projectId,
1169
+ srcType: contentSourceData.srcType,
1170
+ srcProjectId: contentSourceData.srcProjectId,
1079
1171
  srcObjectType: documentField.refType,
1080
1172
  srcObjectId: document.id,
1081
1173
  message: `Can't find referenced ${documentField.refType}: ${documentField.refId}`