prismicio / prismic-ts-codegen

A Prismic model-to-TypeScript-type generator.
Apache License 2.0
18 stars 6 forks source link

Helper types and predicates for type IDs #37

Open hijarian opened 1 year ago

hijarian commented 1 year ago

Types of changes

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:

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:

angeloashmore commented 1 year ago

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 commented 1 year ago

@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.