UNPKG

@prismicio/types-internal

Version:
334 lines (307 loc) 8.75 kB
import { array, either } from "fp-ts" import { isLeft } from "fp-ts/lib/Either" import { pipe } from "fp-ts/lib/function" import * as t from "io-ts" import type { ContentPath, TraverseWidgetContentFn, } from "../../_internal/utils" import { type Group, type NestableWidget, GroupFieldType, } from "../../customtypes" import { FieldOrSliceType, getFieldCtx, LegacyContentCtx, WithTypes, } from "../LegacyContentCtx" import { hasContentType } from "../utils" import { isNestableContent, isRepeatableContent, isTableContent, NestableContent, NestableLegacy, traverseRepeatableContent, traverseTableContent, } from "./nestable" import { repeatableContentWithDefaultNestableContentValues } from "./withDefaultValues" export const GroupItemContentType = "GroupItemContent" as const export const GroupContentType = "GroupContentType" as const export const GroupItemContent: t.Type<GroupItemContent> = t.recursion( "GroupItemContent", () => t.strict({ __TYPE__: t.literal(GroupItemContentType), key: t.string, value: t.array( t.tuple([t.string, t.union([NestableContent, GroupContent])]), ), }), ) export type GroupItemContent = { __TYPE__: typeof GroupItemContentType key: string value: [string, NestableContent | GroupContent][] } export const GroupContent: t.Type<GroupContent> = t.recursion( "GroupContent", () => t.strict({ __TYPE__: t.literal(GroupContentType), value: t.array(GroupItemContent), }), ) export type GroupContent = { __TYPE__: typeof GroupContentType value: GroupItemContent[] } export const isGroupContent = (u: unknown): u is GroupContent => hasContentType(u) && u.__TYPE__ === GroupContentType export const GroupContentDefaultValue: GroupContent = { __TYPE__: GroupContentType, value: [], } const itemLegacyReader = t.record(t.string, t.unknown) export type GroupItemLegacy = t.TypeOf<typeof itemLegacyReader> export const GroupItemLegacy = (ctx: LegacyContentCtx, index: number) => { return new t.Type<GroupItemContent, WithTypes<GroupItemLegacy>>( "GroupItemLegacy", (u): u is GroupItemContent => hasContentType(u) && u.__TYPE__ === GroupItemContentType, (u) => { const parsed = pipe( itemLegacyReader.decode(u), either.map((items) => { const groupItemCtx = ctx.withContentKey(`${index}`) const parsedItems = Object.entries(items).reduce< Array<[string, NestableContent | GroupContent]> >((acc, [itemKey, itemValue]) => { const itemCtx = getFieldCtx({ fieldKey: itemKey, contentKey: itemKey, ctx: groupItemCtx, }) const result = itemCtx.fieldType === GroupFieldType ? GroupLegacy(itemCtx).decode(itemValue) : NestableLegacy(itemCtx).decode(itemValue) if (!result) return acc if (isLeft(result)) return acc return [...acc, [itemKey, result.right]] }, []) return { value: parsedItems, __TYPE__: GroupItemContentType, key: groupItemCtx.fieldContentKey, } }), ) return parsed }, (item) => { const groupItemCtx = ctx.withContentKey(`${index}`) return item.value.reduce<WithTypes<GroupItemLegacy>>( (acc, [key, value]) => { const itemCtx = getFieldCtx({ fieldKey: key, ctx }) const encoded = isGroupContent(value) ? GroupLegacy(itemCtx).encode(value) : NestableLegacy(itemCtx).encode(value) if (!encoded) return acc return { content: { ...acc.content, [key]: encoded.content }, types: { ...acc.types, ...encoded.types }, keys: { ...acc.keys, ...encoded.keys }, } }, { content: {}, types: {}, keys: { [groupItemCtx.keyOfKey]: item.key } }, ) }, ) } export function arrayWithIndexCodec<T, O>( f: (index: number) => t.Type<T, O, unknown>, ): t.Type<Array<T>, O[], unknown> { return new t.Type<Array<T>, O[], unknown>( "ArrayWithIndexCodec", (u): u is Array<T> => Array.isArray(u), (items) => pipe( t.array(t.unknown).decode(items), either.chain((validItems) => { const decodedItems = validItems.map((item, index) => { return f(index).decode(item) }) return array.sequence(either.Applicative)(decodedItems) }), ), (items) => items.map((item, index) => f(index).encode(item)), ) } type GroupLegacy = Array<GroupItemLegacy> export const GroupLegacy = ( ctx: LegacyContentCtx, ): t.Type<GroupContent, WithTypes<GroupLegacy>, unknown> => { const codecDecode = arrayWithIndexCodec< GroupItemContent | null, WithTypes<GroupItemLegacy> | null >((index) => t.union([GroupItemLegacy(ctx, index), t.null])) const codecEncode = (items: GroupItemContent[]) => items.map((item, index) => GroupItemLegacy(ctx, index).encode(item)) return new t.Type<GroupContent, WithTypes<GroupLegacy>, unknown>( "GroupLegacy", isGroupContent, (items) => { return pipe( codecDecode.decode(items), either.map((parsedItems) => { const value = parsedItems.map((i, index) => { if (i === null) { const key = ctx.withContentKey(`${index}`).fieldContentKey return { __TYPE__: GroupItemContentType, key, value: [] } } return i }) return { value, __TYPE__: GroupContentType, } }), ) }, (g: GroupContent) => { const res = codecEncode(g.value) return { content: res.map((block) => block.content), types: res.reduce<Record<string, FieldOrSliceType>>( (acc, block) => { return { ...acc, ...block.types } }, { [ctx.keyOfType]: GroupFieldType }, ), keys: res.reduce<Record<string, string>>((acc, block) => { return { ...acc, ...block.keys } }, {}), } }, ) } export function groupContentWithDefaultValues( customType: Group, content: GroupContent, ): GroupContent { const fields = customType.config?.fields if (!fields) return content return { ...content, value: repeatableContentWithDefaultNestableContentValues( fields, content.value, ), } } export function traverseGroupContent({ path, key, apiId, model, content, }: { path: ContentPath key: string apiId: string content: GroupContent model?: Group | undefined }) { return (transform: TraverseWidgetContentFn): GroupContent | undefined => { const groupItems = traverseGroupItemsContent({ path, model: model?.config?.fields, content: content.value, })(transform) return transform({ path, key, apiId, model, content: { __TYPE__: content.__TYPE__, value: groupItems, }, }) } } export function traverseGroupItemsContent({ path, model, content, }: { path: ContentPath content: Array<GroupItemContent> model?: Record<string, Group | NestableWidget> | undefined }) { return (transform: TraverseWidgetContentFn): Array<GroupItemContent> => { return content.map((groupItem) => { const groupItemPath = path.concat([ { key: groupItem.key, type: "GroupItem" }, ]) const groupItemFields = groupItem.value.reduce<GroupItemContent["value"]>( (acc, [fieldKey, fieldContent]) => { const fieldDef = model?.[fieldKey] let transformedField if (isGroupContent(fieldContent)) { transformedField = traverseGroupContent({ path: groupItemPath.concat([{ key: fieldKey, type: "Widget" }]), key: fieldKey, apiId: fieldKey, model: fieldDef?.type === "Group" ? fieldDef : undefined, content: fieldContent, })(transform) } else if (isRepeatableContent(fieldContent)) { transformedField = traverseRepeatableContent({ path: groupItemPath.concat([{ key: fieldKey, type: "Widget" }]), key: fieldKey, apiId: fieldKey, model: fieldDef?.type === "Link" ? fieldDef : undefined, content: fieldContent, })(transform) } else if (isTableContent(fieldContent)) { transformedField = traverseTableContent({ path: groupItemPath.concat([{ key: fieldKey, type: "Widget" }]), key: fieldKey, apiId: fieldKey, model: fieldDef?.type === "Table" ? fieldDef : undefined, content: fieldContent, })(transform) } else { transformedField = transform({ path: groupItemPath.concat([{ key: fieldKey, type: "Widget" }]), key: fieldKey, apiId: fieldKey, model: fieldDef, content: fieldContent, }) } // Can happen if the transform function returns undefined to filter out a field if ( !transformedField || !( isNestableContent(transformedField) || isGroupContent(transformedField) ) ) return acc return acc.concat([[fieldKey, transformedField]]) }, [], ) return { __TYPE__: groupItem.__TYPE__, key: groupItem.key, value: groupItemFields, } }) } }