@prismicio/types-internal
Version:
Prismic types for Custom Types and Prismic Data
267 lines (266 loc) • 11.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.migrateDocument = exports.collectWidgets = exports.traverseDocument = exports.fillDocumentWithDefaultValues = exports.DocumentLegacy = exports.Document = void 0;
const tslib_1 = require("tslib");
const fp_ts_1 = require("fp-ts");
const Either_1 = require("fp-ts/lib/Either");
const function_1 = require("fp-ts/lib/function");
const t = (0, tslib_1.__importStar)(require("io-ts"));
const common_1 = require("../common");
const UUID_1 = require("../common/UUID");
const customtypes_1 = require("../customtypes");
const fields_1 = require("./fields");
const LegacyContentCtx_1 = require("./LegacyContentCtx");
const utils_1 = require("./utils");
exports.Document = t.record(common_1.WidgetKey, fields_1.WidgetContent);
const legacyDocReader = t.record(common_1.WidgetKey, t.unknown);
/**
* `DocumentLegacyCodec` handles decoding and encoding documents to the legacy
* format used by Prismic DB, therefore, this function itself is not "legacy".
*/
const DocumentLegacyCodec = (allTypes, allKeys) => {
return new t.Type("Document", (u) => !!u && typeof u === "object", (doc) => {
return (0, function_1.pipe)(legacyDocReader.decode(doc), fp_ts_1.either.map((parsedDoc) => {
return Object.entries(parsedDoc).reduce((acc, [widgetKey, widgetValue]) => {
const widgetCtx = (0, LegacyContentCtx_1.defaultCtx)(widgetKey, allTypes, allKeys);
const parsedW = (0, fields_1.WidgetLegacy)(widgetCtx).decode(widgetValue);
if (!parsedW || (0, Either_1.isLeft)(parsedW))
return acc;
return { ...acc, [widgetKey]: parsedW.right };
}, {});
}));
}, (g) => {
return Object.entries(g).reduce((acc, [key, value]) => {
const widgetCtx = (0, LegacyContentCtx_1.defaultCtx)(key, allTypes);
const result = (0, fields_1.WidgetLegacy)(widgetCtx).encode(value);
if (!result)
return acc;
return {
content: { ...acc.content, [key]: result.content },
types: { ...acc.types, ...result.types },
keys: { ...acc.keys, ...result.keys },
};
}, { content: {}, types: {}, keys: {} });
});
};
function extractMetadata(data) {
const fields = Object.entries(data);
const { types, widgets, keys } = fields.reduce((acc, [k, v]) => {
if (k.endsWith("_TYPE")) {
const decodedValue = LegacyContentCtx_1.FieldOrSliceType.decode(v);
if ((0, Either_1.isRight)(decodedValue)) {
return {
...acc,
types: acc.types.set(k.substring(0, k.length - 5), decodedValue.right),
};
}
}
if (k.endsWith("_KEY")) {
const decodedValue = UUID_1.UUID.decode(v);
if ((0, Either_1.isRight)(decodedValue)) {
return {
...acc,
keys: acc.keys.set(k.substring(0, k.length - 4), decodedValue.right),
};
}
}
if (!k.endsWith("_POSITION") &&
!k.endsWith("_TYPE") &&
!k.endsWith("_KEY")) {
return {
...acc,
widgets: {
...acc.widgets,
[k]: v,
},
};
}
return acc;
}, {
types: new Map(),
widgets: {},
keys: new Map(),
});
const slugs = data["slugs_INTERNAL"] || [];
const uid = data["uid"];
return {
widgets,
types,
keys,
uid,
slugs,
};
}
function parseLegacyDocument(legacyDoc, customType) {
const result = (0, function_1.pipe)(
// ensure it's the right document format first
t.record(common_1.WidgetKey, t.unknown).decode(legacyDoc), fp_ts_1.either.chain((doc) => {
// extract all metadata, meaning all _TYPES keys from legacy format + the widgets as unknown
const { types, widgets, keys } = extractMetadata(doc);
// parse the actual widgets
return DocumentLegacyCodec(types, keys).decode(widgets);
}));
return (0, Either_1.isLeft)(result) ? undefined : migrateDocument(result.right, customType);
}
function encodeToLegacyDocument(document) {
const encoded = DocumentLegacyCodec().encode(document);
return { ...encoded.content, ...encoded.types, ...encoded.keys };
}
exports.DocumentLegacy = {
_codec: DocumentLegacyCodec,
extractMetadata,
parse: parseLegacyDocument,
encode: encodeToLegacyDocument,
};
function simplifyCustomType(customType) {
return {
customTypeId: customType === null || customType === void 0 ? void 0 : customType.id,
fields: Object.fromEntries((0, customtypes_1.flattenSections)(customType)),
};
}
function fillDocumentWithDefaultValues(customType, document) {
const { fields } = customType && customtypes_1.StaticCustomType.is(customType)
? simplifyCustomType(customType)
: customType;
return Object.entries(fields).reduce((updatedDocument, [fieldKey, fieldDef]) => {
const fieldContent = document[fieldKey];
const updatedField = (() => {
switch (fieldDef.type) {
case "Group":
return (0, fields_1.isGroupContent)(fieldContent)
? (0, fields_1.groupContentWithDefaultValues)(fieldDef, fieldContent)
: fieldContent;
case "Choice":
case "Slices":
return (0, fields_1.isSlicesContent)(fieldContent)
? (0, fields_1.slicesContentWithDefaultValues)(fieldDef, fieldContent)
: fieldContent;
default:
return fieldContent === undefined && customtypes_1.NestableWidget.is(fieldDef)
? (0, fields_1.NestableContentDefaultValue)(fieldDef)
: fieldContent;
}
})();
return updatedField
? {
...updatedDocument,
[fieldKey]: updatedField,
}
: updatedDocument;
}, document);
}
exports.fillDocumentWithDefaultValues = fillDocumentWithDefaultValues;
/**
* @param model: Can be optional if we simply want to loop through the content
* without any consideration for the attached model
* @param document: The content we actually want to iterate on in an immutable fashion
* @param transform: A user function that provides a way to transform any kind
* of content wherever it is in a structured Prismic object content.
* @returns A transformed document with the user's transformation applied with
* the transform function
*/
function traverseDocument({ document, customType, }) {
const model = customType && customtypes_1.StaticCustomType.is(customType)
? simplifyCustomType(customType)
: customType;
return ({ transformWidget = ({ content }) => content, transformSlice = ({ content }) => content, }) => {
const fieldModels = model &&
Object.entries(model.fields).reduce((acc, [key, def]) => ({ ...acc, [key]: def }), {});
return Object.entries(document).reduce((acc, [key, content]) => {
const fieldModel = fieldModels && fieldModels[key];
const path = utils_1.ContentPath.make([
{ key: model === null || model === void 0 ? void 0 : model.customTypeId, type: "CustomType" },
{ key, type: "Widget" },
]);
const transformedWidget = (() => {
switch (content.__TYPE__) {
case "SliceContentType":
return (0, fields_1.traverseSlices)({
path,
key,
model: (fieldModel === null || fieldModel === void 0 ? void 0 : fieldModel.type) === "Slices" || (fieldModel === null || fieldModel === void 0 ? void 0 : fieldModel.type) === "Choice"
? fieldModel
: undefined,
content,
})({ transformWidget, transformSlice });
case "GroupContentType":
return (0, fields_1.traverseGroupContent)({
path,
key,
apiId: key,
model: (fieldModel === null || fieldModel === void 0 ? void 0 : fieldModel.type) === "Group" ? fieldModel : undefined,
content,
})(transformWidget);
case "RepeatableContent":
return (0, fields_1.traverseRepeatableContent)({
path,
key,
apiId: key,
model: (fieldModel === null || fieldModel === void 0 ? void 0 : fieldModel.type) === "Link" ? fieldModel : undefined,
content,
})(transformWidget);
case "TableContent":
return (0, fields_1.traverseTableContent)({
path,
key,
apiId: key,
model: (fieldModel === null || fieldModel === void 0 ? void 0 : fieldModel.type) === "Table" ? fieldModel : undefined,
content,
})(transformWidget);
default:
return transformWidget({
path,
key,
apiId: key,
model: (fieldModel === null || fieldModel === void 0 ? void 0 : fieldModel.type) !== "Group" &&
(fieldModel === null || fieldModel === void 0 ? void 0 : fieldModel.type) !== "Slices" &&
(fieldModel === null || fieldModel === void 0 ? void 0 : fieldModel.type) !== "Choice"
? fieldModel
: undefined,
content,
});
}
})();
return {
...acc,
...(transformedWidget ? { [key]: transformedWidget } : {}),
};
}, {});
};
}
exports.traverseDocument = traverseDocument;
// /**
// * The goal is to be able to collect all widgets or slices of a given type at any level of nesting inside a prismic content
// *
// * @param document parsed prismic content
// * @param is typeguard to match specifically the type of widget we want to collect
// * @returns a record containing the path of the widget as key and the typed collected content as value
// */
function collectWidgets(document, is) {
const collected = {};
traverseDocument({ document })({
transformWidget: ({ content, path }) => {
const key = utils_1.ContentPath.serialize(path);
if (is(content, path))
collected[key] = content;
return content;
},
});
return collected;
}
exports.collectWidgets = collectWidgets;
function migrateDocument(document, customType) {
const model = customtypes_1.StaticCustomType.is(customType)
? simplifyCustomType(customType)
: customType;
const needsMigration = Object.values((0, customtypes_1.collectSharedSlices)(model)).some((slice) => Boolean(slice.legacyPaths));
if (!needsMigration)
return document;
return traverseDocument({
document,
customType,
})({
transformSlice: fields_1.migrateSliceItem,
});
}
exports.migrateDocument = migrateDocument;