Skip to content

Commit

Permalink
refactor: link types
Browse files Browse the repository at this point in the history
  • Loading branch information
angeloashmore committed Dec 17, 2024
1 parent e2c09a7 commit df81d1d
Show file tree
Hide file tree
Showing 19 changed files with 192 additions and 257 deletions.
11 changes: 8 additions & 3 deletions src/Migration.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as is from "./lib/isValue"
import { getOptionalLinkProperties } from "./lib/getLinkProperties"
import { validateAssetMetadata } from "./lib/validateAssetMetadata"

import type { Asset } from "./types/api/asset/asset"
Expand Down Expand Up @@ -404,28 +405,32 @@ export class Migration<TDocuments extends PrismicDocument = PrismicDocument> {
*/
#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,
text: input.text,
}
}

return {
...optionalLinkProperties,
link_type: LinkType.Document,
id: () => this._getByOriginalID(input.id),
text: input.text,
}
}

if (is.filledLinkToMedia(input)) {
const optionalLinkProperties = getOptionalLinkProperties(input)

return {
...optionalLinkProperties,
link_type: LinkType.Media,
id: this.createAsset(input),
text: input.text,
}
}

Expand Down
25 changes: 25 additions & 0 deletions src/lib/getLinkProperties.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { FilledContentRelationshipField } from "../types/value/contentRelationship"
import type { OptionalLinkProperties } from "../types/value/link"
import type { FilledLinkToMediaField } from "../types/value/linkToMedia"

/**
* Returns optional properties only available to link fields. Link fields can
* have the same shape as content relationship and link to media fields,
* requiring special treatment to extract link-specific properties.
*
* @param input - The content relationship or link to media field from which the
* link properties are extracted.
*
* @returns Optional link properties that `input` might have.
*/
export const getOptionalLinkProperties = (
input: FilledContentRelationshipField | FilledLinkToMediaField,
): OptionalLinkProperties => {
const res: OptionalLinkProperties = {}

if ("text" in input) {
res.text = input.text
}

return res
}
46 changes: 15 additions & 31 deletions src/lib/isValue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { FilledContentRelationshipField } from "../types/value/contentRelat
import type { PrismicDocument } from "../types/value/document"
import type { GroupField } from "../types/value/group"
import type { ImageField } from "../types/value/image"
import type { LinkField } from "../types/value/link"
import { LinkType } from "../types/value/link"
import type { FilledLinkToMediaField } from "../types/value/linkToMedia"
import { type RTImageNode, RichTextNodeType } from "../types/value/richText"
Expand Down Expand Up @@ -33,23 +34,7 @@ type UnknownValue =
export const filledLinkToMedia = (
value: UnknownValue,
): value is FilledLinkToMediaField => {
if (value && typeof value === "object" && !("version" in value)) {
if (
"link_type" in value &&
value.link_type === LinkType.Media &&
"id" in value &&
"name" in value &&
"kind" in value &&
"url" in value &&
"size" in value
) {
value

return true
}
}

return false
return filledLink(value) && value.link_type === LinkType.Media
}

