@prismicio/client
Version:
The official JavaScript + TypeScript client library for Prismic
531 lines (487 loc) • 15.2 kB
text/typescript
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,
)
}
}