Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Helper types and predicates for type IDs #37

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

hijarian
Copy link

Types of changes

  • Chore (a non-breaking change which is related to package maintenance)
  • Bug fix (a non-breaking change which fixes an issue)
  • New feature (a non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Description

If one wants to write a proper TypeScript predicate function which ensures that the given string is a proper Custom Type name, it's not sufficient to have a TypeScript type definition AllDocumentTypes['type'] available, because it's, sadly, literally impossible to infer a JavaScript array of strings out of the union type.

In addition to that, it is significant to differentiate between Custom Types which have uids (basically, which are "Repeatable"), and which don't, because, for example, you can't meaningfully pass a Singleton type ID to a client.getByUID() call.

This PR adds a set of helper types and functions to the generated code which allows, for example, the following:

import { isTypeCode } from 'types.generated.ts';
// ... other Express boilerplate here...
    // imagine that we are inside an Express middleware now
    const { type } = req.query;

    if (!isTypeCode(type)) {
      throw new BadRequest('You must provide a valid Prismic type code with this request!');
    }

You already have pretty good type inference in cases where the type ID is hardcoded in the source code, but this validator function helps to narrow types in cases where we don't control the ID value.

Overall, I suggest the following three functions and two accompanying types:

  • function isTypeCode narrows string to AllDocumentTypes['type']
  • type AnyOurDocumentWithoutUID which is a subset of AllDocumentTypes, where all documents don't have an uid field
  • function isTypeCodeForTypeWithoutUIDs which narrows string to AnyOurDocumentWithoutUID['type']
  • type AnyOurDocumentWithUID which is a subset of AllDocumentTypes, where all documents do have an uid field
  • function isTypeCodeForTypeWithUIDs which narrows string to AnyOurDocumentWithUID['type']

In addition to that, I'd really like to have a type which infers the document type given the type ID provided, something like that:

export type SelectDocumentTypeByTypeCode<
  DocumentType extends AllDocumentTypes,
  TypeCode extends AllDocumentTypes['type']
> = DocumentType['type'] extends TypeCode ? DocumentType : never;

but I am not that well-versed in TypeScript to be completely sure the above type actually does what it should and it's outside of the scope of this PR.

Regarding the second item in the checklist

I admit that I didn't add the documentation because adding these helpers have been a necessity in my own project and there were no real need for me to bother with that. If you can point me at the place where it's explained how to add the TSDoc lines to the generated code, I'll add them.

Regarding the third item in the checklist

These changes fail the last test case:

generateTypes › includes empty @prismicio/client Content namespace if configured and no models are provided

I also admit that I'm not that well-versed in using the namespaces in TypeScript so it's outside of my abilities to correctly write the code generation for that according to your intents. Please advise.

Checklist:

  • My change requires an update to the official documentation.
  • All TSDoc comments are up-to-date and new ones have been added where necessary.
  • All new and existing tests are passing.

@angeloashmore
Copy link
Member

Hi @hijarian, thanks for the extensive PR! This sounds really interesting!

I'm hesitant to accept these additions because they seem a bit too use case-specific. I can see how you would need something like isTypeCode() when you have a public API that determines the Prismic query. However, I believe this is uncommon. These additions might be better suited as a set of helpers in your project rather than a general solution for everyone.

Do you see these helpers being used in projects often? Could you describe some use cases?

Most of the type helpers could be concise and live in a project's types.ts file:

import {
  PrismicDocumentWithUID,
  PrismicDocumentWithoutUID,
} from "@prismicio/types";
import { Content } from "@prismicio/client";

export type AnyDocumentWithUID = Extract<
  Content.AllDocumentTypes,
  PrismicDocumentWithUID
>;

export type AnyDocumentWithoutUID = Extract<
  Content.AllDocumentTypes,
  PrismicDocumentWithoutUID
>;

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

The type guards would require some generated or manually written code since it needs a list of all document types. I would recommend manually writing the code in this case. However, it's possible we could export the loadModels() function from prismic-ts-codegen, which would allow you to read your models and write your own scripts to generate code.


Regarding the second item in the checklist

I admit that I didn't add the documentation because adding these helpers have been a necessity in my own project and there were no real need for me to bother with that. If you can point me at the place where it's explained how to add the TSDoc lines to the generated code, I'll add them.

Not a problem! The "All TSDoc comments are up-to-date and new ones have been added where necessary" item refers to adding documentation to the package's code, not the generated code. However, generating TSDocs in the generated code is still a good idea.

@hijarian
Copy link
Author

@angeloashmore Thanks for explanation. Got it about the PR, that's your call anyway. :)

Indeed, even having just the loadModels available is a huge helper. Otherwise any such helpers would require typing out all the type IDs by hand, adding a risk to diverge from the actual list of custom types in the Prismic account.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants