@stackbit/cms-core 0.7.6-develop.1 → 0.8.0-develop.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 (73) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/content-store-utils.d.ts +44 -0
  3. package/dist/content-store-utils.d.ts.map +1 -1
  4. package/dist/content-store-utils.js +213 -1
  5. package/dist/content-store-utils.js.map +1 -1
  6. package/dist/content-store.d.ts +14 -1
  7. package/dist/content-store.d.ts.map +1 -1
  8. package/dist/content-store.js +70 -6
  9. package/dist/content-store.js.map +1 -1
  10. package/dist/index.d.ts +0 -1
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +0 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/types/content-store-document-fields.d.ts +105 -0
  15. package/dist/types/content-store-document-fields.d.ts.map +1 -1
  16. package/dist/types/content-store-documents.d.ts +4 -1
  17. package/dist/types/content-store-documents.d.ts.map +1 -1
  18. package/dist/types/content-store-types.d.ts +10 -10
  19. package/dist/types/content-store-types.d.ts.map +1 -1
  20. package/dist/types/custom-actions.d.ts +108 -0
  21. package/dist/types/custom-actions.d.ts.map +1 -0
  22. package/dist/types/custom-actions.js +3 -0
  23. package/dist/types/custom-actions.js.map +1 -0
  24. package/dist/types/index.d.ts +1 -0
  25. package/dist/types/index.d.ts.map +1 -1
  26. package/dist/types/index.js +1 -0
  27. package/dist/types/index.js.map +1 -1
  28. package/dist/utils/csi-to-store-docs-converter.d.ts +2 -1
  29. package/dist/utils/csi-to-store-docs-converter.d.ts.map +1 -1
  30. package/dist/utils/csi-to-store-docs-converter.js +144 -11
  31. package/dist/utils/csi-to-store-docs-converter.js.map +1 -1
  32. package/dist/utils/custom-actions.d.ts +53 -0
  33. package/dist/utils/custom-actions.d.ts.map +1 -0
  34. package/dist/utils/custom-actions.js +564 -0
  35. package/dist/utils/custom-actions.js.map +1 -0
  36. package/dist/utils/document-hooks.d.ts +9 -0
  37. package/dist/utils/document-hooks.d.ts.map +1 -1
  38. package/dist/utils/document-hooks.js +3 -2
  39. package/dist/utils/document-hooks.js.map +1 -1
  40. package/dist/utils/search-utils.d.ts +1 -1
  41. package/dist/utils/search-utils.d.ts.map +1 -1
  42. package/dist/utils/site-map.d.ts +4 -4
  43. package/dist/utils/site-map.d.ts.map +1 -1
  44. package/dist/utils/site-map.js +2 -11
  45. package/dist/utils/site-map.js.map +1 -1
  46. package/dist/utils/store-to-api-docs-converter.d.ts.map +1 -1
  47. package/dist/utils/store-to-api-docs-converter.js +77 -29
  48. package/dist/utils/store-to-api-docs-converter.js.map +1 -1
  49. package/dist/utils/store-to-csi-docs-converter.d.ts +3 -0
  50. package/dist/utils/store-to-csi-docs-converter.d.ts.map +1 -1
  51. package/dist/utils/store-to-csi-docs-converter.js +2 -1
  52. package/dist/utils/store-to-csi-docs-converter.js.map +1 -1
  53. package/dist/utils/tree-views.d.ts +10 -0
  54. package/dist/utils/tree-views.d.ts.map +1 -0
  55. package/dist/utils/tree-views.js +64 -0
  56. package/dist/utils/tree-views.js.map +1 -0
  57. package/package.json +5 -5
  58. package/src/content-store-utils.ts +247 -0
  59. package/src/content-store.ts +90 -7
  60. package/src/index.ts +0 -1
  61. package/src/types/content-store-document-fields.ts +55 -2
  62. package/src/types/content-store-documents.ts +4 -1
  63. package/src/types/content-store-types.ts +10 -10
  64. package/src/types/custom-actions.ts +154 -0
  65. package/src/types/index.ts +1 -0
  66. package/src/utils/csi-to-store-docs-converter.ts +220 -12
  67. package/src/utils/custom-actions.ts +707 -0
  68. package/src/utils/document-hooks.ts +3 -3
  69. package/src/utils/search-utils.ts +1 -1
  70. package/src/utils/site-map.ts +4 -14
  71. package/src/utils/store-to-api-docs-converter.ts +88 -33
  72. package/src/utils/store-to-csi-docs-converter.ts +4 -4
  73. package/src/utils/tree-views.ts +72 -0
