@stackbit/cms-core 0.0.20 → 0.0.21-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/annotator/html.d.ts +1 -0
  2. package/dist/annotator/html.d.ts.map +1 -0
  3. package/dist/annotator/index.d.ts +3 -2
  4. package/dist/annotator/index.d.ts.map +1 -0
  5. package/dist/annotator/react.d.ts +1 -0
  6. package/dist/annotator/react.d.ts.map +1 -0
  7. package/dist/common/common-schema.d.ts +3 -10
  8. package/dist/common/common-schema.d.ts.map +1 -0
  9. package/dist/common/common-schema.js +3 -4
  10. package/dist/common/common-schema.js.map +1 -1
  11. package/dist/common/common-types.d.ts +10 -0
  12. package/dist/common/common-types.d.ts.map +1 -0
  13. package/dist/common/common-types.js +3 -0
  14. package/dist/common/common-types.js.map +1 -0
  15. package/dist/consts.d.ts +1 -0
  16. package/dist/consts.d.ts.map +1 -0
  17. package/dist/content-source-interface.d.ts +338 -0
  18. package/dist/content-source-interface.d.ts.map +1 -0
  19. package/dist/content-source-interface.js +28 -0
  20. package/dist/content-source-interface.js.map +1 -0
  21. package/dist/content-store-types.d.ts +347 -0
  22. package/dist/content-store-types.d.ts.map +1 -0
  23. package/dist/content-store-types.js +3 -0
  24. package/dist/content-store-types.js.map +1 -0
  25. package/dist/content-store.d.ts +210 -0
  26. package/dist/content-store.d.ts.map +1 -0
  27. package/dist/content-store.js +1814 -0
  28. package/dist/content-store.js.map +1 -0
  29. package/dist/encoder.d.ts +36 -7
  30. package/dist/encoder.d.ts.map +1 -0
  31. package/dist/encoder.js +63 -40
  32. package/dist/encoder.js.map +1 -1
  33. package/dist/index.d.ts +12 -6
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +38 -11
  36. package/dist/index.js.map +1 -1
  37. package/dist/stackbit/index.d.ts +7 -3
  38. package/dist/stackbit/index.d.ts.map +1 -0
  39. package/dist/stackbit/index.js +13 -10
  40. package/dist/stackbit/index.js.map +1 -1
  41. package/dist/utils/index.d.ts +8 -7
  42. package/dist/utils/index.d.ts.map +1 -0
  43. package/dist/utils/schema-utils.d.ts +3 -2
  44. package/dist/utils/schema-utils.d.ts.map +1 -0
  45. package/dist/utils/timer.d.ts +18 -0
  46. package/dist/utils/timer.d.ts.map +1 -0
  47. package/dist/utils/timer.js +36 -0
  48. package/dist/utils/timer.js.map +1 -0
  49. package/package.json +8 -4
  50. package/src/common/common-schema.ts +12 -0
  51. package/src/common/common-types.ts +10 -0
  52. package/src/content-source-interface.ts +495 -0
  53. package/src/content-store-types.ts +430 -0
  54. package/src/content-store.ts +2333 -0
  55. package/src/{encoder.js → encoder.ts} +55 -17
  56. package/src/index.ts +11 -0
  57. package/src/stackbit/{index.js → index.ts} +5 -9
  58. package/src/utils/timer.ts +42 -0
  59. package/dist/utils/lazy-poller.d.ts +0 -21
  60. package/dist/utils/lazy-poller.js +0 -56
  61. package/dist/utils/lazy-poller.js.map +0 -1
  62. package/src/common/common-schema.js +0 -14
  63. package/src/index.js +0 -13
  64. package/src/utils/lazy-poller.ts +0 -74
