@stackbit/sdk 0.2.38 → 0.2.39-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/dist/analyzer/analyze-schema-types.d.ts +1 -0
  2. package/dist/analyzer/analyze-schema-types.d.ts.map +1 -0
  3. package/dist/analyzer/analyzer-utils.d.ts +1 -0
  4. package/dist/analyzer/analyzer-utils.d.ts.map +1 -0
  5. package/dist/analyzer/cms-matcher.d.ts +1 -0
  6. package/dist/analyzer/cms-matcher.d.ts.map +1 -0
  7. package/dist/analyzer/file-browser.d.ts +1 -0
  8. package/dist/analyzer/file-browser.d.ts.map +1 -0
  9. package/dist/analyzer/schema-generator.d.ts +1 -0
  10. package/dist/analyzer/schema-generator.d.ts.map +1 -0
  11. package/dist/analyzer/site-analyzer.d.ts +1 -0
  12. package/dist/analyzer/site-analyzer.d.ts.map +1 -0
  13. package/dist/analyzer/ssg-matcher.d.ts +1 -0
  14. package/dist/analyzer/ssg-matcher.d.ts.map +1 -0
  15. package/dist/config/config-consts.d.ts +1 -1
  16. package/dist/config/config-consts.d.ts.map +1 -0
  17. package/dist/config/config-consts.js +0 -1
  18. package/dist/config/config-consts.js.map +1 -1
  19. package/dist/config/config-errors.d.ts +1 -0
  20. package/dist/config/config-errors.d.ts.map +1 -0
  21. package/dist/config/config-loader-esbuild.d.ts +1 -0
  22. package/dist/config/config-loader-esbuild.d.ts.map +1 -0
  23. package/dist/config/config-loader.d.ts +16 -1
  24. package/dist/config/config-loader.d.ts.map +1 -0
  25. package/dist/config/config-loader.js +14 -4
  26. package/dist/config/config-loader.js.map +1 -1
  27. package/dist/config/config-schema/style-field-schema.d.ts +1 -0
  28. package/dist/config/config-schema/style-field-schema.d.ts.map +1 -0
  29. package/dist/config/config-schema/style-field-schema.js +4 -4
  30. package/dist/config/config-schema/style-field-schema.js.map +1 -1
  31. package/dist/config/config-schema.d.ts +1 -0
  32. package/dist/config/config-schema.d.ts.map +1 -0
  33. package/dist/config/config-types.d.ts +10 -1
  34. package/dist/config/config-types.d.ts.map +1 -0
  35. package/dist/config/config-validator.d.ts +1 -0
  36. package/dist/config/config-validator.d.ts.map +1 -0
  37. package/dist/config/config-writer.d.ts +1 -0
  38. package/dist/config/config-writer.d.ts.map +1 -0
  39. package/dist/config/presets-loader.d.ts +1 -0
  40. package/dist/config/presets-loader.d.ts.map +1 -0
  41. package/dist/config/presets-loader.js +6 -5
  42. package/dist/config/presets-loader.js.map +1 -1
  43. package/dist/consts.d.ts +1 -0
  44. package/dist/consts.d.ts.map +1 -0
  45. package/dist/content/content-errors.d.ts +1 -0
  46. package/dist/content/content-errors.d.ts.map +1 -0
  47. package/dist/content/content-loader.d.ts +1 -0
  48. package/dist/content/content-loader.d.ts.map +1 -0
  49. package/dist/content/content-schema.d.ts +1 -0
  50. package/dist/content/content-schema.d.ts.map +1 -0
  51. package/dist/content/content-schema.js +44 -36
  52. package/dist/content/content-schema.js.map +1 -1
  53. package/dist/content/content-validator.d.ts +1 -0
  54. package/dist/content/content-validator.d.ts.map +1 -0
  55. package/dist/index.d.ts +2 -1
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +3 -1
  58. package/dist/index.js.map +1 -1
  59. package/dist/utils/index.d.ts +2 -1
  60. package/dist/utils/index.d.ts.map +1 -0
  61. package/dist/utils/model-extender.d.ts +1 -0
  62. package/dist/utils/model-extender.d.ts.map +1 -0
  63. package/dist/utils/model-iterators.d.ts +20 -0
  64. package/dist/utils/model-iterators.d.ts.map +1 -0
  65. package/dist/utils/model-iterators.js +99 -1
  66. package/dist/utils/model-iterators.js.map +1 -1
  67. package/dist/utils/model-matcher.d.ts +1 -0
  68. package/dist/utils/model-matcher.d.ts.map +1 -0
  69. package/dist/utils/model-utils.d.ts +1 -0
  70. package/dist/utils/model-utils.d.ts.map +1 -0
  71. package/package.json +3 -3
  72. package/src/config/config-consts.ts +0 -1
  73. package/src/config/config-loader.ts +31 -6
  74. package/src/config/config-schema/style-field-schema.ts +7 -4
  75. package/src/config/config-types.ts +20 -1
  76. package/src/config/presets-loader.ts +21 -9
  77. package/src/content/content-schema.ts +50 -44
  78. package/src/index.ts +1 -1
  79. package/src/utils/index.ts +1 -1
  80. package/src/utils/model-iterators.ts +145 -0
