UNPKG

@stackbit/sdk

Version:
447 lines 18.9 kB
"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