@@ -0,0 +1,64 @@
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.getSanitizedTreeViews = void 0;
7
+ const lodash_1 = __importDefault(require("lodash"));
8
+ const content_store_utils_1 = require("../content-store-utils");
9
+ async function getSanitizedTreeViews({ configDelegate, stackbitConfig, contentSourceDataById, logger }) {
10
+ var _a, _b;
11
+ const treeViews = (_b = (await ((_a = stackbitConfig === null || stackbitConfig === void 0 ? void 0 : stackbitConfig.treeViews) === null || _a === void 0 ? void 0 : _a.call(stackbitConfig, configDelegate)))) !== null && _b !== void 0 ? _b : [];
12
+ const mapTreeViews = (treeViews) => treeViews
13
+ .map((treeView) => {
14
+ var _a, _b, _c, _d, _e, _f;
15
+ if ('document' in treeView && treeView.document) {
16
+ const contentSourceId = (0, content_store_utils_1.getContentSourceId)(treeView.document.srcType, treeView.document.srcProjectId);
17
+ const document = (_a = contentSourceDataById[contentSourceId]) === null || _a === void 0 ? void 0 : _a.documentMap[treeView.document.id];
18
+ // explicit check because developers can skip TS check and just not define required properties of document object
19
+ if (document) {
20
+ treeView = {
21
+ ...treeView,
22
+ stableId: (_b = treeView.stableId) !== null && _b !== void 0 ? _b : document.srcObjectId,
23
+ label: (_c = treeView.label) !== null && _c !== void 0 ? _c : document.getPreview({ delegate: configDelegate }).previewTitle,
24
+ document: {
25
+ srcType: treeView.document.srcType,
26
+ srcProjectId: treeView.document.srcProjectId,
27
+ id: treeView.document.id
28
+ }
29
+ };
30
+ }
31
+ else {
32
+ logger.warn(`One of required properties is missing in tree view document, document skipped from tree view,
33
+ srcType: ${(_d = treeView.document) === null || _d === void 0 ? void 0 : _d.srcType}, srcProjectId: ${(_e = treeView.document) === null || _e === void 0 ? void 0 : _e.srcProjectId}, id: ${(_f = treeView.document) === null || _f === void 0 ? void 0 : _f.id}.`);
34
+ // don't early return because treeView.children can be defined properly
35
+ if (treeView.children) {
36
+ treeView = lodash_1.default.omit(treeView, 'document');
37
+ }
38
+ else {
39
+ return undefined;
40
+ }
41
+ }
42
+ }
43
+ if ('children' in treeView && treeView.children) {
44
+ // required check because of
45
+ // 1. typecast during omit in previous if
46
+ // 2. developer can ignore TS check and just not define stableId and label
47
+ if (treeView.stableId && treeView.label) {
48
+ treeView = {
49
+ ...treeView,
50
+ children: mapTreeViews(treeView.children)
51
+ };
52
+ }
53
+ else {
54
+ logger.warn('one of required properties (stableId or label) is missing in tree view, children are skipped from tree view');
55
+ return undefined;
56
+ }
57
+ }
58
+ return treeView;
59
+ })
60
+ .filter(Boolean);
61
+ return mapTreeViews(treeViews);
62
+ }
63
+ exports.getSanitizedTreeViews = getSanitizedTreeViews;
64
+ //# sourceMappingURL=tree-views.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tree-views.js","sourceRoot":"","sources":["../../src/utils/tree-views.ts"],"names":[],"mappings":";;;;;;AAAA,oDAAuB;AAIvB,gEAA4D;AAErD,KAAK,UAAU,qBAAqB,CAAC,EACxC,cAAc,EACd,cAAc,EACd,qBAAqB,EACrB,MAAM,EAMT;;IACG,MAAM,SAAS,GAAG,MAAA,CAAC,MAAM,CAAA,MAAA,cAAc,aAAd,cAAc,uBAAd,cAAc,CAAE,SAAS,+CAAzB,cAAc,EAAc,cAAc,CAAC,CAAA,CAAC,mCAAI,EAAE,CAAC;IAE5E,MAAM,YAAY,GAAG,CAAC,SAAkC,EAA2B,EAAE,CACjF,SAAS;SACJ,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE;;QACd,IAAI,UAAU,IAAI,QAAQ,IAAI,QAAQ,CAAC,QAAQ,EAAE;YAC7C,MAAM,eAAe,GAAG,IAAA,wCAAkB,EAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;YACtG,MAAM,QAAQ,GAAG,MAAA,qBAAqB,CAAC,eAAe,CAAC,0CAAE,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;YAE3F,iHAAiH;YACjH,IAAI,QAAQ,EAAE;gBACV,QAAQ,GAAG;oBACP,GAAG,QAAQ;oBACX,QAAQ,EAAE,MAAA,QAAQ,CAAC,QAAQ,mCAAI,QAAQ,CAAC,WAAW;oBACnD,KAAK,EAAE,MAAA,QAAQ,CAAC,KAAK,mCAAI,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC,CAAC,YAAY;oBACvF,QAAQ,EAAE;wBACN,OAAO,EAAE,QAAQ,CAAC,QAAQ,CAAC,OAAO;wBAClC,YAAY,EAAE,QAAQ,CAAC,QAAQ,CAAC,YAAY;wBAC5C,EAAE,EAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE;qBAC3B;iBACJ,CAAC;aACL;iBAAM;gBACH,MAAM,CAAC,IAAI,CACP;uCACW,MAAA,QAAQ,CAAC,QAAQ,0CAAE,OAAO,mBAAmB,MAAA,QAAQ,CAAC,QAAQ,0CAAE,YAAY,SAAS,MAAA,QAAQ,CAAC,QAAQ,0CAAE,EAAE,GAAG,CAC3H,CAAC;gBACF,uEAAuE;gBACvE,IAAI,QAAQ,CAAC,QAAQ,EAAE;oBACnB,QAAQ,GAAG,gBAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,CAA8B,CAAC;iBACxE;qBAAM;oBACH,OAAO,SAAS,CAAC;iBACpB;aACJ;SACJ;QAED,IAAI,UAAU,IAAI,QAAQ,IAAI,QAAQ,CAAC,QAAQ,EAAE;YAC7C,4BAA4B;YAC5B,yCAAyC;YACzC,0EAA0E;YAC1E,IAAI,QAAQ,CAAC,QAAQ,IAAI,QAAQ,CAAC,KAAK,EAAE;gBACrC,QAAQ,GAAG;oBACP,GAAG,QAAQ;oBACX,QAAQ,EAAE,YAAY,CAAC,QAAQ,CAAC,QAAQ,CAAC;iBAC5C,CAAC;aACL;iBAAM;gBACH,MAAM,CAAC,IAAI,CAAC,6GAA6G,CAAC,CAAC;gBAC3H,OAAO,SAAS,CAAC;aACpB;SACJ;QACD,OAAO,QAAQ,CAAC;IACpB,CAAC,CAAC;SACD,MAAM,CAAC,OAAO,CAA4B,CAAC;IAEpD,OAAO,YAAY,CAAC,SAAS,CAAC,CAAC;AACnC,CAAC;AAjED,sDAiEC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackbit/cms-core",
3
- "version": "0.7.6-develop.1",
3
+ "version": "0.8.0-develop.0",
4
4
  "description": "stackbit-dev",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -25,9 +25,9 @@
