import type { ContentTree } from '@financial-times/content-tree' import { ConceptFragment, TranscriptFragment, CustomCodeComponentRichTextFragment, } from '@financial-times/cp-content-pipeline-client' import type { ContentTreeWorkarounds } from '@financial-times/cp-content-pipeline-schema' import type { LiteralToPrimitiveDeep, SetFieldType, SetOptional, Simplify, } from 'type-fest' // Analogous to the Content Tree workarounds we use in the schema package, // these are workarounds for Content Tree types passed to components. These // differ insofar as they also include the reference type for nodes, i.e., they // are the ContentTree.full types to schema's ContentTree.transit types. // Similarly, we should aim to reduce these divergences by either adding them // to Content Tree or finding alternative ways of resolving them in our API. // Unlike the schema's workarounds, I've opted for a more declarative, verbose // approach to listing the differences between the workarounds and the original // types, with the hope that the differences can be fixed more incrementally. /** * Helper type to extend partial/undefined fields to also be null. Optional * fields in the Content Tree are represented by a partial field, but are * represented by null (abstracted by the Maybe type) in the content pipeline. */ type PartialToMaybe = Simplify<{ [K in keyof T]: Extract extends never ? T[K] : T[K] | null }> /** * Helper type to apply PartialToMaybe mapper type to each object field * recursively. */ type PartialToMaybeDeep = T extends object ? PartialToMaybe<{ [K in keyof T]: PartialToMaybeDeep }> : T /** * Helper type to convert arrays to readonly arrays. Content Tree arrays are * mutable but the arrays we return from our API are expected to be readonly. We * should explore which assumption is correct and match that in the other * declaration file. */ type ReadonlyArrays = { [K in keyof T]: ReadonlyArray } // define conditional type separately so that it distributes the mapping over // union types (e.g., to avoid removing the undefined from a union) type ReadonlyArray = T extends Array ? readonly Item[] : T export type TeaserConcept = PartialToMaybe< ReadonlyArrays< SetOptional< ContentTree.TeaserConcept, 'apiUrl' | 'directType' | 'predicate' | 'prefLabel' | 'type' | 'types' > > > export type Image = PartialToMaybe< ReadonlyArrays< LiteralToPrimitiveDeep< SetOptional > > > export type Teaser = PartialToMaybeDeep< LiteralToPrimitiveDeep< SetOptional< SetFieldType< SetFieldType< ContentTree.Teaser, 'metaLink' | 'metaAltLink', TeaserConcept >, 'image', Omit >, 'type' | 'metaLink' | 'metaAltLink' | 'indicators' | 'image' > > > export type Recommended = PartialToMaybe< ContentTree.transit.Recommended & { teaser?: Teaser } > export type RecommendedList = PartialToMaybe< ReadonlyArrays< Omit & { teasers?: (Teaser | null)[] children: Recommended[] } > > export type ImageSetPicture = PartialToMaybe< ReadonlyArrays< SetOptional< SetFieldType< SetFieldType, 'images', Image[] >, 'caption' | 'credit' > > > export type ImageSet = PartialToMaybe< SetOptional< SetFieldType< ContentTree.ImageSet | ContentTree.LayoutImage, 'picture', ImageSetPicture >, 'picture' > > export type ScrollyImage = PartialToMaybe< SetOptional< SetFieldType, 'picture' > > export type Tweet = PartialToMaybe> type ClipFormat = 'standard-inline' | 'mobile' interface ClipSource { audioCodec?: string binaryUrl: string duration?: number mediaType: string pixelHeight?: number pixelWidth?: number videoCodec?: string quality?: string dppx?: number previousSourceWidth?: number } export type Clip = PartialToMaybeDeep< ReadonlyArrays<{ format?: ClipFormat dataSource: ClipSource[] poster?: string }> > interface ClipCaption { mediaType?: string url?: string } type ClipTranscript = TranscriptFragment export type ClipAccessibility = PartialToMaybeDeep< ReadonlyArrays<{ captions?: ClipCaption[] transcript?: ClipTranscript }> > interface ClipSetReferences { id: string type: string accessibility?: ClipAccessibility noAudio?: boolean caption?: string credits?: string description?: string displayTitle?: string systemTitle?: string contentWarning?: string[] source?: string subtitle?: string publishedDate?: string clips?: Clip[] fragmentIdentifier?: string | undefined } export type ClipSet = PartialToMaybeDeep< ReadonlyArrays > export type RawImage = ContentTreeWorkarounds.RawImage & { image: Image } export type MainImageRaw = ContentTreeWorkarounds.MainImageRaw & { image: Image } export type Author = PartialToMaybe< ContentTreeWorkarounds.Author & { concept?: ConceptFragment } > type CustomCodeComponentUI = Pick< ContentTree.CustomCodeComponent, 'path' | 'versionRange' | 'id' | 'layoutWidth' | 'attributes' | 'type' > export type CustomCodeComponent = PartialToMaybeDeep< CustomCodeComponentUI & { body?: CustomCodeComponentRichTextFragment } >