UNPKG

@prismicio/client

Version:

The official JavaScript + TypeScript client library for Prismic

531 lines (487 loc) 15.2 kB
import * as is from "./lib/isValue" import { getOptionalLinkProperties } from "./lib/getOptionalLinkProperties" import { validateAssetMetadata } from "./lib/validateAssetMetadata" import type { Asset } from "./types/api/asset/asset" import type { MigrationAssetConfig, MigrationImage, MigrationLinkToMedia, MigrationRTImageNode, } from "./types/migration/Asset" import { PrismicMigrationAsset } from "./types/migration/Asset" import type { MigrationContentRelationship } from "./types/migration/ContentRelationship" import { PrismicMigrationDocument } from "./types/migration/Document" import type { ExistingPrismicDocument, PendingPrismicDocument, } from "./types/migration/Document" import type { PrismicDocument } from "./types/value/document" import type { FilledImageFieldImage } from "./types/value/image" import { type FilledLinkToWebField, LinkType } from "./types/value/link" import type { FilledLinkToMediaField } from "./types/value/linkToMedia" import { RichTextNodeType } from "./types/value/richText" /** * Extracts one or more Prismic document types that match a given Prismic * document type. If no matches are found, no extraction is performed and the * union of all provided Prismic document types are returned. * * @typeParam TDocuments - Prismic document types from which to extract. * @typeParam TDocumentType - Type(s) to match `TDocuments` against. */ type ExtractDocumentType< TDocuments extends { type: string }, TDocumentType extends TDocuments["type"], > = Extract<TDocuments, { type: TDocumentType }> extends never ? TDocuments : Extract<TDocuments, { type: TDocumentType }> /** * A helper that allows preparing your migration to Prismic. * * @typeParam TDocuments - Document types that are registered for the Prismic * repository. Query methods will automatically be typed based on this type. */ export class Migration<TDocuments extends PrismicDocument = PrismicDocument> { /** * Assets registered in the migration. * * @internal */ _assets: Map<MigrationAssetConfig["file"], PrismicMigrationAsset> = new Map() /** * Documents registered in the migration. * * @internal */ _documents: PrismicMigrationDocument<TDocuments>[] = [] /** * Registers an asset to be created in the migration from an asset object. * * @remarks * This method does not create the asset in Prismic media library right away. * Instead, it registers it in your migration. The asset will be created when * the migration is executed through the `writeClient.migrate()` method. * * @param asset - An asset object from Prismic Asset API. * * @returns A migration asset field instance. * * @internal */ createAsset(asset: Asset): PrismicMigrationAsset /** * Registers an asset to be created in the migration from an image or link to * media field. * * @remarks * This method does not create the asset in Prismic media library right away. * Instead, it registers it in your migration. The asset will be created when * the migration is executed through the `writeClient.migrate()` method. * * @param imageOrLinkToMediaField - An image or link to media field from * Prismic Document API. * * @returns A migration asset field instance. * * @internal */ createAsset( imageOrLinkToMediaField: FilledImageFieldImage | FilledLinkToMediaField, ): PrismicMigrationAsset /** * Registers an asset to be created in the migration from a file. * * @remarks * This method does not create the asset in Prismic media library right away. * Instead, it registers it in your migration. The asset will be created when * the migration is executed through the `writeClient.migrate()` method. * * @param file - The URL or content of the file to be created. * @param filename - The filename of the asset. * @param params - Additional asset data. * * @returns A migration asset field instance. */ createAsset( file: MigrationAssetConfig["file"], filename: MigrationAssetConfig["filename"], params?: { notes?: string credits?: string alt?: string tags?: string[] }, ): PrismicMigrationAsset /** * Registers an asset to be created in the migration from a file, an asset * object, or an image or link to media field. * * @remarks * This method does not create the asset in Prismic media library right away. * Instead, it registers it in your migration. The asset will be created when * the migration is executed through the `writeClient.migrate()` method. * * @returns A migration asset field instance. */ createAsset( fileOrAssetOrField: | MigrationAssetConfig["file"] | Asset | FilledImageFieldImage | FilledLinkToMediaField, filename?: MigrationAssetConfig["filename"], { notes, credits, alt, tags, }: { notes?: string credits?: string alt?: string tags?: string[] } = {}, ): PrismicMigrationAsset { let config: MigrationAssetConfig let maybeInitialField: FilledImageFieldImage | undefined if (typeof fileOrAssetOrField === "object" && "url" in fileOrAssetOrField) { if ( "dimensions" in fileOrAssetOrField || "link_type" in fileOrAssetOrField ) { const url = fileOrAssetOrField.url.split("?")[0] const filename = "name" in fileOrAssetOrField ? fileOrAssetOrField.name : url.split("/").pop()!.split("_").pop()! const credits = "copyright" in fileOrAssetOrField && fileOrAssetOrField.copyright ? fileOrAssetOrField.copyright : undefined const alt = "alt" in fileOrAssetOrField && fileOrAssetOrField.alt ? fileOrAssetOrField.alt : undefined if ("dimensions" in fileOrAssetOrField) { maybeInitialField = fileOrAssetOrField } config = { id: fileOrAssetOrField.id, file: url, filename, notes: undefined, credits, alt, tags: undefined, } } else { config = { id: fileOrAssetOrField.id, file: fileOrAssetOrField.url, filename: fileOrAssetOrField.filename, notes: fileOrAssetOrField.notes, credits: fileOrAssetOrField.credits, alt: fileOrAssetOrField.alt, tags: fileOrAssetOrField.tags?.map(({ name }) => name), } } } else { config = { id: fileOrAssetOrField, file: fileOrAssetOrField, filename: filename!, notes, credits, alt, tags, } } validateAssetMetadata(config) // We create a detached instance of the asset each time to serialize it properly const migrationAsset = new PrismicMigrationAsset(config, maybeInitialField) const maybeAsset = this._assets.get(config.id) if (maybeAsset) { // Consolidate existing asset with new asset value if possible maybeAsset.config.notes = maybeAsset.config.notes || config.notes maybeAsset.config.credits = maybeAsset.config.credits || config.credits maybeAsset.config.alt = maybeAsset.config.alt || config.alt maybeAsset.config.tags = Array.from( new Set([...(maybeAsset.config.tags || []), ...(config.tags || [])]), ) } else { this._assets.set(config.id, migrationAsset) } return migrationAsset } /** * Registers a document to be created in the migration. * * @remarks * This method does not create the document in Prismic right away. Instead, it * registers it in your migration. The document will be created when the * migration is executed through the `writeClient.migrate()` method. * * @typeParam TType - Type of the Prismic document to create. * * @param document - The document to create. * @param title - The title of the document to create which will be displayed * in the editor. * @param params - Document master language document ID. * * @returns A migration document instance. */ createDocument<TType extends TDocuments["type"]>( document: ExtractDocumentType<PendingPrismicDocument<TDocuments>, TType>, title: string, params?: { masterLanguageDocument?: MigrationContentRelationship }, ): PrismicMigrationDocument<ExtractDocumentType<TDocuments, TType>> { const doc = new PrismicMigrationDocument< ExtractDocumentType<TDocuments, TType> >(document, title, params) this._documents.push(doc) return doc } /** * Registers an existing document to be updated in the migration. * * @remarks * This method does not update the document in Prismic right away. Instead, it * registers it in your migration. The document will be updated when the * migration is executed through the `writeClient.migrate()` method. * * @typeParam TType - Type of Prismic documents to update. * * @param document - The document to update. * @param title - The title of the document to update which will be displayed * in the editor. * * @returns A migration document instance. */ updateDocument<TType extends TDocuments["type"]>( document: ExtractDocumentType<ExistingPrismicDocument<TDocuments>, TType>, // Title is optional for existing documents as we might not want to update it. title?: string, ): PrismicMigrationDocument<ExtractDocumentType<TDocuments, TType>> { const doc = new PrismicMigrationDocument< ExtractDocumentType<TDocuments, TType> >(document, title) this._documents.push(doc) return doc } /** * Registers a document from another Prismic repository to be created in the * migration. * * @remarks * This method does not create the document in Prismic right away. Instead, it * registers it in your migration. The document will be created when the * migration is executed through the `writeClient.migrate()` method. * * @param document - The document from Prismic to create. * @param title - The title of the document to create which will be displayed * in the editor. * * @returns A migration document instance. */ createDocumentFromPrismic<TType extends TDocuments["type"]>( document: ExtractDocumentType<ExistingPrismicDocument<TDocuments>, TType>, title: string, ): PrismicMigrationDocument<ExtractDocumentType<TDocuments, TType>> { const doc = new PrismicMigrationDocument( this.#migratePrismicDocumentData({ type: document.type, lang: document.lang, uid: document.uid, tags: document.tags, data: document.data, }) as PendingPrismicDocument<ExtractDocumentType<TDocuments, TType>>, title, { originalPrismicDocument: document }, ) this._documents.push(doc) return doc } /** * Queries a document from the migration instance with a specific UID and * custom type. * * @example * * ```ts * const contentRelationship = migration.createContentRelationship(() => * migration.getByUID("blog_post", "my-first-post"), * ) * ``` * * @typeParam TType - Type of the Prismic document returned. * * @param type - The API ID of the document's custom type. * @param uid - The UID of the document. * * @returns The migration document instance with a UID matching the `uid` * parameter, if a matching document is found. */ getByUID<TType extends TDocuments["type"]>( type: TType, uid: string, ): | PrismicMigrationDocument<ExtractDocumentType<TDocuments, TType>> | undefined { return this._documents.find( ( doc, ): doc is PrismicMigrationDocument< ExtractDocumentType<TDocuments, TType> > => doc.document.type === type && doc.document.uid === uid, ) } /** * Queries a singleton document from the migration instance for a specific * custom type. * * @example * * ```ts * const contentRelationship = migration.createContentRelationship(() => * migration.getSingle("settings"), * ) * ``` * * @typeParam TType - Type of the Prismic document returned. * * @param type - The API ID of the singleton custom type. * * @returns The migration document instance for the custom type, if a matching * document is found. */ getSingle<TType extends TDocuments["type"]>( type: TType, ): | PrismicMigrationDocument<ExtractDocumentType<TDocuments, TType>> | undefined { return this._documents.find( ( doc, ): doc is PrismicMigrationDocument< ExtractDocumentType<TDocuments, TType> > => doc.document.type === type, ) } /** * Migrates a Prismic document data from another repository so that it can be * created through the current repository's Migration API. * * @param input - The Prismic document data to migrate. * * @returns The migrated Prismic document data. */ #migratePrismicDocumentData(input: unknown): unknown { if (is.filledContentRelationship(input)) { const optionalLinkProperties = getOptionalLinkProperties(input) if (input.isBroken) { return { ...optionalLinkProperties, link_type: LinkType.Document, // ID needs to be 16 characters long to be considered valid by the API id: "_____broken_____", isBroken: true, } } return { ...optionalLinkProperties, link_type: LinkType.Document, id: () => this._getByOriginalID(input.id), } } if (is.filledLinkToMedia(input)) { const optionalLinkProperties = getOptionalLinkProperties(input) return { ...optionalLinkProperties, link_type: LinkType.Media, id: this.createAsset(input), } } if (is.rtImageNode(input)) { // Rich text image nodes const rtImageNode: MigrationRTImageNode = { type: RichTextNodeType.image, id: this.createAsset(input), } if (input.linkTo) { rtImageNode.linkTo = this.#migratePrismicDocumentData(input.linkTo) as | MigrationContentRelationship | MigrationLinkToMedia | FilledLinkToWebField } return rtImageNode } if (is.filledImage(input)) { const image: MigrationImage = { id: this.createAsset(input), } const { id: _id, url: _url, dimensions: _dimensions, edit: _edit, alt: _alt, copyright: _copyright, ...thumbnails } = input for (const name in thumbnails) { if (is.filledImage(thumbnails[name])) { image[name] = this.createAsset(thumbnails[name]) } } return image } if (Array.isArray(input)) { return input.map((element) => this.#migratePrismicDocumentData(element)) } if (input && typeof input === "object") { const res: Record<PropertyKey, unknown> = {} for (const key in input) { res[key] = this.#migratePrismicDocumentData( input[key as keyof typeof input], ) } return res } return input } /** * Queries a document from the migration instance for a specific original ID. * * @example * * ```ts * const contentRelationship = migration.createContentRelationship(() => * migration._getByOriginalID("YhdrDxIAACgAcp_b"), * ) * ``` * * @typeParam TType - Type of the Prismic document returned. * * @param id - The original ID of the Prismic document. * * @returns The migration document instance for the original ID, if a matching * document is found. * * @internal */ _getByOriginalID<TType extends TDocuments["type"]>( id: string, ): | PrismicMigrationDocument<ExtractDocumentType<TDocuments, TType>> | undefined { return this._documents.find( ( doc, ): doc is PrismicMigrationDocument< ExtractDocumentType<TDocuments, TType> > => doc.originalPrismicDocument?.id === id, ) } }