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