@stackbit/cms-core 0.8.7-develop.1 → 0.8.7-develop.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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/content-store-utils.d.ts +1 -40
- package/dist/content-store-utils.d.ts.map +1 -1
- package/dist/content-store-utils.js +1 -293
- package/dist/content-store-utils.js.map +1 -1
- package/dist/content-store.d.ts.map +1 -1
- package/dist/content-store.js +8 -21
- package/dist/content-store.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/utils/custom-actions.d.ts.map +1 -1
- package/dist/utils/custom-actions.js +3 -4
- package/dist/utils/custom-actions.js.map +1 -1
- package/package.json +3 -3
- package/src/content-store-utils.ts +0 -367
- package/src/content-store.ts +8 -22
- package/src/index.ts +2 -0
- package/src/utils/custom-actions.ts +2 -7
- package/src/utils/field-path-utils.ts +741 -0
|
@@ -0,0 +1,741 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import { Model } from '@stackbit/sdk';
|
|
3
|
+
import { Field, FieldListItems, FieldListProps, FieldObjectProps } from '@stackbit/types';
|
|
4
|
+
import * as ContentStoreTypes from '../types';
|
|
5
|
+
import { getDocumentFieldForLocale } from '../content-store-utils';
|
|
6
|
+
|
|
7
|
+
export function getModelFieldAtFieldPath(
|
|
8
|
+
document: ContentStoreTypes.Document,
|
|
9
|
+
fieldPath: (string | number)[],
|
|
10
|
+
modelMap: Record<string, Model>,
|
|
11
|
+
locale?: string
|
|
12
|
+
): Field | FieldListItems {
|
|
13
|
+
if (_.isEmpty(fieldPath)) {
|
|
14
|
+
throw new Error('the fieldPath can not be empty');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const model = modelMap[document.srcModelName];
|
|
18
|
+
if (!model) {
|
|
19
|
+
throw new Error(`model with name '${document.srcModelName}' of a document '${document.srcObjectId}' not found`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getField(docField: ContentStoreTypes.DocumentField, modelField: Field | FieldListItems, fieldPath: (string | number)[]): Field | FieldListItems {
|
|
23
|
+
const fieldName = _.head(fieldPath);
|
|
24
|
+
if (typeof fieldName === 'undefined') {
|
|
25
|
+
throw new Error('the first fieldPath item must be string');
|
|
26
|
+
}
|
|
27
|
+
const childFieldPath = _.tail(fieldPath);
|
|
28
|
+
let childDocField: ContentStoreTypes.DocumentField | undefined;
|
|
29
|
+
let childModelField: Field | undefined;
|
|
30
|
+
switch (docField.type) {
|
|
31
|
+
case 'object': {
|
|
32
|
+
const localizedObjectField = getDocumentFieldForLocale(docField, locale);
|
|
33
|
+
if (!localizedObjectField) {
|
|
34
|
+
throw new Error(`locale for field was not found`);
|
|
35
|
+
}
|
|
36
|
+
if (localizedObjectField.isUnset) {
|
|
37
|
+
throw new Error(`field is not set`);
|
|
38
|
+
}
|
|
39
|
+
childDocField = localizedObjectField.fields[fieldName];
|
|
40
|
+
childModelField = _.find((modelField as FieldObjectProps).fields, (field) => field.name === fieldName);
|
|
41
|
+
if (!childDocField || !childModelField) {
|
|
42
|
+
throw new Error(`field ${fieldName} doesn't exist`);
|
|
43
|
+
}
|
|
44
|
+
if (childFieldPath.length === 0) {
|
|
45
|
+
return childModelField;
|
|
46
|
+
}
|
|
47
|
+
return getField(childDocField, childModelField, childFieldPath);
|
|
48
|
+
}
|
|
49
|
+
case 'model': {
|
|
50
|
+
const localizedModelField = getDocumentFieldForLocale(docField, locale);
|
|
51
|
+
if (!localizedModelField) {
|
|
52
|
+
throw new Error(`locale for field was not found`);
|
|
53
|
+
}
|
|
54
|
+
if (localizedModelField.isUnset) {
|
|
55
|
+
throw new Error(`field is not set`);
|
|
56
|
+
}
|
|
57
|
+
const modelName = localizedModelField.srcModelName;
|
|
58
|
+
const childModel = modelMap[modelName];
|
|
59
|
+
if (!childModel) {
|
|
60
|
+
throw new Error(`model ${modelName} doesn't exist`);
|
|
61
|
+
}
|
|
62
|
+
childModelField = _.find(childModel.fields, (field) => field.name === fieldName);
|
|
63
|
+
childDocField = localizedModelField.fields![fieldName];
|
|
64
|
+
if (!childDocField || !childModelField) {
|
|
65
|
+
throw new Error(`field ${fieldName} doesn't exist`);
|
|
66
|
+
}
|
|
67
|
+
if (childFieldPath.length === 0) {
|
|
68
|
+
return childModelField;
|
|
69
|
+
}
|
|
70
|
+
return getField(childDocField, childModelField!, childFieldPath);
|
|
71
|
+
}
|
|
72
|
+
case 'list': {
|
|
73
|
+
const localizedListField = getDocumentFieldForLocale(docField, locale);
|
|
74
|
+
if (!localizedListField) {
|
|
75
|
+
throw new Error(`locale for field was not found`);
|
|
76
|
+
}
|
|
77
|
+
const listItem = localizedListField.items && localizedListField.items[fieldName as number];
|
|
78
|
+
const listItemsModel = (modelField as FieldListProps).items;
|
|
79
|
+
if (!listItem || !listItemsModel) {
|
|
80
|
+
throw new Error(`field ${fieldName} doesn't exist`);
|
|
81
|
+
}
|
|
82
|
+
if (childFieldPath.length === 0) {
|
|
83
|
+
return listItemsModel;
|
|
84
|
+
}
|
|
85
|
+
if (!Array.isArray(listItemsModel)) {
|
|
86
|
+
return getField(listItem, listItemsModel, childFieldPath);
|
|
87
|
+
} else {
|
|
88
|
+
const fieldListItems = (listItemsModel as FieldListItems[]).find((listItemsModel) => listItemsModel.type === listItem.type);
|
|
89
|
+
if (!fieldListItems) {
|
|
90
|
+
throw new Error('cannot find matching field model');
|
|
91
|
+
}
|
|
92
|
+
return getField(listItem, fieldListItems, childFieldPath);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
default:
|
|
96
|
+
if (!_.isEmpty(childFieldPath)) {
|
|
97
|
+
throw new Error('illegal fieldPath');
|
|
98
|
+
}
|
|
99
|
+
return modelField;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const fieldName = _.head(fieldPath);
|
|
104
|
+
const childFieldPath = _.tail(fieldPath);
|
|
105
|
+
|
|
106
|
+
if (typeof fieldName !== 'string') {
|
|
107
|
+
throw new Error('the first fieldPath item must be string');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const childDocField = document.fields[fieldName];
|
|
111
|
+
const childModelField = _.find(model.fields, { name: fieldName });
|
|
112
|
+
|
|
113
|
+
if (!childDocField || !childModelField) {
|
|
114
|
+
throw new Error(`field ${fieldName} doesn't exist`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (childFieldPath.length === 0) {
|
|
118
|
+
return childModelField;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return getField(childDocField, childModelField, childFieldPath);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* This function receives a `document` and returns DocumentFieldNonLocalized at
|
|
126
|
+
* the specified `fieldPath` while resolving any localized fields with the
|
|
127
|
+
* specified `locale`.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* getDocumentFieldAtFieldPath({
|
|
131
|
+
* document,
|
|
132
|
+
* locale,
|
|
133
|
+
* fieldPath: ['sections', 1, 'title']
|
|
134
|
+
* })
|
|
135
|
+
*
|
|
136
|
+
* For improved localization support, use the getModelAndDocumentFieldForLocalizedFieldPath
|
|
137
|
+
* method instead.
|
|
138
|
+
*
|
|
139
|
+
* The `isFullFieldPath` flag specifies if the `fieldPath` includes container
|
|
140
|
+
* specifiers such as "fields" and "items".
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* isFullFieldPath: false => fieldPath: ['sections', 1, 'title']
|
|
144
|
+
* isFullFieldPath: true => fieldPath: ['fields', 'sections', 'items', 1, 'fields', 'title']
|
|
145
|
+
*/
|
|
146
|
+
export function getDocumentFieldAtFieldPath({
|
|
147
|
+
document,
|
|
148
|
+
fieldPath,
|
|
149
|
+
locale,
|
|
150
|
+
isFullFieldPath
|
|
151
|
+
}: {
|
|
152
|
+
document: ContentStoreTypes.Document;
|
|
153
|
+
fieldPath: (string | number)[];
|
|
154
|
+
locale?: string;
|
|
155
|
+
isFullFieldPath?: boolean;
|
|
156
|
+
}): ContentStoreTypes.DocumentFieldNonLocalized {
|
|
157
|
+
if (isFullFieldPath) {
|
|
158
|
+
if (_.head(fieldPath) !== 'fields') {
|
|
159
|
+
throw new Error('fieldPath must start with "fields" specifier');
|
|
160
|
+
}
|
|
161
|
+
fieldPath = _.tail(fieldPath);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (_.isEmpty(fieldPath)) {
|
|
165
|
+
throw new Error('the fieldPath cannot be empty');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const docId = document.srcObjectId;
|
|
169
|
+
const modelName = document.srcModelName;
|
|
170
|
+
const origFieldPath = fieldPath;
|
|
171
|
+
const origFieldPathStr = fieldPath.join('.');
|
|
172
|
+
|
|
173
|
+
function getPrefixOf(fieldPath: (string | number)[], include = 0) {
|
|
174
|
+
return origFieldPath.slice(0, origFieldPath.length - fieldPath.length + include).join('.');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function getField(
|
|
178
|
+
docField: ContentStoreTypes.DocumentField | ContentStoreTypes.DocumentListFieldItems,
|
|
179
|
+
fieldPath: (string | number)[]
|
|
180
|
+
): ContentStoreTypes.DocumentFieldNonLocalized {
|
|
181
|
+
const localizedField = getDocumentFieldForLocale(docField, locale);
|
|
182
|
+
if (!localizedField) {
|
|
183
|
+
throw new Error(
|
|
184
|
+
`the value of a field at fieldPath '${getPrefixOf(fieldPath)}' is undefined ` +
|
|
185
|
+
`for the document '${docId}' of type '${modelName}' for the '${locale}' locale.`
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// if no more items in fieldPath return the found document and model fields
|
|
190
|
+
if (fieldPath.length === 0) {
|
|
191
|
+
return localizedField;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
switch (localizedField.type) {
|
|
195
|
+
case 'object':
|
|
196
|
+
case 'model': {
|
|
197
|
+
if (localizedField.isUnset) {
|
|
198
|
+
throw new Error(
|
|
199
|
+
`the fieldPath '${getPrefixOf(fieldPath, 1)}' of a document '${docId}' of type '${modelName}' points into an object that is not set`
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
if (isFullFieldPath) {
|
|
203
|
+
if (_.head(fieldPath) !== 'fields') {
|
|
204
|
+
throw new Error(`the fieldPath '${getPrefixOf(fieldPath, 1)}' must contain "fields" specifier before a field's name`);
|
|
205
|
+
}
|
|
206
|
+
fieldPath = _.tail(fieldPath);
|
|
207
|
+
}
|
|
208
|
+
const fieldName = _.head(fieldPath);
|
|
209
|
+
if (typeof fieldName === 'undefined') {
|
|
210
|
+
throw new Error(`the fieldPath '${getPrefixOf(fieldPath, 1)}' of a document '${docId}' of type '${modelName}' must point to a field name`);
|
|
211
|
+
}
|
|
212
|
+
fieldPath = _.tail(fieldPath);
|
|
213
|
+
const childDocField = localizedField.fields[fieldName];
|
|
214
|
+
if (!childDocField) {
|
|
215
|
+
throw new Error(`the fieldPath '${getPrefixOf(fieldPath)}' points to a non existing field in a document '${docId}' of type '${modelName}'`);
|
|
216
|
+
}
|
|
217
|
+
return getField(childDocField, fieldPath);
|
|
218
|
+
}
|
|
219
|
+
case 'list': {
|
|
220
|
+
if (isFullFieldPath) {
|
|
221
|
+
if (_.head(fieldPath) !== 'items') {
|
|
222
|
+
throw new Error(`fieldPath '${getPrefixOf(fieldPath, 1)}' must contain "items" specifier after a "list" field`);
|
|
223
|
+
}
|
|
224
|
+
fieldPath = _.tail(fieldPath);
|
|
225
|
+
}
|
|
226
|
+
const itemIndex = _.head(fieldPath) as number;
|
|
227
|
+
if (typeof itemIndex === 'undefined') {
|
|
228
|
+
throw new Error(`fieldPath '${getPrefixOf(fieldPath, 1)}' of a document '${docId}' of type '${modelName}' must point to a list item index`);
|
|
229
|
+
}
|
|
230
|
+
fieldPath = _.tail(fieldPath);
|
|
231
|
+
const listItem = localizedField.items && localizedField.items[itemIndex];
|
|
232
|
+
if (!listItem) {
|
|
233
|
+
throw new Error(
|
|
234
|
+
`the fieldPath '${getPrefixOf(fieldPath)}' points to a non existing list item in a document '${docId}' of type '${modelName}'`
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
return getField(listItem, fieldPath);
|
|
238
|
+
}
|
|
239
|
+
default: {
|
|
240
|
+
const primitiveFieldName = origFieldPath[origFieldPath.length - fieldPath.length - 1];
|
|
241
|
+
throw new Error(
|
|
242
|
+
`the fieldPath '${origFieldPathStr}' is illegal, a primitive field '${primitiveFieldName}' of type '${localizedField.type}' cannot be followed by additional field paths`
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const fieldName = _.head(fieldPath);
|
|
249
|
+
const restFieldPath = _.tail(fieldPath);
|
|
250
|
+
|
|
251
|
+
if (typeof fieldName !== 'string') {
|
|
252
|
+
throw new Error('the first fieldPath item must be string');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const childDocField: ContentStoreTypes.DocumentField | undefined = document.fields[fieldName];
|
|
256
|
+
if (!childDocField) {
|
|
257
|
+
throw new Error(`document '${docId}' of type '${modelName}' doesn't have a field named '${fieldName}'`);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return getField(childDocField, restFieldPath);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* This function receives a `document` and a `modelMap` and returns an object
|
|
265
|
+
* with DocumentFieldNonLocalized and a model Field at the specified `fieldPath`
|
|
266
|
+
* while resolving any localized fields with the specified `locale`.
|
|
267
|
+
*
|
|
268
|
+
* @example
|
|
269
|
+
* getDocumentAndModelFieldAtFieldPath({
|
|
270
|
+
* document,
|
|
271
|
+
* locale,
|
|
272
|
+
* modelMap,
|
|
273
|
+
* fieldPath: ['sections', 1, 'title']
|
|
274
|
+
* })
|
|
275
|
+
*
|
|
276
|
+
* For improved localization support, use the getModelAndDocumentFieldForLocalizedFieldPath
|
|
277
|
+
* method instead.
|
|
278
|
+
*
|
|
279
|
+
* The `isFullFieldPath` flag specifies if the `fieldPath` includes container
|
|
280
|
+
* specifiers such as "fields" and "items".
|
|
281
|
+
*
|
|
282
|
+
* @example
|
|
283
|
+
* isFullFieldPath: false => fieldPath: ['sections', 1, 'title']
|
|
284
|
+
* isFullFieldPath: true => fieldPath: ['fields', 'sections', 'items', 1, 'fields', 'title']
|
|
285
|
+
*/
|
|
286
|
+
export function getDocumentAndModelFieldAtFieldPath({
|
|
287
|
+
document,
|
|
288
|
+
fieldPath,
|
|
289
|
+
modelMap,
|
|
290
|
+
locale,
|
|
291
|
+
isFullFieldPath
|
|
292
|
+
}: {
|
|
293
|
+
document: ContentStoreTypes.Document;
|
|
294
|
+
fieldPath: (string | number)[];
|
|
295
|
+
modelMap: Record<string, Model>;
|
|
296
|
+
locale?: string;
|
|
297
|
+
isFullFieldPath?: boolean;
|
|
298
|
+
}): { modelField: Field | FieldListItems; documentField: ContentStoreTypes.DocumentFieldNonLocalized } {
|
|
299
|
+
if (isFullFieldPath) {
|
|
300
|
+
if (_.head(fieldPath) !== 'fields') {
|
|
301
|
+
throw new Error('fieldPath must start with "fields" specifier');
|
|
302
|
+
}
|
|
303
|
+
fieldPath = _.tail(fieldPath);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (_.isEmpty(fieldPath)) {
|
|
307
|
+
throw new Error('the fieldPath cannot be empty');
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const docId = document.srcObjectId;
|
|
311
|
+
const docModelName = document.srcModelName;
|
|
312
|
+
const origFieldPath = fieldPath;
|
|
313
|
+
const origFieldPathStr = fieldPath.join('.');
|
|
314
|
+
|
|
315
|
+
let parentModel: Model = modelMap[docModelName]!;
|
|
316
|
+
let fieldPathFromParentModel = fieldPath;
|
|
317
|
+
|
|
318
|
+
if (!parentModel) {
|
|
319
|
+
throw new Error(`model with name '${docModelName}' of a document '${docId}' not found`);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function getPrefixOf(fieldPath: (string | number)[], include = 0) {
|
|
323
|
+
return origFieldPath.slice(0, origFieldPath.length - fieldPath.length + include).join('.');
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function getModelPrefixOf(fieldPath: (string | number)[], include = 0) {
|
|
327
|
+
return fieldPathFromParentModel.slice(0, fieldPathFromParentModel.length - fieldPath.length + include).join('.');
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function getField(
|
|
331
|
+
docField: ContentStoreTypes.DocumentField | ContentStoreTypes.DocumentListFieldItems,
|
|
332
|
+
modelField: Field | FieldListItems,
|
|
333
|
+
fieldPath: (string | number)[]
|
|
334
|
+
): { modelField: Field | FieldListItems; documentField: ContentStoreTypes.DocumentFieldNonLocalized } {
|
|
335
|
+
const localizedField = getDocumentFieldForLocale(docField, locale);
|
|
336
|
+
if (!localizedField) {
|
|
337
|
+
throw new Error(
|
|
338
|
+
`the value of a field at fieldPath '${getPrefixOf(fieldPath)}' is undefined ` +
|
|
339
|
+
`for the document '${docId}' of type '${docModelName}' for the '${locale}' locale.`
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// if no more items in fieldPath return the found document and model fields
|
|
344
|
+
if (fieldPath.length === 0) {
|
|
345
|
+
return {
|
|
346
|
+
modelField: modelField,
|
|
347
|
+
documentField: localizedField
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
switch (localizedField.type) {
|
|
352
|
+
case 'object': {
|
|
353
|
+
if (localizedField.isUnset) {
|
|
354
|
+
throw new Error(
|
|
355
|
+
`the fieldPath '${getPrefixOf(fieldPath, 1)}' of a document '${docId}' of type '${docModelName}' points into an object that is not set`
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
if (isFullFieldPath) {
|
|
359
|
+
if (_.head(fieldPath) !== 'fields') {
|
|
360
|
+
throw new Error(`the fieldPath '${getPrefixOf(fieldPath, 1)}' must contain "fields" specifier before a field's name`);
|
|
361
|
+
}
|
|
362
|
+
fieldPath = _.tail(fieldPath);
|
|
363
|
+
}
|
|
364
|
+
const fieldName = _.head(fieldPath);
|
|
365
|
+
if (typeof fieldName === 'undefined') {
|
|
366
|
+
throw new Error(
|
|
367
|
+
`the fieldPath '${getPrefixOf(fieldPath, 1)}' of a document '${docId}' of type '${docModelName}' must point to a field name`
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
fieldPath = _.tail(fieldPath);
|
|
371
|
+
const childDocField = localizedField.fields[fieldName];
|
|
372
|
+
if (!childDocField) {
|
|
373
|
+
throw new Error(
|
|
374
|
+
`the fieldPath '${getPrefixOf(fieldPath)}' points to a non existing field in a document '${docId}' of type '${docModelName}'`
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
if (modelField.type !== 'object') {
|
|
378
|
+
throw new Error(
|
|
379
|
+
`model field of type '${modelField.type}' of model '${parentModel.name}' at field path '${getModelPrefixOf(fieldPath)}' ` +
|
|
380
|
+
`doesn't match document field of type 'object' of document '${docId}' at field path '${getPrefixOf(fieldPath)}'`
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
const childModelField = _.find(modelField.fields, (field) => field.name === fieldName);
|
|
384
|
+
if (!childModelField) {
|
|
385
|
+
throw new Error(`model '${parentModel.name}' doesn't have a field at path: '${getModelPrefixOf(fieldPath)}'`);
|
|
386
|
+
}
|
|
387
|
+
return getField(childDocField, childModelField, fieldPath);
|
|
388
|
+
}
|
|
389
|
+
case 'model': {
|
|
390
|
+
if (localizedField.isUnset) {
|
|
391
|
+
throw new Error(
|
|
392
|
+
`the fieldPath '${getPrefixOf(fieldPath, 1)}' of a document '${docId}' of type '${docModelName}' points into an object that is not set`
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
fieldPathFromParentModel = fieldPath;
|
|
396
|
+
if (isFullFieldPath) {
|
|
397
|
+
if (_.head(fieldPath) !== 'fields') {
|
|
398
|
+
throw new Error(`the fieldPath '${getPrefixOf(fieldPath, 1)}' must contain "fields" specifier before a field's name`);
|
|
399
|
+
}
|
|
400
|
+
fieldPath = _.tail(fieldPath);
|
|
401
|
+
}
|
|
402
|
+
const fieldName = _.head(fieldPath);
|
|
403
|
+
if (typeof fieldName === 'undefined') {
|
|
404
|
+
throw new Error(
|
|
405
|
+
`the fieldPath '${getPrefixOf(fieldPath, 1)}' of a document '${docId}' of type '${docModelName}' must point to a field name`
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
fieldPath = _.tail(fieldPath);
|
|
409
|
+
const childDocField = localizedField.fields[fieldName];
|
|
410
|
+
if (!childDocField) {
|
|
411
|
+
throw new Error(
|
|
412
|
+
`the fieldPath '${getPrefixOf(fieldPath)}' points to a non existing field in a document '${docId}' of type '${docModelName}'`
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
const modelName = localizedField.srcModelName;
|
|
416
|
+
const childModel = modelMap[modelName];
|
|
417
|
+
if (!childModel) {
|
|
418
|
+
throw new Error(
|
|
419
|
+
`the "model" field at path '${getPrefixOf(fieldPath)}' of a document '${docId}' of type '${docModelName}' ` +
|
|
420
|
+
`contains an object with type of non existing model '${modelName}'`
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
parentModel = childModel;
|
|
424
|
+
const childModelField = _.find(childModel.fields, (field) => field.name === fieldName);
|
|
425
|
+
if (!childModelField) {
|
|
426
|
+
throw new Error(`model '${childModel.name}' doesn't have a field at path: '${getModelPrefixOf(fieldPath)}'`);
|
|
427
|
+
}
|
|
428
|
+
return getField(childDocField, childModelField, fieldPath);
|
|
429
|
+
}
|
|
430
|
+
case 'list': {
|
|
431
|
+
if (isFullFieldPath) {
|
|
432
|
+
if (_.head(fieldPath) !== 'items') {
|
|
433
|
+
throw new Error(`the fieldPath '${getPrefixOf(fieldPath, 1)}' must contain "items" specifier before a list item index`);
|
|
434
|
+
}
|
|
435
|
+
fieldPath = _.tail(fieldPath);
|
|
436
|
+
}
|
|
437
|
+
const itemIndex = _.head(fieldPath) as number;
|
|
438
|
+
if (typeof itemIndex === 'undefined') {
|
|
439
|
+
throw new Error(
|
|
440
|
+
`the fieldPath '${getPrefixOf(fieldPath, 1)}' of a document '${docId}' of type '${docModelName}' must point to a list item index`
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
fieldPath = _.tail(fieldPath);
|
|
444
|
+
const listItem = localizedField.items && localizedField.items[itemIndex];
|
|
445
|
+
if (!listItem) {
|
|
446
|
+
throw new Error(
|
|
447
|
+
`the fieldPath '${getPrefixOf(fieldPath)}' points to a non existing list item in a document '${docId}' of type '${docModelName}'`
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
if (modelField.type !== 'list') {
|
|
451
|
+
throw new Error(
|
|
452
|
+
`model field of type '${modelField.type}' of model '${parentModel.name}' at field path '${getModelPrefixOf(fieldPath)}' ` +
|
|
453
|
+
`doesn't match document field of type 'list' of document '${docId}' at field path '${getPrefixOf(fieldPath)}'`
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
const listItemsModel = modelField.items;
|
|
457
|
+
if (!Array.isArray(listItemsModel)) {
|
|
458
|
+
return getField(listItem, listItemsModel, fieldPath);
|
|
459
|
+
} else {
|
|
460
|
+
const fieldListItems = (listItemsModel as FieldListItems[]).find((listItemsModel) => listItemsModel.type === listItem.type);
|
|
461
|
+
if (!fieldListItems) {
|
|
462
|
+
throw new Error(
|
|
463
|
+
`cannot find list item model for list item of type '${listItem.type}' for model '${parentModel.name}' ` +
|
|
464
|
+
`at field path '${getModelPrefixOf(fieldPath)}'`
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
return getField(listItem, fieldListItems, fieldPath);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
default: {
|
|
471
|
+
const primitiveFieldName = origFieldPath[origFieldPath.length - fieldPath.length - 1];
|
|
472
|
+
throw new Error(
|
|
473
|
+
`the fieldPath '${origFieldPathStr}' is illegal, a primitive field '${primitiveFieldName}' of type '${localizedField.type}' cannot be followed by additional field paths`
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const fieldName = _.head(fieldPath);
|
|
480
|
+
const restFieldPath = _.tail(fieldPath);
|
|
481
|
+
|
|
482
|
+
if (typeof fieldName !== 'string') {
|
|
483
|
+
throw new Error('the first fieldPath item must be string');
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const childDocField: ContentStoreTypes.DocumentField | undefined = document.fields[fieldName];
|
|
487
|
+
if (!childDocField) {
|
|
488
|
+
throw new Error(`document '${docId}' of type '${docModelName}' doesn't have a field named '${fieldName}'`);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const childModelField: Field | undefined = _.find(parentModel.fields, { name: fieldName });
|
|
492
|
+
if (!childModelField) {
|
|
493
|
+
throw new Error(`model '${docModelName}' doesn't have a field named '${fieldName}'`);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
return getField(childDocField, childModelField, restFieldPath);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* This function receives a `document` and a `modelMap` and returns an object
|
|
501
|
+
* with DocumentField and a model Field at the specified `fieldPath`.
|
|
502
|
+
*
|
|
503
|
+
* If some fields along the fieldPath are localized, the `fieldPath` must
|
|
504
|
+
* contain the locale of the field under the "locales" property. The locales
|
|
505
|
+
* along the field path don't have to be the same.
|
|
506
|
+
*
|
|
507
|
+
* @example
|
|
508
|
+
* fieldPath: ['fields', 'button', 'locales', 'en', 'fields', 'title', 'locales', 'es']
|
|
509
|
+
*
|
|
510
|
+
* If the provided `fieldPath` points to a list item, the returned model field
|
|
511
|
+
* and document field will belong to a list item. In this case, the model field
|
|
512
|
+
* will contain only field-specific properties and the document field will be
|
|
513
|
+
* localized.
|
|
514
|
+
*
|
|
515
|
+
* @example
|
|
516
|
+
* fieldPath: ['fields', 'buttons', 'items', 2]
|
|
517
|
+
*
|
|
518
|
+
* The `isFullFieldPath` flag specifies if the `fieldPath` includes container
|
|
519
|
+
* specifiers such as "fields" and "items".
|
|
520
|
+
*
|
|
521
|
+
* @example
|
|
522
|
+
* isFullFieldPath: false => fieldPath: ['sections', 1, 'title', 'es']
|
|
523
|
+
* isFullFieldPath: true => fieldPath: ['fields', 'sections', 'items', 1, 'fields', 'title', 'locales', 'es']
|
|
524
|
+
*/
|
|
525
|
+
export function getModelAndDocumentFieldForLocalizedFieldPath({
|
|
526
|
+
document,
|
|
527
|
+
fieldPath,
|
|
528
|
+
modelMap,
|
|
529
|
+
isFullFieldPath
|
|
530
|
+
}: {
|
|
531
|
+
document: ContentStoreTypes.Document;
|
|
532
|
+
fieldPath: (string | number)[];
|
|
533
|
+
modelMap: Record<string, Model>;
|
|
534
|
+
isFullFieldPath?: boolean;
|
|
535
|
+
}): { modelField: Field | FieldListItems; documentField: ContentStoreTypes.DocumentField | ContentStoreTypes.DocumentListFieldItems } {
|
|
536
|
+
if (isFullFieldPath) {
|
|
537
|
+
if (_.head(fieldPath) !== 'fields') {
|
|
538
|
+
throw new Error('fieldPath must start with "fields" specifier');
|
|
539
|
+
}
|
|
540
|
+
fieldPath = _.tail(fieldPath);
|
|
541
|
+
}
|
|
542
|
+
if (_.isEmpty(fieldPath)) {
|
|
543
|
+
throw new Error('the fieldPath cannot be empty');
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const docId = document.srcObjectId;
|
|
547
|
+
const origModelName = document.srcModelName;
|
|
548
|
+
const origFieldPath = fieldPath;
|
|
549
|
+
const origFieldPathStr = fieldPath.join('.');
|
|
550
|
+
const model = modelMap[origModelName];
|
|
551
|
+
if (!model) {
|
|
552
|
+
throw new Error(`model with name '${origModelName}' of a document '${docId}' not found`);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
let parentModel = model;
|
|
556
|
+
let fieldPathFromParentModel = fieldPath;
|
|
557
|
+
|
|
558
|
+
function getPrefixOf(fieldPath: (string | number)[], include = 0) {
|
|
559
|
+
return origFieldPath.slice(0, origFieldPath.length - fieldPath.length + include).join('.');
|
|
560
|
+
}
|
|
561
|
+
function getModelPrefixOf(fieldPath: (string | number)[], include = 0) {
|
|
562
|
+
return fieldPathFromParentModel.slice(0, fieldPathFromParentModel.length - fieldPath.length + include).join('.');
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function getField(
|
|
566
|
+
docField: ContentStoreTypes.DocumentField | ContentStoreTypes.DocumentListFieldItems,
|
|
567
|
+
modelField: Field | FieldListItems,
|
|
568
|
+
fieldPath: (string | number)[]
|
|
569
|
+
): { modelField: Field | FieldListItems; documentField: ContentStoreTypes.DocumentField | ContentStoreTypes.DocumentListFieldItems } {
|
|
570
|
+
// if no more items in fieldPath return the found document and model fields
|
|
571
|
+
if (fieldPath.length === 0) {
|
|
572
|
+
return {
|
|
573
|
+
modelField: modelField,
|
|
574
|
+
documentField: docField
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
if (docField.localized) {
|
|
579
|
+
if (isFullFieldPath) {
|
|
580
|
+
if (_.head(fieldPath) !== 'locales') {
|
|
581
|
+
throw new Error(`fieldPath '${origFieldPath.join('.')}' must contain "locales" specifier for localized field`);
|
|
582
|
+
}
|
|
583
|
+
fieldPath = _.tail(fieldPath);
|
|
584
|
+
}
|
|
585
|
+
const locale = _.head(fieldPath);
|
|
586
|
+
if (typeof locale !== 'string') {
|
|
587
|
+
throw new Error(`the locale specifier must be string in fieldPath '${origFieldPath.join('.')}'`);
|
|
588
|
+
}
|
|
589
|
+
fieldPath = _.tail(fieldPath);
|
|
590
|
+
const localizedDocField = getDocumentFieldForLocale(docField, locale);
|
|
591
|
+
if (!localizedDocField) {
|
|
592
|
+
throw new Error(`fieldPath '${origFieldPath.join('.')}' doesn't specify the locale`);
|
|
593
|
+
}
|
|
594
|
+
docField = localizedDocField;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
switch (docField.type) {
|
|
598
|
+
case 'object': {
|
|
599
|
+
if (docField.isUnset) {
|
|
600
|
+
throw new Error(
|
|
601
|
+
`the fieldPath '${getPrefixOf(fieldPath)}' of document ${docId} points ` +
|
|
602
|
+
`into the fields of an empty 'object' field, full fieldPath '${origFieldPathStr}'.`
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
if (isFullFieldPath) {
|
|
606
|
+
if (_.head(fieldPath) !== 'fields') {
|
|
607
|
+
throw new Error(`fieldPath '${getPrefixOf(fieldPath, 1)}' must contain "fields" specifier after an "object" field`);
|
|
608
|
+
}
|
|
609
|
+
fieldPath = _.tail(fieldPath);
|
|
610
|
+
}
|
|
611
|
+
const fieldName = _.head(fieldPath);
|
|
612
|
+
if (typeof fieldName === 'undefined') {
|
|
613
|
+
throw new Error(`fieldPath '${getPrefixOf(fieldPath, 1)}' must specify a field name for an "object" field`);
|
|
614
|
+
}
|
|
615
|
+
fieldPath = _.tail(fieldPath);
|
|
616
|
+
|
|
617
|
+
const childDocField = docField.fields[fieldName];
|
|
618
|
+
if (!childDocField) {
|
|
619
|
+
throw new Error(`document '${docId}' of type '${origModelName}' doesn't have a field at path: '${getPrefixOf(fieldPath)}'`);
|
|
620
|
+
}
|
|
621
|
+
if (modelField.type !== 'object') {
|
|
622
|
+
throw new Error(
|
|
623
|
+
`model field of type '${modelField.type}' of model '${parentModel.name}' at field path '${getModelPrefixOf(fieldPath)}' ` +
|
|
624
|
+
`doesn't match document field of type 'object' of document '${docId}' at field path '${getPrefixOf(fieldPath)}'`
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
const childModelField = _.find(modelField.fields, (field) => field.name === fieldName);
|
|
628
|
+
if (!childModelField) {
|
|
629
|
+
throw new Error(`model '${parentModel.name}' doesn't have a field at path: '${getModelPrefixOf(fieldPath)}'`);
|
|
630
|
+
}
|
|
631
|
+
return getField(childDocField, childModelField, fieldPath);
|
|
632
|
+
}
|
|
633
|
+
case 'model': {
|
|
634
|
+
if (docField.isUnset) {
|
|
635
|
+
throw new Error(
|
|
636
|
+
`the fieldPath '${getPrefixOf(fieldPath, 1)}' of document ${docId} points ` +
|
|
637
|
+
`into the fields of an empty "model" field, full fieldPath '${origFieldPathStr}'`
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
fieldPathFromParentModel = fieldPath;
|
|
641
|
+
if (isFullFieldPath) {
|
|
642
|
+
if (_.head(fieldPath) !== 'fields') {
|
|
643
|
+
throw new Error(`fieldPath '${getPrefixOf(fieldPath, 1)}' must contain "fields" specifier after a "model" field`);
|
|
644
|
+
}
|
|
645
|
+
fieldPath = _.tail(fieldPath);
|
|
646
|
+
}
|
|
647
|
+
const fieldName = _.head(fieldPath);
|
|
648
|
+
if (typeof fieldName === 'undefined') {
|
|
649
|
+
throw new Error(`fieldPath '${getPrefixOf(fieldPath, 1)}' must specify a field name for an "model" field`);
|
|
650
|
+
}
|
|
651
|
+
fieldPath = _.tail(fieldPath);
|
|
652
|
+
|
|
653
|
+
const modelName = docField.srcModelName;
|
|
654
|
+
const childModel = modelMap[modelName];
|
|
655
|
+
if (!childModel) {
|
|
656
|
+
throw new Error(
|
|
657
|
+
`a "model" field of a document '${docId}' at path '${getPrefixOf(fieldPath)}' ` +
|
|
658
|
+
`contains an object with non existing modelName '${modelName}'`
|
|
659
|
+
);
|
|
660
|
+
}
|
|
661
|
+
parentModel = childModel;
|
|
662
|
+
const childDocField = docField.fields[fieldName];
|
|
663
|
+
if (!childDocField) {
|
|
664
|
+
throw new Error(`document '${docId}' of type '${origModelName}' doesn't have a field at path: '${getPrefixOf(fieldPath)}'`);
|
|
665
|
+
}
|
|
666
|
+
const childModelField = _.find(childModel.fields, (field) => field.name === fieldName);
|
|
667
|
+
if (!childModelField) {
|
|
668
|
+
throw new Error(`model '${childModel.name}' doesn't have a field at path: '${getModelPrefixOf(fieldPath)}'`);
|
|
669
|
+
}
|
|
670
|
+
return getField(childDocField, childModelField, fieldPath);
|
|
671
|
+
}
|
|
672
|
+
case 'list': {
|
|
673
|
+
if (isFullFieldPath) {
|
|
674
|
+
if (_.head(fieldPath) !== 'items') {
|
|
675
|
+
throw new Error(`fieldPath '${getPrefixOf(fieldPath, 1)}' must contain "items" specifier after a "list" field`);
|
|
676
|
+
}
|
|
677
|
+
fieldPath = _.tail(fieldPath);
|
|
678
|
+
}
|
|
679
|
+
const itemIndex = _.head(fieldPath) as number;
|
|
680
|
+
if (typeof itemIndex === 'undefined') {
|
|
681
|
+
throw new Error(`fieldPath '${getPrefixOf(fieldPath, 1)}' must specify a list item index for a "list" field`);
|
|
682
|
+
}
|
|
683
|
+
fieldPath = _.tail(fieldPath);
|
|
684
|
+
|
|
685
|
+
const listItem = docField.items && docField.items[itemIndex as number];
|
|
686
|
+
if (!listItem) {
|
|
687
|
+
throw new Error(`document '${docId}' of type '${origModelName}' doesn't have a list item at path: '${getPrefixOf(fieldPath)}'`);
|
|
688
|
+
}
|
|
689
|
+
if (modelField.type !== 'list') {
|
|
690
|
+
throw new Error(
|
|
691
|
+
`model field type '${modelField.type}' of a model '${parentModel.name}' at field path '${getModelPrefixOf(fieldPath)}' ` +
|
|
692
|
+
`doesn't match document field of type 'list' of document '${docId}' at field path '${getPrefixOf(fieldPath)}'`
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
const listItemsModel = modelField.items;
|
|
696
|
+
if (!Array.isArray(listItemsModel)) {
|
|
697
|
+
return getField(listItem, listItemsModel, fieldPath);
|
|
698
|
+
} else {
|
|
699
|
+
const fieldListItems = (listItemsModel as FieldListItems[]).find((listItemsModel) => listItemsModel.type === listItem.type);
|
|
700
|
+
if (!fieldListItems) {
|
|
701
|
+
throw new Error(
|
|
702
|
+
`cannot find list item model for document list item of type '${listItem.type}' for model '${parentModel.name}' ` +
|
|
703
|
+
`at field path '${getModelPrefixOf(fieldPath, 1)}'`
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
return getField(listItem, fieldListItems, fieldPath);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
default:
|
|
710
|
+
if (!_.isEmpty(fieldPath)) {
|
|
711
|
+
throw new Error(
|
|
712
|
+
`illegal fieldPath, a primitive field of type '${docField.type}' was found in the middle of the fieldPath '${origFieldPathStr}'`
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
return {
|
|
716
|
+
modelField,
|
|
717
|
+
documentField: docField
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
const fieldName = _.head(fieldPath);
|
|
723
|
+
const childFieldPath = _.tail(fieldPath);
|
|
724
|
+
|
|
725
|
+
if (typeof fieldName !== 'string') {
|
|
726
|
+
throw new Error('the first fieldPath item must be string');
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
const childDocField: ContentStoreTypes.DocumentField | undefined = document.fields[fieldName];
|
|
730
|
+
const childModelField: Field | undefined = _.find(model.fields, { name: fieldName });
|
|
731
|
+
|
|
732
|
+
if (!childDocField) {
|
|
733
|
+
throw new Error(`document '${docId}' of type '${model.name}' doesn't have a field named '${fieldName}'`);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
if (!childModelField) {
|
|
737
|
+
throw new Error(`model '${model.name}' doesn't have a field named '${fieldName}'`);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
return getField(childDocField, childModelField, childFieldPath);
|
|
741
|
+
}
|