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