@stackbit/cms-core 0.0.19-alpha.1 → 0.0.19-alpha.4
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/common/common-types.d.ts +10 -0
- package/dist/common/common-types.d.ts.map +1 -0
- package/dist/common/common-types.js +3 -0
- package/dist/common/common-types.js.map +1 -0
- package/dist/content-source-interface.d.ts +21 -7
- package/dist/content-source-interface.d.ts.map +1 -1
- package/dist/content-source-interface.js.map +1 -1
- package/dist/content-store-types.d.ts +25 -6
- package/dist/content-store-types.d.ts.map +1 -1
- package/dist/content-store.d.ts +3 -0
- package/dist/content-store.d.ts.map +1 -1
- package/dist/content-store.js +72 -20
- package/dist/content-store.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/common/common-types.ts +10 -0
- package/src/content-source-interface.ts +36 -9
- package/src/content-store-types.ts +20 -6
- package/src/content-store.ts +102 -23
- package/src/index.ts +2 -1
package/src/content-store.ts
CHANGED
|
@@ -17,10 +17,11 @@ import {
|
|
|
17
17
|
} from '@stackbit/sdk';
|
|
18
18
|
import { mapPromise, omitByNil } from '@stackbit/utils';
|
|
19
19
|
import * as CSITypes from './content-source-interface';
|
|
20
|
-
import { isLocalizedField } from './content-source-interface';
|
|
20
|
+
import { isLocalizedField, getLocalizedFieldForLocale } from './content-source-interface';
|
|
21
21
|
import * as ContentStoreTypes from './content-store-types';
|
|
22
22
|
import { IMAGE_MODEL } from './common/common-schema';
|
|
23
23
|
import { Timer } from './utils/timer';
|
|
24
|
+
import { UserCommandSpawner } from './common/common-types';
|
|
24
25
|
|
|
25
26
|
export interface ContentSourceOptions {
|
|
26
27
|
logger: ContentStoreTypes.Logger;
|
|
@@ -28,6 +29,7 @@ export interface ContentSourceOptions {
|
|
|
28
29
|
localDev: boolean;
|
|
29
30
|
stackbitYamlDir: string;
|
|
30
31
|
contentSources: CSITypes.ContentSourceInterface[];
|
|
32
|
+
userCommandSpawner?: UserCommandSpawner;
|
|
31
33
|
onSchemaChangeCallback: () => void;
|
|
32
34
|
onContentChangeCallback: (contentChanges: ContentStoreTypes.ContentChangeResult) => void;
|
|
33
35
|
handleConfigAssets: (config: Config) => Promise<Config>;
|
|
@@ -38,17 +40,29 @@ interface ContentSourceData {
|
|
|
38
40
|
instance: CSITypes.ContentSourceInterface;
|
|
39
41
|
type: string;
|
|
40
42
|
projectId: string;
|
|
41
|
-
models: Model[];
|
|
42
|
-
modelMap: Record<string, Model>;
|
|
43
43
|
locales?: CSITypes.Locale[];
|
|
44
44
|
defaultLocaleCode?: string;
|
|
45
|
+
/* Array of extended and validated Models */
|
|
46
|
+
models: Model[];
|
|
47
|
+
/* Map of extended and validated Models by model name */
|
|
48
|
+
modelMap: Record<string, Model>;
|
|
49
|
+
/* Map of original Models (as provided by content source) by model name */
|
|
50
|
+
csiModelMap: Record<string, Model>;
|
|
51
|
+
/* Array of original content source Documents */
|
|
45
52
|
csiDocuments: CSITypes.Document[];
|
|
53
|
+
/* Map of original content source Documents by document ID */
|
|
46
54
|
csiDocumentMap: Record<string, CSITypes.Document>;
|
|
55
|
+
/* Array of converted content-store Documents */
|
|
47
56
|
documents: ContentStoreTypes.Document[];
|
|
57
|
+
/* Map of converted content-store Documents by document ID */
|
|
48
58
|
documentMap: Record<string, ContentStoreTypes.Document>;
|
|
59
|
+
/* Array of original content source Assets */
|
|
49
60
|
csiAssets: CSITypes.Asset[];
|
|
61
|
+
/* Map of original content source Assets by asset ID */
|
|
50
62
|
csiAssetMap: Record<string, CSITypes.Asset>;
|
|
63
|
+
/* Array of converted content-store Assets */
|
|
51
64
|
assets: ContentStoreTypes.Asset[];
|
|
65
|
+
/* Map of converted content-store Assets by asset ID */
|
|
52
66
|
assetMap: Record<string, ContentStoreTypes.Asset>;
|
|
53
67
|
}
|
|
54
68
|
|
|
@@ -56,6 +70,7 @@ export class ContentStore {
|
|
|
56
70
|
private readonly contentSources: CSITypes.ContentSourceInterface[];
|
|
57
71
|
private readonly logger: ContentStoreTypes.Logger;
|
|
58
72
|
private readonly userLogger: ContentStoreTypes.Logger;
|
|
73
|
+
private readonly userCommandSpawner?: UserCommandSpawner;
|
|
59
74
|
private readonly localDev: boolean;
|
|
60
75
|
private readonly stackbitYamlDir: string; // TODO: remove stackbitYamlDir, ContentStore should not be aware of filesystem, instead pass autoreloading Config object
|
|
61
76
|
private readonly onSchemaChangeCallback: () => void;
|
|
@@ -70,6 +85,7 @@ export class ContentStore {
|
|
|
70
85
|
this.logger = options.logger.createLogger({ label: 'content-store' });
|
|
71
86
|
this.userLogger = options.userLogger.createLogger({ label: 'content-store' });
|
|
72
87
|
this.localDev = options.localDev;
|
|
88
|
+
this.userCommandSpawner = options.userCommandSpawner;
|
|
73
89
|
this.stackbitYamlDir = options.stackbitYamlDir; // TODO: remove stackbitYamlDir, ContentStore should not be aware of filesystem, instead pass autoreloading Config object
|
|
74
90
|
this.contentSources = options.contentSources;
|
|
75
91
|
this.onSchemaChangeCallback = options.onSchemaChangeCallback;
|
|
@@ -223,6 +239,7 @@ export class ContentStore {
|
|
|
223
239
|
await contentSourceInstance.init({
|
|
224
240
|
logger: this.logger,
|
|
225
241
|
userLogger: this.userLogger,
|
|
242
|
+
userCommandSpawner: this.userCommandSpawner,
|
|
226
243
|
localDev: this.localDev
|
|
227
244
|
});
|
|
228
245
|
} else {
|
|
@@ -230,9 +247,9 @@ export class ContentStore {
|
|
|
230
247
|
await contentSourceInstance.reset();
|
|
231
248
|
}
|
|
232
249
|
|
|
233
|
-
const
|
|
250
|
+
const csiModels = await contentSourceInstance.getModels();
|
|
234
251
|
const locales = await contentSourceInstance?.getLocales();
|
|
235
|
-
const result = await extendConfig({ dirPath: this.stackbitYamlDir, config: this.rawStackbitConfig, externalModels:
|
|
252
|
+
const result = await extendConfig({ dirPath: this.stackbitYamlDir, config: this.rawStackbitConfig, externalModels: csiModels });
|
|
236
253
|
const config = await this.handleConfigAssets(result.config);
|
|
237
254
|
const models = config.models;
|
|
238
255
|
const modelMap = _.keyBy(models, 'name');
|
|
@@ -283,6 +300,7 @@ export class ContentStore {
|
|
|
283
300
|
defaultLocaleCode: defaultLocaleCode,
|
|
284
301
|
models: models,
|
|
285
302
|
modelMap: modelMap,
|
|
303
|
+
csiModelMap: _.keyBy(csiModels, 'name'),
|
|
286
304
|
csiDocuments: csiDocuments,
|
|
287
305
|
csiDocumentMap: csiDocumentMap,
|
|
288
306
|
documents: contentStoreDocuments,
|
|
@@ -296,7 +314,7 @@ export class ContentStore {
|
|
|
296
314
|
contentSourceInstance.startWatchingContentUpdates({
|
|
297
315
|
getModelMap: () => {
|
|
298
316
|
const contentSourceData = this.getContentSourceDataByIdOrThrow(contentSourceId);
|
|
299
|
-
return contentSourceData.
|
|
317
|
+
return contentSourceData.csiModelMap;
|
|
300
318
|
},
|
|
301
319
|
getDocument({ documentId }: { documentId: string }) {
|
|
302
320
|
return csiDocumentMap[documentId];
|
|
@@ -677,15 +695,15 @@ export class ContentStore {
|
|
|
677
695
|
|
|
678
696
|
// get the document model
|
|
679
697
|
const documentModelName = document.srcModelName;
|
|
680
|
-
const
|
|
681
|
-
const model =
|
|
698
|
+
const csiModelMap = contentSourceData.csiModelMap;
|
|
699
|
+
const model = csiModelMap[documentModelName];
|
|
682
700
|
if (!model) {
|
|
683
701
|
throw new Error(`error updating document, could not find document model: '${documentModelName}'`);
|
|
684
702
|
}
|
|
685
703
|
|
|
686
704
|
// get the 'reference' model field in the updated document that will be used to link the new document
|
|
687
705
|
locale = locale ?? contentSourceData.defaultLocaleCode;
|
|
688
|
-
const modelField = getModelFieldForFieldAtPath(document, model, fieldPath,
|
|
706
|
+
const modelField = getModelFieldForFieldAtPath(document, model, fieldPath, csiModelMap, locale);
|
|
689
707
|
if (!modelField) {
|
|
690
708
|
throw Error(`the "fieldPath" points to non existing model field: ${fieldPath.join('.')}`);
|
|
691
709
|
}
|
|
@@ -721,7 +739,7 @@ export class ContentStore {
|
|
|
721
739
|
} as const;
|
|
722
740
|
const updatedDocument = await contentSourceData.instance.updateDocument({
|
|
723
741
|
document: csiDocument,
|
|
724
|
-
modelMap:
|
|
742
|
+
modelMap: csiModelMap,
|
|
725
743
|
userContext: userContext,
|
|
726
744
|
operations: [
|
|
727
745
|
modelField.type === 'list'
|
|
@@ -777,15 +795,15 @@ export class ContentStore {
|
|
|
777
795
|
|
|
778
796
|
// get the document model
|
|
779
797
|
const documentModelName = document.srcModelName;
|
|
780
|
-
const
|
|
781
|
-
const
|
|
782
|
-
if (!
|
|
798
|
+
const csiModelMap = contentSourceData.csiModelMap;
|
|
799
|
+
const csiModel = csiModelMap[documentModelName];
|
|
800
|
+
if (!csiModel) {
|
|
783
801
|
throw new Error(`error updating document, could not find document model: '${documentModelName}'`);
|
|
784
802
|
}
|
|
785
803
|
|
|
786
804
|
// get the 'reference' model field in the updated document that will be used to link the new asset
|
|
787
805
|
locale = locale ?? contentSourceData.defaultLocaleCode;
|
|
788
|
-
const modelField = getModelFieldForFieldAtPath(document,
|
|
806
|
+
const modelField = getModelFieldForFieldAtPath(document, csiModel, fieldPath, csiModelMap, locale);
|
|
789
807
|
if (!modelField) {
|
|
790
808
|
throw Error(`the "fieldPath" points to non existing model field: ${fieldPath.join('.')}`);
|
|
791
809
|
}
|
|
@@ -812,7 +830,7 @@ export class ContentStore {
|
|
|
812
830
|
} as const;
|
|
813
831
|
const updatedDocument = await contentSourceData.instance.updateDocument({
|
|
814
832
|
document: csiDocument,
|
|
815
|
-
modelMap:
|
|
833
|
+
modelMap: csiModelMap,
|
|
816
834
|
userContext: userContext,
|
|
817
835
|
operations: [
|
|
818
836
|
modelField.type === 'list'
|
|
@@ -907,6 +925,7 @@ export class ContentStore {
|
|
|
907
925
|
}
|
|
908
926
|
|
|
909
927
|
const modelMap = contentSourceData.modelMap;
|
|
928
|
+
const csiModelMap = contentSourceData.csiModelMap;
|
|
910
929
|
const documentModelName = document.srcModelName;
|
|
911
930
|
const model = modelMap[documentModelName];
|
|
912
931
|
if (!model) {
|
|
@@ -961,7 +980,7 @@ export class ContentStore {
|
|
|
961
980
|
|
|
962
981
|
const updatedDocumentResult = await contentSourceData.instance.updateDocument({
|
|
963
982
|
document: csiDocument,
|
|
964
|
-
modelMap,
|
|
983
|
+
modelMap: csiModelMap,
|
|
965
984
|
userContext,
|
|
966
985
|
operations
|
|
967
986
|
});
|
|
@@ -1005,7 +1024,7 @@ export class ContentStore {
|
|
|
1005
1024
|
// TODO: handle duplicatable and non duplicatable models
|
|
1006
1025
|
// TODO: handle fields from the provided object
|
|
1007
1026
|
const documentResult = await contentSourceData.instance.createDocument({
|
|
1008
|
-
|
|
1027
|
+
updateOperationFields: mapStoreFieldsToOperationFields({
|
|
1009
1028
|
documentFields: document.fields,
|
|
1010
1029
|
modelFields: model.fields!,
|
|
1011
1030
|
modelMap: contentSourceData.modelMap
|
|
@@ -1113,8 +1132,10 @@ export class ContentStore {
|
|
|
1113
1132
|
locale = locale ?? contentSourceData.defaultLocaleCode;
|
|
1114
1133
|
const { documents, assets } = getCSIDocumentsAndAssetsFromContentSourceDataByIds(contentSourceData, contentSourceObjects);
|
|
1115
1134
|
const userContext = getUserContextForSrcType(contentSourceData.type, user);
|
|
1135
|
+
const internalValidationErrors = internalValidateContent(documents, assets, contentSourceData);
|
|
1116
1136
|
const validationResult = await contentSourceData.instance.validateDocuments({ documents, assets, locale, userContext });
|
|
1117
1137
|
errors = errors.concat(
|
|
1138
|
+
internalValidationErrors,
|
|
1118
1139
|
validationResult.errors.map((validationError) => ({
|
|
1119
1140
|
message: validationError.message,
|
|
1120
1141
|
srcType: contentSourceData.type,
|
|
@@ -1487,7 +1508,7 @@ function mapMarkdownField(csiDocumentField: CSITypes.DocumentValueField): Conten
|
|
|
1487
1508
|
};
|
|
1488
1509
|
}
|
|
1489
1510
|
|
|
1490
|
-
function
|
|
1511
|
+
function mapStoreFieldsToOperationFields({
|
|
1491
1512
|
documentFields,
|
|
1492
1513
|
modelFields,
|
|
1493
1514
|
modelMap
|
|
@@ -1495,7 +1516,7 @@ function mapStoreFieldsToSourceFields({
|
|
|
1495
1516
|
documentFields: Record<string, ContentStoreTypes.DocumentField>;
|
|
1496
1517
|
modelFields: Field[];
|
|
1497
1518
|
modelMap: Record<string, Model>;
|
|
1498
|
-
}): Record<string, CSITypes.
|
|
1519
|
+
}): Record<string, CSITypes.UpdateOperationField> {
|
|
1499
1520
|
// TODO: implement
|
|
1500
1521
|
throw new Error(`duplicateDocument not implemented yet`);
|
|
1501
1522
|
}
|
|
@@ -1585,6 +1606,7 @@ function toLocalizedAPIField(docField: ContentStoreTypes.DocumentField, locale?:
|
|
|
1585
1606
|
if (docFieldLocalized.type === 'object' || docFieldLocalized.type === 'model') {
|
|
1586
1607
|
return {
|
|
1587
1608
|
...docFieldLocalized,
|
|
1609
|
+
type: 'object',
|
|
1588
1610
|
...commonProps,
|
|
1589
1611
|
...(docFieldLocalized.isUnset
|
|
1590
1612
|
? null
|
|
@@ -1725,8 +1747,10 @@ async function createDocumentRecursively({
|
|
|
1725
1747
|
contentSourceInstance
|
|
1726
1748
|
});
|
|
1727
1749
|
const document = await contentSourceInstance.createDocument({
|
|
1728
|
-
|
|
1750
|
+
updateOperationFields: nestedResult.fields,
|
|
1751
|
+
// TODO: pass csiModel
|
|
1729
1752
|
model,
|
|
1753
|
+
// TODO: pass csiModelMap
|
|
1730
1754
|
modelMap,
|
|
1731
1755
|
locale,
|
|
1732
1756
|
userContext
|
|
@@ -2172,9 +2196,6 @@ async function convertOperationField({
|
|
|
2172
2196
|
}
|
|
2173
2197
|
return operationField as CSITypes.UpdateOperationField;
|
|
2174
2198
|
case 'image':
|
|
2175
|
-
if (typeof operationField.value !== 'string') {
|
|
2176
|
-
throw new Error('image value was not specified');
|
|
2177
|
-
}
|
|
2178
2199
|
return operationField as CSITypes.UpdateOperationField;
|
|
2179
2200
|
default:
|
|
2180
2201
|
return operationField as CSITypes.UpdateOperationField;
|
|
@@ -2231,3 +2252,61 @@ function getCSIDocumentsAndAssetsFromContentSourceDataByIds(
|
|
|
2231
2252
|
assets
|
|
2232
2253
|
};
|
|
2233
2254
|
}
|
|
2255
|
+
|
|
2256
|
+
function internalValidateContent(
|
|
2257
|
+
documents: CSITypes.Document[],
|
|
2258
|
+
assets: CSITypes.Asset[],
|
|
2259
|
+
contentSourceData: ContentSourceData
|
|
2260
|
+
): ContentStoreTypes.ValidationError[] {
|
|
2261
|
+
|
|
2262
|
+
const errors: ContentStoreTypes.ValidationError[] = [];
|
|
2263
|
+
_.forEach(documents, (document) => {
|
|
2264
|
+
_.forEach(document.fields, (documentField, fieldName) => {
|
|
2265
|
+
const localizedField = getLocalizedFieldForLocale(documentField)!;
|
|
2266
|
+
errors.push(...validateDocumentFields(document, localizedField, [fieldName], contentSourceData));
|
|
2267
|
+
})
|
|
2268
|
+
})
|
|
2269
|
+
return errors;
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
function validateDocumentFields(
|
|
2273
|
+
document: CSITypes.Document,
|
|
2274
|
+
documentField: CSITypes.DocumentFieldNonLocalized,
|
|
2275
|
+
fieldPath: (string | number)[],
|
|
2276
|
+
contentSourceData: ContentSourceData
|
|
2277
|
+
): ContentStoreTypes.ValidationError[] {
|
|
2278
|
+
const errors: ContentStoreTypes.ValidationError[] = [];
|
|
2279
|
+
|
|
2280
|
+
if (documentField.type === 'object') {
|
|
2281
|
+
_.forEach(documentField.fields, (documentField, fieldName) => {
|
|
2282
|
+
const localizedField = getLocalizedFieldForLocale(documentField)!;
|
|
2283
|
+
errors.push(...validateDocumentFields(document, localizedField, fieldPath.concat(fieldName), contentSourceData));
|
|
2284
|
+
});
|
|
2285
|
+
} else if (documentField.type === 'model') {
|
|
2286
|
+
_.forEach(documentField.fields, (documentField, fieldName) => {
|
|
2287
|
+
const localizedField = getLocalizedFieldForLocale(documentField)!;
|
|
2288
|
+
errors.push(...validateDocumentFields(document, localizedField, fieldPath.concat(fieldName), contentSourceData));
|
|
2289
|
+
});
|
|
2290
|
+
} else if (documentField.type === 'reference') {
|
|
2291
|
+
const objRef = documentField.refType === 'asset'
|
|
2292
|
+
? contentSourceData.assetMap[documentField.refId]
|
|
2293
|
+
: contentSourceData.documentMap[documentField.refId];
|
|
2294
|
+
if (!objRef) {
|
|
2295
|
+
errors.push({
|
|
2296
|
+
fieldPath,
|
|
2297
|
+
srcType: contentSourceData.type,
|
|
2298
|
+
srcProjectId: contentSourceData.projectId,
|
|
2299
|
+
srcObjectType: documentField.refType,
|
|
2300
|
+
srcObjectId: document.id,
|
|
2301
|
+
message: `Can't find referenced ${documentField.refType}: ${documentField.refId}`
|
|
2302
|
+
});
|
|
2303
|
+
}
|
|
2304
|
+
} else if (documentField.type === 'list') {
|
|
2305
|
+
_.forEach(documentField.items, (documentField, i) => {
|
|
2306
|
+
const localizedField = getLocalizedFieldForLocale(documentField)!;
|
|
2307
|
+
errors.push(...validateDocumentFields(document, documentField, fieldPath.concat(i), contentSourceData));
|
|
2308
|
+
});
|
|
2309
|
+
}
|
|
2310
|
+
|
|
2311
|
+
return errors;
|
|
2312
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -3,8 +3,9 @@ export * as annotator from './annotator';
|
|
|
3
3
|
export * as utils from './utils';
|
|
4
4
|
export * as consts from './consts';
|
|
5
5
|
export * from './common/common-schema';
|
|
6
|
+
export * from './common/common-types';
|
|
6
7
|
export * from './content-store';
|
|
7
8
|
export * as ContentSourceTypes from './content-source-interface';
|
|
8
9
|
export * as ContentStoreTypes from './content-store-types';
|
|
9
10
|
export { default as encodeData } from './encoder';
|
|
10
|
-
export * from './encoder';
|
|
11
|
+
export * from './encoder';
|