import type { WidgetKey } from "@prismicio/types-internal/lib/common" import { defaultCtx, Document, FieldOrSliceType, isSlicesContent, migrateDocument, migrateSliceItem, traverseSlices, WidgetContent, WidgetLegacy, } from "@prismicio/types-internal/lib/content" import { DocumentLegacy } from "@prismicio/types-internal/lib/content" import { StaticSlices } from "@prismicio/types-internal/lib/customtypes" import { isLeft, isRight } from "fp-ts/lib/Either" import type { CustomTypeDef } from "../customtypes" import type { DocumentMetadata } from "./DocumentMetadata" import type { ApiDocument } from "./index" export class DocumentReader { doc: ApiDocument //doc metadata id: string version: string type: string groupLangId: string tags: string[] language: string first_publication_date: string | undefined last_publication_date: string | undefined metadata: DocumentMetadata | undefined // computed meta for the content _contentMeta: | { fieldTypes: Map widgets: Partial> } | undefined slugs: ReadonlyArray uid: string | undefined customType?: CustomTypeDef | undefined private parsed: Document = {} // specific when we parse all at once, we don't need to parse again private fullyParsed = false constructor(doc: ApiDocument, customType?: CustomTypeDef | undefined) { const { uid, slugs } = extractUidAndSlugs(doc.data) this.doc = doc this.id = doc.id this.version = doc.version this.type = doc.type this.groupLangId = doc.groupLangId this.tags = doc.tags this.language = doc.language this.first_publication_date = doc.first_publication_date this.last_publication_date = doc.last_publication_date this.metadata = doc.metadata ?? undefined this.slugs = slugs this.uid = uid this.customType = customType } get contentMeta() { if (!this._contentMeta) { const { types, widgets } = extractTypesAndWidgets(this.doc.data) this._contentMeta = { fieldTypes: types, widgets: widgets, } } return this._contentMeta } getWidget(key: string): WidgetContent | undefined { const cachedWidget = this.parsed[key] if (cachedWidget) return cachedWidget const { widgets, fieldTypes } = this.contentMeta const unparsedWidget = widgets[key] const parsedWidget = (() => { const parsed = WidgetLegacy(defaultCtx(key, fieldTypes)).decode(unparsedWidget) if (!parsed || isLeft(parsed)) return if (!this.customType) return parsed.right const fieldDef = this.customType.fields[key] // apply the slice migration in case we match a slicezone. if (isSlicesContent(parsed.right) && StaticSlices.is(fieldDef)) { return traverseSlices({ path: [ { key: this.customType.customTypeId, type: "CustomType" }, { key, type: "Widget" }, ], key, model: fieldDef, content: parsed.right, })({ transformSlice: migrateSliceItem, }) } return parsed.right })() if (!parsedWidget) return this.parsed[key] = parsedWidget return parsedWidget } parseAll(): { doc: Document slugs: readonly string[] uid: string | undefined } { if (this.fullyParsed) { return { doc: this.parsed, slugs: this.slugs, uid: this.uid, } } const { fieldTypes, widgets } = this.contentMeta const parsed = DocumentLegacy._codec(fieldTypes).decode(widgets) const result = isRight(parsed) ? parsed.right : {} const migratedDoc = this.customType ? migrateDocument(result, this.customType) : result this.parsed = migratedDoc this.fullyParsed = true return { doc: migratedDoc, slugs: this.slugs, uid: this.uid, } } } function extractUidAndSlugs(data: { [p: string]: unknown }): { slugs: ReadonlyArray uid: string | undefined } { const slugs = (data["slugs_INTERNAL"] as string[]) || [] const uid = data["uid"] as string | undefined return { uid, slugs, } } function extractTypesAndWidgets(data: { [p: string]: unknown }): { types: Map widgets: Partial> } { const fields: [string, unknown][] = Object.entries(data) const { types, widgets } = fields.reduce( (acc, [k, v]) => { if (k.endsWith("_TYPE")) { const decodedValue = FieldOrSliceType.decode(v) if (isRight(decodedValue)) { return { ...acc, types: acc.types.set(k.substring(0, k.length - 5), decodedValue.right), } } } if (!k.endsWith("_POSITION") && !k.endsWith("_TYPE")) { return { ...acc, widgets: { ...acc.widgets, [k]: v, }, } } return acc }, { types: new Map(), widgets: {}, }, ) return { widgets, types, } }