25
25
  "@babel/parser": "^7.11.5",
26
26
  "@babel/traverse": "^7.11.5",
27
27
  "@iarna/toml": "^2.2.3",
28
- "@stackbit/sdk": "0.6.6-develop.1",
29
- "@stackbit/types": "0.8.6-develop.1",
30
- "@stackbit/utils": "0.2.43-develop.1",
28
+ "@stackbit/sdk": "0.7.0-develop.0",
29
+ "@stackbit/types": "0.9.0-develop.0",
30
+ "@stackbit/utils": "0.3.0-develop.0",
31
31
  "chalk": "^4.0.1",
32
32
  "esm": "^3.2.25",
33
33
  "fs-extra": "^8.1.0",
@@ -42,5 +42,5 @@
42
42
  "slugify": "^1.6.5",
43
43
  "uuid": "^9.0.0"
44
44
  },
45
- "gitHead": "d09511dcd6d38ce30e32dfa371a530fdd4bb64c9"
45
+ "gitHead": "817724f480726a53c0b2708a23d5c752965a87b4"
46
46
  }
@@ -14,6 +14,23 @@ export function getContentSourceId(contentSourceType: string, srcProjectId: stri
14
14
  return contentSourceType + ':' + srcProjectId;
15
15
  }
16
16
 
