@dossierhq/core
Version:
The core Dossier library used by clients and server alike, used to interact with schema and entities directly, as well as remotely through a client.
177 lines • 7.94 kB
JavaScript
/// <reference types="./ContentTransformer.d.ts" />
import { notOk, ok } from '../ErrorResult.js';
import { contentValuePathToString } from './ContentPath.js';
import { isComponentItemField, isRichTextComponentNode, isRichTextItemField, isStringItemField, } from './ContentTypeUtils.js';
import { checkFieldItemTraversable, checkFieldTraversable } from './ContentUtils.js';
import { transformRichText } from './RichTextTransformer.js';
export const IDENTITY_TRANSFORMER = {
transformField(_schema, _path, _fieldSpec, value) {
return ok(value);
},
transformFieldItem(_schema, _path, _fieldSpec, value) {
return ok(value);
},
transformRichTextNode(_schema, _path, _fieldSpec, node) {
return ok(node);
},
};
export function transformEntityFields(schema, path, entity, transformer, options) {
const typeSpec = schema.getEntityTypeSpecification(entity.info.type);
if (!typeSpec) {
return notOk.BadRequest(`${contentValuePathToString(path)}: Couldn’t find spec for entity type ${entity.info.type}`);
}
const transformResult = transformContentFields(schema, path, typeSpec, 'entity', entity.fields, transformer, options);
if (transformResult.isError())
return transformResult;
if (transformResult.value === entity.fields)
return ok(entity.fields);
return ok(transformResult.value);
}
export function transformComponent(schema, path, component, transformer, options) {
if (!component.type) {
return notOk.BadRequest(`${contentValuePathToString([...path, 'type'])}: Missing a component type`);
}
const typeSpec = schema.getComponentTypeSpecification(component.type);
if (!typeSpec) {
return notOk.BadRequest(`${contentValuePathToString(path)}: Couldn’t find spec for component type ${component.type}`);
}
const transformResult = transformContentFields(schema, path, typeSpec, 'component', component, transformer, options);
if (transformResult.isError())
return transformResult;
if (transformResult.value === component)
return ok(component);
return ok({ ...transformResult.value, type: component.type });
}
function transformContentFields(schema, path, typeSpec, kind, fields, transformer, options) {
const extraFieldNames = new Set(Object.keys(fields));
if (kind === 'component') {
extraFieldNames.delete('type');
}
const excludeOmittedEntityFields = !!(options &&
'excludeOmittedEntityFields' in options &&
options.excludeOmittedEntityFields);
const keepExtraFields = !!options?.keepExtraFields;
let changedFields = false;
const newFields = {};
for (const fieldSpec of typeSpec.fields) {
const fieldPath = [...path, fieldSpec.name];
const originalValue = fields[fieldSpec.name];
extraFieldNames.delete(fieldSpec.name);
if (kind === 'entity' && excludeOmittedEntityFields && !(fieldSpec.name in fields)) {
continue;
}
const transformResult = transformContentField(schema, fieldPath, fieldSpec, originalValue, transformer, options);
if (transformResult.isError())
return transformResult;
let transformedValue = transformResult.value;
if (transformedValue === undefined) {
transformedValue = null;
}
if (transformedValue !== originalValue) {
changedFields = true;
}
newFields[fieldSpec.name] = transformedValue;
}
if (extraFieldNames.size > 0) {
if (keepExtraFields) {
if (changedFields) {
// Copy extra fields as is when we want to keep them
for (const fieldName of extraFieldNames) {
newFields[fieldName] = fields[fieldName];
}
}
}
else {
// So that we only return the fields that we know about
changedFields = true;
}
}
if (!changedFields) {
return ok(fields);
}
return ok(newFields);
}
export function transformContentField(schema, path, fieldSpec, originalValue, transformer, options) {
const traversableError = checkFieldTraversable(fieldSpec, originalValue);
if (traversableError) {
return notOk.BadRequest(`${contentValuePathToString([...path, ...traversableError.path])}: ${traversableError.message}`);
}
const transformFieldResult = transformer.transformField(schema, path, fieldSpec, originalValue);
if (transformFieldResult.isError())
return transformFieldResult;
let value = transformFieldResult.value;
if (value === null || value === undefined) {
return ok(null);
}
if (fieldSpec.list) {
let changedItems = false;
const newItems = [];
for (let i = 0; i < value.length; i += 1) {
const fieldItemPath = [...path, i];
const fieldItem = value[i];
const transformFieldValueResult = transformContentFieldValue(schema, fieldItemPath, fieldSpec, fieldItem, transformer, options);
if (transformFieldValueResult.isError())
return transformFieldValueResult;
const newFieldItem = transformFieldValueResult.value;
if (newFieldItem !== fieldItem) {
changedItems = true;
}
if (newFieldItem !== null && newFieldItem !== undefined) {
newItems.push(newFieldItem);
}
}
if (changedItems) {
value = newItems;
}
if (Array.isArray(value) && value.length === 0) {
value = null;
}
}
else {
const transformFieldValueResult = transformContentFieldValue(schema, path, fieldSpec, value, transformer, options);
if (transformFieldValueResult.isError())
return transformFieldValueResult;
value = transformFieldValueResult.value;
}
return ok(value);
}
function transformContentFieldValue(schema, path, fieldSpec, originalValue, transformer, options) {
const traversableError = checkFieldItemTraversable(fieldSpec, originalValue);
if (traversableError) {
return notOk.BadRequest(`${contentValuePathToString([...path, ...traversableError.path])}: ${traversableError.message}`);
}
const transformFieldItemResult = transformer.transformFieldItem(schema, path, fieldSpec, originalValue);
if (transformFieldItemResult.isError())
return transformFieldItemResult;
const value = transformFieldItemResult.value;
if (isComponentItemField(fieldSpec, value) && value) {
return transformComponent(schema, path, value, transformer, options);
}
else if (isRichTextItemField(fieldSpec, value) && value) {
return transformRichText(path, value, (path, node) => {
const nodeResult = transformer.transformRichTextNode(schema, path, fieldSpec, node);
if (nodeResult.isError())
return nodeResult;
const transformedNode = nodeResult.value;
if (transformedNode && isRichTextComponentNode(transformedNode)) {
const component = transformedNode.data;
const componentResult = transformComponent(schema, path, component, transformer, options);
if (componentResult.isError())
return componentResult;
const transformedComponent = componentResult.value;
if (transformedComponent !== component) {
return ok(transformedComponent ? { ...transformedNode, data: transformedComponent } : null);
}
}
return ok(transformedNode);
});
}
else if (isStringItemField(fieldSpec, value)) {
//TODO support trimming of strings?
if (!value) {
return ok(null); // Empty string => null
}
}
return ok(value);
}
//# sourceMappingURL=ContentTransformer.js.map