@stackbit/cms-core 0.1.5 → 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,19 +16,26 @@ 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;
32
+ this.webhookUrl = options.webhookUrl;
27
33
  this.userCommandSpawner = options.userCommandSpawner;
28
34
  this.onSchemaChangeCallback = options.onSchemaChangeCallback;
29
35
  this.onContentChangeCallback = options.onContentChangeCallback;
30
36
  this.handleConfigAssets = options.handleConfigAssets;
31
37
  this.contentUpdatesWatchTimer = new timer_1.Timer({ timerCallback: () => this.handleTimerTimeout(), logger: this.logger });
38
+ this.devAppRestartNeeded = options.devAppRestartNeeded;
32
39
  // The `loadContentSourceData` method can be called for different
33
40
  // reasons: user restarted SSG from Stackbit, Stackbit Config updated,
34
41
  // the Content-Source's schema updated, etc.
@@ -61,6 +68,10 @@ class ContentStore {
61
68
  }
62
69
  async init({ stackbitConfig }) {
63
70
  this.logger.debug('init');
71
+ if (stackbitConfig) {
72
+ this.yamlModels = await this.loadYamlModels({ stackbitConfig });
73
+ this.presets = await this.loadPresets({ stackbitConfig });
74
+ }
64
75
  await this.setStackbitConfig({ stackbitConfig, init: true });
65
76
  }