/**
Expand Down Expand Up @@ -135,6 +120,18 @@ export const rtImageNode = (value: UnknownValue): value is RTImageNode => {
return false
}

export const filledLink = (
value: UnknownValue,
): value is LinkField<string, string, unknown, "filled"> => {
return (
typeof value === "object" &&
value !== null &&
"link_type" in value &&
typeof value.link_type === "string" &&
value.link_type !== LinkType.Any
)
}

/**
* Checks if a value is a content relationship field.
*
Expand All @@ -148,20 +145,7 @@ export const rtImageNode = (value: UnknownValue): value is RTImageNode => {
export const filledContentRelationship = (
value: UnknownValue,
): value is FilledContentRelationshipField => {
if (value && typeof value === "object" && !("version" in value)) {
if (
"link_type" in value &&
value.link_type === LinkType.Document &&
"id" in value &&
"type" in value &&
"tags" in value &&
"lang" in value
) {
return true
}
}

return false
return filledLink(value) && value.link_type === LinkType.Document
}

/**
Expand Down
6 changes: 3 additions & 3 deletions src/lib/resolveMigrationDocumentData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export async function resolveMigrationContentRelationship(
if (relation instanceof PrismicMigrationDocument) {
return relation.document.id
? { link_type: LinkType.Document, id: relation.document.id }
: { link_type: LinkType.Document }
: { link_type: LinkType.Any }
}

if (relation) {
Expand All @@ -57,7 +57,7 @@ export async function resolveMigrationContentRelationship(
return { link_type: LinkType.Document, id: relation.id }
}

return { link_type: LinkType.Document }
return { link_type: LinkType.Any }
}

/**
Expand Down Expand Up @@ -167,7 +167,7 @@ export const resolveMigrationLinkToMedia = (
}
}

return { link_type: LinkType.Media }
return { link_type: LinkType.Any }
}

/**
Expand Down
3 changes: 1 addition & 2 deletions src/types/migration/Asset.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { Asset } from "../api/asset/asset"
import type { FilledImageFieldImage } from "../value/image"
import type { EmptyLinkField } from "../value/link"
import type { LinkToMediaField } from "../value/linkToMedia"
import { type RTImageNode } from "../value/richText"

Expand Down Expand Up @@ -89,7 +88,7 @@ export type MigrationLinkToMedia = Pick<
*/
export type MigrationLinkToMediaField =
| Pick<LinkToMediaField<"filled">, "link_type" | "id" | "text">
| EmptyLinkField<"Media">
| LinkToMediaField<"empty">

/**
* A rich text image node in a migration.
Expand Down
19 changes: 10 additions & 9 deletions src/types/migration/ContentRelationship.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { FilledContentRelationshipField } from "../value/contentRelationship"
import type {
ContentRelationshipField,
FilledContentRelationshipField,
} from "../value/contentRelationship"
import type { PrismicDocument } from "../value/document"
import type { EmptyLinkField } from "../value/link"

import type { PrismicMigrationDocument } from "./Document"

Expand All @@ -13,17 +15,16 @@ export type MigrationContentRelationship<
TDocuments extends PrismicDocument = PrismicDocument,
> =
| ValueOrThunk<TDocuments | PrismicMigrationDocument<TDocuments> | undefined>
| (Pick<FilledContentRelationshipField, "link_type"> &
Partial<Pick<FilledContentRelationshipField, "text">> & {
id: ValueOrThunk<
TDocuments | PrismicMigrationDocument<TDocuments> | undefined
>
})
| (Pick<ContentRelationshipField, "link_type"> & {
id: ValueOrThunk<
TDocuments | PrismicMigrationDocument<TDocuments> | undefined
>
})

/**
* The minimum amount of information needed to represent a content relationship
* field with the migration API.
*/
export type MigrationContentRelationshipField =
| Pick<FilledContentRelationshipField, "link_type" | "id">
| EmptyLinkField<"Document">
| ContentRelationshipField<string, string, unknown, "empty">
1 change: 0 additions & 1 deletion src/types/model/contentRelationship.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,5 @@ export interface CustomTypeModelContentRelationshipField<
select: typeof CustomTypeModelLinkSelectType.Document
customtypes?: readonly CustomTypeIDs[]
tags?: readonly Tags[]
allowText?: boolean
}
}
11 changes: 7 additions & 4 deletions src/types/value/contentRelationship.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { AnyRegularField, FieldState } from "./types"

import type { GroupField } from "./group"
import type { EmptyLinkField, LinkType } from "./link"
import type { LinkType } from "./link"
import type { SliceZone } from "./sliceZone"

/**
Expand All @@ -21,9 +21,13 @@ export type ContentRelationshipField<
| unknown = unknown,
State extends FieldState = FieldState,
> = State extends "empty"
? EmptyLinkField<typeof LinkType.Document>
? EmptyContentRelationshipField
: FilledContentRelationshipField<TypeEnum, LangEnum, DataInterface>

type EmptyContentRelationshipField = {
link_type: typeof LinkType.Any
}

/**
* Links that refer to documents
*/
Expand All @@ -34,7 +38,7 @@ export interface FilledContentRelationshipField<
| Record<string, AnyRegularField | GroupField | SliceZone>
| unknown = unknown,
> {
link_type: typeof LinkType.Document
link_type: "Document"
id: string
uid?: string
type: TypeEnum
Expand All @@ -44,5 +48,4 @@ export interface FilledContentRelationshipField<
slug?: string
isBroken?: boolean
data?: DataInterface
text?: string
}
76 changes: 45 additions & 31 deletions src/types/value/link.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { AnyRegularField, FieldState } from "./types"