@@ -0,0 +1,1814 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getContentSourceId = exports.ContentStore = void 0;
7
+ const lodash_1 = __importDefault(require("lodash"));
8
+ const slugify_1 = __importDefault(require("slugify"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const sanitize_filename_1 = __importDefault(require("sanitize-filename"));
11
+ const sdk_1 = require("@stackbit/sdk");
12
+ const utils_1 = require("@stackbit/utils");
13
+ const content_source_interface_1 = require("./content-source-interface");
14
+ const common_schema_1 = require("./common/common-schema");
15
+ const timer_1 = require("./utils/timer");
16
+ class ContentStore {
17
+ constructor(options) {
18
+ this.contentSourceDataById = {};
19
+ this.logger = options.logger.createLogger({ label: 'content-store' });
20
+ this.userLogger = options.userLogger.createLogger({ label: 'content-store' });
21
+ this.localDev = options.localDev;
22
+ this.userCommandSpawner = options.userCommandSpawner;
23
+ this.stackbitYamlDir = options.stackbitYamlDir; // TODO: remove stackbitYamlDir, ContentStore should not be aware of filesystem, instead pass autoreloading Config object
24
+ this.contentSources = options.contentSources;
25
+ this.onSchemaChangeCallback = options.onSchemaChangeCallback;
26
+ this.onContentChangeCallback = options.onContentChangeCallback;
27
+ this.handleConfigAssets = options.handleConfigAssets;
28
+ this.contentUpdatesWatchTimer = new timer_1.Timer({ timerCallback: () => this.handleTimerTimeout(), logger: this.logger });
29
+ }
30
+ async init() {
31
+ this.logger.debug('init');
32
+ this.rawStackbitConfig = await this.loadStackbitConfig();
33
+ this.logger.debug('init => load content source data');
34
+ for (const contentSourceInstance of this.contentSources) {
35
+ await this.loadContentSourceData({ contentSourceInstance, init: true });
36
+ }
37
+ this.contentUpdatesWatchTimer.startTimer();
38
+ }
39
+ async reset() {
40
+ this.logger.debug('reset');
41
+ this.contentUpdatesWatchTimer.stopTimer();
42
+ this.rawStackbitConfig = await this.loadStackbitConfig();
43
+ this.logger.debug('reset => load content source data');
44
+ for (const contentSourceInstance of this.contentSources) {
45
+ await this.loadContentSourceData({ contentSourceInstance, init: false });
46
+ }
47
+ this.contentUpdatesWatchTimer.startTimer();
48
+ }
49
+ /**
50
+ * This method is called when contentUpdatesWatchTimer receives timeout.
51
+ * It then notifies all content sources to stop watching for content changes.
52
+ */
53
+ handleTimerTimeout() {
54
+ for (const contentSourceInstance of this.contentSources) {
55
+ contentSourceInstance.stopWatchingContentUpdates();
56
+ }
57
+ }
58
+ /**
59
+ * This method is called when user interacts with Stackbit application.
60
+ * It is used to reset contentUpdatesWatchTimer. When the timer is over
61
+ * all content sources are notified to stop watching for content updates.
62
+ */
63
+ async keepAlive() {
64
+ if (!this.contentUpdatesWatchTimer.isRunning()) {
65
+ this.logger.debug('keepAlive => contentUpdatesWatchTimer is not running => load content source data');
66
+ for (const contentSourceInstance of this.contentSources) {
67
+ await this.loadContentSourceData({ contentSourceInstance, init: false });
68
+ }
69
+ }
70
+ this.contentUpdatesWatchTimer.resetTimer();
71
+ }
72
+ /**
73
+ * This method is called when a content source notifies Stackbit of models
74
+ * changes via webhook. When this happens, all content source data
75
+ *
76
+ * For example, Contentful notifies Stackbit of any content-type changes via
77
+ * special webhook.
78
+ *
79
+ * @param contentSourceId
80
+ */
81
+ async onContentSourceSchemaChange({ contentSourceId }) {
82
+ this.logger.debug('onContentSourceSchemaChange', { contentSourceId });
83
+ const contentSourceData = this.getContentSourceDataByIdOrThrow(contentSourceId);
84
+ await this.loadContentSourceData({
85
+ contentSourceInstance: contentSourceData.instance,
86
+ init: false
87
+ });
88
+ this.onSchemaChangeCallback();
89
+ }
90
+ async onFilesChange(updatedFiles) {
91
+ var _a, _b;
92
+ this.logger.debug('onFilesChange');
93
+ const stackbitConfigUpdated = lodash_1.default.some(updatedFiles, (filePath) => isStackbitConfigFile(filePath));
94
+ this.logger.debug(`stackbitConfigUpdated: ${stackbitConfigUpdated}`);
95
+ if (stackbitConfigUpdated) {
96
+ this.rawStackbitConfig = await this.loadStackbitConfig();
97
+ }
98
+ let someContentSourceSchemaUpdated = false;
99
+ const contentChanges = {
100
+ updatedDocuments: [],
101
+ updatedAssets: [],
102
+ deletedDocuments: [],
103
+ deletedAssets: []
104
+ };
105
+ for (const contentSourceInstance of this.contentSources) {
106
+ const contentSourceId = getContentSourceIdForContentSource(contentSourceInstance);
107
+ this.logger.debug(`call onFilesChange for contentSource: ${contentSourceId}`);
108
+ const { schemaChanged, contentChangeEvent } = (_b = (_a = contentSourceInstance.onFilesChange) === null || _a === void 0 ? void 0 : _a.call(contentSourceInstance, { updatedFiles: updatedFiles })) !== null && _b !== void 0 ? _b : {};
109
+ this.logger.debug(`schemaChanged: ${schemaChanged}, has contentChangeEvent: ${!!contentChangeEvent}`);
110
+ // if schema is changed, there is no need to return contentChanges
111
+ // because schema changes reloads everything and implies content changes
112
+ if (stackbitConfigUpdated || schemaChanged) {
113
+ if (schemaChanged) {
114
+ someContentSourceSchemaUpdated = true;
115
+ }
116
+ await this.loadContentSourceData({ contentSourceInstance, init: false });
117
+ }
118
+ else if (contentChangeEvent) {
119
+ const contentChangeResult = this.onContentChange(contentSourceId, contentChangeEvent);
120
+ contentChanges.updatedDocuments = contentChanges.updatedDocuments.concat(contentChangeResult.updatedDocuments);
121
+ contentChanges.updatedAssets = contentChanges.updatedAssets.concat(contentChangeResult.updatedAssets);
122
+ contentChanges.deletedDocuments = contentChanges.deletedDocuments.concat(contentChangeResult.deletedDocuments);
123
+ contentChanges.deletedAssets = contentChanges.deletedAssets.concat(contentChangeResult.deletedAssets);
124
+ }
125
+ }
126
+ return {
127
+ stackbitConfigUpdated,
128
+ schemaChanged: someContentSourceSchemaUpdated || stackbitConfigUpdated,
129
+ contentChanges: contentChanges
130
+ };
131
+ }
132
+ async loadStackbitConfig() {
133
+ this.logger.debug('loadStackbitConfig');
134
+ // TODO: use esbuild to watch for stackbit.config.js changes and notify
135
+ // the content-store via onStackbitConfigChange with updated rawConfig
136
+ const { config, errors } = await (0, sdk_1.loadConfigFromDir)({ dirPath: this.stackbitYamlDir });
137
+ for (const error of errors) {
138
+ this.userLogger.warn(error.message);
139
+ }
140
+ if (!config) {
141
+ this.userLogger.error(`could not load stackbit config`);
142
+ }
143
+ return config;
144
+ }
145
+ async loadContentSourceData({ contentSourceInstance, init }) {
146
+ // TODO: defer loading content if content is already loading for a specific content source
147
+ var _a;
148
+ // TODO: optimize: cache raw responses from contentSource
149
+ // e.g.: getModels(), getDocuments(), getAssets()
150
+ // ana use the cached response to remap documents when only stackbitConfig changes
151
+ const contentSourceId = getContentSourceIdForContentSource(contentSourceInstance);
152
+ this.logger.debug('loadContentSourceData', { contentSourceId, init });
153
+ if (init) {
154
+ await contentSourceInstance.init({
155
+ logger: this.logger,
156
+ userLogger: this.userLogger,
157
+ userCommandSpawner: this.userCommandSpawner,
158
+ localDev: this.localDev
159
+ });
160
+ }
161
+ else {
162
+ contentSourceInstance.stopWatchingContentUpdates();
163
+ await contentSourceInstance.reset();
164
+ }
165
+ const csiModels = await contentSourceInstance.getModels();
166
+ const locales = await (contentSourceInstance === null || contentSourceInstance === void 0 ? void 0 : contentSourceInstance.getLocales());
167
+ const result = await (0, sdk_1.extendConfig)({ dirPath: this.stackbitYamlDir, config: this.rawStackbitConfig, externalModels: csiModels });
168
+ const config = await this.handleConfigAssets(result.config);
169
+ const models = config.models;
170
+ const modelMap = lodash_1.default.keyBy(models, 'name');
171
+ const defaultLocaleCode = (_a = locales === null || locales === void 0 ? void 0 : locales.find((locale) => locale.default)) === null || _a === void 0 ? void 0 : _a.code;
172
+ for (const error of result.errors) {
173
+ this.userLogger.warn(error.message);
174
+ }
175
+ // TODO: load presets externally from config, and create additional map
176
+ // that maps presetIds by model name instead of storing that map inside every model
177
+ this.presets = config.presets;
178
+ const csiDocuments = await contentSourceInstance.getDocuments({ modelMap });
179
+ const csiAssets = await contentSourceInstance.getAssets();
180
+ const csiDocumentMap = lodash_1.default.keyBy(csiDocuments, 'id');
181
+ const csiAssetMap = lodash_1.default.keyBy(csiAssets, 'id');
182
+ const contentStoreDocuments = mapCSIDocumentsToStoreDocuments({
183
+ csiDocuments,
184
+ contentSourceInstance,
185
+ modelMap,
186
+ defaultLocaleCode
187
+ });
188
+ const contentStoreAssets = mapCSIAssetsToStoreAssets({
189
+ csiAssets,
190
+ contentSourceInstance,
191
+ defaultLocaleCode
192
+ });
193
+ const documentMap = lodash_1.default.keyBy(contentStoreDocuments, 'srcObjectId');
194
+ const assetMap = lodash_1.default.keyBy(contentStoreAssets, 'srcObjectId');
195
+ this.logger.debug('loaded content source data', {
196
+ contentSourceId,
197
+ locales,
198
+ defaultLocaleCode,
199
+ modelCount: models.length,
200
+ documentCount: contentStoreDocuments.length,
201
+ assetCount: contentStoreAssets.length
202
+ });
203
+ this.contentSourceDataById[contentSourceId] = {
204
+ id: contentSourceId,
205
+ type: contentSourceInstance.getContentSourceType(),
206
+ projectId: contentSourceInstance.getProjectId(),
207
+ instance: contentSourceInstance,
208
+ locales: locales,
209
+ defaultLocaleCode: defaultLocaleCode,
210
+ models: models,
211
+ modelMap: modelMap,
212
+ csiModelMap: lodash_1.default.keyBy(csiModels, 'name'),
213
+ csiDocuments: csiDocuments,
214
+ csiDocumentMap: csiDocumentMap,
215
+ documents: contentStoreDocuments,
216
+ documentMap: documentMap,
217
+ csiAssets: csiAssets,
218
+ csiAssetMap: csiAssetMap,
219
+ assets: contentStoreAssets,
220
+ assetMap: assetMap
221
+ };
222
+ contentSourceInstance.startWatchingContentUpdates({
223
+ getModelMap: () => {
224
+ const contentSourceData = this.getContentSourceDataByIdOrThrow(contentSourceId);
225
+ return contentSourceData.csiModelMap;
226
+ },
227
+ getDocument({ documentId }) {
228
+ return csiDocumentMap[documentId];
229
+ },
230
+ getAsset({ assetId }) {
231
+ return csiAssetMap[assetId];
232
+ },
233
+ onContentChange: (contentChangeEvent) => {
234
+ this.logger.debug('content source called onContentChange', { contentSourceId });
235
+ const result = this.onContentChange(contentSourceId, contentChangeEvent);
236
+ this.onContentChangeCallback(result);
237
+ },
238
+ onSchemaChange: async () => {
239
+ this.logger.debug('content source called onSchemaChange', { contentSourceId });
240
+ await this.loadContentSourceData({
241
+ contentSourceInstance: contentSourceInstance,
242
+ init: false
243
+ });
244
+ this.onSchemaChangeCallback();
245
+ }
246
+ });
247
+ }
248
+ onContentChange(contentSourceId, contentChangeEvent) {
249
+ // TODO: prevent content change process for contentSourceId if loading content is in progress
250
+ this.logger.debug('onContentChange', {
251
+ contentSourceId,
252
+ documentCount: contentChangeEvent.documents.length,
253
+ assetCount: contentChangeEvent.assets.length,
254
+ deletedDocumentCount: contentChangeEvent.deletedDocumentIds.length,
255
+ deletedAssetCount: contentChangeEvent.deletedAssetIds.length
256
+ });
257
+ const result = {
258
+ updatedDocuments: [],
259
+ updatedAssets: [],
260
+ deletedDocuments: [],
261
+ deletedAssets: []
262
+ };
263
+ const contentSourceData = this.getContentSourceDataByIdOrThrow(contentSourceId);
264
+ // update contentSourceData with deleted documents
265
+ contentChangeEvent.deletedDocumentIds.forEach((docId) => {
266
+ // delete document from documents map
267
+ delete contentSourceData.documentMap[docId];
268
+ delete contentSourceData.csiDocumentMap[docId];
269
+ // delete document from document array
270
+ const index = contentSourceData.documents.findIndex((document) => document.srcObjectId === docId);
271
+ if (index !== -1) {
272
+ // the indexes of documents and csiDocuments are always the same as they are always updated at the same time
273
+ contentSourceData.documents.splice(index, 1);
274
+ contentSourceData.csiDocuments.splice(index, 1);
275
+ }
276
+ result.deletedDocuments.push({
277
+ srcType: contentSourceData.type,
278
+ srcProjectId: contentSourceData.projectId,
279
+ srcObjectId: docId
280
+ });
281
+ });
282
+ // update contentSourceData with deleted assets
283
+ contentChangeEvent.deletedAssetIds.forEach((assetId) => {
284
+ // delete document from asset map
285
+ delete contentSourceData.assetMap[assetId];
286
+ delete contentSourceData.csiAssetMap[assetId];
287
+ // delete document from asset array
288
+ const index = contentSourceData.assets.findIndex((asset) => asset.srcObjectId === assetId);
289
+ if (index !== -1) {
290
+ // the indexes of assets and csiAssets are always the same as they are always updated at the same time
291
+ contentSourceData.assets.splice(index, 1);
292
+ contentSourceData.csiAssets.splice(index, 1);
293
+ }
294
+ result.deletedAssets.push({
295
+ srcType: contentSourceData.type,
296
+ srcProjectId: contentSourceData.projectId,
297
+ srcObjectId: assetId
298
+ });
299
+ });
300
+ // map csi documents and assets to content store documents and assets
301
+ const documents = mapCSIDocumentsToStoreDocuments({
302
+ csiDocuments: contentChangeEvent.documents,
303
+ contentSourceInstance: contentSourceData.instance,
304
+ modelMap: contentSourceData.modelMap,
305
+ defaultLocaleCode: contentSourceData.defaultLocaleCode
306
+ });
307
+ const assets = mapCSIAssetsToStoreAssets({
308
+ csiAssets: contentChangeEvent.assets,
309
+ contentSourceInstance: contentSourceData.instance,
310
+ defaultLocaleCode: contentSourceData.defaultLocaleCode
311
+ });
312
+ // update contentSourceData with new or updated documents and assets
313
+ Object.assign(contentSourceData.csiDocumentMap, lodash_1.default.keyBy(contentChangeEvent.documents, 'id'));
314
+ Object.assign(contentSourceData.csiAssets, lodash_1.default.keyBy(contentChangeEvent.assets, 'id'));
315
+ Object.assign(contentSourceData.documentMap, lodash_1.default.keyBy(documents, 'srcObjectId'));
316
+ Object.assign(contentSourceData.assetMap, lodash_1.default.keyBy(assets, 'srcObjectId'));
317
+ for (let idx = 0; idx < documents.length; idx++) {
318
+ // the indexes of mapped documents and documents from changeEvent are the same
319
+ const document = documents[idx];
320
+ const csiDocument = contentChangeEvent.documents[idx];
321
+ const dataIndex = contentSourceData.documents.findIndex((existingDoc) => existingDoc.srcObjectId === document.srcObjectId);
322
+ if (dataIndex === -1) {
323
+ contentSourceData.documents.push(document);
324
+ contentSourceData.csiDocuments.push(csiDocument);
325
+ }
326
+ else {
327
+ // the indexes of documents and csiDocuments are always the same as they are always updated at the same time
328
+ contentSourceData.documents.splice(dataIndex, 1, document);
329
+ contentSourceData.csiDocuments.splice(dataIndex, 1, csiDocument);
330
+ }
331
+ result.updatedDocuments.push({
332
+ srcType: contentSourceData.type,
333
+ srcProjectId: contentSourceData.projectId,
334
+ srcObjectId: document.srcObjectId
335
+ });
336
+ }
337
+ for (let idx = 0; idx < assets.length; idx++) {
338
+ // the indexes of mapped assets and assets from changeEvent are the same
339
+ const asset = assets[idx];
340
+ const csiAsset = contentChangeEvent.assets[idx];
341
+ const index = contentSourceData.assets.findIndex((existingAsset) => existingAsset.srcObjectId === asset.srcObjectId);
342
+ if (index === -1) {
343
+ contentSourceData.assets.push(asset);
344
+ contentSourceData.csiAssets.push(csiAsset);
345
+ }
346
+ else {
347
+ // the indexes of assets and csiAssets are always the same as they are always updated at the same time
348
+ contentSourceData.assets.splice(index, 1, asset);
349
+ contentSourceData.csiAssets.splice(index, 1, csiAsset);
350
+ }
351
+ result.updatedAssets.push({
352
+ srcType: contentSourceData.type,
353
+ srcProjectId: contentSourceData.projectId,
354
+ srcObjectId: asset.srcObjectId
355
+ });
356
+ }
357
+ return result;
358
+ }
359
+ getModels() {
360
+ return lodash_1.default.reduce(this.contentSourceDataById, (result, contentSourceData) => {
361
+ const contentSourceType = contentSourceData.instance.getContentSourceType();
362
+ const srcProjectId = contentSourceData.instance.getProjectId();
363
+ lodash_1.default.set(result, [contentSourceType, srcProjectId], contentSourceData.modelMap);
364
+ return result;
365
+ }, {});
366
+ }
367
+ getLocales() {
368
+ return lodash_1.default.reduce(this.contentSourceDataById, (result, contentSourceData) => {
369
+ var _a;
370
+ const locales = ((_a = contentSourceData.locales) !== null && _a !== void 0 ? _a : []).map((locale) => locale.code);
371
+ return result.concat(locales);
372
+ }, []);
373
+ }
374
+ getPresets() {
375
+ var _a;
376
+ return (_a = this.presets) !== null && _a !== void 0 ? _a : {};
377
+ }
378
+ getContentSourceEnvironment({ srcProjectId, srcType }) {
379
+ const contentSourceId = getContentSourceId(srcType, srcProjectId);
380
+ const contentSourceData = this.getContentSourceDataByIdOrThrow(contentSourceId);
381
+ return contentSourceData.instance.getProjectEnvironment();
382
+ }
383
+ hasAccess({ srcType, srcProjectId, user }) {
384
+ const contentSourceId = getContentSourceId(srcType, srcProjectId);
385
+ const contentSourceData = this.getContentSourceDataByIdOrThrow(contentSourceId);
386
+ const userContext = getUserContextForSrcType(srcType, user);
387
+ return contentSourceData.instance.hasAccess({ userContext });
388
+ }
389
+ hasChanges({ srcType, srcProjectId, documents }) {
390
+ let result;
391
+ if (srcType && srcProjectId) {
392
+ const contentSourceId = getContentSourceId(srcType, srcProjectId);
393
+ const contentSourceData = this.getContentSourceDataByIdOrThrow(contentSourceId);
394
+ result = [...contentSourceData.documents, ...contentSourceData.assets];
395
+ }
396
+ else if (documents && documents.length > 0) {
397
+ const documentsBySourceId = lodash_1.default.groupBy(documents, (document) => getContentSourceId(document.srcType, document.srcProjectId));
398
+ result = lodash_1.default.reduce(documentsBySourceId, (result, documents, contentSourceId) => {
399
+ const contentSourceData = this.getContentSourceDataByIdOrThrow(contentSourceId);
400
+ for (const document of documents) {
401
+ if (document.srcObjectId in contentSourceData.documentMap) {
402
+ result.push(contentSourceData.documentMap[document.srcObjectId]);
403
+ }
404
+ else if (document.srcObjectId in contentSourceData.assetMap) {
405
+ result.push(contentSourceData.assetMap[document.srcObjectId]);
406
+ }
407
+ }
408
+ return result;
409
+ }, []);
410
+ }
411
+ else {
412
+ result = lodash_1.default.reduce(this.contentSourceDataById, (result, contentSourceData) => {
413
+ return result.concat(contentSourceData.documents, contentSourceData.assets);
414
+ }, []);
415
+ }
416
+ const changedDocuments = result.filter((document) => document.status === 'added' || document.status === 'modified');
417
+ return {
418
+ hasChanges: !lodash_1.default.isEmpty(changedDocuments),
419
+ changedObjects: changedDocuments.map((item) => ({
420
+ srcType: item.srcType,
421
+ srcProjectId: item.srcProjectId,
422
+ srcObjectId: item.srcObjectId
423
+ }))
424
+ };
425
+ }
426
+ getDocument({ srcDocumentId, srcProjectId, srcType }) {
427
+ const contentSourceId = getContentSourceId(srcType, srcProjectId);
428
+ const contentSourceData = this.getContentSourceDataByIdOrThrow(contentSourceId);
429
+ return contentSourceData.documentMap[srcDocumentId];
430
+ }
431
+ getDocuments() {
432
+ return lodash_1.default.reduce(this.contentSourceDataById, (documents, contentSourceData) => {
433
+ return documents.concat(contentSourceData.documents);
434
+ }, []);
435
+ }
436
+ getAsset({ srcAssetId, srcProjectId, srcType }) {
437
+ const contentSourceId = getContentSourceId(srcType, srcProjectId);
438
+ const contentSourceData = this.getContentSourceDataByIdOrThrow(contentSourceId);
439
+ return contentSourceData.assetMap[srcAssetId];
440
+ }
441
+ getAssets() {
442
+ return lodash_1.default.reduce(this.contentSourceDataById, (assets, contentSourceData) => {
443
+ return assets.concat(contentSourceData.assets);
444
+ }, []);
445
+ }
446
+ getLocalizedApiObjects({ locale }) {
447
+ return lodash_1.default.reduce(this.contentSourceDataById, (objects, contentSourceData) => {
448
+ locale = locale !== null && locale !== void 0 ? locale : contentSourceData.defaultLocaleCode;
449
+ const documentObjects = mapDocumentsToLocalizedApiObjects(contentSourceData.documents, locale);
450
+ const imageObjects = mapAssetsToLocalizedApiImages(contentSourceData.assets, locale);
451
+ return objects.concat(documentObjects, imageObjects);
452
+ }, []);
453
+ }
454
+ getApiAssets({ srcType, srcProjectId, pageSize = 20, pageNum = 1, searchQuery } = {}) {
455
+ let assets;
456
+ if (srcProjectId && srcType) {
457
+ const contentSourceId = getContentSourceId(srcType, srcProjectId);
458
+ const contentSourceData = this.getContentSourceDataByIdOrThrow(contentSourceId);
459
+ assets = mapStoreAssetsToAPIAssets(contentSourceData.assets, contentSourceData.defaultLocaleCode);
460
+ }
461
+ else {
462
+ assets = lodash_1.default.reduce(this.contentSourceDataById, (result, contentSourceData) => {
463
+ const assets = mapStoreAssetsToAPIAssets(contentSourceData.assets, contentSourceData.defaultLocaleCode);
464
+ return result.concat(assets);
465
+ }, []);
466
+ }
467
+ let filteredFiles = assets;
468
+ if (searchQuery) {
469
+ const sanitizedSearchQuery = (0, sanitize_filename_1.default)(searchQuery).toLowerCase();
470
+ filteredFiles = assets.filter((asset) => asset.fileName && path_1.default.basename(asset.fileName).toLowerCase().includes(sanitizedSearchQuery));
471
+ }
472
+ const sortedAssets = lodash_1.default.orderBy(filteredFiles, ['fileName'], ['asc']);
473
+ const skip = (pageNum - 1) * pageSize;
474
+ const totalPages = Math.ceil(filteredFiles.length / pageSize);
475
+ const pagesAssets = sortedAssets.slice(skip, skip + pageSize);
476
+ return {
477
+ assets: pagesAssets,
478
+ pageSize: pageSize,
479
+ pageNum: pageNum,
480
+ totalPages: totalPages
481
+ };
482
+ }
483
+ async createAndLinkDocument({ srcType, srcProjectId, srcDocumentId, fieldPath, modelName, object, index, locale, user }) {
484
+ this.logger.debug('createAndLinkDocument', { srcType, srcProjectId, srcDocumentId, fieldPath, modelName, index, locale });
485
+ const contentSourceId = getContentSourceId(srcType, srcProjectId);
486
+ const contentSourceData = this.getContentSourceDataByIdOrThrow(contentSourceId);
487
+ // get the document that is being updated
488
+ const document = contentSourceData.documentMap[srcDocumentId];
489
+ const csiDocument = contentSourceData.csiDocumentMap[srcDocumentId];
490
+ if (!document || !csiDocument) {
491
+ throw new Error(`no document with id '${srcDocumentId}' was found in ${contentSourceData.id}`);
492
+ }
493
+ // get the document model
494
+ const documentModelName = document.srcModelName;
495
+ const csiModelMap = contentSourceData.csiModelMap;
496
+ const model = csiModelMap[documentModelName];
497
+ if (!model) {
498
+ throw new Error(`error updating document, could not find document model: '${documentModelName}'`);
499
+ }
500
+ // get the 'reference' model field in the updated document that will be used to link the new document
501
+ locale = locale !== null && locale !== void 0 ? locale : contentSourceData.defaultLocaleCode;
502
+ const modelField = getModelFieldForFieldAtPath(document, model, fieldPath, csiModelMap, locale);
503
+ if (!modelField) {
504
+ throw Error(`the "fieldPath" points to non existing model field: ${fieldPath.join('.')}`);
505
+ }
506
+ const fieldProps = modelField.type === 'list' ? modelField.items : modelField;
507
+ if (fieldProps.type !== 'reference') {
508
+ throw Error(`error in "createAndLinkDocument", this operation can only be used on reference field: ${fieldPath.join('.')}`);
509
+ }
510
+ // get the model name for the new document
511
+ if (!modelName && fieldProps.models.length === 1) {
512
+ modelName = fieldProps.models[0];
513
+ }
514
+ if (!modelName) {
515
+ throw Error(`error in "createAndLinkDocument", missing "modelName": ${fieldPath.join('.')}`);
516
+ }
517
+ // create the new document
518
+ const result = await this.createDocument({
519
+ object: object,
520
+ srcProjectId: srcProjectId,
521
+ srcType: srcType,
522
+ modelName: modelName,
523
+ locale: locale,
524
+ user: user
525
+ });
526
+ // update the document by linking the field to the created document
527
+ const userContext = getUserContextForSrcType(srcType, user);
528
+ const field = {
529
+ type: 'reference',
530
+ refType: 'document',
531
+ refId: result.srcDocumentId
532
+ };
533
+ const updatedDocument = await contentSourceData.instance.updateDocument({
534
+ document: csiDocument,
535
+ modelMap: csiModelMap,
536
+ userContext: userContext,
537
+ operations: [
538
+ modelField.type === 'list'
539
+ ? {
540
+ opType: 'insert',
541
+ fieldPath: fieldPath,
542
+ modelField: modelField,
543
+ locale: locale,
544
+ index: index,
545
+ item: field
546
+ }
547
+ : {
548
+ opType: 'set',
549
+ fieldPath: fieldPath,
550
+ modelField: modelField,
551
+ locale: locale,
552
+ field: field
553
+ }
554
+ ]
555
+ });
556
+ return { srcDocumentId: updatedDocument.id };
557
+ }
558
+ async uploadAndLinkAsset({ srcType, srcProjectId, srcDocumentId, fieldPath, asset, index, locale, user }) {
559
+ this.logger.debug('uploadAndLinkAsset', { srcType, srcProjectId, srcDocumentId, fieldPath, index, locale });
560
+ // get the document that is being updated
561
+ const contentSourceId = getContentSourceId(srcType, srcProjectId);
562
+ const contentSourceData = this.getContentSourceDataByIdOrThrow(contentSourceId);
563
+ const document = contentSourceData.documentMap[srcDocumentId];
564
+ const csiDocument = contentSourceData.csiDocumentMap[srcDocumentId];
565
+ if (!document || !csiDocument) {
566
+ throw new Error(`no document with id '${srcDocumentId}' was found in ${contentSourceData.id}`);
567
+ }
568
+ // get the document model
569
+ const documentModelName = document.srcModelName;
570
+ const csiModelMap = contentSourceData.csiModelMap;
571
+ const csiModel = csiModelMap[documentModelName];
572
+ if (!csiModel) {
573
+ throw new Error(`error updating document, could not find document model: '${documentModelName}'`);
574
+ }
575
+ // get the 'reference' model field in the updated document that will be used to link the new asset
576
+ locale = locale !== null && locale !== void 0 ? locale : contentSourceData.defaultLocaleCode;
577
+ const modelField = getModelFieldForFieldAtPath(document, csiModel, fieldPath, csiModelMap, locale);
578
+ if (!modelField) {
579
+ throw Error(`the "fieldPath" points to non existing model field: ${fieldPath.join('.')}`);
580
+ }
581
+ const fieldProps = modelField.type === 'list' ? modelField.items : modelField;
582
+ if (fieldProps.type !== 'reference' && fieldProps.type !== 'image') {
583
+ throw Error(`error in "uploadAndLinkAsset", this operation can only be used on reference and image fields: ${fieldPath.join('.')}`);
584
+ }
585
+ // upload the new asset
586
+ const userContext = getUserContextForSrcType(srcType, user);
587
+ const result = await contentSourceData.instance.uploadAsset({
588
+ url: asset.url,
589
+ fileName: asset.metadata.name,
590
+ mimeType: asset.metadata.type,
591
+ locale: locale,
592
+ userContext: userContext
593
+ });
594
+ // update the document by linking the field to the created asset
595
+ const field = {
596
+ type: 'reference',
597
+ refType: 'asset',
598
+ refId: result.id
599
+ };
600
+ const updatedDocument = await contentSourceData.instance.updateDocument({
601
+ document: csiDocument,
602
+ modelMap: csiModelMap,
603
+ userContext: userContext,
604
+ operations: [
605
+ modelField.type === 'list'
606
+ ? {
607
+ opType: 'insert',
608
+ fieldPath: fieldPath,
609
+ modelField: modelField,
610
+ locale: locale,
611
+ index: index,
612
+ item: field
613
+ }
614
+ : {
615
+ opType: 'set',
616
+ fieldPath: fieldPath,
617
+ modelField: modelField,
618
+ locale: locale,
619
+ field: field
620
+ }
621
+ ]
622
+ });
623
+ return { srcDocumentId: updatedDocument.id };
624
+ }
625
+ async createDocument({ srcType, srcProjectId, modelName, object, locale, user }) {
626
+ this.logger.debug('createDocument', { srcType, srcProjectId, modelName, locale });
627
+ const contentSourceId = getContentSourceId(srcType, srcProjectId);
628
+ const contentSourceData = this.getContentSourceDataByIdOrThrow(contentSourceId);
629
+ const modelMap = contentSourceData.modelMap;
630
+ const model = modelMap[modelName];
631
+ if (!model) {
632
+ throw new Error(`no model with name '${modelName}' was found`);
633
+ }
634
+ locale = locale !== null && locale !== void 0 ? locale : contentSourceData.defaultLocaleCode;
635
+ const userContext = getUserContextForSrcType(srcType, user);
636
+ const result = await createDocumentRecursively({
637
+ object,
638
+ model,
639
+ modelMap,
640
+ locale,
641
+ userContext,
642
+ contentSourceInstance: contentSourceData.instance
643
+ });
644
+ this.logger.debug('created document', { srcType, srcProjectId, srcDocumentId: result.document.id, modelName });
645
+ // do not update cache in contentSourceData.documents and documentMap,
646
+ // instead wait for contentSource to call onContentChange(contentChangeEvent)
647
+ // and use data from contentChangeEvent to update the cache
648
+ // const newDocuments = [result.document, ...result.referencedDocuments];
649
+ // contentSourceData.documentMap = Object.assign(contentSourceData.documentMap, _.keyBy(newDocuments, 'srcObjectId'));
650
+ return { srcDocumentId: result.document.id };
651
+ }
652
+ async updateDocument({ srcType, srcProjectId, srcDocumentId, updateOperations, user }) {
653
+ this.logger.debug('updateDocument');
654
+ const contentSourceId = getContentSourceId(srcType, srcProjectId);
655
+ const contentSourceData = this.getContentSourceDataByIdOrThrow(contentSourceId);
656
+ const userContext = getUserContextForSrcType(srcType, user);
657
+ const document = contentSourceData.documentMap[srcDocumentId];
658
+ const csiDocument = contentSourceData.csiDocumentMap[srcDocumentId];
659
+ if (!document || !csiDocument) {
660
+ throw new Error(`no document with id '${srcDocumentId}' was found in ${contentSourceData.id}`);
661
+ }
662
+ const modelMap = contentSourceData.modelMap;
663
+ const csiModelMap = contentSourceData.csiModelMap;
664
+ const documentModelName = document.srcModelName;
665
+ const model = modelMap[documentModelName];
666
+ if (!model) {
667
+ throw new Error(`error updating document, could not find document model: '${documentModelName}'`);
668
+ }
669
+ const operations = await (0, utils_1.mapPromise)(updateOperations, async (updateOperation) => {
670
+ var _a;
671
+ const locale = (_a = updateOperation.locale) !== null && _a !== void 0 ? _a : contentSourceData.defaultLocaleCode;
672
+ const modelField = getModelFieldForFieldAtPath(document, model, updateOperation.fieldPath, modelMap, locale);
673
+ switch (updateOperation.opType) {
674
+ case 'set':
675
+ const field = await convertOperationField({
676
+ operationField: updateOperation.field,
677
+ fieldPath: updateOperation.fieldPath,
678
+ locale: updateOperation.locale,
679
+ modelField,
680
+ modelMap,
681
+ userContext,
682
+ contentSourceInstance: contentSourceData.instance
683
+ });
684
+ return {
685
+ ...updateOperation,
686
+ modelField,
687
+ field
688
+ };
689
+ case 'unset':
690
+ return { ...updateOperation, modelField };
691
+ case 'insert':
692
+ const item = await convertOperationField({
693
+ operationField: updateOperation.item,
694
+ fieldPath: updateOperation.fieldPath,
695
+ locale: updateOperation.locale,
696
+ modelField,
697
+ modelMap,
698
+ userContext,
699
+ contentSourceInstance: contentSourceData.instance
700
+ });
701
+ return {
702
+ ...updateOperation,
703
+ modelField,
704
+ item
705
+ };
706
+ case 'remove':
707
+ return { ...updateOperation, modelField };
708
+ case 'reorder':
709
+ return { ...updateOperation, modelField };
710
+ }
711
+ });
712
+ const updatedDocumentResult = await contentSourceData.instance.updateDocument({
713
+ document: csiDocument,
714
+ modelMap: csiModelMap,
715
+ userContext,
716
+ operations
717
+ });
718
+ // do not update cache in contentSourceData.documents and documentMap,
719
+ // instead wait for contentSource to call onContentChange(contentChangeEvent)
720
+ // and use data from contentChangeEvent to update the cache
721
+ // contentSourceData.documentMap = Object.assign(contentSourceData.documentMap, { [document.srcObjectId]: document });
722
+ return { srcDocumentId: updatedDocumentResult.id };
723
+ }
724
+ async duplicateDocument({ srcType, srcProjectId, srcDocumentId, object, user }) {
725
+ this.logger.debug('duplicateDocument');
726
+ const contentSourceId = getContentSourceId(srcType, srcProjectId);
727
+ const contentSourceData = this.getContentSourceDataByIdOrThrow(contentSourceId);
728
+ const document = contentSourceData.documentMap[srcDocumentId];
729
+ if (!document) {
730
+ throw new Error(`no document with id '${srcDocumentId}' was found in ${contentSourceData.id}`);
731
+ }
732
+ const modelMap = contentSourceData.modelMap;
733
+ const model = modelMap[document.srcModelName];
734
+ if (!model) {
735
+ throw new Error(`no model with name '${document.srcModelName}' was found`);
736
+ }
737
+ const userContext = getUserContextForSrcType(srcType, user);
738
+ // TODO: take the data from the provided 'object' and merge them with
739
+ // DocumentFields of the existing Document:
740
+ // Option 1: Map the DocumentFields of the existing Document into flat
741
+ // object with '$$ref' and '$type' properties for references and
742
+ // nested objects (needs to be implemented), and then merge it with
743
+ // the provided object recursively, and then pass that object to
744
+ // createNestedObjectRecursively()
745
+ // Options 2: Converting the provided object into a Document with
746
+ // DocumentFields and then merging both documents into a single
747
+ // Document and then calling mapStoreFieldsToOperationFields (needs to be implemented)
748
+ // While doing all that, need to handle existing references in the
749
+ // existing Document and to duplicate the referenced documents if their
750
+ // model is marked as 'duplicatable' and to reuse the existing documents
751
+ // if their model is marked as non-duplicatable.
752
+ const updateOperationFields = mapStoreFieldsToOperationFields({
753
+ documentFields: document.fields,
754
+ modelFields: model.fields,
755
+ modelMap: contentSourceData.modelMap
756
+ });
757
+ const documentResult = await contentSourceData.instance.createDocument({
758
+ updateOperationFields,
759
+ // TODO: pass csiModel
760
+ model,
761
+ // TODO: pass csiModelMap
762
+ modelMap,
763
+ locale: contentSourceData.defaultLocaleCode,
764
+ userContext
765
+ });
766
+ return { srcDocumentId: documentResult.id };
767
+ }
768
+ async uploadAssets({ srcType, srcProjectId, assets, locale, user }) {
769
+ this.logger.debug('uploadAssets');
770
+ const contentSourceId = getContentSourceId(srcType, srcProjectId);
771
+ const contentSourceData = this.getContentSourceDataByIdOrThrow(contentSourceId);
772
+ const sourceAssets = [];
773
+ const userContext = getUserContextForSrcType(srcType, user);
774
+ locale = locale !== null && locale !== void 0 ? locale : contentSourceData.defaultLocaleCode;
775
+ for (const asset of assets) {
776
+ let base64 = undefined;
777
+ if (asset.data) {
778
+ const matchResult = asset.data.match(/;base64,([\s\S]+)$/);
779
+ if (matchResult) {
780
+ base64 = matchResult[1];
781
+ }
782
+ }
783
+ const sourceAsset = await contentSourceData.instance.uploadAsset({
784
+ url: asset.url,
785
+ base64: base64,
786
+ fileName: asset.metadata.name,
787
+ mimeType: asset.metadata.type,
788
+ locale: locale,
789
+ userContext: userContext
790
+ });
791
+ sourceAssets.push(sourceAsset);
792
+ }
793
+ const storeAssets = mapCSIAssetsToStoreAssets({
794
+ csiAssets: sourceAssets,
795
+ contentSourceInstance: contentSourceData.instance,
796
+ defaultLocaleCode: contentSourceData.defaultLocaleCode
797
+ });
798
+ return mapStoreAssetsToAPIAssets(storeAssets, locale);
799
+ }
800
+ async deleteDocument({ srcType, srcProjectId, srcDocumentId, user }) {
801
+ this.logger.debug('deleteDocument');
802
+ const userContext = getUserContextForSrcType(srcType, user);
803
+ const contentSourceId = getContentSourceId(srcType, srcProjectId);
804
+ const contentSourceData = this.getContentSourceDataByIdOrThrow(contentSourceId);
805
+ const csiDocument = contentSourceData.csiDocumentMap[srcDocumentId];
806
+ if (!csiDocument) {
807
+ throw new Error(`no document with id '${srcDocumentId}' was found in ${contentSourceData.id}`);
808
+ }
809
+ await contentSourceData.instance.deleteDocument({ document: csiDocument, userContext });
810
+ // do not update cache in contentSourceData.documents and documentMap,
811
+ // instead wait for contentSource to call onContentChange(contentChangeEvent)
812
+ // and use data from contentChangeEvent to update the cache
813
+ // delete contentSourceData.documentMap[srcDocumentId];
814
+ }
815
+ async validateDocuments({ objects, locale, user }) {
816
+ this.logger.debug('validateDocuments');
817
+ const objectsBySourceId = lodash_1.default.groupBy(objects, (object) => getContentSourceId(object.srcType, object.srcProjectId));
818
+ let errors = [];
819
+ for (const [contentSourceId, contentSourceObjects] of Object.entries(objectsBySourceId)) {
820
+ const contentSourceData = this.getContentSourceDataByIdOrThrow(contentSourceId);
821
+ locale = locale !== null && locale !== void 0 ? locale : contentSourceData.defaultLocaleCode;
822
+ const { documents, assets } = getCSIDocumentsAndAssetsFromContentSourceDataByIds(contentSourceData, contentSourceObjects);
823
+ const userContext = getUserContextForSrcType(contentSourceData.type, user);
824
+ const internalValidationErrors = internalValidateContent(documents, assets, contentSourceData);
825
+ const validationResult = await contentSourceData.instance.validateDocuments({ documents, assets, locale, userContext });
826
+ errors = errors.concat(internalValidationErrors, validationResult.errors.map((validationError) => ({
827
+ message: validationError.message,
828
+ srcType: contentSourceData.type,
829
+ srcProjectId: contentSourceData.projectId,
830
+ srcObjectType: validationError.objectType,
831
+ srcObjectId: validationError.objectId,
832
+ fieldPath: validationError.fieldPath,
833
+ isUniqueValidation: validationError.isUniqueValidation
834
+ })));
835
+ }
836
+ return { errors };
837
+ /* validate for multiple sources
838
+ const objectsBySourceId = _.groupBy(objects, (document) => getContentSourceId(document.srcType, document.srcProjectId));
839
+ const contentSourceIds = Object.keys(objectsBySourceId);
840
+ return reducePromise(
841
+ contentSourceIds,
842
+ async (result: ContentStoreTypes.ValidationError[], contentSourceId) => {
843
+ const documents = documentsBySourceId[contentSourceId]!;
844
+ const contentSourceData = this.getContentSourceDataByIdOrThrow(contentSourceId);
845
+ const validationErrors = await contentSourceData.instance.validateDocuments({ documentIds: documents.map((document) => document.srcObjectId) });
846
+ return result.concat(validationErrors);
847
+ },
848
+ []
849
+ );
850
+ */
851
+ }
852
+ async publishDocuments({ objects, user }) {
853
+ this.logger.debug('publishDocuments');
854
+ const objectsBySourceId = lodash_1.default.groupBy(objects, (object) => getContentSourceId(object.srcType, object.srcProjectId));
855
+ for (const [contentSourceId, contentSourceObjects] of Object.entries(objectsBySourceId)) {
856
+ const contentSourceData = this.getContentSourceDataByIdOrThrow(contentSourceId);
857
+ const userContext = getUserContextForSrcType(contentSourceData.type, user);
858
+ const { documents, assets } = getCSIDocumentsAndAssetsFromContentSourceDataByIds(contentSourceData, contentSourceObjects);
859
+ await contentSourceData.instance.publishDocuments({ documents, assets, userContext });
860
+ }
861
+ }
862
+ getContentSourceDataByIdOrThrow(contentSourceId) {
863
+ const contentSourceData = this.contentSourceDataById[contentSourceId];
864
+ if (!contentSourceData) {
865
+ throw new Error(`no content source for id '${contentSourceId}' was found`);
866
+ }
867
+ return contentSourceData;
868
+ }
869
+ }
870
+ exports.ContentStore = ContentStore;
871
+ function getContentSourceId(contentSourceType, srcProjectId) {
872
+ return contentSourceType + ':' + srcProjectId;
873
+ }
874
+ exports.getContentSourceId = getContentSourceId;
875
+ function getUserContextForSrcType(srcType, user) {
876
+ var _a;
877
+ return (_a = user === null || user === void 0 ? void 0 : user.connections) === null || _a === void 0 ? void 0 : _a.find((connection) => connection.type === srcType);
878
+ }
879
+ function mapCSIAssetsToStoreAssets({ csiAssets, contentSourceInstance, defaultLocaleCode }) {
880
+ const extra = {
881
+ srcType: contentSourceInstance.getContentSourceType(),
882
+ srcProjectId: contentSourceInstance.getProjectId(),
883
+ srcProjectUrl: contentSourceInstance.getProjectManageUrl(),
884
+ srcEnvironment: contentSourceInstance.getProjectEnvironment()
885
+ };
886
+ return csiAssets.map((csiAsset) => sourceAssetToStoreAsset({ csiAsset, defaultLocaleCode, extra }));
887
+ }
888
+ function sourceAssetToStoreAsset({ csiAsset, defaultLocaleCode, extra }) {
889
+ return {
890
+ type: 'asset',
891
+ ...extra,
892
+ srcObjectId: csiAsset.id,
893
+ srcObjectUrl: csiAsset.manageUrl,
894
+ srcObjectLabel: getObjectLabel(csiAsset.fields, common_schema_1.IMAGE_MODEL, defaultLocaleCode),
895
+ srcModelName: common_schema_1.IMAGE_MODEL.name,
896
+ srcModelLabel: common_schema_1.IMAGE_MODEL.label,
897
+ isChanged: csiAsset.status === 'added' || csiAsset.status === 'modified',
898
+ status: csiAsset.status,
899
+ createdAt: csiAsset.createdAt,
900
+ createdBy: csiAsset.createdBy,
901
+ updatedAt: csiAsset.updatedAt,
902
+ updatedBy: csiAsset.updatedBy,
903
+ fields: {
904
+ title: {
905
+ label: 'Title',
906
+ ...csiAsset.fields.title
907
+ },
908
+ file: {
909
+ label: 'File',
910
+ ...csiAsset.fields.file
911
+ }
912
+ }
913
+ };
914
+ }
915
+ function mapCSIDocumentsToStoreDocuments({ csiDocuments, contentSourceInstance, modelMap, defaultLocaleCode }) {
916
+ const extra = {
917
+ srcType: contentSourceInstance.getContentSourceType(),
918
+ srcProjectId: contentSourceInstance.getProjectId(),
919
+ srcProjectUrl: contentSourceInstance.getProjectManageUrl(),
920
+ srcEnvironment: contentSourceInstance.getProjectEnvironment()
921
+ };
922
+ return csiDocuments.map((csiDocument) => mapCSIDocumentToStoreDocument({ csiDocument, model: modelMap[csiDocument.modelName], modelMap, defaultLocaleCode, extra }));
923
+ }
924
+ function mapCSIDocumentToStoreDocument({ csiDocument, model, modelMap, defaultLocaleCode, extra }) {
925
+ var _a, _b;
926
+ return {
927
+ type: 'document',
928
+ ...extra,
929
+ srcObjectId: csiDocument.id,
930
+ srcObjectUrl: csiDocument.manageUrl,
931
+ srcObjectLabel: getObjectLabel(csiDocument.fields, model, defaultLocaleCode),
932
+ srcModelLabel: (_a = model.label) !== null && _a !== void 0 ? _a : lodash_1.default.startCase(csiDocument.modelName),
933
+ srcModelName: csiDocument.modelName,
934
+ isChanged: csiDocument.status === 'added' || csiDocument.status === 'modified',
935
+ status: csiDocument.status,
936
+ createdAt: csiDocument.createdAt,
937
+ createdBy: csiDocument.createdBy,
938
+ updatedAt: csiDocument.updatedAt,
939
+ updatedBy: csiDocument.updatedBy,
940
+ fields: mapCSIFieldsToStoreFields({
941
+ csiDocumentFields: csiDocument.fields,
942
+ modelFields: (_b = model.fields) !== null && _b !== void 0 ? _b : [],
943
+ context: {
944
+ modelMap,
945
+ defaultLocaleCode
946
+ }
947
+ })
948
+ };
949
+ }
950
+ function mapCSIFieldsToStoreFields({ csiDocumentFields, modelFields, context }) {
951
+ return modelFields.reduce((result, modelField) => {
952
+ const csiDocumentField = csiDocumentFields[modelField.name];
953
+ const docField = mapCSIFieldToStoreField({
954
+ csiDocumentField,
955
+ modelField,
956
+ context
957
+ });
958
+ docField.label = modelField.label;
959
+ result[modelField.name] = docField;
960
+ return result;
961
+ }, {});
962
+ }
963
+ function mapCSIFieldToStoreField({ csiDocumentField, modelField, context }) {
964
+ if (!csiDocumentField) {
965
+ const isUnset = ['object', 'model', 'reference', 'richText', 'markdown', 'image', 'file', 'json'].includes(modelField.type);
966
+ return {
967
+ type: modelField.type,
968
+ ...(isUnset ? { isUnset } : null),
969
+ ...(modelField.type === 'list' ? { items: [] } : null)
970
+ };
971
+ }
972
+ // TODO: check if need to add "options" to "enum" and subtype/min/max to "number"
973
+ switch (modelField.type) {
974
+ case 'object':
975
+ return mapObjectField(csiDocumentField, modelField, context);
976
+ case 'model':
977
+ return mapModelField(csiDocumentField, modelField, context);
978
+ case 'list':
979
+ return mapListField(csiDocumentField, modelField, context);
980
+ case 'richText':
981
+ return mapRichTextField(csiDocumentField);
982
+ case 'markdown':
983
+ return mapMarkdownField(csiDocumentField);
984
+ default:
985
+ return csiDocumentField;
986
+ }
987
+ }
988
+ function mapObjectField(csiDocumentField, modelField, context) {
989
+ var _a, _b, _c;
990
+ if (!(0, content_source_interface_1.isLocalizedField)(csiDocumentField)) {
991
+ return {
992
+ type: csiDocumentField.type,
993
+ srcObjectLabel: getObjectLabel((_a = csiDocumentField.fields) !== null && _a !== void 0 ? _a : {}, modelField !== null && modelField !== void 0 ? modelField : [], context.defaultLocaleCode),
994
+ fields: mapCSIFieldsToStoreFields({
995
+ csiDocumentFields: (_b = csiDocumentField.fields) !== null && _b !== void 0 ? _b : {},
996
+ modelFields: (_c = modelField.fields) !== null && _c !== void 0 ? _c : [],
997
+ context
998
+ })
999
+ };
1000
+ }
1001
+ return {
1002
+ type: csiDocumentField.type,
1003
+ localized: true,
1004
+ locales: lodash_1.default.mapValues(csiDocumentField.locales, (locale) => {
1005
+ var _a, _b, _c;
1006
+ return {
1007
+ locale: locale.locale,
1008
+ srcObjectLabel: getObjectLabel((_a = locale.fields) !== null && _a !== void 0 ? _a : {}, modelField, locale.locale),
1009
+ fields: mapCSIFieldsToStoreFields({
1010
+ csiDocumentFields: (_b = locale.fields) !== null && _b !== void 0 ? _b : {},
1011
+ modelFields: (_c = modelField.fields) !== null && _c !== void 0 ? _c : [],
1012
+ context
1013
+ })
1014
+ };
1015
+ })
1016
+ };
1017
+ }
1018
+ function mapModelField(csiDocumentField, modelField, context) {
1019
+ var _a, _b, _c, _d;
1020
+ if (!(0, content_source_interface_1.isLocalizedField)(csiDocumentField)) {
1021
+ const model = context.modelMap[csiDocumentField.modelName];
1022
+ return {
1023
+ type: csiDocumentField.type,
1024
+ srcObjectLabel: getObjectLabel((_a = csiDocumentField.fields) !== null && _a !== void 0 ? _a : {}, model, context.defaultLocaleCode),
1025
+ srcModelName: csiDocumentField.modelName,
1026
+ srcModelLabel: (_b = model.label) !== null && _b !== void 0 ? _b : lodash_1.default.startCase(model.name),
1027
+ fields: mapCSIFieldsToStoreFields({
1028
+ csiDocumentFields: (_c = csiDocumentField.fields) !== null && _c !== void 0 ? _c : {},
1029
+ modelFields: (_d = model.fields) !== null && _d !== void 0 ? _d : [],
1030
+ context
1031
+ })
1032
+ };
1033
+ }
1034
+ return {
1035
+ type: csiDocumentField.type,
1036
+ localized: true,
1037
+ locales: lodash_1.default.mapValues(csiDocumentField.locales, (locale) => {
1038
+ var _a, _b, _c, _d;
1039
+ const model = context.modelMap[locale.modelName];
1040
+ return {
1041
+ locale: locale.locale,
1042
+ srcObjectLabel: getObjectLabel((_a = locale.fields) !== null && _a !== void 0 ? _a : {}, model, locale.locale),
1043
+ srcModelName: locale.modelName,
1044
+ srcModelLabel: (_b = model.label) !== null && _b !== void 0 ? _b : lodash_1.default.startCase(model.name),
1045
+ fields: mapCSIFieldsToStoreFields({
1046
+ csiDocumentFields: (_c = locale.fields) !== null && _c !== void 0 ? _c : {},
1047
+ modelFields: (_d = model.fields) !== null && _d !== void 0 ? _d : [],
1048
+ context
1049
+ })
1050
+ };
1051
+ })
1052
+ };
1053
+ }
1054
+ function mapListField(csiDocumentField, modelField, context) {
1055
+ if (!(0, content_source_interface_1.isLocalizedField)(csiDocumentField)) {
1056
+ return {
1057
+ type: csiDocumentField.type,
1058
+ items: csiDocumentField.items.map((item) => {
1059
+ var _a;
1060
+ return mapCSIFieldToStoreField({
1061
+ csiDocumentField: item,
1062
+ modelField: (_a = modelField.items) !== null && _a !== void 0 ? _a : { type: 'string' },
1063
+ context
1064
+ });
1065
+ })
1066
+ };
1067
+ }
1068
+ return {
1069
+ type: csiDocumentField.type,
1070
+ localized: true,
1071
+ locales: lodash_1.default.mapValues(csiDocumentField.locales, (locale) => {
1072
+ var _a;
1073
+ return {
1074
+ locale: locale.locale,
1075
+ items: ((_a = locale.items) !== null && _a !== void 0 ? _a : []).map((item) => {
1076
+ var _a;
1077
+ return mapCSIFieldToStoreField({
1078
+ csiDocumentField: item,
1079
+ modelField: (_a = modelField.items) !== null && _a !== void 0 ? _a : { type: 'string' },
1080
+ context
1081
+ });
1082
+ })
1083
+ };
1084
+ })
1085
+ };
1086
+ }
1087
+ function mapRichTextField(csiDocumentField) {
1088
+ if (!(0, content_source_interface_1.isLocalizedField)(csiDocumentField)) {
1089
+ return {
1090
+ ...csiDocumentField,
1091
+ multiElement: true
1092
+ };
1093
+ }
1094
+ return {
1095
+ type: csiDocumentField.type,
1096
+ localized: true,
1097
+ locales: lodash_1.default.mapValues(csiDocumentField.locales, (locale) => {
1098
+ return {
1099
+ ...locale,
1100
+ multiElement: true
1101
+ };
1102
+ })
1103
+ };
1104
+ }
1105
+ function mapMarkdownField(csiDocumentField) {
1106
+ if (!(0, content_source_interface_1.isLocalizedField)(csiDocumentField)) {
1107
+ return {
1108
+ type: 'markdown',
1109
+ value: csiDocumentField.value,
1110
+ multiElement: true
1111
+ };
1112
+ }
1113
+ return {
1114
+ type: 'markdown',
1115
+ localized: true,
1116
+ locales: lodash_1.default.mapValues(csiDocumentField.locales, (locale) => {
1117
+ return {
1118
+ ...locale,
1119
+ multiElement: true
1120
+ };
1121
+ })
1122
+ };
1123
+ }
1124
+ function mapStoreFieldsToOperationFields({ documentFields, modelFields, modelMap }) {
1125
+ // TODO: implement
1126
+ throw new Error(`duplicateDocument not implemented yet`);
1127
+ }
1128
+ function getContentSourceIdForContentSource(contentSource) {
1129
+ return getContentSourceId(contentSource.getContentSourceType(), contentSource.getProjectId());
1130
+ }
1131
+ function extractTokensFromString(input) {
1132
+ return input.match(/(?<={)[^}]+(?=})/g) || [];
1133
+ }
1134
+ function sanitizeSlug(slug) {
1135
+ return slug
1136
+ .split('/')
1137
+ .map((part) => (0, slugify_1.default)(part, { lower: true }))
1138
+ .join('/');
1139
+ }
1140
+ function getObjectLabel(documentFields, modelOrObjectField, locale) {
1141
+ const labelField = modelOrObjectField.labelField;
1142
+ let label = null;
1143
+ if (labelField) {
1144
+ const field = lodash_1.default.get(documentFields, labelField, null);
1145
+ if (field && ['string', 'url', 'slug', 'text', 'markdown', 'number', 'enum', 'date', 'datetime', 'color', 'image', 'file'].includes(field.type)) {
1146
+ if ((0, content_source_interface_1.isLocalizedField)(field) && locale) {
1147
+ label = lodash_1.default.get(field, ['locales', locale, 'value'], null);
1148
+ }
1149
+ else if (!(0, content_source_interface_1.isLocalizedField)(field)) {
1150
+ label = lodash_1.default.get(field, 'value', null);
1151
+ }
1152
+ }
1153
+ }
1154
+ if (!label) {
1155
+ label = lodash_1.default.get(modelOrObjectField, 'label');
1156
+ }
1157
+ if (!label && lodash_1.default.has(modelOrObjectField, 'name')) {
1158
+ label = lodash_1.default.startCase(lodash_1.default.get(modelOrObjectField, 'name'));
1159
+ }
1160
+ return label;
1161
+ }
1162
+ function mapDocumentsToLocalizedApiObjects(documents, locale) {
1163
+ return documents.map((document) => documentToLocalizedApiObject(document, locale));
1164
+ }
1165
+ function documentToLocalizedApiObject(document, locale) {
1166
+ const { type, fields, ...rest } = document;
1167
+ return {
1168
+ type: 'object',
1169
+ ...rest,
1170
+ fields: toLocalizedAPIFields(fields, locale)
1171
+ };
1172
+ }
1173
+ function toLocalizedAPIFields(docFields, locale) {
1174
+ return lodash_1.default.mapValues(docFields, (docField) => toLocalizedAPIField(docField, locale));
1175
+ }
1176
+ function toLocalizedAPIField(docField, locale, isListItem = false) {
1177
+ const hasUnsetFlag = ['object', 'model', 'reference', 'richText', 'markdown', 'image', 'file', 'json'].includes(docField.type);
1178
+ let docFieldLocalized;
1179
+ let unset = false;
1180
+ if (docField.localized) {
1181
+ const { locales, localized, ...base } = docField;
1182
+ const localeProps = locale ? locales[locale] : undefined;
1183
+ docFieldLocalized = {
1184
+ ...base,
1185
+ ...localeProps,
1186
+ ...(hasUnsetFlag && !localeProps ? { isUnset: true } : null)
1187
+ };
1188
+ }
1189
+ else {
1190
+ docFieldLocalized = docField;
1191
+ }
1192
+ locale = locale !== null && locale !== void 0 ? locale : docFieldLocalized.locale;
1193
+ const commonProps = isListItem
1194
+ ? null
1195
+ : {
1196
+ localized: !!docField.localized,
1197
+ ...(locale ? { locale } : null)
1198
+ };
1199
+ if (docFieldLocalized.type === 'object' || docFieldLocalized.type === 'model') {
1200
+ return {
1201
+ ...docFieldLocalized,
1202
+ type: 'object',
1203
+ ...commonProps,
1204
+ ...(docFieldLocalized.isUnset
1205
+ ? null
1206
+ : {
1207
+ fields: toLocalizedAPIFields(docFieldLocalized.fields, locale)
1208
+ })
1209
+ };
1210
+ }
1211
+ else if (docFieldLocalized.type === 'reference') {
1212
+ const { type, refType, ...rest } = docFieldLocalized;
1213
+ // if reference field isUnset === true, it behaves like a regular object
1214
+ if (rest.isUnset) {
1215
+ return {
1216
+ type: 'object',
1217
+ ...rest,
1218
+ ...commonProps
1219
+ };
1220
+ }
1221
+ return {
1222
+ type: 'unresolved_reference',
1223
+ refType: refType === 'asset' ? 'image' : 'object',
1224
+ ...rest,
1225
+ ...commonProps
1226
+ };
1227
+ }
1228
+ else if (docFieldLocalized.type === 'list') {
1229
+ const { items, ...rest } = docFieldLocalized;
1230
+ return {
1231
+ ...rest,
1232
+ ...commonProps,
1233
+ items: items.map((field) => toLocalizedAPIField(field, locale, true))
1234
+ };
1235
+ }
1236
+ else {
1237
+ return {
1238
+ ...docFieldLocalized,
1239
+ ...commonProps
1240
+ };
1241
+ }
1242
+ }
1243
+ function mapAssetsToLocalizedApiImages(assets, locale) {
1244
+ return assets.map((asset) => assetToLocalizedApiImage(asset, locale));
1245
+ }
1246
+ function assetToLocalizedApiImage(asset, locale) {
1247
+ const { type, fields, ...rest } = asset;
1248
+ return {
1249
+ type: 'image',
1250
+ ...rest,
1251
+ fields: localizeAssetFields(fields, locale)
1252
+ };
1253
+ }
1254
+ function localizeAssetFields(assetFields, locale) {
1255
+ var _a, _b;
1256
+ const fields = {
1257
+ title: {
1258
+ type: 'string',
1259
+ value: null
1260
+ },
1261
+ url: {
1262
+ type: 'string',
1263
+ value: null
1264
+ }
1265
+ };
1266
+ const titleFieldNonLocalized = getDocumentFieldForLocale(assetFields.title, locale);
1267
+ fields.title.value = titleFieldNonLocalized === null || titleFieldNonLocalized === void 0 ? void 0 : titleFieldNonLocalized.value;
1268
+ fields.title.locale = locale !== null && locale !== void 0 ? locale : titleFieldNonLocalized === null || titleFieldNonLocalized === void 0 ? void 0 : titleFieldNonLocalized.locale;
1269
+ const assetFileField = assetFields.file;
1270
+ if (assetFileField.localized) {
1271
+ if (locale) {
1272
+ fields.url.value = (_b = (_a = assetFileField.locales[locale]) === null || _a === void 0 ? void 0 : _a.url) !== null && _b !== void 0 ? _b : null;
1273
+ fields.url.locale = locale;
1274
+ }
1275
+ }
1276
+ else {
1277
+ fields.url.value = assetFileField.url;
1278
+ fields.url.locale = assetFileField.locale;
1279
+ }
1280
+ return fields;
1281
+ }
1282
+ function mapStoreAssetsToAPIAssets(assets, locale) {
1283
+ return assets.map((asset) => storeAssetToAPIAsset(asset, locale));
1284
+ }
1285
+ function storeAssetToAPIAsset(asset, locale) {
1286
+ var _a, _b;
1287
+ const assetTitleField = asset.fields.title;
1288
+ const localizedTitleField = assetTitleField.localized ? assetTitleField.locales[locale] : assetTitleField;
1289
+ const assetFileField = asset.fields.file;
1290
+ const localizedFileField = assetFileField.localized ? assetFileField.locales[locale] : assetFileField;
1291
+ return {
1292
+ objectId: asset.srcObjectId,
1293
+ createdAt: asset.createdAt,
1294
+ url: localizedFileField.url,
1295
+ ...(0, utils_1.omitByNil)({
1296
+ title: localizedTitleField.value,
1297
+ fileName: localizedFileField.fileName,
1298
+ contentType: localizedFileField.contentType,
1299
+ size: localizedFileField.size,
1300
+ width: (_a = localizedFileField.dimensions) === null || _a === void 0 ? void 0 : _a.width,
1301
+ height: (_b = localizedFileField.dimensions) === null || _b === void 0 ? void 0 : _b.height
1302
+ })
1303
+ };
1304
+ }
1305
+ /**
1306
+ * Iterates recursively objects with $$type and $$ref, creating nested objects
1307
+ * as needed and returns standard ContentSourceInterface Documents
1308
+ */
1309
+ async function createDocumentRecursively({ object, model, modelMap, locale, userContext, contentSourceInstance }) {
1310
+ var _a;
1311
+ if (model.type === 'page') {
1312
+ const tokens = extractTokensFromString(String(model.urlPath));
1313
+ const slugField = lodash_1.default.last(tokens);
1314
+ if (object && slugField && slugField in object) {
1315
+ const slugFieldValue = object[slugField];
1316
+ object[slugField] = sanitizeSlug(slugFieldValue);
1317
+ }
1318
+ }
1319
+ const nestedResult = await createNestedObjectRecursively({
1320
+ object,
1321
+ modelFields: (_a = model.fields) !== null && _a !== void 0 ? _a : [],
1322
+ fieldPath: [],
1323
+ modelMap,
1324
+ locale,
1325
+ userContext,
1326
+ contentSourceInstance
1327
+ });
1328
+ const document = await contentSourceInstance.createDocument({
1329
+ updateOperationFields: nestedResult.fields,
1330
+ // TODO: pass csiModel
1331
+ model,
1332
+ // TODO: pass csiModelMap
1333
+ modelMap,
1334
+ locale,
1335
+ userContext
1336
+ });
1337
+ return {
1338
+ document: document,
1339
+ newRefDocuments: nestedResult.newRefDocuments
1340
+ };
1341
+ }
1342
+ async function createNestedObjectRecursively({ object, modelFields, fieldPath, modelMap, locale, userContext, contentSourceInstance }) {
1343
+ object = object !== null && object !== void 0 ? object : {};
1344
+ const result = {
1345
+ fields: {},
1346
+ newRefDocuments: []
1347
+ };
1348
+ const objectFieldNames = Object.keys(object);
1349
+ for (const modelField of modelFields) {
1350
+ const fieldName = modelField.name;
1351
+ let value;
1352
+ if (fieldName in object) {
1353
+ value = object[fieldName];
1354
+ lodash_1.default.pull(objectFieldNames, fieldName);
1355
+ }
1356
+ else if (modelField.const) {
1357
+ value = modelField.const;
1358
+ }
1359
+ else if (!lodash_1.default.isNil(modelField.default)) {
1360
+ value = modelField.default;
1361
+ }
1362
+ if (!lodash_1.default.isNil(value)) {
1363
+ const fieldResult = await createNestedField({
1364
+ value,
1365
+ modelField,
1366
+ fieldPath: fieldPath.concat(fieldName),
1367
+ modelMap,
1368
+ locale,
1369
+ userContext,
1370
+ contentSourceInstance
1371
+ });
1372
+ result.fields[fieldName] = fieldResult.field;
1373
+ result.newRefDocuments = result.newRefDocuments.concat(fieldResult.newRefDocuments);
1374
+ }
1375
+ }
1376
+ if (objectFieldNames.length > 0) {
1377
+ throw new Error(`no model fields found when creating a document with fields: '${objectFieldNames.join(', ')}'`);
1378
+ }
1379
+ return result;
1380
+ }
1381
+ async function createNestedField({ value, modelField, fieldPath, modelMap, locale, userContext, contentSourceInstance }) {
1382
+ var _a;
1383
+ if (modelField.type === 'object') {
1384
+ const result = await createNestedObjectRecursively({
1385
+ object: value,
1386
+ modelFields: modelField.fields,
1387
+ fieldPath,
1388
+ modelMap,
1389
+ locale,
1390
+ userContext,
1391
+ contentSourceInstance
1392
+ });
1393
+ return {
1394
+ field: {
1395
+ type: 'object',
1396
+ fields: result.fields
1397
+ },
1398
+ newRefDocuments: result.newRefDocuments
1399
+ };
1400
+ }
1401
+ else if (modelField.type === 'model') {
1402
+ let { $$type, ...rest } = value;
1403
+ const modelNames = modelField.models;
1404
+ // for backward compatibility check if the object has 'type' instead of '$$type' because older projects use
1405
+ // the 'type' property in default values
1406
+ if (!$$type && 'type' in rest) {
1407
+ $$type = rest.type;
1408
+ rest = lodash_1.default.omit(rest, 'type');
1409
+ }
1410
+ const modelName = $$type !== null && $$type !== void 0 ? $$type : (modelNames.length === 1 ? modelNames[0] : null);
1411
+ if (!modelName) {
1412
+ throw new Error(`no $$type was specified for nested model`);
1413
+ }
1414
+ const model = modelMap[modelName];
1415
+ if (!model) {
1416
+ throw new Error(`no model with name '${modelName}' was found`);
1417
+ }
1418
+ const result = await createNestedObjectRecursively({
1419
+ object: rest,
1420
+ modelFields: (_a = model.fields) !== null && _a !== void 0 ? _a : [],
1421
+ fieldPath,
1422
+ modelMap,
1423
+ locale,
1424
+ userContext,
1425
+ contentSourceInstance
1426
+ });
1427
+ return {
1428
+ field: {
1429
+ type: 'model',
1430
+ modelName: modelName,
1431
+ fields: result.fields
1432
+ },
1433
+ newRefDocuments: result.newRefDocuments
1434
+ };
1435
+ }
1436
+ else if (modelField.type === 'image') {
1437
+ let refId;
1438
+ if (lodash_1.default.isPlainObject(value)) {
1439
+ refId = value.$$ref;
1440
+ }
1441
+ else {
1442
+ refId = value;
1443
+ }
1444
+ if (!refId) {
1445
+ throw new Error(`reference field must specify a value`);
1446
+ }
1447
+ return {
1448
+ field: {
1449
+ type: 'reference',
1450
+ refType: 'asset',
1451
+ refId: refId
1452
+ },
1453
+ newRefDocuments: []
1454
+ };
1455
+ }
1456
+ else if (modelField.type === 'reference') {
1457
+ let { $$ref: refId = null, $$type: modelName = null, ...rest } = lodash_1.default.isPlainObject(value) ? value : { $$ref: value };
1458
+ if (refId) {
1459
+ return {
1460
+ field: {
1461
+ type: 'reference',
1462
+ refType: 'document',
1463
+ refId: refId
1464
+ },
1465
+ newRefDocuments: []
1466
+ };
1467
+ }
1468
+ else {
1469
+ const modelNames = modelField.models;
1470
+ if (!modelName) {
1471
+ // for backward compatibility check if the object has 'type' instead of '$$type' because older projects use
1472
+ // the 'type' property in default values
1473
+ if ('type' in rest) {
1474
+ modelName = rest.type;
1475
+ rest = lodash_1.default.omit(rest, 'type');
1476
+ }
1477
+ else if (modelNames.length === 1) {
1478
+ modelName = modelNames[0];
1479
+ }
1480
+ }
1481
+ const model = modelMap[modelName];
1482
+ if (!model) {
1483
+ throw new Error(`no model with name '${modelName}' was found`);
1484
+ }
1485
+ const { document, newRefDocuments } = await createDocumentRecursively({
1486
+ object: rest,
1487
+ model: model,
1488
+ modelMap,
1489
+ locale,
1490
+ userContext,
1491
+ contentSourceInstance
1492
+ });
1493
+ return {
1494
+ field: {
1495
+ type: 'reference',
1496
+ refType: 'document',
1497
+ refId: document.id
1498
+ },
1499
+ newRefDocuments: [document, ...newRefDocuments]
1500
+ };
1501
+ }
1502
+ }
1503
+ else if (modelField.type === 'list') {
1504
+ if (!Array.isArray(value)) {
1505
+ throw new Error(`value for list field must be array`);
1506
+ }
1507
+ const itemsField = modelField.items;
1508
+ if (!itemsField) {
1509
+ throw new Error(`list field does not define items`);
1510
+ }
1511
+ const arrayResult = await (0, utils_1.mapPromise)(value, async (item, index) => {
1512
+ return createNestedField({
1513
+ value: item,
1514
+ modelField: itemsField,
1515
+ fieldPath: fieldPath.concat(index),
1516
+ modelMap,
1517
+ locale,
1518
+ userContext,
1519
+ contentSourceInstance
1520
+ });
1521
+ });
1522
+ return {
1523
+ field: {
1524
+ type: 'list',
1525
+ items: arrayResult.map((result) => result.field)
1526
+ },
1527
+ newRefDocuments: arrayResult.reduce((result, { newRefDocuments }) => result.concat(newRefDocuments), [])
1528
+ };
1529
+ }
1530
+ return {
1531
+ field: {
1532
+ type: modelField.type,
1533
+ value: value
1534
+ },
1535
+ newRefDocuments: []
1536
+ };
1537
+ }
1538
+ function getModelFieldForFieldAtPath(document, model, fieldPath, modelMap, locale) {
1539
+ if (lodash_1.default.isEmpty(fieldPath)) {
1540
+ throw new Error('the fieldPath can not be empty');
1541
+ }
1542
+ function getField(docField, modelField, fieldPath) {
1543
+ const fieldName = lodash_1.default.head(fieldPath);
1544
+ if (typeof fieldName === 'undefined') {
1545
+ throw new Error('the first fieldPath item must be string');
1546
+ }
1547
+ const childFieldPath = lodash_1.default.tail(fieldPath);
1548
+ let childDocField;
1549
+ let childModelField;
1550
+ switch (docField.type) {
1551
+ case 'object':
1552
+ const localizedObjectField = getDocumentFieldForLocale(docField, locale);
1553
+ if (!localizedObjectField) {
1554
+ throw new Error(`locale for field was not found`);
1555
+ }
1556
+ if (localizedObjectField.isUnset) {
1557
+ throw new Error(`field is not set`);
1558
+ }
1559
+ childDocField = localizedObjectField.fields[fieldName];
1560
+ childModelField = lodash_1.default.find(modelField.fields, (field) => field.name === fieldName);
1561
+ if (!childDocField || !childModelField) {
1562
+ throw new Error(`field ${fieldName} doesn't exist`);
1563
+ }
1564
+ if (childFieldPath.length === 0) {
1565
+ return childModelField;
1566
+ }
1567
+ return getField(childDocField, childModelField, childFieldPath);
1568
+ case 'model':
1569
+ const localizedModelField = getDocumentFieldForLocale(docField, locale);
1570
+ if (!localizedModelField) {
1571
+ throw new Error(`locale for field was not found`);
1572
+ }
1573
+ if (localizedModelField.isUnset) {
1574
+ throw new Error(`field is not set`);
1575
+ }
1576
+ const modelName = localizedModelField.srcModelName;
1577
+ const childModel = modelMap[modelName];
1578
+ if (!childModel) {
1579
+ throw new Error(`model ${modelName} doesn't exist`);
1580
+ }
1581
+ childModelField = lodash_1.default.find(childModel.fields, (field) => field.name === fieldName);
1582
+ childDocField = localizedModelField.fields[fieldName];
1583
+ if (!childDocField || !childModelField) {
1584
+ throw new Error(`field ${fieldName} doesn't exist`);
1585
+ }
1586
+ if (childFieldPath.length === 0) {
1587
+ return childModelField;
1588
+ }
1589
+ return getField(childDocField, childModelField, childFieldPath);
1590
+ case 'list':
1591
+ const localizedListField = getDocumentFieldForLocale(docField, locale);
1592
+ if (!localizedListField) {
1593
+ throw new Error(`locale for field was not found`);
1594
+ }
1595
+ const listItem = localizedListField.items && localizedListField.items[fieldName];
1596
+ const listItemsModel = modelField.items;
1597
+ if (!listItem || !listItemsModel) {
1598
+ throw new Error(`field ${fieldName} doesn't exist`);
1599
+ }
1600
+ if (childFieldPath.length === 0) {
1601
+ return modelField;
1602
+ }
1603
+ if (!Array.isArray(listItemsModel)) {
1604
+ return getField(listItem, listItemsModel, childFieldPath);
1605
+ }
1606
+ else {
1607
+ const fieldListItems = listItemsModel.find((listItemsModel) => listItemsModel.type === listItem.type);
1608
+ if (!fieldListItems) {
1609
+ throw new Error('cannot find matching field model');
1610
+ }
1611
+ return getField(listItem, fieldListItems, childFieldPath);
1612
+ }
1613
+ default:
1614
+ if (!lodash_1.default.isEmpty(childFieldPath)) {
1615
+ throw new Error('illegal fieldPath');
1616
+ }
1617
+ return modelField;
1618
+ }
1619
+ }
1620
+ const fieldName = lodash_1.default.head(fieldPath);
1621
+ const childFieldPath = lodash_1.default.tail(fieldPath);
1622
+ if (typeof fieldName !== 'string') {
1623
+ throw new Error('the first fieldPath item must be string');
1624
+ }
1625
+ const childDocField = document.fields[fieldName];
1626
+ const childModelField = lodash_1.default.find(model.fields, { name: fieldName });
1627
+ if (!childDocField || !childModelField) {
1628
+ throw new Error(`field ${fieldName} doesn't exist`);
1629
+ }
1630
+ if (childFieldPath.length === 0) {
1631
+ return childModelField;
1632
+ }
1633
+ return getField(childDocField, childModelField, childFieldPath);
1634
+ }
1635
+ async function convertOperationField({ operationField, fieldPath, modelField, modelMap, locale, userContext, contentSourceInstance }) {
1636
+ // for insert operations, the modelField will be of the list, so get the modelField of the list items
1637
+ const modelFieldOrListItems = modelField.type === 'list' ? modelField.items : modelField;
1638
+ switch (operationField.type) {
1639
+ case 'object': {
1640
+ const result = await createNestedObjectRecursively({
1641
+ object: operationField.object,
1642
+ modelFields: modelFieldOrListItems.fields,
1643
+ fieldPath: fieldPath,
1644
+ modelMap,
1645
+ locale,
1646
+ userContext,
1647
+ contentSourceInstance
1648
+ });
1649
+ return {
1650
+ type: operationField.type,
1651
+ fields: result.fields
1652
+ };
1653
+ }
1654
+ case 'model': {
1655
+ const model = modelMap[operationField.modelName];
1656
+ if (!model) {
1657
+ throw new Error(`error updating document, could not find document model: '${operationField.modelName}'`);
1658
+ }
1659
+ const result = await createNestedObjectRecursively({
1660
+ object: operationField.object,
1661
+ modelFields: model.fields,
1662
+ fieldPath,
1663
+ modelMap,
1664
+ locale,
1665
+ userContext,
1666
+ contentSourceInstance
1667
+ });
1668
+ return {
1669
+ type: operationField.type,
1670
+ modelName: operationField.modelName,
1671
+ fields: result.fields
1672
+ };
1673
+ }
1674
+ case 'list': {
1675
+ if (modelField.type !== 'list') {
1676
+ throw new Error(`'the operation field type '${operationField.type}' does not match the model field type '${modelField.type}'`);
1677
+ }
1678
+ const result = await (0, utils_1.mapPromise)(operationField.items, async (item, index) => {
1679
+ const result = await createNestedField({
1680
+ value: item,
1681
+ modelField: modelField.items,
1682
+ fieldPath,
1683
+ modelMap,
1684
+ locale,
1685
+ userContext,
1686
+ contentSourceInstance
1687
+ });
1688
+ return result.field;
1689
+ });
1690
+ return {
1691
+ type: operationField.type,
1692
+ items: result
1693
+ };
1694
+ }
1695
+ case 'string':
1696
+ // When inserting new string value into a list, the client does not
1697
+ // send value. Set an empty string value.
1698
+ if (typeof operationField.value !== 'string') {
1699
+ return {
1700
+ type: operationField.type,
1701
+ value: ''
1702
+ };
1703
+ }
1704
+ return operationField;
1705
+ case 'enum':
1706
+ // When inserting new enum value into a list, the client does not
1707
+ // send value. Set first option as the value.
1708
+ if (typeof operationField.value !== 'string') {
1709
+ if (modelFieldOrListItems.type !== 'enum') {
1710
+ throw new Error(`'the operation field type 'enum' does not match the model field type '${modelFieldOrListItems.type}'`);
1711
+ }
1712
+ const option = modelFieldOrListItems.options[0];
1713
+ const optionValue = typeof option === 'object' ? option.value : option;
1714
+ return {
1715
+ type: operationField.type,
1716
+ value: optionValue
1717
+ };
1718
+ }
1719
+ return operationField;
1720
+ case 'image':
1721
+ return operationField;
1722
+ default:
1723
+ return operationField;
1724
+ }
1725
+ }
1726
+ function getDocumentFieldForLocale(docField, locale) {
1727
+ if (docField.localized) {
1728
+ if (!locale) {
1729
+ return null;
1730
+ }
1731
+ const { localized, locales, ...base } = docField;
1732
+ const localizedField = locales[locale];
1733
+ if (!localizedField) {
1734
+ return null;
1735
+ }
1736
+ return {
1737
+ ...base,
1738
+ ...localizedField
1739
+ };
1740
+ }
1741
+ else {
1742
+ return docField;
1743
+ }
1744
+ }
1745
+ function isStackbitConfigFile(filePath) {
1746
+ const pathObject = path_1.default.parse(filePath);
1747
+ const isInDotStackbitFolder = pathObject.dir.startsWith('.stackbit');
1748
+ const isMainStackbitConfigFile = pathObject.name === 'stackbit' && ['yaml', 'yml'].includes(pathObject.ext.substring(1));
1749
+ return isMainStackbitConfigFile || isInDotStackbitFolder;
1750
+ }
1751
+ function getCSIDocumentsAndAssetsFromContentSourceDataByIds(contentSourceData, objects) {
1752
+ const documents = [];
1753
+ const assets = [];
1754
+ for (const object of objects) {
1755
+ if (object.srcObjectId in contentSourceData.csiDocumentMap) {
1756
+ documents.push(contentSourceData.csiDocumentMap[object.srcObjectId]);
1757
+ }
1758
+ else if (object.srcObjectId in contentSourceData.csiAssetMap) {
1759
+ assets.push(contentSourceData.csiAssetMap[object.srcObjectId]);
1760
+ }
1761
+ }
1762
+ return {
1763
+ documents,
1764
+ assets
1765
+ };
1766
+ }
1767
+ function internalValidateContent(documents, assets, contentSourceData) {
1768
+ const errors = [];
1769
+ lodash_1.default.forEach(documents, (document) => {
1770
+ lodash_1.default.forEach(document.fields, (documentField, fieldName) => {
1771
+ const localizedField = (0, content_source_interface_1.getLocalizedFieldForLocale)(documentField);
1772
+ errors.push(...validateDocumentFields(document, localizedField, [fieldName], contentSourceData));
1773
+ });
1774
+ });
1775
+ return errors;
1776
+ }
1777
+ function validateDocumentFields(document, documentField, fieldPath, contentSourceData) {
1778
+ const errors = [];
1779
+ if (documentField.type === 'object') {
1780
+ lodash_1.default.forEach(documentField.fields, (documentField, fieldName) => {
1781
+ const localizedField = (0, content_source_interface_1.getLocalizedFieldForLocale)(documentField);
1782
+ errors.push(...validateDocumentFields(document, localizedField, fieldPath.concat(fieldName), contentSourceData));
1783
+ });
1784
+ }
1785
+ else if (documentField.type === 'model') {
1786
+ lodash_1.default.forEach(documentField.fields, (documentField, fieldName) => {
1787
+ const localizedField = (0, content_source_interface_1.getLocalizedFieldForLocale)(documentField);
1788
+ errors.push(...validateDocumentFields(document, localizedField, fieldPath.concat(fieldName), contentSourceData));
1789
+ });
1790
+ }
1791
+ else if (documentField.type === 'reference') {
1792
+ const objRef = documentField.refType === 'asset'
1793
+ ? contentSourceData.assetMap[documentField.refId]
1794
+ : contentSourceData.documentMap[documentField.refId];
1795
+ if (!objRef) {
1796
+ errors.push({
1797
+ fieldPath,
1798
+ srcType: contentSourceData.type,
1799
+ srcProjectId: contentSourceData.projectId,
1800
+ srcObjectType: documentField.refType,
1801
+ srcObjectId: document.id,
1802
+ message: `Can't find referenced ${documentField.refType}: ${documentField.refId}`
1803
+ });
1804
+ }
1805
+ }
1806
+ else if (documentField.type === 'list') {
1807
+ lodash_1.default.forEach(documentField.items, (documentField, i) => {
1808
+ const localizedField = (0, content_source_interface_1.getLocalizedFieldForLocale)(documentField);
1809
+ errors.push(...validateDocumentFields(document, documentField, fieldPath.concat(i), contentSourceData));
1810
+ });
1811
+ }
1812
+ return errors;
1813
+ }
1814
+ //# sourceMappingURL=content-store.js.map