TypeScript types for collections #225
Replies: 4 comments 3 replies
-
We have this on our radar as well! The conversation keeps coming up internally so it is something we know we need to do. It will be one of the items on the roadmap coming. I'm going to set aside a day next week to take a deep dive in and see what I can come up for this. It is really a great topic, and I'm excited you brought it up and provided expected usage too. This is great! |
Beta Was this translation helpful? Give feedback.
-
@DanRibbens Sounds good, thank you! FWIW, this is my early attempt to define something (I'm a newbie when it comes to TS generics and advanced types, so it's likely invalid, but sharing it anyway): const Test = {
slug: "tests",
fields: [
{
name: "f1",
type: "text",
} as const,
{
name: "f2",
type: "richText",
} as const,
{
name: "f3",
type: "number",
} as const,
{
name: "f4",
type: "select",
options: ["o1", "o2"],
} as const,
],
};
type MapTuple<T extends Array<{ readonly name: string }>> = {
[K in T[number]["name"]]: Extract<T[number], { name: K }> extends {
type: "text" | "richText";
}
? string
: Extract<T[number], { name: K }> extends {
type: "number";
}
? number
: Extract<T[number], { name: K }> extends {
type: "select";
options: any;
}
? Extract<T[number], { name: K; options: any }>["options"][number]
: any;
};
type Collection<T extends { fields: Array<{ readonly name: string }> }> =
MapTuple<T["fields"]> & {
id: string;
createdAt: string;
updatedAt: string;
};
type Test = Collection<typeof Test>;
// same as:
// type Test = {
// id: string;
// f1: string;
// f2: string;
// f3: number;
// f4: "o1" | "o2";
// createdAt: string;
// updatedAt: string;
// }
{ |
Beta Was this translation helpful? Give feedback.
-
// EDIT: Added a relationship field to example Hey, any update on this? I was thinking that because Payload wants to support plugins and JavaScript, pure typescript inference approach might turn out to be insufficient, and type generation approach might be more bulletproof. The POC approach could be to transform the collection config to JSON Schema, and use https://github.com/bcherny/json-schema-to-typescript to generate types. Example: import { compile } from "json-schema-to-typescript";
import { CollectionConfig } from "payload/types";
const User: CollectionConfig = {
slug: "users",
labels: {
singular: "User",
},
fields: [
{
name: "name",
type: "text",
required: true,
},
],
};
const Test: CollectionConfig = {
slug: "tests",
labels: {
singular: "Test",
},
fields: [
{
name: "f1",
type: "text",
required: true,
},
{
name: "f2",
type: "richText",
},
{
name: "f3",
type: "number",
defaultValue: 10,
},
{
name: "f4",
type: "select",
options: ["o1", "o2"],
required: true,
},
{
type: "relationship",
name: "user",
relationTo: "users",
},
],
};
const Config = {
collections: [User, Test],
};
function collectionToJsonSchema(
collection: CollectionConfig,
slugToLabel: Record<string, string>
): any {
return {
title: collection.labels.singular,
type: "object",
properties: Object.fromEntries(
collection.fields.map((field) => {
const type =
field.type == "number"
? { type: "integer" }
: field.type == "relationship"
? {
$ref: `#/definitions/${
slugToLabel[field.relationTo as string]
}`,
}
: { type: "string" };
const enum_ = field.type == "select" ? { enum: field.options } : {};
const default_ = field.defaultValue
? { default: field.defaultValue }
: {};
return [
field.name,
{
...type,
...enum_,
...default_,
},
];
})
),
required: collection.fields
.filter((field) => field.required === true)
.map((field) => field.name),
additionalProperties: false,
};
}
function configToJsonSchema(config: { collections: CollectionConfig[] }): any {
const slugToLabel = Object.fromEntries(
config.collections.map((collection) => [
collection.slug,
collection.labels.singular,
])
);
return {
definitions: Object.fromEntries(
config.collections.map((collection) => [
collection.labels.singular,
collectionToJsonSchema(collection, slugToLabel),
])
),
additionalProperties: false,
};
}
await compile(configToJsonSchema(Config), "Config", {
bannerComment: "// auto-generated by payload",
unreachableDefinitions: true,
}); Output: // auto-generated by payload
export interface Config {}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "User".
*/
export interface User {
name: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "Test".
*/
export interface Test {
f1: string;
f2?: string;
f3?: number;
f4: "o1" | "o2";
user?: User;
} |
Beta Was this translation helpful? Give feedback.
-
Hey all, SUPER excited to say that the Payload team has been working on a way to generate TypeScript interfaces from collection and global configs, and we've got a PR open that does it perfectly! Going to do some testing and then add documentation but the PR is pretty close to being ready to merge. 🎉 Thanks to @sixers — we went your direction by first creating a JSON schema out of a fully built config and then used |
Beta Was this translation helpful? Give feedback.
-
Could Payload provide TypeScript definitions for collections? Also, it would be great if Local API was fully typed.
I couldn't find anything in the documentation, also current TS definitions for
Payload
class don't seem to support generics. The closest thing I found was to use https://www.graphql-code-generator.com on GraphQL schema.It would be awesome if types could be inferred, e.g. similar to zod library:
So something like:
I'm not sure if it's possible to infer given how collection configs are structured, but would be super awesome if possible.
If not possible, it would be great to be able to run some code-generation tool to generate types & typed APIs (e.g. what Prisma does).
WDYT?
Beta Was this translation helpful? Give feedback.
All reactions