import type { ContentRelationshipField } from "./contentRelationship"
import type { FilledContentRelationshipField } from "./contentRelationship"
import type { GroupField } from "./group"
import type { LinkToMediaField } from "./linkToMedia"
import type { FilledLinkToMediaField } from "./linkToMedia"
import type { SliceZone } from "./sliceZone"

/**
Expand All @@ -15,35 +15,13 @@ export const LinkType = {
Web: "Web",
} as const

/**
* For link fields that haven't been filled
*
* @typeParam Type - The type of link.
*/
export type EmptyLinkField<
Type extends (typeof LinkType)[keyof typeof LinkType] = typeof LinkType.Any,
> = {
link_type: Type | string
text?: string
}

/**
* Link that points to external website
*/
export interface FilledLinkToWebField {
link_type: typeof LinkType.Web
url: string
target?: string
text?: string
}

/**
* A link field.
*
* @typeParam TypeEnum - Type API ID of the document.
* @typeParam LangEnum - Language API ID of the document.
* @typeParam DataInterface - Data fields for the document (filled in via
* GraphQuery of `fetchLinks`).
* @typeParam DataInterface - Data fields for the document (filled via the
* `fetchLinks` or `graphQuery` query parameter).
* @typeParam State - State of the field which determines its shape.
*/
export type LinkField<
Expand All @@ -53,9 +31,45 @@ export type LinkField<
| Record<string, AnyRegularField | GroupField | SliceZone>
| unknown = unknown,
State extends FieldState = FieldState,
> = State extends "empty"
? EmptyLinkField<typeof LinkType.Any>
> = (State extends "empty"
? EmptyLinkField
:
| ContentRelationshipField<TypeEnum, LangEnum, DataInterface, State>
| FilledLinkToWebField
| LinkToMediaField<State>
| FilledContentRelationshipField<TypeEnum, LangEnum, DataInterface>
| FilledLinkToMediaField
| FilledLinkToWebField) &
OptionalLinkProperties

/**
* A link field that is not filled.
*
* @typeParam Type - Unused. An empty link field will always have a `link_type`
* of "Any".
*/
// This type needs `OptionalLinkProperties` because this type may be used on its own.
export type EmptyLinkField<
_Unused extends
(typeof LinkType)[keyof typeof LinkType] = typeof LinkType.Any,
> = {
link_type: typeof LinkType.Any
} & OptionalLinkProperties

/**
* A link field pointing to a relative or absolute URL.
*/
// This type needs `OptionalLinkProperties` because this type may be used on its own.
export type FilledLinkToWebField = {
link_type: typeof LinkType.Web
url: string
target?: string
} & OptionalLinkProperties

/**
* Optional properties available to link fields. It is used to augment existing
* link-like fields (like content relationship fields) with field-specific
* properties.
*
* @internal
*/
export type OptionalLinkProperties = {
text?: string
}
13 changes: 8 additions & 5 deletions src/types/value/linkToMedia.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
import type { FieldState } from "./types"

import type { EmptyLinkField, LinkType } from "./link"
import type { LinkType } from "./link"

/**
* A link field that points to media.
*
* @typeParam State - State of the field which determines its shape.
*/
export type LinkToMediaField<State extends FieldState = FieldState> =
State extends "empty"
? EmptyLinkField<typeof LinkType.Media>
: FilledLinkToMediaField
State extends "empty" ? EmptyLinkToMediaField : FilledLinkToMediaField

type EmptyLinkToMediaField = {
link_type: typeof LinkType.Any
text?: string
}

/**
* A link that points to media.
*/
export interface FilledLinkToMediaField {
id: string
link_type: typeof LinkType.Media
link_type: "Media"
name: string
kind: string
url: string
Expand Down
Loading

0 comments on commit df81d1d

Please sign in to comment.