Thinkmill / Thinkmill/keystatic

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

Open
#858 0 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
2.4k
Forks
159
Avg merge
21h 41m
Merged PRs (30d)
2

Description

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

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at the createReader-related types in packages/keystatic/src/app, particularly the linked lines around 98-124, and compare them with the requested ResolvedSchemaFragment type. Trace how the existing type is exposed through the reader factory, then make the resolved entry type publicly importable and verify that composed schema fragments can use it as shown.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
developer-experience
Issue type
Feature
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.