@@ -5,7 +5,12 @@ import semver from 'semver';
5
5
  import _ from 'lodash';
6
6
 
7
7
  import { ConfigValidationResult, validateConfig, validateContentModels } from './config-validator';
8
- import { ConfigError, ConfigLoadError, ConfigValidationError } from './config-errors';
8
+ import {
9
+ ConfigError,
10
+ ConfigLoadError,
11
+ ConfigPresetsError,
12
+ ConfigValidationError
13
+ } from './config-errors';
9
14
  import { loadStackbitConfigFromJs, LoadStackbitConfigResult, StopConfigWatch } from './config-loader-esbuild';
10
15
  import {
11
16
  assignLabelFieldIfNeeded,
@@ -60,7 +65,7 @@ export async function loadConfig({
60
65
  stackbitConfigESBuildOutDir,
61
66
  watchCallback
62
67
  }: ConfigLoaderOptions): Promise<ConfigLoaderResult & StopConfigWatch> {
63
- const rawConfigResult = await loadStackbitConfigFromDir({
68
+ const rawConfigResult = await loadConfigFromDir({
64
69
  dirPath,
65
70
  stackbitConfigESBuildOutDir,
66
71
  watchCallback: watchCallback
@@ -96,14 +101,34 @@ async function processConfigLoaderResult({
96
101
 
97
102
  const { models: externalModels, errors: externalModelsLoadErrors } = await loadModelsFromExternalSource(config, dirPath, modelsSource);
98
103
 
99
- const normalizedResult = validateAndNormalizeConfig(config, externalModels);
104
+ const extendedConfig = await extendConfig({ dirPath, config, externalModels });
100
105
 
101
- const presetsResult = await loadPresets(dirPath, normalizedResult.config);
106
+ return {
107
+ valid: extendedConfig.valid,
108
+ config: extendedConfig.config,
109
+ errors: [...configLoadErrors, ...externalModelsLoadErrors, ...extendedConfig.errors]
110
+ };
111
+ }
102
112
 
113
+ export async function extendConfig({
114
+ dirPath,
115
+ config,
116
+ externalModels
117
+ }: {
118
+ dirPath: string;
119
+ config: Record<string, any>;
120
+ externalModels?: Model[];
121
+ }): Promise<{
122
+ valid: boolean;
123
+ config: Config;
124
+ errors: (ConfigValidationError | ConfigPresetsError)[];
125
+ }> {
126
+ const normalizedResult = validateAndNormalizeConfig(config, externalModels);
127
+ const presetsResult = await loadPresets(dirPath, normalizedResult.config);
103
128
  return {
104
129
  valid: normalizedResult.valid,
105
130
  config: presetsResult.config,
106
- errors: [...configLoadErrors, ...externalModelsLoadErrors, ...normalizedResult.errors, ...presetsResult.errors]
131
+ errors: [...normalizedResult.errors, ...presetsResult.errors]
107
132
  };
108
133
  }
109
134
 
@@ -139,7 +164,7 @@ export function validateAndNormalizeConfig(config: Record<string, any>, external
139
164
  });
140
165
  }
141
166
 
142
- async function loadStackbitConfigFromDir({
167
+ export async function loadConfigFromDir({
143
168
  dirPath,
144
169
  stackbitConfigESBuildOutDir,
145
170
  watchCallback
@@ -19,15 +19,15 @@ const styleSizeSchema = stylePropWithAll(
19
19
  errors: { wrap: { label: false } }
20
20
  });
21
21
 
22
- const borderWidthPattern = /^\d+(?::\d+(?::\d+)?)?$/;
23
- const borderWidthSchema = stylePropWithAll(Joi.array().items(Joi.string().pattern(borderWidthPattern)).single()).prefs({
22
+ const anyRangePattern = /^\d+(?::\d+(?::\d+)?)?$/;
23
+ const borderWidthSchema = stylePropWithAll(Joi.array().items(Joi.string().pattern(anyRangePattern)).single()).prefs({
24
24
  messages: {
25
25
  'string.pattern.base': 'Illegal definition "{{#value}}" of style field "{{#label}}". This field must match "borderWidth" style pattern'
26
26
  },
27
27
  errors: { wrap: { label: false } }
28
28
  });
29
29
 
30
- const fontWeightPattern = /^[1-8]00:[2-9]00$/;
30
+ const fontWeightPattern = /^[1-8]00:[2-9]00(?::\d+)?$/;
31
31
  const opacityPattern = /^[1-9]?[05]:(?:5|[1-9][05]|100)$/;
32
32
 
33
33
  const styleColorSchema = arrayOf(
@@ -59,7 +59,10 @@ const stylePropsSchema = Joi.object({
59
59
  label: Joi.string().required()
60
60
  })
61
61
  ),
62
- fontSize: arrayOfStringsWithAll(...STYLE_PROPS_VALUES.fontSize),
62
+ fontSize: stylePropWithAll(
63
+ Joi.string().pattern(sizePattern),
64
+ arrayOf(Joi.string().pattern(sizePattern), Joi.number().integer().min(0).multiple(1))
65
+ ),
63
66
  fontStyle: arrayOfStringsWithAll(...STYLE_PROPS_VALUES.fontStyle),
64
67
  fontWeight: stylePropWithAll(
65
68
  Joi.string().pattern(fontWeightPattern),
@@ -3,7 +3,7 @@ import { CMS_NAMES, FIELD_TYPES, SSG_NAMES, STYLE_PROPS } from './config-consts'
3
3
 
4
4
  export interface Config extends BaseConfig {
5
5
  models: Model[];
6
- presets?: Record<string, any>;
6
+ presets?: Record<string, Preset>;
7
7
  }
8
8
 
9
9
  export interface YamlConfig extends BaseConfig {
@@ -113,6 +113,13 @@ export interface ModelsSourceSanity {
113
113
  [key: string]: any;
114
114
  }
115
115
 
116
+ export interface Preset {
117
+ label: string;
118
+ modelName: string;
119
+ thumbnail?: string;
120
+ data: Record<string, any>;
121
+ }
122
+
116
123
  /*******************
117
124
  *** Model Types ***
118
125
  *******************/
@@ -213,6 +220,17 @@ export interface ImageModel {
213
220
 
214
221
  export type Field = FieldSimple | FieldEnum | FieldImage | FieldNumber | FieldObject | FieldModel | FieldReference | FieldStyle | FieldList;
215
222
 
223
+ export type FieldSpecificProps =
224
+ | FieldSimpleProps
225
+ | FieldEnumProps
226
+ | FieldImageProps
227
+ | FieldNumberProps
228
+ | FieldObjectProps
229
+ | FieldModelProps
230
+ | FieldReferenceProps
231
+ | FieldStyleProps
232
+ | FieldListProps;
233
+
216
234
  export type FieldSimple = FieldCommonProps & FieldSimpleProps;
217
235
  export type FieldEnum = FieldCommonProps & FieldEnumProps;
218
236
  export type FieldImage = FieldCommonProps & FieldImageProps;
@@ -238,6 +256,7 @@ export interface FieldCommonProps {
238
256
  const?: unknown;
239
257
  hidden?: boolean;
240
258
  readOnly?: boolean;
259
+ localized?: boolean;
241
260
  }
242
261
 
243
262
  export type FieldType = typeof FIELD_TYPES[number];
@@ -3,7 +3,7 @@ import path from 'path';
3
3
  import fse from 'fs-extra';
4
4
  import { parseFile, append } from '@stackbit/utils';
5
5
 
6
- import { Config } from './config-types';
6
+ import { Config, Preset } from './config-types';
7
7
  import { ConfigPresetsError } from './config-errors';
8
8
 
9
9
  export interface PresetsLoaderResult {
@@ -11,6 +11,17 @@ export interface PresetsLoaderResult {
11
11
  errors: ConfigPresetsError[];
12
12
  }
13
13
 
14
+ interface RawPresetData {
15
+ model: string;
16
+ presets: RawPreset[];
17
+ }
18
+
19
+ interface RawPreset {
20
+ label: string;
21
+ thumbnail?: string;
22
+ data: Record<string, any>;
23
+ }
24
+
14
25
  export async function loadPresets(dirPath: string, config: Config): Promise<PresetsLoaderResult> {
15
26
  const presetFiles = [];
16
27
  let presetsRelDirs = ['.stackbit/presets', 'node_modules/@stackbit/components/presets'];
@@ -30,14 +41,14 @@ export async function loadPresets(dirPath: string, config: Config): Promise<Pres
30
41
  presetFiles.push(...files);
31
42
  }
32
43
 
33
- const presets: Record<string, any> = {};
34
- const presetsIdsByModel: Record<string, any> = {};
44
+ const presets: Record<string, Preset> = {};
45
+ const presetsIdsByModel: Record<string, string[]> = {};
35
46
  const errors: ConfigPresetsError[] = [];
36
47
 
37
48
  for (const presetFile of presetFiles) {
38
49
  const presetsRelDir = path.dirname(presetFile);
39
50
  const presetPath = path.join(dirPath, presetFile);
40
- let presetData: any;
51
+ let presetData: RawPresetData;
41
52
  try {
42
53
  presetData = await parseFile(presetPath);
43
54
  } catch (err: any) {
@@ -46,11 +57,12 @@ export async function loadPresets(dirPath: string, config: Config): Promise<Pres
46
57
  }
47
58
  _.forEach(_.get(presetData, 'presets', []), (preset, i) => {
48
59
  const presetId = `${presetFile}:presets[${i}]`;
49
- presets[presetId] = preset;
50
- if (preset.thumbnail) {
51
- preset.thumbnail = resolveThumbnailPath(preset.thumbnail, presetsRelDir);
52
- }
53
- _.set(preset, 'modelName', presetData.model);
60
+ const { thumbnail, ...rest } = preset;
61
+ presets[presetId] = {
62
+ ...rest,
63
+ ...(thumbnail ? { thumbnail: resolveThumbnailPath(thumbnail, presetsRelDir) } : null),
64
+ modelName: presetData.model
65
+ };
54
66
  append(presetsIdsByModel, presetData.model, presetId);
55
67
  });
56
68
  }
@@ -1,7 +1,6 @@
1
- import Joi from 'joi';
1
+ import Joi, { CustomHelpers, ErrorReport } from 'joi';
2
2
  import _ from 'lodash';
3
3
  import { append } from '@stackbit/utils';
4
-
5
4
  import { STYLE_PROPS_VALUES } from '../config/config-consts';
6
5
  import { isDataModel, isPageModel } from '../utils';
7
6
  import {
@@ -119,11 +118,16 @@ function joiSchemaForField(field: Field | FieldListItems, config: Config, fieldP
119
118
  case 'text':
120
119
  case 'markdown':
121
120
  case 'html':
122
- case 'image':
123
121
  case 'file':
124
122
  case 'color':
125
123
  fieldSchema = Joi.string().allow('', null);
126
124
  break;
125
+ case 'image':
126
+ fieldSchema = Joi.alternatives([
127
+ Joi.string().allow('', null),
128
+ Joi.object()
129
+ ]);
130
+ break;
127
131
  case 'boolean':
128
132
  fieldSchema = Joi.boolean();
129
133
  break;
@@ -298,12 +302,12 @@ const StylePropContentSchemas: Record<StyleProps, (styleConfig: any) => Joi.Sche
298
302
  width: stylePropSchemaWithValidValues(STYLE_PROPS_VALUES.width),
299
303
  height: stylePropSchemaWithValidValues(STYLE_PROPS_VALUES.height),
300
304
  fontFamily: stylePropObjectValueSchema,
301
- fontSize: stylePropSchemaWithValidValues(STYLE_PROPS_VALUES.fontSize),
305
+ fontSize: stylePropFontSizeSchema,
302
306
  fontStyle: stylePropSchemaWithValidValues(STYLE_PROPS_VALUES.fontStyle),
303
307
  fontWeight: stylePropFontWeightSchema,
304
308
  textAlign: stylePropSchemaWithValidValues(STYLE_PROPS_VALUES.textAlign),
305
309
  textColor: stylePropObjectValueSchema,
306
- textDecoration: stylePropSchemaWithValidValues(STYLE_PROPS_VALUES.textDecoration),
310
+ textDecoration: stylePropTextDecorationSchema,
307
311
  backgroundColor: stylePropObjectValueSchema,
308
312
  backgroundPosition: stylePropSchemaWithValidValues(STYLE_PROPS_VALUES.nineRegions),
309
313
  backgroundSize: stylePropSchemaWithValidValues(STYLE_PROPS_VALUES.backgroundSize),
@@ -327,6 +331,26 @@ function stylePropSchemaWithValidValues(validValues: any[]) {
327
331
  };
328
332
  }
329
333
 
334
+ const textDecorationWrongValue = 'textDecoration.wrong.value';
335
+
336
+ function stylePropTextDecorationSchema(styleConfig: any) {
337
+ const values = styleConfig === '*' ? STYLE_PROPS_VALUES.textDecoration : styleConfig;
338
+ return Joi.string().custom((value: string, { error, errorsArray }: CustomHelpers & { errorsArray?: () => ErrorReport[] }) => {
339
+ const parts = value.split?.(' ');
340
+ const errors = errorsArray!();
341
+ parts.forEach((part) => {
342
+ if (!values.includes(part)) {
343
+ errors.push(error(textDecorationWrongValue));
344
+ }
345
+ });
346
+ return errors && errors.length ? errors : value;
347
+ }).prefs({
348
+ messages: {
349
+ [textDecorationWrongValue]: `{{#label}} must be a space separated string values from [${values.join(', ')}]`
350
+ }
351
+ });
352
+ }
353
+
330
354
  function stylePropSizeSchema(styleConfig: any) {
331
355
  if (styleConfig === '*') {
332
356
  return Joi.object({
@@ -432,49 +456,31 @@ function stylePropObjectValueSchema(styleConfig: any) {
432
456
  return Joi.valid(..._.map(styleConfig, (object) => _.get(object, 'value')));
433
457
  }
434
458
 
459
+ function stylePropFontSizeSchema(styleConfig: any) {
460
+ if (styleConfig === '*') {
461
+ return Joi.number().integer().min(0).max(100).multiple(1);
462
+ }
463
+ return Joi.number().valid(...parseRange(styleConfig));
464
+ }
465
+
435
466
  function stylePropFontWeightSchema(styleConfig: any) {
467
+ const values = STYLE_PROPS_VALUES.fontWeight.map((valueStr) => Number(valueStr));
436
468
  if (styleConfig === '*') {
437
- return Joi.string().valid(...STYLE_PROPS_VALUES.fontWeight);
469
+ return Joi.number().valid(...values);
438
470
  }
439
- styleConfig = _.castArray(styleConfig);
440
- const validValues = _.reduce(
441
- styleConfig,
442
- (validValues, value) => {
443
- if (_.isNumber(value)) {
444
- return validValues.add(String(value));
445
- }
446
- if (!_.isString(value)) {
447
- return validValues;
448
- }
449
- if (_.isEmpty(value)) {
450
- return validValues;
451
- }
452
- const parts = value.split(':').map((value) => Number(value));
453
- if (_.some(parts, _.isNaN)) {
454
- return validValues;
455
- }
456
- if (parts.length === 1) {
457
- return validValues.add(String(parts[0]!));
458
- }
459
- const start = parts[0]!;
460
- const end = parts[1]!;
461
- for (let i = start; i <= end; i += 100) {
462
- validValues.add(String(i));
463
- }
464
- return validValues;
465
- },
466
- new Set<string>()
467
- );
468
- return Joi.valid(...[...validValues]);
471
+ return Joi.number().valid(...parseRange(styleConfig, { defaultStep: 100 }));
469
472
  }
470
473
 
471
474
  function stylePropOpacitySchema(styleConfig: any) {
472
475
  if (styleConfig === '*') {
473
476
  return Joi.number().integer().min(0).max(100).multiple(5);
474
477
  }
475
- styleConfig = _.castArray(styleConfig);
476
- const validValues = _.reduce(
477
- styleConfig,
478
+ return Joi.number().valid(...parseRange(styleConfig, { userDefinedStep: false, defaultStep: 5 }));
479
+ }
480
+
481
+ function parseRange(styleConfig: any, { userDefinedStep = true, defaultStep = 1 } = {}) {
482
+ const values = _.reduce(
483
+ _.castArray(styleConfig),
478
484
  (validValues, value) => {
479
485
  if (_.isNumber(value)) {
480
486
  return validValues.add(value);
@@ -492,14 +498,14 @@ function stylePropOpacitySchema(styleConfig: any) {
492
498
  if (parts.length === 1) {
493
499
  return validValues.add(parts[0]!);
494
500
  }
495
- const start = parts[0]!;
496
- const end = parts[1]!;
497
- for (let i = start; i <= end; i += 5) {
501
+ const [start, end, userStep = defaultStep] = parts;
502
+ const step = userDefinedStep ? userStep : defaultStep;
503
+ for (let i = start!; i <= end!; i += step) {
498
504
  validValues.add(i);
499
505
  }
500
506
  return validValues;
501
507
  },
502
508
  new Set<number>()
503
509
  );
504
- return Joi.valid(...[...validValues]);
505
- }
510
+ return Array.from(values);
511
+ }
package/src/index.ts CHANGED
@@ -5,7 +5,7 @@ export * from './content/content-errors';
5
5
  export * from './config/config-errors';
6
6
  export * from './analyzer/file-browser';
7
7
  export * from './utils';
8
- export { loadConfig, ConfigLoaderOptions, ConfigLoaderResult } from './config/config-loader';
8
+ export { loadConfig, loadConfigFromDir, extendConfig, ConfigLoaderOptions, ConfigLoaderResult } from './config/config-loader';
9
9
  export { writeConfig, WriteConfigOptions, convertToYamlConfig } from './config/config-writer';
10
10
  export { loadContent, ContentItem, ContentLoaderOptions, ContentLoaderResult } from './content/content-loader';
11
11
  export { matchSSG, SSGMatcherOptions, SSGMatchResult } from './analyzer/ssg-matcher';
@@ -9,7 +9,7 @@ type StricterUnionMember<T, Union, Keys extends string = UnionKeys<Union> extend
9
9
 
10
10
  export type StricterUnion<Union, Union2 = Union> = Union2 extends any ? StricterUnionMember<Union2, Union> : never;
11
11
 
12
- export type Logger = Pick<Console, 'log' | 'info' | 'warn' | 'error' | 'group' | 'groupEnd'>;
12
+ export type Logger = Pick<Console, 'log' | 'debug' | 'info' | 'warn' | 'error' | 'group' | 'groupEnd'>;
13
13
 
14
14
  export * from './model-utils';
15
15
  export * from './model-matcher';
@@ -1,5 +1,6 @@
1
1
  import _ from 'lodash';
2
2
 
3
+ import { mapPromise, mapValuesPromise } from '@stackbit/utils';
3
4
  import { getListFieldItems, isListDataModel, isListField, isObjectListItems, isModelField, isObjectField, isModelListItems } from './model-utils';
4
5
  import { DataModel, Field, FieldList, FieldListItems, FieldModelProps, FieldObjectProps, Model } from '../config/config-types';
5
6
 
@@ -366,6 +367,150 @@ export function mapObjectFieldsWithModelRecursively(
366
367
  });
367
368
  }
368
369
 
370
+ export async function asyncMapObjectFieldsWithModelRecursively({
371
+ value,
372
+ model,
373
+ modelsByName,
374
+ iteratee,
375
+ pageLayoutKey = 'layout',
376
+ objectTypeKey = 'type',
377
+ valueId
378
+ }: {
379
+ value: any;
380
+ model: Model;
381
+ modelsByName: Record<string, Model>;
382
+ iteratee: (options: {
383
+ value: any;
384
+ model: Model | null;
385
+ field: Field | null;
386
+ fieldListItem: FieldListItems | null;
387
+ error: string | null;
388
+ valueKeyPath: (string | number)[];
389
+ modelKeyPath: string[];
390
+ objectStack: any[];
391
+ skipNested: () => void;
392
+ }) => Promise<any>;
393
+ pageLayoutKey?: string;
394
+ objectTypeKey?: string;
395
+ valueId?: string;
396
+ }): Promise<any> {
397
+ async function _mapDeep({
398
+ value,
399
+ model,
400
+ field,
401
+ fieldListItem,
402
+ valueKeyPath,
403
+ modelKeyPath,
404
+ objectStack
405
+ }: {
406
+ value: any;
407
+ model: Model | null;
408
+ field: Field | null;
409
+ fieldListItem: FieldListItems | null;
410
+ valueKeyPath: (string | number)[];
411
+ modelKeyPath: string[];
412
+ objectStack: any[];
413
+ }) {
414
+ let error: string | null = null;
415
+ let modelField: FieldModelProps | null = null;
416
+
417
+ if (!model && !field && !fieldListItem) {
418
+ error = `could not match model/field ${modelKeyPath.join('.')} to content at ${valueKeyPath.join('.')}`;
419
+ }
420
+
421
+ if (field && isModelField(field)) {
422
+ modelField = field;
423
+ } else if (fieldListItem && isModelListItems(fieldListItem)) {
424
+ modelField = fieldListItem;
425
+ }
426
+
427
+ if (modelField) {
428
+ const modelResult = getModelOfObject({
429
+ object: value,
430
+ field: modelField,
431
+ modelsByName,
432
+ pageLayoutKey,
433
+ objectTypeKey,
434
+ valueKeyPath,
435
+ modelKeyPath
436
+ });
437
+ if ('error' in modelResult) {
438
+ error = modelResult.error;
439
+ } else {
440
+ model = modelResult.model;
441
+ }
442
+ field = null;
443
+ fieldListItem = null;
444
+ modelKeyPath = model ? [model.name] : [];
445
+ }
446
+
447
+ let shouldSkipNested = false;
448
+ const skipNested = () => {
449
+ shouldSkipNested = true;
450
+ };
451
+
452
+ const res = await iteratee({ value, model, field, fieldListItem, error, valueKeyPath, modelKeyPath, objectStack, skipNested });
453
+ if (!_.isUndefined(res)) {
454
+ value = res;
455
+ }
456
+
457
+ if (shouldSkipNested) {
458
+ return value;
459
+ }
460
+
461
+ if (_.isPlainObject(value)) {
462
+ // if fields will not be resolved or the object will have a key that
463
+ // doesn't exist among fields, the nested calls to _iterateDeep will
464
+ // include an error.
465
+ const fields = getFieldsOfModelOrField(model, field, fieldListItem);
466
+ const fieldsByName = _.keyBy(fields, 'name');
467
+ modelKeyPath = _.concat(modelKeyPath, 'fields');
468
+ value = await mapValuesPromise(value, (val, key) => {
469
+ const field = _.get(fieldsByName, key, null);
470
+ return _mapDeep({
471
+ value: val,
472
+ model: null,
473
+ field: field,
474
+ fieldListItem: null,
475
+ valueKeyPath: _.concat(valueKeyPath, key),
476
+ modelKeyPath: _.concat(modelKeyPath, key),
477
+ objectStack: _.concat(objectStack, value)
478
+ });
479
+ });
480
+ } else if (_.isArray(value)) {
481
+ let fieldListItems: FieldListItems | null = null;
482
+ if (field && isListField(field)) {
483
+ fieldListItems = getListFieldItems(field);
484
+ } else if (model && isListDataModel(model)) {
485
+ fieldListItems = model.items;
486
+ }
487
+ value = await mapPromise(value, (val, idx) => {
488
+ return _mapDeep({
489
+ value: val,
490
+ model: null,
491
+ field: null,
492
+ fieldListItem: fieldListItems,
493
+ valueKeyPath: _.concat(valueKeyPath, idx),
494
+ modelKeyPath: _.concat(modelKeyPath, 'items'),
495
+ objectStack: _.concat(objectStack, value)
496
+ });
497
+ });
498
+ }
499
+
500
+ return value;
501
+ }
502
+
503
+ return _mapDeep({
504
+ value: value,
505
+ model: model,
506
+ field: null,
507
+ fieldListItem: null,
508
+ valueKeyPath: valueId ? [valueId] : [],
509
+ modelKeyPath: [model.name],
510
+ objectStack: []
511
+ });
512
+ }
513
+
369
514
  export function getModelOfObject({
370
515
  object,
371
516
  field,