@stackbit/sdk
Version:
447 lines • 18.9 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getModelOfObject = exports.asyncMapObjectFieldsWithModelRecursively = exports.mapObjectFieldsWithModelRecursively = exports.iterateObjectFieldsWithModelRecursively = exports.mapModelFieldsRecursively = exports.iterateModelFieldsRecursively = void 0;
const lodash_1 = __importDefault(require("lodash"));
const utils_1 = require("@stackbit/utils");
const model_utils_1 = require("./model-utils");
/**
* This function invokes the `iteratee` function for every field of the `model`.
* It recursively traverses through fields of type `object` and `list` with
* items of type `object` and invokes the `iteratee` on their child fields,
* and so on. The traversal is a depth-first and the `iteratee` is invoked
* before traversing the field's child fields.
*
* The iteratee is invoked with two parameters, `field` and `fieldPath`. The
* `field` is the currently iterated field, and `fieldPath` is an array of
* strings indicating the path of the `field` relative to the model.
*
* @example
* model = {
* fields: [
* { name: "title", type: "string" },
* { name: "tags", type: "list" },
* { name: "banner", type: "object", fields: [{ name: "logo", type: "image" }] }
* {
* name: "actions",
* type: "list",
* items: { type: "object", fields: [{ name: "label", type: "string" }] }
* }
* ]
* }
* iterateModelFieldsRecursively(model, iteratee);
* // will call the iteratee 6 times with the following "field" and "fieldPath" arguments
* field = { name: "title", ... }, fieldPath = ['fields', 'title']
* field = { name: "tags", ... }, fieldPath = ['fields', 'tags']
* field = { name: "banner", ... }, fieldPath = ['fields', 'banner']
* field = { name: "logo", ... }, fieldPath = ['fields', 'banner', 'fields', 'logo']
* field = { name: "actions", ... }, fieldPath = ['fields', 'actions']
* field = { name: "label", ... }, fieldPath = ['fields', 'actions', 'items', 'fields', 'label']
*
* @param model The model to iterate fields
* @param iteratee The callback function
*/
function iterateModelFieldsRecursively(model, iteratee) {
function _iterateDeep({ fields, modelKeyPath }) {
modelKeyPath = modelKeyPath.concat('fields');
lodash_1.default.forEach(fields, (field) => {
if (!field) {
return;
}
const childModelKeyPath = modelKeyPath.concat(field.name);
iteratee(field, childModelKeyPath);
if ((0, model_utils_1.isObjectField)(field)) {
_iterateDeep({
fields: field.fields,
modelKeyPath: childModelKeyPath
});
}
else if ((0, model_utils_1.isListField)(field) && field.items && (0, model_utils_1.isObjectListItems)(field.items)) {
_iterateDeep({
fields: field.items?.fields,
modelKeyPath: childModelKeyPath.concat('items')
});
}
});
}
if (model && (0, model_utils_1.isListDataModel)(model) && model.items && (0, model_utils_1.isObjectListItems)(model.items)) {
_iterateDeep({
fields: model.items?.fields,
modelKeyPath: ['items']
});
}
else {
_iterateDeep({
fields: model?.fields || [],
modelKeyPath: []
});
}
}
exports.iterateModelFieldsRecursively = iterateModelFieldsRecursively;
function mapModelFieldsRecursively(model, iteratee) {
function _mapField(field, modelKeyPath) {
if (!field) {
return field;
}
modelKeyPath = modelKeyPath.concat(field.name);
field = iteratee(field, modelKeyPath);
if ((0, model_utils_1.isObjectField)(field)) {
return _mapObjectField(field, modelKeyPath);
}
else if ((0, model_utils_1.isListField)(field)) {
return _mapListField(field, modelKeyPath);
}
else {
return field;
}
}
function _mapObjectField(field, modelKeyPath) {
const fields = field.fields;
if (!fields) {
return field;
}
modelKeyPath = modelKeyPath.concat('fields');
return {
...field,
fields: lodash_1.default.map(fields, (field) => _mapField(field, modelKeyPath))
};
}
function _mapListField(field, modelKeyPath) {
const items = field.items;
if (!items || !(0, model_utils_1.isObjectListItems)(items)) {
return field;
}
return {
...field,
items: _mapObjectField(items, modelKeyPath.concat('items'))
};
}
if (!model) {
return model;
}
else if ((0, model_utils_1.isListDataModel)(model)) {
return _mapListField(model, []);
}
else {
return _mapObjectField(model, []);
}
}
exports.mapModelFieldsRecursively = mapModelFieldsRecursively;
function iterateObjectFieldsWithModelRecursively(value, model, modelsByName, iteratee, { pageLayoutKey = 'layout', objectTypeKey = 'type', valueId } = {}) {
function _iterateDeep({ value, model, field, fieldListItem, valueKeyPath, modelKeyPath, objectStack }) {
let error = null;
let modelField = null;
if (!model && !field && !fieldListItem) {
error = `could not match model/field ${modelKeyPath.join('.')} for content at ${valueKeyPath.join('.')}`;
}
if (field && (0, model_utils_1.isModelField)(field)) {
modelField = field;
}
else if (fieldListItem && (0, model_utils_1.isModelListItems)(fieldListItem)) {
modelField = fieldListItem;
}
if (modelField) {
const modelResult = getModelOfObject({
object: value,
field: modelField,
modelsByName,
pageLayoutKey,
objectTypeKey,
valueKeyPath,
modelKeyPath
});
if ('error' in modelResult) {
error = modelResult.error;
}
else {
model = modelResult.model;
}
field = null;
fieldListItem = null;
modelKeyPath = model ? [model.name] : [];
}
iteratee({ value, model, field, fieldListItem, error, valueKeyPath, modelKeyPath, objectStack });
if (lodash_1.default.isPlainObject(value)) {
// if fields will not be resolved or the object will have a key that
// doesn't exist among fields, the nested calls to _iterateDeep will
// include an error.
const fields = getFieldsOfModelOrField(model, field, fieldListItem);
const fieldsByName = lodash_1.default.keyBy(fields, 'name');
modelKeyPath = lodash_1.default.concat(modelKeyPath, 'fields');
lodash_1.default.forEach(value, (val, key) => {
const field = lodash_1.default.get(fieldsByName, key, null);
_iterateDeep({
value: val,
model: null,
field: field,
fieldListItem: null,
valueKeyPath: lodash_1.default.concat(valueKeyPath, key),
modelKeyPath: lodash_1.default.concat(modelKeyPath, key),
objectStack: lodash_1.default.concat(objectStack, value)
});
});
}
else if (lodash_1.default.isArray(value)) {
let fieldListItems = null;
if (field && (0, model_utils_1.isListField)(field)) {
fieldListItems = (0, model_utils_1.getListFieldItems)(field);
}
else if (model && (0, model_utils_1.isListDataModel)(model)) {
fieldListItems = model.items;
}
lodash_1.default.forEach(value, (val, idx) => {
_iterateDeep({
value: val,
model: null,
field: null,
fieldListItem: fieldListItems,
valueKeyPath: lodash_1.default.concat(valueKeyPath, idx),
modelKeyPath: lodash_1.default.concat(modelKeyPath, 'items'),
objectStack: lodash_1.default.concat(objectStack, value)
});
});
}
}
_iterateDeep({
value: value,
model: model,
field: null,
fieldListItem: null,
valueKeyPath: valueId ? [valueId] : [],
modelKeyPath: [model.name],
objectStack: []
});
}
exports.iterateObjectFieldsWithModelRecursively = iterateObjectFieldsWithModelRecursively;
function mapObjectFieldsWithModelRecursively(value, model, modelsByName, iteratee, { pageLayoutKey = 'layout', objectTypeKey = 'type', valueId } = {}) {
function _mapDeep({ value, model, field, fieldListItem, valueKeyPath, modelKeyPath, objectStack }) {
let error = null;
let modelField = null;
if (!model && !field && !fieldListItem) {
error = `could not match model/field ${modelKeyPath.join('.')} to content at ${valueKeyPath.join('.')}`;
}
if (field && (0, model_utils_1.isModelField)(field)) {
modelField = field;
}
else if (fieldListItem && (0, model_utils_1.isModelListItems)(fieldListItem)) {
modelField = fieldListItem;
}
if (modelField) {
const modelResult = getModelOfObject({
object: value,
field: modelField,
modelsByName,
pageLayoutKey,
objectTypeKey,
valueKeyPath,
modelKeyPath
});
if ('error' in modelResult) {
error = modelResult.error;
}
else {
model = modelResult.model;
}
field = null;
fieldListItem = null;
modelKeyPath = model ? [model.name] : [];
}
const res = iteratee({ value, model, field, fieldListItem, error, valueKeyPath, modelKeyPath, objectStack });
if (!lodash_1.default.isUndefined(res)) {
value = res;
}
if (lodash_1.default.isPlainObject(value)) {
// if fields will not be resolved or the object will have a key that
// doesn't exist among fields, the nested calls to _iterateDeep will
// include an error.
const fields = getFieldsOfModelOrField(model, field, fieldListItem);
const fieldsByName = lodash_1.default.keyBy(fields, 'name');
modelKeyPath = lodash_1.default.concat(modelKeyPath, 'fields');
value = lodash_1.default.mapValues(value, (val, key) => {
const field = lodash_1.default.get(fieldsByName, key, null);
return _mapDeep({
value: val,
model: null,
field: field,
fieldListItem: null,
valueKeyPath: lodash_1.default.concat(valueKeyPath, key),
modelKeyPath: lodash_1.default.concat(modelKeyPath, key),
objectStack: lodash_1.default.concat(objectStack, value)
});
});
}
else if (lodash_1.default.isArray(value)) {
let fieldListItems = null;
if (field && (0, model_utils_1.isListField)(field)) {
fieldListItems = (0, model_utils_1.getListFieldItems)(field);
}
else if (model && (0, model_utils_1.isListDataModel)(model)) {
fieldListItems = model.items;
}
value = lodash_1.default.map(value, (val, idx) => {
return _mapDeep({
value: val,
model: null,
field: null,
fieldListItem: fieldListItems,
valueKeyPath: lodash_1.default.concat(valueKeyPath, idx),
modelKeyPath: lodash_1.default.concat(modelKeyPath, 'items'),
objectStack: lodash_1.default.concat(objectStack, value)
});
});
}
return value;
}
return _mapDeep({
value: value,
model: model,
field: null,
fieldListItem: null,
valueKeyPath: valueId ? [valueId] : [],
modelKeyPath: [model.name],
objectStack: []
});
}
exports.mapObjectFieldsWithModelRecursively = mapObjectFieldsWithModelRecursively;
async function asyncMapObjectFieldsWithModelRecursively({ value, model, modelsByName, iteratee, pageLayoutKey = 'layout', objectTypeKey = 'type', valueId }) {
async function _mapDeep({ value, model, field, fieldListItem, valueKeyPath, modelKeyPath, objectStack }) {
let error = null;
let modelField = null;
if (!model && !field && !fieldListItem) {
error = `could not match model/field ${modelKeyPath.join('.')} to content at ${valueKeyPath.join('.')}`;
}
if (field && (0, model_utils_1.isModelField)(field)) {
modelField = field;
}
else if (fieldListItem && (0, model_utils_1.isModelListItems)(fieldListItem)) {
modelField = fieldListItem;
}
if (modelField) {
const modelResult = getModelOfObject({
object: value,
field: modelField,
modelsByName,
pageLayoutKey,
objectTypeKey,
valueKeyPath,
modelKeyPath
});
if ('error' in modelResult) {
error = modelResult.error;
}
else {
model = modelResult.model;
}
field = null;
fieldListItem = null;
modelKeyPath = model ? [model.name] : [];
}
let shouldSkipNested = false;
const skipNested = () => {
shouldSkipNested = true;
};
const res = await iteratee({ value, model, field, fieldListItem, error, valueKeyPath, modelKeyPath, objectStack, skipNested });
if (!lodash_1.default.isUndefined(res)) {
value = res;
}
if (shouldSkipNested) {
return value;
}
if (lodash_1.default.isPlainObject(value)) {
// if fields will not be resolved or the object will have a key that
// doesn't exist among fields, the nested calls to _iterateDeep will
// include an error.
const fields = getFieldsOfModelOrField(model, field, fieldListItem);
const fieldsByName = lodash_1.default.keyBy(fields, 'name');
modelKeyPath = lodash_1.default.concat(modelKeyPath, 'fields');
value = await (0, utils_1.mapValuesPromise)(value, (val, key) => {
const field = lodash_1.default.get(fieldsByName, key, null);
return _mapDeep({
value: val,
model: null,
field: field,
fieldListItem: null,
valueKeyPath: lodash_1.default.concat(valueKeyPath, key),
modelKeyPath: lodash_1.default.concat(modelKeyPath, key),
objectStack: lodash_1.default.concat(objectStack, value)
});
});
}
else if (lodash_1.default.isArray(value)) {
let fieldListItems = null;
if (field && (0, model_utils_1.isListField)(field)) {
fieldListItems = (0, model_utils_1.getListFieldItems)(field);
}
else if (model && (0, model_utils_1.isListDataModel)(model)) {
fieldListItems = model.items;
}
value = await (0, utils_1.mapPromise)(value, (val, idx) => {
return _mapDeep({
value: val,
model: null,
field: null,
fieldListItem: fieldListItems,
valueKeyPath: lodash_1.default.concat(valueKeyPath, idx),
modelKeyPath: lodash_1.default.concat(modelKeyPath, 'items'),
objectStack: lodash_1.default.concat(objectStack, value)
});
});
}
return value;
}
return _mapDeep({
value: value,
model: model,
field: null,
fieldListItem: null,
valueKeyPath: valueId ? [valueId] : [],
modelKeyPath: [model.name],
objectStack: []
});
}
exports.asyncMapObjectFieldsWithModelRecursively = asyncMapObjectFieldsWithModelRecursively;
function getModelOfObject({ object, field, modelsByName, pageLayoutKey, objectTypeKey, valueKeyPath, modelKeyPath }) {
const modelNames = field.models ?? [];
let modelName;
if (modelNames.length === 0) {
return { error: `invalid field, no 'models' property specified at '${modelKeyPath.join('.')}'` };
}
if (modelNames.length === 1) {
modelName = modelNames[0];
if (!lodash_1.default.has(modelsByName, modelName)) {
return { error: `invalid field, model name '${modelName}' specified at '${modelKeyPath.join('.')}' doesn't match any model` };
}
}
else {
// we don't know if the object at hand belongs to a model of type "page" or type "data"
// so we try to get the model using both pageLayoutKey and objectTypeKey keys
if (!lodash_1.default.has(object, pageLayoutKey) && !lodash_1.default.has(object, objectTypeKey)) {
return { error: `cannot identify the model of an object, no '${pageLayoutKey}' or '${objectTypeKey}' field exist at ${valueKeyPath.join('.')}` };
}
modelName = object[pageLayoutKey] || object[objectTypeKey];
if (!lodash_1.default.has(modelsByName, modelName)) {
const typeKey = object[pageLayoutKey] ? pageLayoutKey : objectTypeKey;
return { error: `invalid content, '${typeKey}=${modelName}' specified at ${valueKeyPath.join('.')} doesn't match any model` };
}
}
return {
modelName,
model: modelsByName[modelName]
};
}
exports.getModelOfObject = getModelOfObject;
function getFieldsOfModelOrField(model, field, fieldListItems) {
if (model && model.fields) {
return model.fields;
}
else if (field && (0, model_utils_1.isObjectField)(field)) {
return field.fields;
}
else if (fieldListItems && (0, model_utils_1.isObjectListItems)(fieldListItems)) {
return fieldListItems.fields;
}
return [];
}
//# sourceMappingURL=model-iterators.js.map