17
+ export function getContentSourceDataByTypeAndProjectIdOrThrow(
18
+ srcType: string,
19
+ srcProjectId: string,
20
+ contentSourceDataById: Record<string, ContentStoreTypes.ContentSourceData>
21
+ ) {
22
+ const contentSourceId = getContentSourceId(srcType, srcProjectId);
23
+ return getContentSourceDataByIdOrThrow(contentSourceId, contentSourceDataById);
24
+ }
25
+
26
+ export function getContentSourceDataByIdOrThrow(contentSourceId: string, contentSourceDataById: Record<string, ContentStoreTypes.ContentSourceData>) {
27
+ const contentSourceData = contentSourceDataById[contentSourceId];
28
+ if (!contentSourceData) {
29
+ throw new Error(`Content source not found: '${contentSourceId}'.`);
30
+ }
31
+ return contentSourceData;
32
+ }
33
+
17
34
  export function findContentSourcesDataForTypeOrId({
18
35
  contentSourceDataById,
19
36
  srcType,
@@ -30,6 +47,10 @@ export function findContentSourcesDataForTypeOrId({
30
47
  });
31
48
  }
32
49
 
50
+ export function getUserContextForSrcTypeThunk(user?: ContentStoreTypes.User) {
51
+ return (srcType: string) => getUserContextForSrcType(srcType, user);
52
+ }
53
+
33
54
  export function getUserContextForSrcType(srcType: string, user?: ContentStoreTypes.User): CSITypes.User | undefined {
34
55
  if (!user) {
35
56
  return undefined;
@@ -184,6 +205,232 @@ export function getModelFieldForFieldAtPath(
184
205
  return getField(childDocField, childModelField, childFieldPath);
185
206
  }
186
207
 
208
+ /**
209
+ * Returns a model field and a document field at the specified field path.
210
+ *
211
+ * If some fields along the fieldPath are localized, the fieldPath must
212
+ * container the locale of the field under the "locales" property. The locales
213
+ * along the field path don't have to be the same.
214
+ * @example
215
+ * fieldPath: ['fields', 'button', 'locales', 'en', 'fields', 'title', 'locales', 'es']
216
+ *
217
+ * If the provided fieldPath points to a list item, the returned model field
218
+ * and document field will belong to a list item. In this case, the model field
219
+ * will contain only field-specific properties and the document field will be
220
+ * localized.
221
+ * @example
222
+ * fieldPath: ['fields', 'buttons', 'items', 2]
223
+ *
224
+ * The "fullFieldPath" flag specifies if the fieldPath includes container specifiers
225
+ * such as "fields", "items" and "locales".
226
+ * @example
227
+ * fullFieldPath: false => fieldPath: ['sections', 1, 'title', 'es']
228
+ * fullFieldPath: true => fieldPath: ['fields', 'sections', 'items', 1, 'fields', 'title', 'locales', 'es']
229
+ *
230
+ * @param document
231
+ * @param model
232
+ * @param fieldPath
233
+ * @param modelMap
234
+ * @param fullFieldPath
235
+ */
236
+ export function getModelAndDocumentFieldForLocalizedFieldPath({
237
+ document,
238
+ model,
239
+ fieldPath,
240
+ modelMap,
241
+ fullFieldPath
242
+ }: {
243
+ document: ContentStoreTypes.Document;
244
+ model: Model;
245
+ fieldPath: (string | number)[];
246
+ modelMap: Record<string, Model>;
247
+ fullFieldPath?: boolean;
248
+ }): { modelField: Field | FieldListItems; documentField: ContentStoreTypes.DocumentField | ContentStoreTypes.DocumentListFieldItems } {
249
+ const origFieldPath = fieldPath;
250
+ if (fullFieldPath) {
251
+ if (_.head(fieldPath) !== 'fields') {
252
+ throw new Error('fieldPath must start with "fields" specifier');
253
+ }
254
+ fieldPath = _.tail(fieldPath);
255
+ }
256
+ if (_.isEmpty(fieldPath)) {
257
+ throw new Error('the fieldPath can not be empty');
258
+ }
259
+
260
+ function getFieldPathPrefixOfTail(fieldPath: (string | number)[]) {
261
+ return origFieldPath.slice(0, origFieldPath.length - fieldPath.length);
262
+ }
263
+
264
+ function getField(
265
+ docField: ContentStoreTypes.DocumentField | ContentStoreTypes.DocumentListFieldItems,
266
+ modelField: Field | FieldListItems,
267
+ fieldPath: (string | number)[]
268
+ ): { modelField: Field | FieldListItems; documentField: ContentStoreTypes.DocumentField | ContentStoreTypes.DocumentListFieldItems } {
269
+ if (docField.localized) {
270
+ if (fullFieldPath) {
271
+ if (_.head(fieldPath) !== 'locales') {
272
+ throw new Error('fieldPath must contain "locales" specifier for localized field');
273
+ }
274
+ fieldPath = _.tail(fieldPath);
275
+ }
276
+ const locale = _.head(fieldPath);
277
+ if (typeof locale !== 'string') {
278
+ throw new Error('fieldPath must contain string locale');
279
+ }
280
+ fieldPath = _.tail(fieldPath);
281
+ const localizedDocField = getDocumentFieldForLocale(docField, locale);
282
+ if (!localizedDocField) {
283
+ throw new Error(`locale for field was not found`);
284
+ }
285
+ docField = localizedDocField;
286
+ }
287
+
288
+ switch (docField.type) {
289
+ case 'object': {
290
+ if (docField.isUnset) {
291
+ throw new Error(`field is not set`);
292
+ }
293
+ if (fullFieldPath) {
294
+ if (_.head(fieldPath) !== 'fields') {
295
+ throw new Error('fieldPath must contain "fields" specifier for "object" field');
296
+ }
297
+ fieldPath = _.tail(fieldPath);
298
+ }
299
+ const fieldName = _.head(fieldPath);
300
+ if (typeof fieldName === 'undefined') {
301
+ throw new Error('the field path of object must contain a field name');
302
+ }
303
+ fieldPath = _.tail(fieldPath);
304
+
305
+ const childDocField = docField.fields[fieldName];
306
+ if (!childDocField) {
307
+ throw new Error(`document field doesn't exist at field path: '${getFieldPathPrefixOfTail(fieldPath).join('.')}'`);
308
+ }
309
+ if (modelField.type !== 'object') {
310
+ throw new Error(`model field type '${modelField.type}' doesn't match document field type '${docField.type}'`);
311
+ }
312
+ const childModelField = _.find(modelField.fields, (field) => field.name === fieldName);
313
+ if (!childModelField) {
314
+ throw new Error(`model field doesn't exist at field path: '${getFieldPathPrefixOfTail(fieldPath).join('.')}'`);
315
+ }
316
+ if (fieldPath.length === 0) {
317
+ return {
318
+ modelField: childModelField,
319
+ documentField: childDocField
320
+ };
321
+ }
322
+ return getField(childDocField, childModelField, fieldPath);
323
+ }
324
+ case 'model': {
325
+ if (docField.isUnset) {
326
+ throw new Error(`field is not set`);
327
+ }
328
+ if (fullFieldPath) {
329
+ if (_.head(fieldPath) !== 'fields') {
330
+ throw new Error('fieldPath must contain "fields" specifier for "model" field');
331
+ }
332
+ fieldPath = _.tail(fieldPath);
333
+ }
334
+ const fieldName = _.head(fieldPath);
335
+ if (typeof fieldName === 'undefined') {
336
+ throw new Error('the field path of model must contain a field name');
337
+ }
338
+ fieldPath = _.tail(fieldPath);
339
+
340
+ const modelName = docField.srcModelName;
341
+ const childModel = modelMap[modelName];
342
+ if (!childModel) {
343
+ throw new Error(`model ${modelName} doesn't exist`);
344
+ }
345
+ const childModelField = _.find(childModel.fields, (field) => field.name === fieldName);
346
+ if (!childModelField) {
347
+ throw new Error(`model field doesn't exist at field path: '${getFieldPathPrefixOfTail(fieldPath).join('.')}'`);
348
+ }
349
+ const childDocField = docField.fields[fieldName];
350
+ if (!childDocField) {
351
+ throw new Error(`document field doesn't exist at field path: '${getFieldPathPrefixOfTail(fieldPath).join('.')}'`);
352
+ }
353
+ if (fieldPath.length === 0) {
354
+ return {
355
+ modelField: childModelField,
356
+ documentField: childDocField
357
+ };
358
+ }
359
+ return getField(childDocField, childModelField, fieldPath);
360
+ }
361
+ case 'list': {
362
+ if (fullFieldPath) {
363
+ if (_.head(fieldPath) !== 'items') {
364
+ throw new Error('fieldPath must contain "items" specifier for "list" field');
365
+ }
366
+ fieldPath = _.tail(fieldPath);
367
+ }
368
+ const itemIndex = _.head(fieldPath) as number;
369
+ if (typeof itemIndex === 'undefined') {
370
+ throw new Error('the field path of list must contain an item index');
371
+ }
372
+ fieldPath = _.tail(fieldPath);
373
+
374
+ const listItem = docField.items && docField.items[itemIndex as number];
375
+ if (!listItem) {
376
+ throw new Error(`list item doesn't exist at field path: '${getFieldPathPrefixOfTail(fieldPath).join('.')}'`);
377
+ }
378
+ if (modelField.type !== 'list') {
379
+ throw new Error(`model field type '${modelField.type}' doesn't match document field type '${docField.type}'`);
380
+ }
381
+ const listItemsModel = modelField.items;
382
+ if (fieldPath.length === 0) {
383
+ return {
384
+ modelField: listItemsModel,
385
+ documentField: listItem
386
+ };
387
+ }
388
+ if (!Array.isArray(listItemsModel)) {
389
+ return getField(listItem, listItemsModel, fieldPath);
390
+ } else {
391
+ const fieldListItems = (listItemsModel as FieldListItems[]).find((listItemsModel) => listItemsModel.type === listItem.type);
392
+ if (!fieldListItems) {
393
+ throw new Error(
394
+ `cannot find list item model for document field of type '${listItem.type}' at field path: '${getFieldPathPrefixOfTail(
395
+ fieldPath
396
+ ).join('.')}'`
397
+ );
398
+ }
399
+ return getField(listItem, fieldListItems, fieldPath);
400
+ }
401
+ }
402
+ default:
403
+ if (!_.isEmpty(fieldPath)) {
404
+ throw new Error('illegal fieldPath');
405
+ }
406
+ return {
407
+ modelField,
408
+ documentField: docField
409
+ };
410
+ }
411
+ }
412
+
413
+ const fieldName = _.head(fieldPath);
414
+ const childFieldPath = _.tail(fieldPath);
415
+
416
+ if (typeof fieldName !== 'string') {
417
+ throw new Error('the first fieldPath item must be string');
418
+ }
419
+
420
+ const childDocField: ContentStoreTypes.DocumentField | undefined = document.fields[fieldName];
421
+ const childModelField: Field | undefined = _.find(model.fields, { name: fieldName });
422
+
423
+ if (!childDocField || !childModelField) {
424
+ throw new Error(`field ${fieldName} doesn't exist`);
425
+ }
426
+
427
+ if (childFieldPath.length === 0) {
428
+ return { modelField: childModelField, documentField: childDocField };
429
+ }
430
+
431
+ return getField(childDocField, childModelField, childFieldPath);
432
+ }
433
+
187
434
  export function groupModelsByContentSource({ models }: { models: CSITypes.ModelWithSource[] }): Record<string, Record<string, Record<string, Model>>> {
188
435
  const modelMapByContentSource: Record<string, Record<string, Record<string, Model>>> = {};
189
436
  for (const model of models) {
@@ -22,11 +22,12 @@ import { append, DeferredPromise, deferredPromise, deferWhileRunning, mapPromise
22
22
 
23
23
  import * as ContentStoreTypes from './types';
24
24
  import { Timer } from './utils/timer';
25
- import { SearchFilter } from './types/search-filter';
25
+ import { SearchFilter } from './types';
26
26
  import { searchDocuments } from './utils/search-utils';
27
27
  import { mapCSIAssetsToStoreAssets, mapCSIDocumentsToStoreDocuments } from './utils/csi-to-store-docs-converter';
28
28
  import {
29
29
  getContentSourceId,
30
+ getContentSourceDataByIdOrThrow,
30
31
  getContentSourceIdForContentSource,
31
32
  getCSIDocumentsAndAssetsFromContentSourceDataByIds,
32
33
  getModelFieldForFieldAtPath,
@@ -58,6 +59,7 @@ import { createConfigDelegate, getCreateConfigDelegateThunk } from './utils/conf
58
59
  import { GitService } from './services';
59
60
  import { getAssetSourcesForClient } from './utils/asset-sources-utils';
60
61
  import { deleteDocumentHooked, publishDocumentHooked, updateDocumentHooked } from './utils/document-hooks';
62
+ import { resolveCustomActionsById, getGlobalAndBulkAPIActions, runCustomAction, stripModelActions } from './utils/custom-actions';
61
63
  import {
62
64
  logCreateDocument,
63
65
  logDeleteDocument,
@@ -67,6 +69,7 @@ import {
67
69
  logUploadAssets,
68
70
  pluralize
69
71
  } from './utils/user-log-utils';
72
+ import { getSanitizedTreeViews } from './utils/tree-views';
70
73
 
71
74
  export type HandleConfigAssets = <T extends Model>({ models, presets }: { models?: T[]; presets?: PresetMap }) => Promise<{ models: T[]; presets: PresetMap }>;
72
75
 
@@ -81,6 +84,7 @@ export interface ContentSourceOptions {
81
84
  userCommandSpawner?: UserCommandSpawner; //TODO remove
82
85
  onSchemaChangeCallback: () => void;
83
86
  onContentChangeCallback: (contentChanges: ContentStoreTypes.ContentChangeResult) => void;
87
+ onActionStateChangeCallback: (actionResult: ContentStoreTypes.CustomActionStateChange) => void;
84
88
  handleConfigAssets: HandleConfigAssets;
85
89
  devAppRestartNeeded?: () => void;
86
90
  }
@@ -126,6 +130,7 @@ export class ContentStore {
126
130
  private readonly git: GitService;
127
131
  private readonly onSchemaChangeCallback: () => void;
128
132
  private readonly onContentChangeCallback: (contentChanges: ContentStoreTypes.ContentChangeResult) => void;
133
+ private readonly onActionStateChangeCallback: (actionStateChange: ContentStoreTypes.CustomActionStateChange) => void;
129
134
  private readonly handleConfigAssets: HandleConfigAssets;
130
135
  private readonly devAppRestartNeeded?: () => void;
131
136
  private contentSources: BackCompatContentSourceInterface[] = [];
@@ -140,6 +145,8 @@ export class ContentStore {
140
145
  private siteMapEntryGroups: SiteMapEntryGroups = {};
141
146
  private processingContentSourcesPromise: DeferredPromise<void> | null = null;
142
147
  private contentStoreEventQueue: ContentStoreEventQueue = [];
148
+ private treeViews: CSITypes.TreeViewNode[] = [];
149
+ private customActionMap: ContentStoreTypes.ContentStoreCustomActionMap = {};
143
150
 
144
151
  constructor(options: ContentSourceOptions) {
145
152
  this.logger = options.logger.createLogger({ label: 'content-store' });
@@ -152,6 +159,7 @@ export class ContentStore {
152
159
  this.userCommandSpawner = options.userCommandSpawner;
153
160
  this.onSchemaChangeCallback = options.onSchemaChangeCallback;
154
161
  this.onContentChangeCallback = options.onContentChangeCallback;
162
+ this.onActionStateChangeCallback = options.onActionStateChangeCallback;
155
163
  this.handleConfigAssets = options.handleConfigAssets;
156
164
  this.contentUpdatesWatchTimer = new Timer({ timerCallback: () => this.handleTimerTimeout(), logger: this.logger });
157
165
  this.devAppRestartNeeded = options.devAppRestartNeeded;
@@ -461,8 +469,20 @@ export class ContentStore {
461
469
  contentSourceRawDataArr: contentSourceRawDataArr
462
470
  });
463
471
 
472
+ const configDelegate = createConfigDelegate({
473
+ contentSourceDataById: this.contentSourceDataById,
474
+ logger: this.userLogger
475
+ });
476
+
464
477
  // generate create site map entries
465
478
  this.siteMapEntryGroups = await getSiteMapEntriesFromStackbitConfig({
479
+ stackbitConfig: this.stackbitConfig,
480
+ contentSourceDataById: this.contentSourceDataById,
481
+ configDelegate
482
+ });
483
+
484
+ this.treeViews = await getSanitizedTreeViews({
485
+ configDelegate,
466
486
  stackbitConfig: this.stackbitConfig,
467
487
  contentSourceDataById: this.contentSourceDataById,
468
488
  logger: this.userLogger
@@ -543,12 +563,22 @@ export class ContentStore {
543
563
  });
544
564
  } else {
545
565
  this.logger.debug('processContentStoreEvents => content changes', { ...contentChangeResultCounts(contentChanges), presetsUpdated });
566
+ const configDelegate = createConfigDelegate({
567
+ contentSourceDataById: this.contentSourceDataById,
568
+ logger: this.userLogger
569
+ });
546
570
  // If the schema wasn't changed, update SiteMapEntries with the changed content.
547
571
  this.siteMapEntryGroups = await updateSiteMapEntriesWithContentChanges({
548
572
  siteMapEntryGroups: this.siteMapEntryGroups,
549
573
  contentChanges: contentChanges,
550
574
  stackbitConfig: this.stackbitConfig,
551
575
  contentSourceDataById: this.contentSourceDataById,
576
+ configDelegate
577
+ });
578
+ this.treeViews = await getSanitizedTreeViews({
579
+ configDelegate,
580
+ stackbitConfig: this.stackbitConfig,
581
+ contentSourceDataById: this.contentSourceDataById,
552
582
  logger: this.userLogger
553
583
  });
554
584
  // If presets were updated, call onSchemaChangeCallback to notify the Studio.
@@ -1016,6 +1046,7 @@ export class ContentStore {
1016
1046
  modelMap: contentSourceData.modelMap,
1017
1047
  defaultLocaleCode: contentSourceData.defaultLocaleCode,
1018
1048
  assetSources: this.stackbitConfig?.assetSources ?? [],
1049
+ customActionMap: this.customActionMap,
1019
1050
  createConfigDelegate: getCreateConfigDelegateThunk({
1020
1051
  getContentSourceDataById: () => this.contentSourceDataById,
1021
1052
  logger: this.userLogger
@@ -1254,6 +1285,7 @@ export class ContentStore {
1254
1285
  modelMap: modelMap,
1255
1286
  defaultLocaleCode: csData.defaultLocaleCode,
1256
1287
  assetSources: this.stackbitConfig?.assetSources ?? [],
1288
+ customActionMap: this.customActionMap,
1257
1289
  createConfigDelegate: getCreateConfigDelegateThunk({
1258
1290
  getContentSourceDataById: () => this.contentSourceDataById,
1259
1291
  logger: this.userLogger
@@ -1302,9 +1334,10 @@ export class ContentStore {
1302
1334
  const contentSourceType = contentSourceData.instance.getContentSourceType();
1303
1335
  const srcProjectId = contentSourceData.instance.getProjectId();
1304
1336
  const filteredModels = _.omitBy(contentSourceData.modelMap, (model) => model.name === StackbitPresetModelName);
1337
+ const mappedModels = stripModelActions({ modelMap: filteredModels });
1305
1338
  // if `projectId` is number (even as string) e.g., '1234', _.set() will create an array of length 1235 and insert the item at the end.
1306
1339
  // _.setWith(..., Object) ensures the values are always created as object keys, not as array indexes.
1307
- _.setWith(result, [contentSourceType, srcProjectId], filteredModels, Object);
1340
+ _.setWith(result, [contentSourceType, srcProjectId], mappedModels, Object);
1308
1341
  _.setWith(result, [contentSourceType, srcProjectId, '__image_model'], IMAGE_MODEL, Object);
1309
1342
  return result;
1310
1343
  },
@@ -1327,6 +1360,56 @@ export class ContentStore {
1327
1360
  );
1328
1361
  }
1329
1362
 
1363
+ async getGlobalActions({
1364
+ pageUrl,
1365
+ user,
1366
+ locale,
1367
+ currentPageDocument
1368
+ }: {
1369
+ pageUrl?: string;
1370
+ user?: ContentStoreTypes.User;
1371
+ locale?: string;
1372
+ currentPageDocument?: ContentStoreTypes.APICustomActionDocumentSpecifier;
1373
+ }): Promise<(ContentStoreTypes.APICustomActionGlobal | ContentStoreTypes.APICustomActionBulk)[]> {
1374
+ return getGlobalAndBulkAPIActions({
1375
+ stackbitConfig: this.stackbitConfig,
1376
+ customActionMap: this.customActionMap,
1377
+ contentSourceDataById: this.contentSourceDataById,
1378
+ userLogger: this.userLogger,
1379
+ locale,
1380
+ pageUrl,
1381
+ user,
1382
+ currentPageDocument
1383
+ });
1384
+ }
1385
+
1386
+ async getCustomActions(getActionRequest: ContentStoreTypes.APIGetCustomActionRequest): Promise<ContentStoreTypes.APICustomAction[]> {
1387
+ return resolveCustomActionsById({
1388
+ getActionRequest,
1389
+ customActionMap: this.customActionMap,
1390
+ contentSourceDataById: this.contentSourceDataById,
1391
+ userLogger: this.userLogger
1392
+ });
1393
+ }
1394
+
1395
+ async runCustomAction(runActionRequest: ContentStoreTypes.APIRunCustomActionRequest): Promise<void> {
1396
+ this.onActionStateChangeCallback({
1397
+ actionId: runActionRequest.actionId,
1398
+ actionName: runActionRequest.actionName,
1399
+ actionType: runActionRequest.actionType,
1400
+ state: 'running'
1401
+ });
1402
+ runCustomAction({
1403
+ runActionRequest: runActionRequest,
1404
+ contentSourceDataById: this.contentSourceDataById,
1405
+ customActionMap: this.customActionMap,
1406
+ userLogger: this.userLogger,
1407
+ stackbitConfig: this.stackbitConfig
1408
+ }).then((actionStateChange) => {
1409
+ this.onActionStateChangeCallback(actionStateChange);
1410
+ });
1411
+ }
1412
+
1330
1413
  getPresets({ locale }: { locale?: string } = {}): Record<string, any> {
1331
1414
  if (!this.presets || !locale) {
1332
1415
  return this.presets ?? {};
@@ -1474,6 +1557,10 @@ export class ContentStore {
1474
1557
  return _.isEmpty(locale) ? siteMapEntries : siteMapEntries.filter((siteMapEntry) => !siteMapEntry.locale || siteMapEntry.locale === locale);
1475
1558
  }
1476
1559
 
1560
+ getTreeViews(): CSITypes.TreeViewNode[] {
1561
+ return this.treeViews;
1562
+ }
1563
+
1477
1564
  getSiteMapEntriesForDocument({
1478
1565
  srcType,
1479
1566
  srcProjectId,
@@ -2532,11 +2619,7 @@ export class ContentStore {
2532
2619
  }
2533
2620
 
2534
2621
  private getContentSourceDataByIdOrThrow(contentSourceId: string): ContentSourceData {
2535
- const contentSourceData = this.contentSourceDataById[contentSourceId];
2536
- if (!contentSourceData) {
2537
- throw new Error(`Connection failed for content source: '${contentSourceId}'.`);
2538
- }
2539
- return contentSourceData;
2622
+ return getContentSourceDataByIdOrThrow(contentSourceId, this.contentSourceDataById);
2540
2623
  }
2541
2624
 
2542
2625
  onWebhook({ srcType, srcProjectId, data, headers }: { srcType: string; srcProjectId: string; data: unknown; headers: Record<string, string> }) {
package/src/index.ts CHANGED
@@ -9,5 +9,4 @@ export * from './content-store';
9
9
  export * as ContentStoreTypes from './types';
10
10
  export { default as encodeData } from './encoder';
11
11
  export * from './encoder';
12
- export * from './types/search-filter';
13
12
  export * from './services';