prismicio / prismic-client

The official JavaScript + TypeScript client library for Prismic
https://prismic.io/docs/technical-reference/prismicio-client
Apache License 2.0
169 stars 61 forks source link

A helper to check if a content relationship's field was queried #330

Open angeloashmore opened 1 year ago

angeloashmore commented 1 year ago

Is your feature request related to a problem? Please describe.

Content from a content relationship field's document can be included in a query using the fetchLinks or graphQuery options. Typing those fields with TypeScript is difficult, however, requiring type assertions (as) or manual type parameters (getByUID<...>).

Describe the solution you'd like

A helper function could be provided that checks if a field is included in the query and automatically types the property. We can accomplish this using @prismicio/client's Content namespace.

Something like the following isFilledRelatedData could be used:

import {
  Content,
  FilledContentRelationshipField,
  LinkField,
  isFilled,
} from "@prismicio/client";

type DocumentData<TDocumentType extends Content.AllDocumentTypes["type"]> =
  Extract<Content.AllDocumentTypes, { type: TDocumentType }>["data"];

export function isFilledRelatedData<
  TDocumentType extends Content.AllDocumentTypes["type"],
  TFieldID extends keyof DocumentData<TDocumentType>,
>(
  linkField: LinkField,
  documentType: TDocumentType,
  fieldID: TFieldID,
): linkField is FilledContentRelationshipField & {
  data: {
    [P in keyof DocumentData<TDocumentType> as P extends TFieldID
      ? P
      : never]: DocumentData<TDocumentType>[P];
  };
} {
  return (
    isFilled.contentRelationship(linkField) &&
    linkField.type === documentType &&
    typeof linkField.data === "object" &&
    linkField.data !== null &&
    fieldID in linkField.data
  );
}

isFilledRelatedData()'s name can be misleading since it doesn't actually check if the field is filled. Instead, it checks that the linked field was queried via fetchLinks/graphQuery.

We would probably refocus the helper into something like hasRelationshipField(), which would still require you to check that the field was filled with isFilled().

if (
    hasRelationshipField(item.brand, "brand", "title") &&
    isFilled(item.brand)
) {
    // ...
}

Describe alternatives you've considered

Project-specific runtime checkers can be written using isFilled, but it is cumbersome to write and maintain.

See https://community.prismic.io/t/types-for-content-relationship-in-a-slice/12067/2 for an example.

Additional context

mnzsss commented 6 months ago

I created this helper for case when I need return the typed relation data or undefined if is not filled.

import { Content, LinkField, isFilled } from '@prismicio/client'

type DocumentData<TDocumentType extends Content.AllDocumentTypes['type']> =
  Extract<Content.AllDocumentTypes, { type: TDocumentType }>['data']

type RelatedData<T extends Content.AllDocumentTypes['type'], K> = {
  [P in keyof DocumentData<T> as P]: DocumentData<T>[P]
}

export function isFilledRelatedData<
  TDocumentType extends Content.AllDocumentTypes['type']
>({
  linkField,
  documentType
}: {
  linkField?: LinkField
  documentType: TDocumentType
}): RelatedData<TDocumentType> | undefined {
  const isFilledRelation =
    linkField &&
    isFilled.contentRelationship(linkField) &&
    linkField.type === documentType &&
    typeof linkField.data === 'object' &&
    linkField.data !== null

  if (!isFilledRelation) return

  return linkField.data as RelatedData<TDocumentType>
}

Example:

isFilledRelatedData({
  linkField: content?.data.relation,
  documentType: 'page'
})

In my case if use the recommended function above, I need verify if relation is loaded and return data in ternary check, like that:

const relation = content?.data.relation
  ? isFilledRelatedData(content?.data.relation, 'page', 'slug')
    ? content?.data.relation.data
    : undefined
  : undefined