keystatic icon indicating copy to clipboard operation
keystatic copied to clipboard

Expose a type that accepts a Record<string, ComponentSchema> and returns a resolved entry type

Open airtonix opened this issue 1 year ago • 0 comments

related discussion: https://github.com/Thinkmill/keystatic/discussions/211#discussioncomment-8105588

Currently I'm trying to compose types of my schema fragments that go into the keystatic config. I don't want types for an item in a collection.

What I'm after here would be analogous to types created by tsgql for GQL fragments.

I've come up with the follow that provides me with resolved types from fragments:

import type { ComponentSchema, ValueForReadingDeep, ValueForReading, SlugFormField } from '@keystatic/core';


type ValueForReadingWithMode<
    Schema extends ComponentSchema,
    ResolveLinkedFiles extends boolean | undefined
> = ResolveLinkedFiles extends true
    ? ValueForReadingDeep<Schema>
    : ValueForReading<Schema>;

type ResolvedSchemaFragment<Schema extends Record<string, ComponentSchema>, SlugField = void> = {
    [Key in keyof Schema]: SlugField extends Key
        ? Schema[Key] extends SlugFormField<any, any, any, infer SlugSerializedValue>
            ? SlugSerializedValue
            : ValueForReadingWithMode<Schema[Key], true>
        : ValueForReadingWithMode<Schema[Key], true>;
}

The ResolvedSchemaFragment is already present in keystatic, but it's hidden behind the reader factory return type.

The above allows me to create schema fragments that I can compose into collections within the keystatic config and export them as usable types in the context of how they're finally consumed.

import { config, fields, collection } from '@keystatic/core';
import type { ComponentSchema, ValueForReadingDeep, ValueForReading, SlugFormField } from '@keystatic/core';


type ValueForReadingWithMode<
    Schema extends ComponentSchema,
    ResolveLinkedFiles extends boolean | undefined
> = ResolveLinkedFiles extends true
    ? ValueForReadingDeep<Schema>
    : ValueForReading<Schema>;

type ResolvedSchemaFragment<Schema extends Record<string, ComponentSchema>, SlugField = void> = {
    [Key in keyof Schema]: SlugField extends Key
        ? Schema[Key] extends SlugFormField<any, any, any, infer SlugSerializedValue>
            ? SlugSerializedValue
            : ValueForReadingWithMode<Schema[Key], true>
        : ValueForReadingWithMode<Schema[Key], true>;
}




const PublishablePageSchema = {
    title: fields.slug({ name: { label: 'Title' }}),
    date: fields.date({ label: 'date' }),
    stage: fields.text({ label: 'Status' }),
}

export type PublishablePageFragment = ResolvedSchemaFragment<
    typeof PublishablePageSchema, 'title'
>


const TemplatablePageSchema = {
    theme: fields.text({ label: 'Theme' }),
    template: fields.select({
        label: 'Template',
        options: [{
            label: 'Post',
            value: 'post'
        },{
            label: 'Page',
            value: 'page'
        }],
        defaultValue: 'post',
    }),
}
export type TemplatablePageFragment = ResolvedSchemaFragment<
    typeof TemplatablePageSchema
>


const ContentfulPageSchema = {
    content: fields.document({
        label: 'Content',
        formatting: true,
        dividers: true,
        links: true,
        images: true,
    }),
}
export type ContentfulPageFragment = ResolvedSchemaFragment<
    typeof ContentfulPageSchema
>

export default config({
    storage: {
        kind: 'local',
    },
    collections: {
        pages: collection({
            label: 'About',
            slugField: 'title',
            path: 'src/content/about/*',
            format: { contentField: 'content' },
            entryLayout: 'content',
            schema: {
                ...PublishablePageSchema,
                ...TemplatablePageSchema,
                ...ContentfulPageSchema,
            }
        }),
    }
});

This results in allow me to compose types for my components like thus:

image

I guess where I'm going with this is that it would be useful to others if the types behind the createReader were available as public types for the above purpose.

Here's the part I extracted and adapted https://github.com/Thinkmill/keystatic/tree/main/packages/keystatic/src/app#L98C1-L124C5

wh

airtonix avatar Jan 12 '24 07:01 airtonix