66
77
  async onStackbitConfigChange({ stackbitConfig }) {
@@ -68,38 +79,17 @@ class ContentStore {
68
79
  await this.setStackbitConfig({ stackbitConfig, init: true });
69
80
  }
70
81
  async setStackbitConfig({ stackbitConfig, init }) {
71
- // TODO: Use stackbitConfig instead of loading rawStackbitConfig here
72
- // by splitting config loader into independent phases:
73
- // 1. load and validate only the root config props
74
- // 2. load content-source models
75
- // 3. merge content-source models with config.models or via config.mapModels, sanitize and validate
76
- // 4. load presets, adjust presets to have srcType and srcProjectId
77
- if (!stackbitConfig) {
78
- this.rawStackbitConfig = null;
79
- }
80
- else {
81
- const rawConfigResult = await (0, sdk_1.loadConfigFromDir)({
82
- dirPath: stackbitConfig.dirPath,
83
- logger: this.logger
84
- });
85
- for (const error of rawConfigResult.errors) {
86
- this.userLogger.warn(error.message);
87
- }
88
- if (rawConfigResult.config) {
89
- this.rawStackbitConfig = {
90
- ...rawConfigResult.config,
91
- contentSources: stackbitConfig.contentSources
92
- };
93
- }
94
- else {
95
- this.rawStackbitConfig = null;
96
- }
97
- }
98
- await this.loadContentSources({ init });
82
+ this.stackbitConfig = stackbitConfig;
83
+ this.configModels = this.mergeConfigModels(this.stackbitConfig, this.yamlModels);
84
+ await this.loadAllContentSourcesAndProcessData({ init });
99
85
  }
100
86
  /**
101
87
  * This method is called when contentUpdatesWatchTimer receives timeout.
102
- * 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.
103
93
  */
104
94
  handleTimerTimeout() {
105
95
  for (const contentSourceInstance of this.contentSources) {
@@ -117,7 +107,7 @@ class ContentStore {
117
107
  return;
118
108
  }
119
109
  this.logger.debug('keepAlive => contentUpdatesWatchTimer is not running => load content source data');
120
- await this.loadContentSources({ init: false });
110
+ await this.loadAllContentSourcesAndProcessData({ init: false });
121
111
  }
122
112
  /**
123
113
  * This method is called when a content source notifies Stackbit of models
@@ -130,65 +120,155 @@ class ContentStore {
130
120
  */
131
121
  async onContentSourceSchemaChange({ contentSourceId }) {
132
122
  this.logger.debug('onContentSourceSchemaChange', { contentSourceId });
133
- const contentSourceData = this.getContentSourceDataByIdOrThrow(contentSourceId);
134
- this.contentSourceDataById[contentSourceId] = await this.loadContentSourceData({
135
- contentSourceInstance: contentSourceData.instance,
136
- init: false
137
- });
138
- this.onSchemaChangeCallback();
123
+ await this.reloadContentSourcesByIdAndProcessData({ contentSourceIds: [contentSourceId] });
139
124
  }
140
125
  async onFilesChange(updatedFiles) {
141
126
  var _a, _b;
142
127
  this.logger.debug('onFilesChange');
143
- let someContentSourceSchemaUpdated = false;
144
- const contentChanges = {
145
- updatedDocuments: [],
146
- updatedAssets: [],
147
- deletedDocuments: [],
148
- deletedAssets: []
149
- };
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 = [];
150
151
  for (const contentSourceInstance of this.contentSources) {
151
152
  const contentSourceId = (0, content_store_utils_1.getContentSourceIdForContentSource)(contentSourceInstance);
152
153
  this.logger.debug(`call onFilesChange for contentSource: ${contentSourceId}`);
153
- const { schemaChanged, contentChangeEvent } = (_b = (await ((_a = contentSourceInstance.onFilesChange) === null || _a === void 0 ? void 0 : _a.call(contentSourceInstance, { updatedFiles: updatedFiles })))) !== null && _b !== void 0 ? _b : {};
154
- 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}`);
155
156
  // if schema is changed, there is no need to return contentChanges
156
157
  // because schema changes reloads everything and implies content changes
157
- if (schemaChanged) {
158
- someContentSourceSchemaUpdated = true;
159
- 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 });
160
164
  }
161
- 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 }) => {
162
180
  const contentChangeResult = this.onContentChange(contentSourceId, contentChangeEvent);
163
181
  contentChanges.updatedDocuments = contentChanges.updatedDocuments.concat(contentChangeResult.updatedDocuments);
164
182
  contentChanges.updatedAssets = contentChanges.updatedAssets.concat(contentChangeResult.updatedAssets);
165
183
  contentChanges.deletedDocuments = contentChanges.deletedDocuments.concat(contentChangeResult.deletedDocuments);
166
184
  contentChanges.deletedAssets = contentChanges.deletedAssets.concat(contentChangeResult.deletedAssets);
167
- }
185
+ return contentChanges;
186
+ }, contentChanges);
168
187
  }
169
188
  return {
170
- schemaChanged: someContentSourceSchemaUpdated,
189
+ schemaChanged: schemaChanged,
171
190
  contentChanges: contentChanges
172
191
  };
173
192
  }
174
- 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 }) {
175
228
  var _a, _b;
176
- this.logger.debug('loadContentSources', { init });
229
+ this.logger.debug('loadAllContentSourcesAndProcessData', { init });
177
230
  this.contentUpdatesWatchTimer.stopTimer();
178
- // TODO: move CSITypes to separate package so Config will be able to use them
179
- 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 : [];
180
232
  const promises = contentSources.map((contentSourceInstance) => {
181
233
  return this.loadContentSourceData({ contentSourceInstance, init });
182
234
  });
183
- const contentSourceDataArr = await Promise.all(promises);
184
- const contentSourceDataById = lodash_1.default.keyBy(contentSourceDataArr, 'id');
235
+ const contentSourceRawDataArr = await Promise.all(promises);
185
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
+ });
186
243
  this.contentSources = contentSources;
187
- 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
+ });
188
268
  this.contentUpdatesWatchTimer.startTimer();
189
269
  }
190
270
  async loadContentSourceData({ contentSourceInstance, init }) {
191
- var _a, _b, _c, _d, _e;
271
+ var _a;
192
272
  const contentSourceId = (0, content_store_utils_1.getContentSourceIdForContentSource)(contentSourceInstance);
193
273
  this.logger.debug('loadContentSourceData', { contentSourceId, init });
194
274
  if (init) {
@@ -196,94 +276,36 @@ class ContentStore {
196
276
  logger: this.logger,
197
277
  userLogger: this.userLogger,
198
278
  userCommandSpawner: this.userCommandSpawner,
199
- localDev: this.localDev
279
+ localDev: this.localDev,
280
+ webhookUrl: this.getWebhookUrl(contentSourceInstance.getContentSourceType(), contentSourceInstance.getProjectId()),
281
+ devAppRestartNeeded: this.devAppRestartNeeded
200
282
  });
201
283
  }
202
284
  else {
203
285
  contentSourceInstance.stopWatchingContentUpdates();
204
286
  await contentSourceInstance.reset();
205
287
  }
206
- // TODO: introduce optimization: don't fetch content source models,
207
- // documents, assets if only stackbitConfig was changed
208
288
  const csiModels = await contentSourceInstance.getModels();
209
- const locales = await (contentSourceInstance === null || contentSourceInstance === void 0 ? void 0 : contentSourceInstance.getLocales());
210
- const defaultLocaleCode = (_a = locales === null || locales === void 0 ? void 0 : locales.find((locale) => locale.default)) === null || _a === void 0 ? void 0 : _a.code;
211
- // for older versions of stackbit, it uses models to extend content source models
212
- let modelsNoImage = [];
213
- let imageModel;
214
- if (this.rawStackbitConfig) {
215
- const result = await (0, sdk_1.extendConfig)({
216
- config: this.rawStackbitConfig,
217
- externalModels: csiModels
218
- });
219
- for (const error of (_b = result === null || result === void 0 ? void 0 : result.errors) !== null && _b !== void 0 ? _b : []) {
220
- this.userLogger.warn(error.message);
221
- }
222
- const config = await this.handleConfigAssets(result.config);
223
- const modelsWithImageModel = (_c = config === null || config === void 0 ? void 0 : config.models) !== null && _c !== void 0 ? _c : [];
224
- const imageModelIndex = modelsWithImageModel.findIndex((model) => (0, sdk_1.isImageModel)(model));
225
- if (imageModelIndex > -1) {
226
- imageModel = modelsWithImageModel[imageModelIndex];
227
- modelsWithImageModel.splice(imageModelIndex, 1);
228
- }
229
- modelsNoImage = modelsWithImageModel;
230
- // TODO: load presets externally from config, and create additional map
231
- // that maps presetIds by model name instead of storing that map inside every model
232
- // Augment presets with srcType and srcProjectId if they don't exist
233
- 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) => {
234
- var _a, _b, _c;
235
- const preset = (_a = config === null || config === void 0 ? void 0 : config.presets) === null || _a === void 0 ? void 0 : _a[presetId];
236
- lodash_1.default.set(accum, [presetId], {
237
- ...preset,
238
- srcType: (_b = preset === null || preset === void 0 ? void 0 : preset.srcType) !== null && _b !== void 0 ? _b : contentSourceInstance.getContentSourceType(),
239
- srcProjectId: (_c = preset === null || preset === void 0 ? void 0 : preset.srcProjectId) !== null && _c !== void 0 ? _c : contentSourceInstance.getProjectId()
240
- });
241
- return accum;
242
- }, {});
243
- }
244
- if ((_e = this.rawStackbitConfig) === null || _e === void 0 ? void 0 : _e.mapModels) {
245
- const srcType = contentSourceInstance.getContentSourceType();
246
- const srcProjectId = contentSourceInstance.getProjectId();
247
- const modelsWithSource = modelsNoImage.map((model) => ({
248
- srcType,
249
- srcProjectId,
250
- ...model
251
- }));
252
- const mappedModels = this.rawStackbitConfig.mapModels({
253
- models: modelsWithSource
254
- });
255
- modelsNoImage = mappedModels.map((model) => {
256
- const { srcType, srcProjectId, ...rest } = model;
257
- return rest;
258
- });
259
- }
260
- const models = imageModel ? [...modelsNoImage, imageModel] : modelsNoImage;
261
- const modelMap = lodash_1.default.keyBy(models, 'name');
262
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;
263
292
  const csiDocuments = await contentSourceInstance.getDocuments({ modelMap: csiModelMap });
264
293
  const csiAssets = await contentSourceInstance.getAssets();
265
294
  const csiDocumentMap = lodash_1.default.keyBy(csiDocuments, 'id');
266
295
  const csiAssetMap = lodash_1.default.keyBy(csiAssets, 'id');
267
- const contentStoreDocuments = (0, csi_to_store_docs_converter_1.mapCSIDocumentsToStoreDocuments)({
268
- csiDocuments,
269
- contentSourceInstance,
270
- modelMap,
271
- defaultLocaleCode
272
- });
273
296
  const contentStoreAssets = (0, csi_to_store_docs_converter_1.mapCSIAssetsToStoreAssets)({
274
297
  csiAssets,
275
298
  contentSourceInstance,
276
299
  defaultLocaleCode
277
300
  });
278
- const documentMap = lodash_1.default.keyBy(contentStoreDocuments, 'srcObjectId');
279
301
  const assetMap = lodash_1.default.keyBy(contentStoreAssets, 'srcObjectId');
280
302
  this.logger.debug('loaded content source data', {
281
303
  contentSourceId,
282
- locales,
283
304
  defaultLocaleCode,
284
- modelCount: models.length,
285
- documentCount: contentStoreDocuments.length,
286
- assetCount: contentStoreAssets.length
305
+ localesCount: locales.length,
306
+ modelCount: csiModels.length,
307
+ documentCount: csiDocuments.length,
308
+ assetCount: csiAssets.length
287
309
  });
288
310
  contentSourceInstance.startWatchingContentUpdates({
289
311
  getModelMap: () => {
@@ -302,27 +324,20 @@ class ContentStore {
302
324
  },
303
325
  onSchemaChange: async () => {
304
326
  this.logger.debug('content source called onSchemaChange', { contentSourceId });
305
- this.contentSourceDataById[contentSourceId] = await this.loadContentSourceData({
306
- contentSourceInstance: contentSourceInstance,
307
- init: false
308
- });
309
- this.onSchemaChangeCallback();
327
+ await this.reloadContentSourcesByIdAndProcessData({ contentSourceIds: [contentSourceId] });
310
328
  }
311
329
  });
312
330
  return {
313
331
  id: contentSourceId,
314
- type: contentSourceInstance.getContentSourceType(),
315
- projectId: contentSourceInstance.getProjectId(),
332
+ srcType: contentSourceInstance.getContentSourceType(),
333
+ srcProjectId: contentSourceInstance.getProjectId(),
316
334
  instance: contentSourceInstance,
317
335
  locales: locales,
318
336
  defaultLocaleCode: defaultLocaleCode,
319
- models: models,
320
- modelMap: modelMap,
337
+ csiModels: csiModels,
321
338
  csiModelMap: csiModelMap,
322
339
  csiDocuments: csiDocuments,
323
340
  csiDocumentMap: csiDocumentMap,
324
- documents: contentStoreDocuments,
325
- documentMap: documentMap,
326
341
  csiAssets: csiAssets,
327
342
  csiAssetMap: csiAssetMap,
328
343
  assets: contentStoreAssets,
@@ -331,6 +346,7 @@ class ContentStore {
331
346
  }
332
347
  onContentChange(contentSourceId, contentChangeEvent) {
333
348
  // TODO: prevent content change process for contentSourceId if loading content is in progress
349
+ var _a, _b, _c, _d;
334
350
  this.logger.debug('onContentChange', {
335
351
  contentSourceId,
336
352
  documentCount: contentChangeEvent.documents.length,
@@ -358,8 +374,8 @@ class ContentStore {
358
374
  contentSourceData.csiDocuments.splice(index, 1);
359
375
  }
360
376
  result.deletedDocuments.push({
361
- srcType: contentSourceData.type,
362
- srcProjectId: contentSourceData.projectId,
377
+ srcType: contentSourceData.srcType,
378
+ srcProjectId: contentSourceData.srcProjectId,
363
379
  srcObjectId: docId
364
380
  });
365
381
  });
@@ -376,14 +392,35 @@ class ContentStore {
376
392
  contentSourceData.csiAssets.splice(index, 1);
377
393
  }
378
394
  result.deletedAssets.push({
379
- srcType: contentSourceData.type,
380
- srcProjectId: contentSourceData.projectId,
395
+ srcType: contentSourceData.srcType,
396
+ srcProjectId: contentSourceData.srcProjectId,
381
397
  srcObjectId: assetId
382
398
  });
383
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
+ }
384
421
  // map csi documents and assets to content store documents and assets
385
422
  const documents = (0, csi_to_store_docs_converter_1.mapCSIDocumentsToStoreDocuments)({
386
- csiDocuments: contentChangeEvent.documents,
423
+ csiDocuments: mappedDocs,
387
424
  contentSourceInstance: contentSourceData.instance,
388
425
  modelMap: contentSourceData.modelMap,
389
426
  defaultLocaleCode: contentSourceData.defaultLocaleCode
@@ -395,7 +432,7 @@ class ContentStore {
395
432
  });
396
433
  // update contentSourceData with new or updated documents and assets
397
434
  Object.assign(contentSourceData.csiDocumentMap, lodash_1.default.keyBy(contentChangeEvent.documents, 'id'));
398
- Object.assign(contentSourceData.csiAssets, lodash_1.default.keyBy(contentChangeEvent.assets, 'id'));
435
+ Object.assign(contentSourceData.csiAssetMap, lodash_1.default.keyBy(contentChangeEvent.assets, 'id'));
399
436
  Object.assign(contentSourceData.documentMap, lodash_1.default.keyBy(documents, 'srcObjectId'));
400
437
  Object.assign(contentSourceData.assetMap, lodash_1.default.keyBy(assets, 'srcObjectId'));
401
438
  for (let idx = 0; idx < documents.length; idx++) {
@@ -413,8 +450,8 @@ class ContentStore {
413
450
  contentSourceData.csiDocuments.splice(dataIndex, 1, csiDocument);
414
451
  }
415
452
  result.updatedDocuments.push({
416
- srcType: contentSourceData.type,
417
- srcProjectId: contentSourceData.projectId,
453
+ srcType: contentSourceData.srcType,
454
+ srcProjectId: contentSourceData.srcProjectId,
418
455
  srcObjectId: document.srcObjectId
419
456
  });
420
457
  }
@@ -433,18 +470,77 @@ class ContentStore {
433
470
  contentSourceData.csiAssets.splice(index, 1, csiAsset);
434
471
  }
435
472
  result.updatedAssets.push({
436
- srcType: contentSourceData.type,
437
- srcProjectId: contentSourceData.projectId,
473
+ srcType: contentSourceData.srcType,
474
+ srcProjectId: contentSourceData.srcProjectId,
438
475
  srcObjectId: asset.srcObjectId
439
476
  });
440
477
  }
441
478
  return result;
442
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
+ }
443
538
  getModels() {
444
539
  return lodash_1.default.reduce(this.contentSourceDataById, (result, contentSourceData) => {
445
540
  const contentSourceType = contentSourceData.instance.getContentSourceType();
446
541
  const srcProjectId = contentSourceData.instance.getProjectId();
447
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);
448
544
  return result;
449
545
  }, {});
450
546
  }
@@ -475,8 +571,8 @@ class ContentStore {
475
571
  contentSourceDataArr = Object.values(this.contentSourceDataById);
476
572
  }
477
573
  return (0, utils_1.reducePromise)(contentSourceDataArr, async (accum, contentSourceData) => {
478
- const srcType = contentSourceData.type;
479
- const srcProjectId = contentSourceData.projectId;
574
+ const srcType = contentSourceData.srcType;
575
+ const srcProjectId = contentSourceData.srcProjectId;
480
576
  const userContext = (0, content_store_utils_1.getUserContextForSrcType)(srcType, user);
481
577
  let result = await contentSourceData.instance.hasAccess({ userContext });
482
578
  // backwards compatibility with older CSI version
@@ -945,13 +1041,13 @@ class ContentStore {
945
1041
  const contentSourceData = this.getContentSourceDataByIdOrThrow(contentSourceId);
946
1042
  locale = locale !== null && locale !== void 0 ? locale : contentSourceData.defaultLocaleCode;
947
1043
  const { documents, assets } = getCSIDocumentsAndAssetsFromContentSourceDataByIds(contentSourceData, contentSourceObjects);
948
- const userContext = (0, content_store_utils_1.getUserContextForSrcType)(contentSourceData.type, user);
1044
+ const userContext = (0, content_store_utils_1.getUserContextForSrcType)(contentSourceData.srcType, user);
949
1045
  const internalValidationErrors = internalValidateContent(documents, assets, contentSourceData);
950
1046
  const validationResult = await contentSourceData.instance.validateDocuments({ documents, assets, locale, userContext });
951
1047
  errors = errors.concat(internalValidationErrors, validationResult.errors.map((validationError) => ({
952
1048
  message: validationError.message,
953
- srcType: contentSourceData.type,
954
- srcProjectId: contentSourceData.projectId,
1049
+ srcType: contentSourceData.srcType,
1050
+ srcProjectId: contentSourceData.srcProjectId,
955
1051
  srcObjectType: validationError.objectType,
956
1052
  srcObjectId: validationError.objectId,
957
1053
  fieldPath: validationError.fieldPath,
@@ -982,7 +1078,7 @@ class ContentStore {
982
1078
  Object.keys(objectsBySourceId).forEach((contentSourceId) => {
983
1079
  const contentSourceData = this.getContentSourceDataByIdOrThrow(contentSourceId);
984
1080
  documents.push(...contentSourceData.documents);
985
- lodash_1.default.set(schema, [contentSourceData.type, contentSourceData.projectId], contentSourceData.modelMap);
1081
+ lodash_1.default.set(schema, [contentSourceData.srcType, contentSourceData.srcProjectId], contentSourceData.modelMap);
986
1082
  });
987
1083
  return (0, search_utils_1.searchDocuments)({
988
1084
  ...data,
@@ -995,7 +1091,7 @@ class ContentStore {
995
1091
  const objectsBySourceId = lodash_1.default.groupBy(objects, (object) => (0, content_store_utils_1.getContentSourceId)(object.srcType, object.srcProjectId));
996
1092
  for (const [contentSourceId, contentSourceObjects] of Object.entries(objectsBySourceId)) {
997
1093
  const contentSourceData = this.getContentSourceDataByIdOrThrow(contentSourceId);
998
- const userContext = (0, content_store_utils_1.getUserContextForSrcType)(contentSourceData.type, user);
1094
+ const userContext = (0, content_store_utils_1.getUserContextForSrcType)(contentSourceData.srcType, user);
999
1095
  const { documents, assets } = getCSIDocumentsAndAssetsFromContentSourceDataByIds(contentSourceData, contentSourceObjects);
1000
1096
  await contentSourceData.instance.publishDocuments({ documents, assets, userContext });
1001
1097
  }
@@ -1007,6 +1103,18 @@ class ContentStore {
1007
1103
  }
1008
1104
  return contentSourceData;
1009
1105
  }
1106
+ onWebhook({ srcType, srcProjectId, data, headers }) {
1107
+ var _a, _b;
1108
+ const contentSourceId = (0, content_store_utils_1.getContentSourceId)(srcType, srcProjectId);
1109
+ const contentSourceData = this.getContentSourceDataByIdOrThrow(contentSourceId);
1110
+ return (_b = (_a = contentSourceData.instance).onWebhook) === null || _b === void 0 ? void 0 : _b.call(_a, { data, headers });
1111
+ }
1112
+ getWebhookUrl(contentSourceType, projectId) {
1113
+ if (!this.webhookUrl) {
1114
+ return undefined;
1115
+ }
1116
+ return `${this.webhookUrl}/${encodeURIComponent(contentSourceType)}/${encodeURIComponent(projectId)}`;
1117
+ }
1010
1118
  }
1011
1119
  exports.ContentStore = ContentStore;
1012
1120
  function mapStoreFieldsToOperationFields({ documentFields, modelFields, modelMap }) {
@@ -1058,8 +1166,8 @@ function validateDocumentFields(document, documentField, fieldPath, contentSourc
1058
1166
  if (!objRef) {
1059
1167
  errors.push({
1060
1168
  fieldPath,
1061
- srcType: contentSourceData.type,
1062
- srcProjectId: contentSourceData.projectId,
1169
+ srcType: contentSourceData.srcType,
1170
+ srcProjectId: contentSourceData.srcProjectId,
1063
1171
  srcObjectType: documentField.refType,
1064
1172
  srcObjectId: document.id,
1065
1173
  message: `Can't find referenced ${documentField.refType}: ${documentField.refId}`