@stackbit/cms-core 0.1.13 → 0.1.14-canary.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.ContentStore = void 0;
6
+ exports.ContentStore = exports.StackbitPresetModelName = void 0;
7
7
  const lodash_1 = __importDefault(require("lodash"));
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const sanitize_filename_1 = __importDefault(require("sanitize-filename"));
@@ -13,13 +13,15 @@ const utils_1 = require("@stackbit/utils");
13
13
  const timer_1 = require("./utils/timer");
14
14
  const search_utils_1 = require("./utils/search-utils");
15
15
  const csi_to_store_docs_converter_1 = require("./utils/csi-to-store-docs-converter");
16
- const store_to_csi_docs_converter_1 = require("./utils/store-to-csi-docs-converter");
17
16
  const content_store_utils_1 = require("./content-store-utils");
17
+ const site_map_1 = require("./utils/site-map");
18
18
  const store_to_api_docs_converter_1 = require("./utils/store-to-api-docs-converter");
19
19
  const create_update_csi_docs_1 = require("./utils/create-update-csi-docs");
20
20
  const duplicate_document_1 = require("./utils/duplicate-document");
21
21
  const model_utils_1 = require("./utils/model-utils");
22
22
  const common_schema_1 = require("./common/common-schema");
23
+ const preset_utils_1 = require("./utils/preset-utils");
24
+ exports.StackbitPresetModelName = 'stackbitPreset';
23
25
  class ContentStore {
24
26
  constructor(options) {
25
27
  this.contentSources = [];
@@ -29,6 +31,7 @@ class ContentStore {
29
31
  this.configModels = [];
30
32
  this.modelExtensions = null;
31
33
  this.presets = {};
34
+ this.siteMapEntryGroups = {};
32
35
  this.logger = options.logger.createLogger({ label: 'content-store' });
33
36
  this.userLogger = options.userLogger.createLogger({ label: 'content-store' });
34
37
  this.localDev = options.localDev;
@@ -79,7 +82,6 @@ class ContentStore {
79
82
  this.yamlModels = await this.loadYamlModels({ stackbitConfig });
80
83
  this.configModels = this.mergeConfigModels((_a = stackbitConfig.models) !== null && _a !== void 0 ? _a : [], this.yamlModels);
81
84
  }
82
- this.presets = await this.loadPresets({ stackbitConfig });
83
85
  }
84
86
  await this.loadContentSourcesAndProcessData({ init: true });
85
87
  this.contentUpdatesWatchTimer.startTimer();
@@ -146,10 +148,10 @@ class ContentStore {
146
148
  // Check if any of the preset files were changed. If presets were changed, reload them.
147
149
  const presetDirs = (0, sdk_1.getPresetDirs)(this.stackbitConfig);
148
150
  const presetsChanged = updatedFiles.find((updatedFile) => lodash_1.default.some(presetDirs, (presetDir) => updatedFile.startsWith(presetDir)));
149
- if (presetsChanged) {
151
+ if (presetsChanged && !this.usesContentSourcePresets()) {
150
152
  this.logger.debug('identified change in stackbit preset files');
151
153
  schemaChanged = true;
152
- this.presets = await this.loadPresets({ stackbitConfig: this.stackbitConfig });
154
+ this.presets = await this.loadPresetsFromConfig({ stackbitConfig: this.stackbitConfig });
153
155
  }
154
156
  }
155
157
  const contentSourceIdsWithChangedSchema = [];
@@ -169,7 +171,7 @@ class ContentStore {
169
171
  contentChangeEvents.push({ contentSourceId, contentChangeEvent: onFilesChangeResult.contentChangeEvent });
170
172
  }
171
173
  }
172
- const contentChanges = {
174
+ let contentChanges = {
173
175
  updatedDocuments: [],
174
176
  updatedAssets: [],
175
177
  deletedDocuments: [],
@@ -182,14 +184,21 @@ class ContentStore {
182
184
  await this.loadContentSourcesAndProcessData({ init: false, contentSourceIds: contentSourceIdsWithChangedSchema });
183
185
  }
184
186
  else {
185
- contentChangeEvents.reduce((contentChanges, { contentSourceId, contentChangeEvent }) => {
187
+ contentChanges = contentChangeEvents.reduce((contentChanges, { contentSourceId, contentChangeEvent }) => {
186
188
  const contentChangeResult = this.onContentChange(contentSourceId, contentChangeEvent);
187
- contentChanges.updatedDocuments = contentChanges.updatedDocuments.concat(contentChangeResult.updatedDocuments);
188
- contentChanges.updatedAssets = contentChanges.updatedAssets.concat(contentChangeResult.updatedAssets);
189
- contentChanges.deletedDocuments = contentChanges.deletedDocuments.concat(contentChangeResult.deletedDocuments);
190
- contentChanges.deletedAssets = contentChanges.deletedAssets.concat(contentChangeResult.deletedAssets);
191
- return contentChanges;
189
+ return {
190
+ updatedDocuments: contentChanges.updatedDocuments.concat(contentChangeResult.updatedDocuments),
191
+ updatedAssets: contentChanges.updatedAssets.concat(contentChangeResult.updatedAssets),
192
+ deletedDocuments: contentChanges.deletedDocuments.concat(contentChangeResult.deletedDocuments),
193
+ deletedAssets: contentChanges.deletedAssets.concat(contentChangeResult.deletedAssets)
194
+ };
192
195
  }, contentChanges);
196
+ this.siteMapEntryGroups = await (0, site_map_1.updateSiteMapEntriesWithContentChanges)({
197
+ siteMapEntryGroups: this.siteMapEntryGroups,
198
+ contentChanges,
199
+ stackbitConfig: this.stackbitConfig,
200
+ contentSourceDataById: this.contentSourceDataById
201
+ });
193
202
  }
194
203
  // TODO: maybe instead of returning object with results
195
204
  // replace with this.onSchemaChangeCallback() and this.onContentChangeCallback(contentChanges) for consistency of data flow?
@@ -212,7 +221,7 @@ class ContentStore {
212
221
  }
213
222
  return configModelsResult.models;
214
223
  }
215
- async loadPresets({ stackbitConfig }) {
224
+ async loadPresetsFromConfig({ stackbitConfig }) {
216
225
  var _a;
217
226
  const contentSources = (_a = stackbitConfig === null || stackbitConfig === void 0 ? void 0 : stackbitConfig.contentSources) !== null && _a !== void 0 ? _a : [];
218
227
  const singleContentSource = contentSources.length === 1 ? contentSources[0] : null;
@@ -231,6 +240,24 @@ class ContentStore {
231
240
  const { presets } = await this.handleConfigAssets({ presets: presetResult.presets });
232
241
  return presets;
233
242
  }
243
+ async loadPresetsFromContentSource(contentSourceData) {
244
+ const presets = lodash_1.default.reduce(contentSourceData.csiDocuments, (result, csiDocument) => {
245
+ if (csiDocument.modelName === exports.StackbitPresetModelName) {
246
+ const preset = (0, preset_utils_1.getPresetFromDocument)({
247
+ srcType: contentSourceData.srcType,
248
+ srcProjectId: contentSourceData.srcProjectId,
249
+ csiDocument,
250
+ csiAssetMap: contentSourceData.csiAssetMap,
251
+ logger: this.logger
252
+ });
253
+ if (preset) {
254
+ result[csiDocument.id] = preset;
255
+ }
256
+ }
257
+ return result;
258
+ }, {});
259
+ return presets;
260
+ }
234
261
  /**
235
262
  * This function reloads the data of the specified content-sources, while
236
263
  * reusing the cached data of the rest of the content-sources, then processes
@@ -249,7 +276,7 @@ class ContentStore {
249
276
  * @private
250
277
  */
251
278
  async loadContentSourcesAndProcessData({ init, contentSourceIds }) {
252
- var _a, _b, _c, _d;
279
+ var _a, _b, _c, _d, _e;
253
280
  this.logger.debug('loadContentSourcesAndProcessData', { init, contentSourceIds });
254
281
  const contentSources = (_b = (_a = this.stackbitConfig) === null || _a === void 0 ? void 0 : _a.contentSources) !== null && _b !== void 0 ? _b : [];
255
282
  const promises = contentSources.map((contentSourceInstance) => {
@@ -262,14 +289,38 @@ class ContentStore {
262
289
  }
263
290
  });
264
291
  const contentSourceRawDataArr = await Promise.all(promises);
292
+ // find first content source that supports presets
293
+ for (let i = 0; i < contentSources.length; i++) {
294
+ const contentSourceDataRaw = contentSourceRawDataArr[i];
295
+ if ((_c = contentSourceDataRaw === null || contentSourceDataRaw === void 0 ? void 0 : contentSourceDataRaw.csiModelMap) === null || _c === void 0 ? void 0 : _c[exports.StackbitPresetModelName]) {
296
+ this.presetsContentSource = contentSources[i];
297
+ if (this.presetsContentSource) {
298
+ const contentSourceId = (0, content_store_utils_1.getContentSourceIdForContentSource)(this.presetsContentSource);
299
+ // reload presets from content source only if needed
300
+ if (init || !contentSourceIds || contentSourceIds.includes(contentSourceId)) {
301
+ this.presets = await this.loadPresetsFromContentSource(contentSourceDataRaw);
302
+ }
303
+ }
304
+ break;
305
+ }
306
+ }
307
+ // fallback to loading presets from config as usual
308
+ if (init && this.stackbitConfig && !this.presetsContentSource) {
309
+ this.presets = await this.loadPresetsFromConfig({ stackbitConfig: this.stackbitConfig });
310
+ }
265
311
  // update all content sources at once to prevent race conditions
266
312
  this.contentSourceDataById = await this.processData({
267
313
  stackbitConfig: this.stackbitConfig,
268
- configModels: (_d = (_c = this.modelExtensions) !== null && _c !== void 0 ? _c : this.configModels) !== null && _d !== void 0 ? _d : [],
314
+ configModels: (_e = (_d = this.modelExtensions) !== null && _d !== void 0 ? _d : this.configModels) !== null && _e !== void 0 ? _e : [],
269
315
  presets: this.presets,
270
316
  contentSourceRawDataArr: contentSourceRawDataArr
271
317
  });
272
318
  this.contentSources = contentSources;
319
+ // generate create site map entries
320
+ this.siteMapEntryGroups = await (0, site_map_1.getSiteMapEntriesFromStackbitConfig)({
321
+ stackbitConfig: this.stackbitConfig,
322
+ contentSourceDataById: this.contentSourceDataById
323
+ });
273
324
  }
274
325
  async loadContentSourceData({ contentSourceInstance, init }) {
275
326
  var _a;
@@ -321,9 +372,15 @@ class ContentStore {
321
372
  getAsset({ assetId }) {
322
373
  return csiAssetMap[assetId];
323
374
  },
324
- onContentChange: (contentChangeEvent) => {
375
+ onContentChange: async (contentChangeEvent) => {
325
376
  this.logger.debug('content source called onContentChange', { contentSourceId });
326
377
  const result = this.onContentChange(contentSourceId, contentChangeEvent);
378
+ this.siteMapEntryGroups = await (0, site_map_1.updateSiteMapEntriesWithContentChanges)({
379
+ siteMapEntryGroups: this.siteMapEntryGroups,
380
+ contentChanges: result,
381
+ stackbitConfig: this.stackbitConfig,
382
+ contentSourceDataById: this.contentSourceDataById
383
+ });
327
384
  this.onContentChangeCallback(result);
328
385
  },
329
386
  onSchemaChange: async () => {
@@ -351,7 +408,9 @@ class ContentStore {
351
408
  }
352
409
  onContentChange(contentSourceId, contentChangeEvent) {
353
410
  // TODO: prevent content change process for contentSourceId if loading content is in progress
354
- var _a, _b, _c, _d;
411
+ var _a, _b, _c, _d, _e, _f, _g;
412
+ // certain content changes, like preset changes are interpreted as schema changes
413
+ let schemaChanged = false;
355
414
  this.logger.debug('onContentChange', {
356
415
  contentSourceId,
357
416
  documentCount: contentChangeEvent.documents.length,
@@ -368,6 +427,19 @@ class ContentStore {
368
427
  const contentSourceData = this.getContentSourceDataByIdOrThrow(contentSourceId);
369
428
  // update contentSourceData with deleted documents
370
429
  contentChangeEvent.deletedDocumentIds.forEach((docId) => {
430
+ var _a;
431
+ // remove preset, make sure there is something to remove first because
432
+ // were explicitly calling onContentChange from deletePreset as well
433
+ if (this.presets[docId] && ((_a = contentSourceData.csiDocumentMap[docId]) === null || _a === void 0 ? void 0 : _a.modelName) === exports.StackbitPresetModelName) {
434
+ schemaChanged = true;
435
+ const preset = this.presets[docId];
436
+ const model = contentSourceData.modelMap[preset.modelName];
437
+ delete this.presets[docId];
438
+ if (model && model.presets) {
439
+ const presetIdIndex = model.presets.findIndex((presetId) => presetId === docId);
440
+ model.presets.splice(presetIdIndex, 1);
441
+ }
442
+ }
371
443
  // delete document from documents map
372
444
  delete contentSourceData.documentMap[docId];
373
445
  delete contentSourceData.csiDocumentMap[docId];
@@ -450,10 +522,26 @@ class ContentStore {
450
522
  contentSourceData.csiDocuments.push(csiDocument);
451
523
  }
452
524
  else {
453
- // the indexes of documents and csiDocuments are always the same as they are always updated at the same time
454
525
  contentSourceData.documents.splice(dataIndex, 1, document);
455
526
  contentSourceData.csiDocuments.splice(dataIndex, 1, csiDocument);
456
527
  }
528
+ if (csiDocument.modelName === exports.StackbitPresetModelName) {
529
+ schemaChanged = true;
530
+ const preset = (0, preset_utils_1.getPresetFromDocument)({
531
+ srcType: contentSourceData.srcType,
532
+ srcProjectId: contentSourceData.srcProjectId,
533
+ csiDocument,
534
+ csiAssetMap: contentSourceData.csiAssetMap,
535
+ logger: this.logger
536
+ });
537
+ if (preset) {
538
+ this.presets[csiDocument.id] = preset;
539
+ if (dataIndex === -1) {
540
+ //TODO recalculate assets as well
541
+ (_f = (_e = contentSourceData.modelMap[preset.modelName]) === null || _e === void 0 ? void 0 : _e.presets) === null || _f === void 0 ? void 0 : _f.push(csiDocument.id);
542
+ }
543
+ }
544
+ }
457
545
  result.updatedDocuments.push({
458
546
  srcType: contentSourceData.srcType,
459
547
  srcProjectId: contentSourceData.srcProjectId,
@@ -480,6 +568,9 @@ class ContentStore {
480
568
  srcObjectId: asset.srcObjectId
481
569
  });
482
570
  }
571
+ if (schemaChanged) {
572
+ (_g = this.onSchemaChangeCallback) === null || _g === void 0 ? void 0 : _g.call(this);
573
+ }
483
574
  return result;
484
575
  }
485
576
  async processData({ stackbitConfig, configModels, presets, contentSourceRawDataArr }) {
@@ -503,44 +594,58 @@ class ContentStore {
503
594
  // srcProjectId. If after the comparison, there are more than one model left,
504
595
  // log a warning and filter out that config model so it won't be merged with any
505
596
  // of the content source models.
506
- const modelMatchErrors = [];
507
- const filteredConfigModels = configModels.filter((configModel) => {
597
+ const nonMatchedModels = [];
598
+ const configModelsByContentSourceId = configModels.reduce((modelGroups, configModel) => {
508
599
  const csiModels = csiModelGroups[configModel.name];
509
600
  if (!csiModels) {
510
- return false;
601
+ nonMatchedModels.push({
602
+ configModel,
603
+ matchedCSIModels: []
604
+ });
605
+ return modelGroups;
511
606
  }
512
607
  const matchedCSIModels = csiModels.filter((model) => {
513
608
  const matchesType = !configModel.srcType || model.srcType === configModel.srcType;
514
609
  const matchesId = !configModel.srcProjectId || model.srcProjectId === configModel.srcProjectId;
515
610
  return matchesType && matchesId;
516
611
  });
517
- if (matchedCSIModels.length === 0) {
518
- return false;
519
- }
520
- if (matchedCSIModels.length === 1) {
521
- return true;
612
+ if (matchedCSIModels.length !== 1) {
613
+ nonMatchedModels.push({
614
+ configModel,
615
+ matchedCSIModels
616
+ });
617
+ return modelGroups;
522
618
  }
523
- modelMatchErrors.push({
524
- configModel,
525
- matchedCSIModels
526
- });
527
- return false;
528
- });
619
+ const contentSource = matchedCSIModels[0];
620
+ const contentSourceId = (0, content_store_utils_1.getContentSourceId)(contentSource.srcType, contentSource.srcProjectId);
621
+ (0, utils_1.append)(modelGroups, contentSourceId, configModel);
622
+ return modelGroups;
623
+ }, {});
529
624
  // Log model matching warnings using user logger
530
- for (const { configModel, matchedCSIModels } of modelMatchErrors) {
531
- let message = `name: '${configModel.name}'`;
625
+ for (const { configModel, matchedCSIModels } of nonMatchedModels) {
626
+ let configModelMessage = `model name: '${configModel.name}'`;
532
627
  if (configModel.srcType) {
533
- message += `, srcType: '${configModel.srcType}'`;
628
+ configModelMessage += `, srcType: '${configModel.srcType}'`;
534
629
  }
535
630
  if (configModel.srcProjectId) {
536
- message += `, srcProjectId: '${configModel.srcProjectId}'`;
631
+ configModelMessage += `, srcProjectId: '${configModel.srcProjectId}'`;
537
632
  }
538
- const matchesModelsMessage = matchedCSIModels.map((model) => `srcType: '${model.srcType}', srcProjectId: '${model.srcProjectId}'`).join('; ');
539
- this.userLogger.warn(`model ${message} defined in stackbit config matches more that 1 model in the following content sources: ${matchesModelsMessage}`);
633
+ configModelMessage = configModelMessage + ` defined in stackbit config`;
634
+ let contentSourceModelsMessage;
635
+ if (matchedCSIModels.length) {
636
+ const matchesModelsMessage = matchedCSIModels.map((model) => `srcType: '${model.srcType}', srcProjectId: '${model.srcProjectId}'`).join('; ');
637
+ contentSourceModelsMessage = ` matches more that 1 model in the following content sources: ${matchesModelsMessage}`;
638
+ }
639
+ else {
640
+ contentSourceModelsMessage = ' does not match any content source model';
641
+ }
642
+ this.userLogger.warn(configModelMessage + contentSourceModelsMessage);
540
643
  }
541
644
  const modelsWithSource = contentSourceRawDataArr.reduce((accum, csData) => {
645
+ var _a;
646
+ const contentSourceId = (0, content_store_utils_1.getContentSourceId)(csData.srcType, csData.srcProjectId);
542
647
  const mergedModels = (0, sdk_1.mergeConfigModelsWithExternalModels)({
543
- configModels: filteredConfigModels,
648
+ configModels: (_a = configModelsByContentSourceId[contentSourceId]) !== null && _a !== void 0 ? _a : [],
544
649
  externalModels: csData.csiModels
545
650
  });
546
651
  const modelsWithSource = mergedModels.map((model) => {
@@ -559,27 +664,32 @@ class ContentStore {
559
664
  const validatedModels = (0, model_utils_1.validateModels)({ models: normalizedModels, logger: this.userLogger });
560
665
  const modelsWithPresetsIds = (0, sdk_1.extendModelsWithPresetsIds)({ models: validatedModels, presets });
561
666
  const { models } = await this.handleConfigAssets({ models: modelsWithPresetsIds });
562
- const csiDocumentsWithSource = contentSourceRawDataArr.reduce((accum, csData) => {
563
- const csiDocumentsWithSource = csData.csiDocuments.map((csiDocument) => ({
564
- srcType: csData.srcType,
565
- srcProjectId: csData.srcProjectId,
566
- ...csiDocument
567
- }));
568
- return accum.concat(csiDocumentsWithSource);
569
- }, []);
570
- // TODO: Is there a better way than deep cloning objects before passing them to user methods?
571
- // Not cloning mutable objects will break the internal state if user mutates the objects.
572
- const mappedDocs = (_d = (_c = stackbitConfig === null || stackbitConfig === void 0 ? void 0 : stackbitConfig.mapDocuments) === null || _c === void 0 ? void 0 : _c.call(stackbitConfig, {
573
- documents: lodash_1.default.cloneDeep(csiDocumentsWithSource),
574
- models: lodash_1.default.cloneDeep(models)
575
- })) !== null && _d !== void 0 ? _d : csiDocumentsWithSource;
667
+ let documentMapByContentSource = null;
668
+ if (stackbitConfig === null || stackbitConfig === void 0 ? void 0 : stackbitConfig.mapDocuments) {
669
+ const csiDocumentsWithSource = contentSourceRawDataArr.reduce((accum, csData) => {
670
+ const csiDocumentsWithSource = csData.csiDocuments.map((csiDocument) => ({
671
+ srcType: csData.srcType,
672
+ srcProjectId: csData.srcProjectId,
673
+ ...csiDocument
674
+ }));
675
+ return accum.concat(csiDocumentsWithSource);
676
+ }, []);
677
+ // TODO: Is there a better way than deep cloning objects before passing them to user methods?
678
+ // Not cloning mutable objects will break the internal state if user mutates the objects.
679
+ const mappedDocs = (_d = (_c = stackbitConfig === null || stackbitConfig === void 0 ? void 0 : stackbitConfig.mapDocuments) === null || _c === void 0 ? void 0 : _c.call(stackbitConfig, {
680
+ documents: lodash_1.default.cloneDeep(csiDocumentsWithSource),
681
+ models: lodash_1.default.cloneDeep(models)
682
+ })) !== null && _d !== void 0 ? _d : csiDocumentsWithSource;
683
+ documentMapByContentSource = (0, content_store_utils_1.groupDocumentsByContentSource)({ documents: mappedDocs });
684
+ }
576
685
  const modelMapByContentSource = (0, content_store_utils_1.groupModelsByContentSource)({ models: models });
577
- const documentMapByContentSource = (0, content_store_utils_1.groupDocumentsByContentSource)({ documents: mappedDocs });
578
686
  const contentSourceDataArr = contentSourceRawDataArr.map((csData) => {
579
687
  const modelMap = lodash_1.default.get(modelMapByContentSource, [csData.srcType, csData.srcProjectId], {});
580
- const mappedCSIDocuments = lodash_1.default.get(documentMapByContentSource, [csData.srcType, csData.srcProjectId], []);
688
+ const csiDocuments = documentMapByContentSource
689
+ ? lodash_1.default.get(documentMapByContentSource, [csData.srcType, csData.srcProjectId], [])
690
+ : csData.csiDocuments;
581
691
  const documents = (0, csi_to_store_docs_converter_1.mapCSIDocumentsToStoreDocuments)({
582
- csiDocuments: mappedCSIDocuments,
692
+ csiDocuments: csiDocuments,
583
693
  contentSourceInstance: csData.instance,
584
694
  defaultLocaleCode: csData.defaultLocaleCode,
585
695
  modelMap: modelMap
@@ -622,6 +732,9 @@ class ContentStore {
622
732
  const contentSourceData = this.getContentSourceDataByIdOrThrow(contentSourceId);
623
733
  return contentSourceData.instance.getProjectEnvironment();
624
734
  }
735
+ usesContentSourcePresets() {
736
+ return Boolean(this.presetsContentSource);
737
+ }
625
738
  async hasAccess({ srcType, srcProjectId, user }) {
626
739
  let contentSourceDataArr;
627
740
  if (srcType && srcProjectId) {
@@ -697,50 +810,22 @@ class ContentStore {
697
810
  };
698
811
  }
699
812
  getSiteMapEntries({ locale } = {}) {
700
- var _a;
701
- if (!((_a = this.stackbitConfig) === null || _a === void 0 ? void 0 : _a.siteMap)) {
702
- return [];
703
- }
704
- // TODO: cache siteMap in processData
705
- const siteMapOptions = lodash_1.default.reduce(this.contentSourceDataById, (accum, contentSourceData) => {
706
- return {
707
- models: accum.models.concat(contentSourceData.models.map((model) => ({
708
- srcType: contentSourceData.srcType,
709
- srcProjectId: contentSourceData.srcProjectId,
710
- ...model
711
- }))),
712
- documents: accum.documents.concat((0, store_to_csi_docs_converter_1.mapStoreDocumentsToCSIDocumentsWithSource)(contentSourceData.documents))
713
- };
714
- }, { models: [], documents: [] });
715
- const siteMapEntries = this.stackbitConfig.siteMap(siteMapOptions).reduce((accum, siteMapEntry) => {
716
- // The site map entries are provided by user, sanitize them and filter out illegal entries
717
- if (!siteMapEntry) {
718
- return accum;
719
- }
720
- if (typeof siteMapEntry.urlPath !== 'string') {
721
- return accum;
722
- }
723
- if ('document' in siteMapEntry) {
724
- const doc = siteMapEntry.document;
725
- if (!doc.srcType || !doc.srcProjectId || !doc.modelName || !doc.id) {
726
- return accum;
813
+ const siteMapEntries = lodash_1.default.reduce(this.siteMapEntryGroups, (accum, siteMapEntryGroup) => {
814
+ return lodash_1.default.reduce(siteMapEntryGroup, (accum, siteMapEntry) => {
815
+ if (!siteMapEntry.label) {
816
+ const fieldLabelValue = (0, site_map_1.getDocumentFieldLabelValueForSiteMapEntry)({
817
+ siteMapEntry,
818
+ locale,
819
+ contentSourceDataById: this.contentSourceDataById
820
+ });
821
+ siteMapEntry = {
822
+ ...siteMapEntry,
823
+ label: fieldLabelValue !== null && fieldLabelValue !== void 0 ? fieldLabelValue : siteMapEntry.urlPath
824
+ };
727
825
  }
728
- }
729
- if (!siteMapEntry.label) {
730
- const fieldLabelValue = (0, content_store_utils_1.getDocumentFieldLabelValueForSiteMapEntry)({ siteMapEntry, locale, contentSourceDataById: this.contentSourceDataById });
731
- siteMapEntry = {
732
- ...siteMapEntry,
733
- label: fieldLabelValue !== null && fieldLabelValue !== void 0 ? fieldLabelValue : siteMapEntry.urlPath
734
- };
735
- }
736
- if (!siteMapEntry.stableId) {
737
- siteMapEntry = {
738
- ...siteMapEntry,
739
- stableId: 'document' in siteMapEntry ? siteMapEntry.document.id : siteMapEntry.urlPath
740
- };
741
- }
742
- accum.push(siteMapEntry);
743
- return accum;
826
+ accum.push(siteMapEntry);
827
+ return accum;
828
+ }, accum);
744
829
  }, []);
745
830
  return lodash_1.default.isEmpty(locale) ? siteMapEntries : siteMapEntries.filter((siteMapEntry) => !siteMapEntry.locale || siteMapEntry.locale === locale);
746
831
  }
@@ -754,7 +839,8 @@ class ContentStore {
754
839
  const currentDocuments = lodash_1.default.isEmpty(locale)
755
840
  ? contentSourceData.documents
756
841
  : contentSourceData.documents.filter((document) => !document.locale || document.locale === locale);
757
- return documents.concat(currentDocuments);
842
+ const filteredDocuments = currentDocuments.filter((document) => document.srcModelName !== exports.StackbitPresetModelName);
843
+ return documents.concat(filteredDocuments);
758
844
  }, []);
759
845
  }
760
846
  getAsset({ srcAssetId, srcProjectId, srcType }) {
@@ -780,7 +866,8 @@ class ContentStore {
780
866
  ? contentSourceData.assets.filter((asset) => !asset.locale || asset.locale === locale)
781
867
  : contentSourceData.assets;
782
868
  const currentLocale = locale !== null && locale !== void 0 ? locale : contentSourceData.defaultLocaleCode;
783
- const documentObjects = (0, store_to_api_docs_converter_1.mapDocumentsToLocalizedApiObjects)(documents, currentLocale);
869
+ const filteredDocuments = documents.filter((document) => document.srcModelName !== exports.StackbitPresetModelName);
870
+ const documentObjects = (0, store_to_api_docs_converter_1.mapDocumentsToLocalizedApiObjects)(filteredDocuments, currentLocale);
784
871
  const imageObjects = (0, store_to_api_docs_converter_1.mapAssetsToLocalizedApiImages)(assets, currentLocale);
785
872
  return objects.concat(documentObjects, imageObjects);
786
873
  }, []);
@@ -889,6 +976,53 @@ class ContentStore {
889
976
  });
890
977
  return { srcDocumentId: updatedDocument.id };
891
978
  }
979
+ async createPreset({ preset, thumbnailAsset, user }) {
980
+ var _a;
981
+ if (!this.presetsContentSource) {
982
+ throw new Error('No content source available for preset saving');
983
+ }
984
+ let thumbnail;
985
+ if (thumbnailAsset) {
986
+ const assets = await this.uploadAssets({
987
+ srcType: this.presetsContentSource.getContentSourceType(),
988
+ srcProjectId: this.presetsContentSource.getProjectId(),
989
+ assets: [thumbnailAsset],
990
+ user
991
+ });
992
+ thumbnail = (_a = assets[0]) === null || _a === void 0 ? void 0 : _a.objectId;
993
+ }
994
+ const contentSourceData = this.getContentSourceDataByIdOrThrow((0, content_store_utils_1.getContentSourceIdForContentSource)(this.presetsContentSource));
995
+ const document = await this.createDocument({
996
+ srcType: this.presetsContentSource.getContentSourceType(),
997
+ srcProjectId: this.presetsContentSource.getProjectId(),
998
+ modelName: exports.StackbitPresetModelName,
999
+ object: {
1000
+ ...(0, preset_utils_1.getDocumentObjectFromPreset)(preset, contentSourceData.modelMap[exports.StackbitPresetModelName]),
1001
+ thumbnail
1002
+ },
1003
+ user
1004
+ });
1005
+ return { srcDocumentId: document.srcDocumentId };
1006
+ }
1007
+ async deletePreset({ presetId, user }) {
1008
+ if (!this.presetsContentSource) {
1009
+ throw new Error('No content source available for preset deleting');
1010
+ }
1011
+ await this.deleteDocument({
1012
+ srcType: this.presetsContentSource.getContentSourceType(),
1013
+ srcProjectId: this.presetsContentSource.getProjectId(),
1014
+ srcDocumentId: presetId,
1015
+ user
1016
+ });
1017
+ // we delete presets immediately because some CMSs don't notify us
1018
+ // when documents have been deleted.
1019
+ this.onContentChange((0, content_store_utils_1.getContentSourceIdForContentSource)(this.presetsContentSource), {
1020
+ documents: [],
1021
+ deletedDocumentIds: [presetId],
1022
+ assets: [],
1023
+ deletedAssetIds: []
1024
+ });
1025
+ }
892
1026
  async uploadAndLinkAsset({ srcType, srcProjectId, srcDocumentId, fieldPath, asset, index, locale, user }) {
893
1027
  this.logger.debug('uploadAndLinkAsset', { srcType, srcProjectId, srcDocumentId, fieldPath, index, locale });
894
1028
  // get the document that is being updated
@@ -1199,7 +1333,8 @@ class ContentStore {
1199
1333
  const contentSourceDocuments = lodash_1.default.isEmpty(locale)
1200
1334
  ? contentSourceData.documents
1201
1335
  : contentSourceData.documents.filter((document) => !document.locale || document.locale === locale);
1202
- documents.push(...contentSourceDocuments);
1336
+ const filteredDocuments = contentSourceDocuments.filter((document) => document.srcModelName !== exports.StackbitPresetModelName);
1337
+ documents.push(...filteredDocuments);
1203
1338
  if (contentSourceData.defaultLocaleCode) {
1204
1339
  defaultLocales[contentSourceId] = contentSourceData.defaultLocaleCode;
1205
1340
  }