GithubHelp home page GithubHelp logo

saiichihashimoto / sanity-typed Goto Github PK

View Code? Open in Web Editor NEW
148.0 148.0 7.0 4.33 MB

Completing sanity's developer experience with typescript (and more)!

License: MIT License

Shell 0.03% Ruby 0.01% JavaScript 0.05% TypeScript 99.92% CSS 0.01%
cms groq sanity sanity-io types typescript

sanity-typed's People

Contributors

cereallarceny avatar dependabot[bot] avatar hamzah-syed avatar kodiakhq[bot] avatar mckelveygreg avatar saiichihashimoto avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

sanity-typed's Issues

Passing a `name` or `title` to an `reference` or `object` (in `s.array`) returns Typescript error

I'm trying to create a reference, but for some reason, Typescript throws a fit when I pass it a name and a title field:

{
  name: 'advisors',
  title: 'Advisors',
  type: s.array({
    of: [
      s.reference({
        name: 'advisor', // ts error is here
        title: 'Advisor',
        to: [person],
      }),
    ],
  }),
}

To be clear, the code works perfectly, but Typescript is giving me grief:

Argument of type '{ name: string; title: string; to: [DocumentType<"person", { image: { _type: "image"; asset: Omit<Omit<Reference, "_weak">, "_type"> & { _type: "reference"; }; }; name: string; ... 9 more ...; _updatedAt: string; }, { ...; }, { ...; }>]; }' is not assignable to parameter of type '{ options?: ReferenceOptions | undefined; validation?: ValidationBuilder<TypedValueRule<SanityReference>, SanityReference> | undefined; ... 9 more ...; weak?: false | undefined; }'.
Object literal may only specify known properties, and 'name' does not exist in type '{ options?: ReferenceOptions | undefined; validation?: ValidationBuilder<TypedValueRule<SanityReference>, SanityReference> | undefined; ... 9 more ...; weak?: false | undefined; }'.ts(2345)
(property) name: string

Write Breaking Change Migration Guide

@sanity-typed/types will need another migration guide after #121, and needs one for 1.x to 2.x anyways. Neither are difficult, just something that needs doing. Put it directly into the README, no need for a separate document.

`s.document` should allow unrestricted unknown values for plugins

I'm using the document-internationalization plugin from Sanity, which requires the inclusion of a i18n boolean property on all documents I want to have internationalization support for. It looks like this:

s.document({
  name: 'about',
  title: 'About',
  i18n: true, // ts error
  ...
})

But it's throwing me the following Typescript error:

Argument of type '{ name: "about"; title: string; i18n: boolean; preview: { prepare: () => { title: string; }; }; groups: { name: string; title: string; }[]; fields: [{ name: "hero"; title: string; group: string; type: SanityType<{ type: string; ... 10 more ...; fieldsets?: FieldsetDefinition[] | undefined; }, { ...; }, { ...; }, { ....' is not assignable to parameter of type '{ description?: string | ReactElement<any, string | JSXElementConstructor> | undefined; title?: string | undefined; options?: DocumentOptions | undefined; ... 17 more ...; preview?: Preview<...> | undefined; }'.
Object literal may only specify known properties, and 'i18n' does not exist in type '{ description?: string | ReactElement<any, string | JSXElementConstructor> | undefined; title?: string | undefined; options?: DocumentOptions | undefined; ... 17 more ...; preview?: Preview<...> | undefined; }'.ts(2345)
(property) i18n: boolean

Ability to disable new on a reference?

Hello,

I am trying to use a reference but disable the "Create New" button which is mentioned here in the options:

https://www.sanity.io/docs/reference-type#disableNew-0ab01dd4f50c

const caseStudies = s.document({
  name: "caseStudies",
  title: "Case Studies",
  fields: [
    ...omitedForBrevity,
    {
      name: "tags",
      title: "Tags",
      type: s.reference({ to: [caseStudyTag] }), // How can I add `disableNew` here?
    },
  ],
});

Is this possible? I can't seem to find the value in the typing or any documentation on it

Preview, intialValue, etc types

@sanity-typed/types's version of #15 & #22.

The issue is ultimately that some methods within a schema need the derived type of the schema (or worse, something more upstream up to document types) to have the parameters fully typed. This might be complicated, considering cyclical types, many of the types need aliased types expanded, etc. This definitely needs to be broken down but I left it as one issue mainly because of the core issue of methods needing types that might come from the overall derived type(s).

Renaming `resolve` methods.

Migrated from saiichihashimoto/sanity-typed-schema-builder#160

Essentially the resolve methods and zods are named weirdly. They imply that they'll replace all the references with the actual documents, but that's untrue. That's not something we could even do. These libraries aren't going to groq for you, or know you have local references or anything of that nature. What it actually does is replace those references with mocks, which should be a lot clearer.

The other issue is that we should also infer a type that has everything resolved, so someone (@miklschmidt) can use that after they've resolved their own references. Essentially, resolving a doc for someone is not the library's job but giving the type of a resolved doc should be.

Unable to map through types and assign to a field

Passing in a mapped array of ArrayMembers causes this error:

  Source provides no match for required element at position 0 in target.

Whereas using a literal is fine

import quote from "./quote";
import ticker from "./ticker";

const allComponents = [quote, ticker];

const mappedArray = allComponents.map(component => 
  defineArrayMember({
    name: component.name,
    type: component.name,
  }),
);

const normalArray = [ // <-- working fine
  defineArrayMember({
    name: quote.name,
    type: quote.name,
  }),
  defineArrayMember({
    name: ticker.name,
    type: ticker.name,
  }),
] as const;

export const allComponentsField = defineField({
  name: "components",
  title: "Components",
  type: "array",

  of: [...normalArray],
  // of: [...mappedArray], <-- Throws error
});

Changing:

of: [TMemberDefinition, ...TMemberDefinition[]]

to:

of: TMemberDefinition[]

in the array type fixes this, but there are probably knock on negative effects to making this change.

Cyclical References

saiichihashimoto/sanity-typed-schema-builder#206
saiichihashimoto/sanity-typed-schema-builder#114
saiichihashimoto/sanity-typed-schema-builder#138

I'm going to leave this open because I believe it's generally unsolvable but will continue to be brought up.

There's no sane way to derive cyclical types (a tweet by @mattpocock), at least with our current pattern. There might be ways to accomplish a heuristic (deriving up to a certain level or allowing a next level derivation on demand, etc) but it's not going to "just work". Even finding a way to allow manual typing to mix-in has been a challenge. Until we can find a creative proof of concept, we'll have to leave this open as a "sorry this really sucks".

Zod version mismatch

Migrated from saiichihashimoto/sanity-typed-schema-builder#204 (@donalffons, @DanielMontilla, @danteissaias, @mckelveygreg).

When a project uses zod, this @sanity-typed/schema-builder gets really unhappy. Relying on a specific internal and external version of zod isn't great.

  • zod could be a peerDependency. I don't really like forcing users to maintain a version of zod if they aren't really using it.
  • Have the user provide z and we'd do all our operations off of that. Not my favorite again.

Generate Zod Parsers

Generating a zod parser from @sanity-typed/types, similar in functionality to @sanity-typed/schema-builder. Ideally, it would be given a Config and just work. Giving it options for specific types/fields/members will be tough, without adding to their specific define* methods (which I don't want to do, I want to keep those pure).

Allow Objects in config

Putting an "object" into defineType makes InferSchemaValues' result never, instead of allowing them.

I might make InferSchemaValues only return the document types? Maybe I should let the end user make these calls.

Type Error on validation method

Hey! Just noticed the transition to more of a types focus and that's awesome!
I just ran into an issue where I'm not able to use different Rule methods beyond .required(). I tried to crack it open, but I wasn't sure where / able to help the types narrow to the RuleDef's for each schema type?

Also, I tried to fallback to the @sanity/types Rule export, but it didn't like that either and gave me a sort of cryptic error.

And lastly, I think using a document defined with defineField in Sanity's defineConfig isn't working because of the validation bits.

I pulled down the repo and here is an example of a failing test:

    it("returns the same object as sanity", () => {
      const doc = defineType({
        name: "foo",
        type: "document",
        fields: [
          defineField({
            name: "bar",
            type: "boolean",
          }),
        ],
      });
      const sanityDoc = defineTypeNative({
        name: "foo",
        type: "document",
        fields: [
          defineFieldNative({
            name: "bar",
            type: "boolean",
          }),
        ],
      });
      // passing
      expect(doc).toStrictEqual(sanityDoc);
      // failing
      expectType<typeof doc>().toBeAssignableTo<SchemaTypeDefinition>();
    });

Let me know if I can provide any further information, as I would love to help propel this library and use case forward

Allow native `define*` to be nested (and vice versa)

I tried dropping in @sanity-types/types' defineField and defineType into my schema definitions, but now TS is complaining about schema definition fields added by the orderable-document-list plugin:

import { defineField, defineType } from '@sanity-typed/types';
import { orderRankField, orderRankOrdering } from '@sanity/orderable-document-list';

const nav = defineType({
  name: 'nav',
  type: 'document',
  title: 'Navigation',
  orderings: [orderRankOrdering],
  fields: [
    // ts2322 error for orderRankField
    orderRankField({ type: 'nav' }),
    defineField({
      name: 'name',
      type: 'string',
      title: 'Name',
      validation: (Rule) => Rule.required(),
    }),
  // ...
Type '{ type: "string"; name: "orderRank"; } & Omit<StringDefinition, "preview"> & FieldDefinitionBase & WidenValidation & WidenInitialValue' is not assignable to type 'DefinitionBase<any, any, any> & { name: string; [requiredSymbol]?: boolean | undefined; }'.
  Type '{ type: "string"; name: "orderRank"; } & Omit<StringDefinition, "preview"> & FieldDefinitionBase & WidenValidation & WidenInitialValue' is not assignable to type 'DefinitionBase<any, any, any>'.
    Types of property 'validation' are incompatible.
      Type '(ValidationBuilder<StringRule, string> & (false | Rule | SchemaValidationValue[] | ((rule: Rule) => SchemaValidationValue))) | undefined' is not assignable to type 'ValidationBuilder<any, any, any> | undefined'.
        Type 'ValidationBuilder<StringRule, string> & false' is not assignable to type 'ValidationBuilder<any, any, any> | undefined'.
          Type 'ValidationBuilder<StringRule, string> & false' is not assignable to type 'ValidationBuilder<any, any, any>'.
            Types of parameters 'rule' and 'rule' are incompatible.
              Type '{ [x: string]: any; required: () => { [x: string]: any; required: ...; [requiredSymbol]: true; }; [requiredSymbol]: false; }' is missing the following properties from type 'StringRule': min, max, length, uppercase, and 7 more.ts(2322)

Should sanity-typed be able to handle this out of the box? Do I need to help it along and define the plugin field types?

#18 seems to talk about this issue, but it's not clear if this is supposed to be fixed/handled in sanity-typed.

`defineConfig` type does not match native type

Object attributes are not available of a defineConfig object.

Example:

import { defineConfig } from '@sanity-typed/types';


const config = defineConfig({
  name: 'project',
  //...
});

config.name; // Does not exist according to typescript. All properties are available when using native `defineConfig` function.

// The only two properties available
config.toLocaleString();
config.toString();

export default config;

`defineConfig` apiVersion is for some reason not allowed

Not entirely sure why, but apiVersion is not supported:

Argument of type '{ apiVersion: string; title: string; projectId: string; dataset: string; basePath: string; schema: { types: ((Omit<{ type: "object"; groups?: FieldGroupDefinition[] | undefined; ... 12 more ...; fields: ((Omit<...> & ... 1 more ... & FieldDefinitionBase) | ... 1 more ... | (Omit<...> & ... 1 more ... & FieldDefiniti...' is not assignable to parameter of type 'Config<(Omit<{ type: "object"; groups?: FieldGroupDefinition[] | undefined; fieldsets?: FieldsetDefinition[] | undefined; options?: ObjectOptions | undefined; ... 10 more ...; fields: ((Omit<...> & ... 1 more ... & FieldDefinitionBase) | ... 1 more ... | (Omit<...> & ... 1 more ... & FieldDefinitionBase))[]; }, "FIX...'.
  Object literal may only specify known properties, and 'apiVersion' does not exist in type 'Config<(Omit<{ type: "object"; groups?: FieldGroupDefinition[] | undefined; fieldsets?: FieldsetDefinition[] | undefined; options?: ObjectOptions | undefined; ... 10 more ...; fields: ((Omit<...> & ... 1 more ... & FieldDefinitionBase) | ... 1 more ... | (Omit<...> & ... 1 more ... & FieldDefinitionBase))[]; }, "FIX...'.ts(2345)
(property) apiVersion: string

I ended up explicitly casting for now...

const config = defineConfig({
  apiVersion: "2023-06-04",
  title: "myapp",
  projectId: env.NEXT_PUBLIC_SANITY_PROJECT_ID,
  dataset: env.NEXT_PUBLIC_SANITY_DATASET,
  basePath: "/studio",
  schema: { types },
  plugins: [ deskTool(), visionTool({ defaultApiVersion: "2023-06-04" }) ],
} as Parameters<typeof defineConfig>[0]);

`_type` field of arrayMember is unknown

Thank you for all the swift replies and fantastic work @saiichihashimoto! The new structure is resolving a lot of the previously encountered problems and is very nice to work with.

Right now the _type field of an arrayMember is unknown.
Would be great to be able to use the _type field of an arrayMember as a type guard. I've tried to clarify what would be nice to achieve with the code below.

Schemas

const RowSection = defineType({
  title: 'Row',
  name: 'section.row',
  type: 'object',
  fields: [
    defineField({
      name: 'rowText',
      type: 'string'
    })
  ]
});

const ParagraphSection = defineType({
  title: 'Paragraph',
  name: 'section.paragraph',
  type: 'object',
  fields: [
    defineField({
      name: 'paragraphText',
      type: 'string'
    })
  ]
});

const BodyComponent = defineType({
  title: 'Body sections',
  name: 'component.body',
  type: 'array',
  of: [
    defineArrayMember({ type: 'section.row' }),
    defineArrayMember({ type: 'section.paragraph' }),
  ],
});

type Schema = InferSchemaValues<typeof config>;

Implementing

/**
 * Expected usage
 */
const body: Schema['component.body'];

body.map((section) => {
  section._type // This is `unknown` instead of `section.row` or `section.paragraph`
});


/**
 * Work around
 * This allows using `_type` field as type guard.
 */
type SectionType = Schema['section.row'] | Schema['section.paragraph'];
type BodyType = SectionType[];

const body: BodyType;

body.map((section) => {
  section._type // This is `section.row` or `section.paragraph`
});

Types defined in third party plugins renders as never

The extraction fails and returns never when trying to use a type defined in a plugin. Both cases below fails.

Case 1

import { defineType } from '@sanity-typed/types';

import type { SchemaValues } from '../../sanity.config'; // The result of using `InferSchemaValues`

const VideoComponent = defineType({
  title: 'Video',
  name: 'component.video',
  type: 'mux.video',
  preview: {
    select: {
      asset: 'media.asset.data',
    },
  },
});

export default VideoComponent;
export type VideoComponentSchemaType = Extract<SchemaValues, { _type: 'component.video' }>; // This is never

Case 2

import { defineField, defineType } from '@sanity-typed/types';

import type { SchemaValues } from '../../sanity.config'; // The result of using `InferSchemaValues`

const VideoComponent = defineType({
  title: 'Video',
  name: 'component.video',
  type: 'object',
  fields: [
    defineField({
      name: 'value',
      type: 'mux.video',
      preview: {
        select: {
          asset: 'media.asset.data',
        },
      },
    }),
  ],
});

export default VideoComponent;
export type VideoComponentSchemaType = Extract<SchemaValues, { _type: 'component.video' }>; // This is also never

Cannot create `objectNamed` that contains an `array`

I've been trying to create an objectNamed schema that I can use in my documents like this:

import faq from '../shared/faq';

...
{
  name: 'faq',
  title: 'Page FAQ',
  type: faq.ref()
}
...

And while I've been able to use many other objectNamed schemas in my documents, I cannot get this one to work. I'm not sure if this is a problem with how I'm writing it, a feature missing in the library, or a bug. I'd love your help getting to the bottom of it. Here's my objectNamed for faq:

import { s } from '@sanity-typed/schema-builder';

import wysiwyg from './wysiwyg';

export default s.objectNamed({
  name: 'faq',
  title: 'FAQ',
  fields: [
    {
      name: 'title',
      title: 'Title',
      type: s.string(),
    },
    {
      name: 'subtitle',
      title: 'Subtitle',
      type: s.text(),
    },
    {
      name: 'questions',
      title: 'Questions',
      type: s.array({
        of: [
          {
            name: 'question',
            title: 'Question',
            type: s.object({
              fields: [
                {
                  name: 'question',
                  title: 'Question',
                  type: s.string(),
                },
                wysiwyg({
                  name: 'answer',
                  title: 'Answer',
                }),
              ],
            }),
          },
        ],
      }),
    },
  ],
});

// export default defineField({
//   name: 'faq',
//   type: 'object',
//   title: 'FAQ',
//   fields: [
//     defineField({
//       name: 'title',
//       title: 'Title',
//       type: 'string',
//     }),
//     defineField({
//       name: 'subtitle',
//       title: 'Subtitle',
//       type: 'text',
//     }),
//     defineField({
//       name: 'questions',
//       title: 'Questions',
//       type: 'array',
//       of: [
//         defineArrayMember({
//           name: 'question',
//           title: 'Question',
//           type: 'object',
//           fields: [
//             defineField({
//               name: 'question',
//               title: 'Question',
//               type: 'string',
//             }),
//             defineField({
//               name: 'answer',
//               title: 'Answer',
//               type: 'wysiwyg',
//             }),
//           ],
//         }),
//       ],
//     }),
//   ],
// });
Screenshot 2023-05-20 at 11 09 11 AM

Basically I get a schema is not a function error. I've learned from converting other types to the schema builder that I want to perhaps do something like this instead:

import { s } from '@sanity-typed/schema-builder';

import wysiwyg from './wysiwyg';

export default s.objectNamed({
  name: 'faq',
  title: 'FAQ',
  fields: [
    {
      name: 'title',
      title: 'Title',
      type: s.string(),
    },
    {
      name: 'subtitle',
      title: 'Subtitle',
      type: s.text(),
    },
    {
      name: 'questions',
      title: 'Questions',
      ...s.array({
        of: [
          {
            ...s.object({
              fields: [
                {
                  name: 'question',
                  title: 'Question',
                  type: s.string(),
                },
                wysiwyg({
                  name: 'answer',
                  title: 'Answer',
                }),
              ],
            }),
            name: 'question',
            title: 'Question',
          },
        ],
      }),
    },
  ],
});

That then returns this error:

Screenshot 2023-05-20 at 11 21 31 AM

I also have a typescript error, but that's the least of my concerns right now. You'll note that I use a field called wysiwyg, which has its own problems that I'll describe in another issue.

Current typescript error on top-level fields array:

Type '[{ name: string; title: string; type: SanityType<{ options: (Omit<StringOptions | undefined, "list"> & { list?: [string | { title: string; value: string; }, ...(string | { ...; })[]] | undefined; }) | undefined; ... 7 more ...; placeholder?: string | undefined; }, string, string, string>; }, { ...; }, { ...; }]' is not assignable to type '[FieldOptions<string, ZodTypeAny, unknown, boolean>, ...FieldOptions<string, ZodTypeAny, unknown, boolean>[]]'.
Type at positions 1 through 2 in source is not compatible with type at position 1 in target.
Type '{ mock: (faker: Faker, path?: string | undefined) => { _key: string; }[]; parse: (data: unknown) => { _key: string; }[]; resolve: (data: unknown) => { _key: string; }[]; schema: () => { type: string; of: ItemDefinitions[]; ... 6 more ...; icon?: React.ReactNode | React.ComponentType<...>; }; zod: ZodType<...>; zodRe...' is not assignable to type 'FieldOptions<string, ZodTypeAny, unknown, boolean>'.
Property 'type' is missing in type '{ mock: (faker: Faker, path?: string | undefined) => { _key: string; }[]; parse: (data: unknown) => { _key: string; }[]; resolve: (data: unknown) => { _key: string; }[]; schema: () => { type: string; of: ItemDefinitions[]; ... 6 more ...; icon?: React.ReactNode | React.ComponentType<...>; }; zod: ZodType<...>; zodRe...' but required in type '{ name: string; optional?: boolean | undefined; type: SanityType<Omit<any, NamedSchemaFields>, any, any, unknown>; }'.ts(2322)
index.d.ts(81, 5): 'type' is declared here.
index.d.ts(618, 5): The expected type comes from property 'fields' which is declared here on type '{ description?: string | ReactElement<any, string | JSXElementConstructor> | undefined; title?: string | undefined; options?: ObjectOptions | undefined; ... 13 more ...; preview?: Preview<...> | undefined; }'
(property) fields: [FieldOptions<string, ZodTypeAny, unknown, boolean>, ...FieldOptions<string, ZodTypeAny, unknown, boolean>[]]

Type instantiation is excessively deep and possibly infinite. ts(2589)

Overview

I'm getting the following Typescript error on my schema. This is caused by a single image type I'm adding that's deeply nested in my schema. Commenting out this one field causes the type to inference correctly, and having other sibling fields (that aren't images) seems to work perfectly fine. Here's a screenshot of what I'm talking about:

image

I feel this is likely to be a reflection of Typescript setting a hard, internal limit at the recursive depth it's willing to handle. There's really only two ways around this:

  1. Refactor the implementation
  2. Find a more efficient way to calculate types

At the moment, I'm hard-wired into my implementation and can't modify it to avoid this problem. Any thoughts on what could be done inside of the typed package to support greater depth? I suppose that this could also problem be a problem for objects at this same depth, as an image is ultimately just an object itself.

Potential Solution/Ideas

Perhaps one way to get around this would to be able to generate intermediary types, so if I have a document called About with two inner fields called Hero and Team, then InferSchemaValues would generate 3 types: About, AboutHero, and AboutTeam (which, themselves may have many types internally). I could access the About document like this:

type Values = InferSchemaValues<typeof config>;

export type AboutDocument = Extract<Values, { _type: "about" }>;

But I could also extract other "sub-types" of the About document like this:

type Values = InferSchemaValues<typeof config>;

export type AboutDocument = Extract<Values, { _type: "about" }>;

// New
export type AboutHeroSection = Extract<Values, { _type: "about--hero" }>;
export type AboutTeamSection = Extract<Values, { _type: "about--team" }>;

In this solution, notice how the AboutHero and AboutTeam sections are at the same, root level as the About document. Anyhow, I have no idea is this would actually solve the problem or if there's a better idea altogether, but that's what I was able to come up with.

Use `TupleOfLength<..., 1>` in `fields`/`of`

Config, "object", "document", and "array" have to have at least one member for the runtime to function, so we might as well have typescript enforce it as well. It was breaking other things so I took it out, but we should bring it back.

Could we make `_InferValue` public?

Is there a specific reason _InferValue is considered private? I have a use case where I'd like a library of schema types that could be shared across projects. It appears the only way to expose the inferred type is to to infer from config however it would make sense to be able to do that beforehand:

i.e. (in library)

// my-entities-lib/blog-post.ts
export const blogPost = defineType({
  title: "Blog post"
  type: "document"
  fields: [
    defineField({
      name: "title",
      type: "string"
    }),
    defineField({
      name: "title",
      type: "string"
    })
    // ...
  ]
});

// Local type inference ๐Ÿ‘‡
export type BlogPost = InferType<typeof blogPost>;

Then in a project sanity config

// import the schema def ๐Ÿ‘‡
import { blogPost } from "my-entities-lib";

const config = defineConfig({
  schema: {
    types: [blogPost]
  }
});

Then in a project client code

// import the type ๐Ÿ‘‡
import { BlogPost } from "my-entities-lib";

const BlogPostPage = ({ blogPost }: { blogPost: BlogPost }) => {
  // ...
}

InferSchemaValues not working

Using the example from the readme yields a Values type where the specific schemas are not key but joined together.
Hence Values[...] is not working either. Am I doing something wrong here?

Sorry updating to version 3 fixed it.

Type instantiation is excessively deep and possibly infinite

image

Hi! Love the new approach but i'm running into this issue that I'm not able to solve on my side, do you have any tips/tricks to solve this? I'm guessing that the inferred type needs to be more calculated step by step but with my limited TS knowledge, no idea where to start

One way I solved this in the schema builder was creating multiple named objects and using ref so typescript would comply. I don't see a way of doing that here tho

Happens when using a defineArrayMember inside another defineArrayMember

`@sanity-typed/groq`

Update:
Been working on this in the alpha branch! Getting it to feature complete-enough and then we'll launch this!

Initial Description:
With types (and previously schema-builder), typing sanity documents has become possible. However, querying for these documents always goes through a groq query, which is completely unhandled by this library. This is all due to my original use case: query for the whole document and us JS/TS to deconstruct, so using the native types was fine. For other use cases, you end up doing a lot of Pick and Omit magic along with your queries, ultimately rewriting large parts of your queries in typescript anyways.

Sanity schemas are just JS objects, so typing them was a doable challenge. The issue with typing GROQs comes from the fact that they are giant strings, similar to GraphQL queries. Deriving a type from a string is... difficult, if not impossible. Pair that with the fact that most of this library exists in an attempt to merge into sanity and we run into the few possible approaches and their major issues:

  1. Type the GROQ strings anyways. This might be possible with typescript string literals and lots of typescript magic. If possible, it would provide the best DX: a drop in replacement for groq or whatever is currently being used for querying. Plus, it's the closest to a native solution, which ideally means that sanity could take ownership. The issue is that the scope of this seems massive, unmaintainable, and could be impossible. Even the types library uses some expert typescript magic. Deriving types from a GROQ string is definitely a write-once operation, where anyone coming in to make changes will be massively overwhelmed.
  2. Codegen. @ricokahler's work in sanity-codegen is the best path forward on this and it's clear that a feature complete solution is quite big (looks to be a month of dedicated work according to his estimates in ricokahler/sanity-codegen#297). I'm generally opposed to codegen but the fact that Apollo takes this approach with GraphQL is very telling that it's likely the most viable solution. The time investment is high and the likelihood that sanity will want to maintain a codegen library is low, which ultimately means running into the same issues he did.
  3. A builder pattern library, similar to schema-builder. This seems like the cleanest solution and the least prone to bugs. In terms of implementation, this feels like the most straightforward. A lot of the issues are similar to schema-builder. In schema-builder, rectifying sanity's schema documentation and the library's documentation was a lot of overhead for developers writing schemas and far from a drop-in solution, which left the problem feeling relatively unsolved. This is the same approach. It's definitely the farthest from a solution sanity would take ownership of, which also makes me disinterested in it. Also, I know there's a lot of work on this already (links are escaping me) so it doesn't seem worth treading the same ground.

My personal goal is to have the closest to a drop-in solution so sanity can eventually own it themselves. The issue is that these solutions each have a unique reason that sanity wouldn't (or shouldn't) maintain it internally and are the exact issues that other solutions run into. I can't think of a viable solution for this.

Better Documentation

The docs aren't great. A few things:

  • It used to be on github pages before the migration. Now, since we're in a monorepo, it's a bit fuzzy what goes where.
  • Examples (@bursteways).
  • Right now, it's just a huge page. I'd like to break it up in a sensible way, where API pages are separate from "here's how to use the library"
  • I also want to write all documentation just once. README, github pages, npm documentation, jsdocs, etc should come from one place, but let us do cool things (jsx example on the github pages but reverts to a code block everywhere else? Just spitballing). I don't want to maintain a bunch of different pieces of documentation, because I won't consistently do it.

`s.infer` returning unreliable typescript types for simple document schemas

Given the following document schema:

import { s } from '@sanity-typed/schema-builder';

import { wysiwyg } from './shared';

export default s.document({
  name: 'page',
  title: 'Page',
  preview: {
    select: {
      title: 'title',
      subtitle: 'lastUpdated.value',
    },
  },
  fields: [
    {
      name: 'title',
      title: 'Title',
      type: s.string(),
    },
    {
      name: 'lastUpdated',
      title: 'Last Updated',
      type: s.object({
        fields: [
          {
            name: 'title',
            title: 'Title',
            type: s.string(),
          },
          {
            name: 'value',
            title: 'Value',
            type: s.string(),
          },
        ],
      }),
    },
    wysiwyg({
      name: 'content',
      title: 'Content',
    }),
  ],
});

I get the following types produced via export type PageDocument = s.infer<typeof page>;:

type PageDocument = {
    _type: "page";
    _createdAt: string;
    _id: string;
    _rev: string;
    _updatedAt: string;
}

Note that I don't get any types related to title, lastUpdated, lastUpdated.title, lastUpdated.value, or content.

(For reference on wysiwyg, look at this issue)

Comb through for augmenting options

I want @sanity-typed/types in sanity, as much as possible. However, I haven't done much to augment the existing types so much as importing, mutating, and reexporting them. I think we can start augmenting them somewhat, so that importing this library just adds to existing types. That isn't 100% possible, but I think it'll be cleaner (and have a more direct migration path) if we can show certain things can just be directly changed.

Types: Types of referenced fields

Does the new @sanity-typed/types have a strategy to for referenced types?
For example,

export const headlines = defineType({
  type: "document",
  name: "headline",
  title: "Headline",
  fields: [
    defineField({
      name: "categories",
      title: "Headline Category",
      type: "array",
      of: [
        defineArrayMember({
          type: "reference",
          to: [{ type: headlinesCategory.name }],
        }),
      ],
    }),
...
  ],
})

export type Headlines = Omit<
  Extract<SCHEMA_VALUES, { _type: "headline" }>,
  "categories"
> & {
  categories: Array<Extract<SCHEMA_VALUES, { _type: "headlinesCategory" }>>
}

Currently I am using the strategy from the previous iteration of the Schema builder, otherwise my types for categories are just the sanity reference values ๐Ÿคท

Handle initialValue completely

Migrated from saiichihashimoto/sanity-typed-schema-builder#218 (@m-bp).

initialValue needs some love:

  • Show how to use it in docs.
  • Since we split types and fields, it's not clear where it goes.
  • Does this have implications with s.array? This might be a non issue. Since initialValue goes on the type, eg s.boolean({ initialValue: true }), this should work with s.array so that those are able to carry it.
  • I think the only technical bug here is that objectNamed({ ... }).ref({ initialValue: something }) doesn't work. Without that, we can't initialValue everything. I'm not 100% on what should be allowed there (ref() is also a really misleading name, might rename this), but I believe that we might solve most of our issues there.

Deprecate schema-builder

What a journey this library's been on, and the number of times it's been deprecated/moved ๐Ÿ™ƒ

The goal has always been a drop-in replacement and now that @sanity-typed/types exists, schema-builder is an artifact. It needs to find a deprecation path.

I'd like to have one version in npm that clearly states its deprecated and link to @sanity-typed/types (and some other alternatives, if anyone's interested), then remove it from the repo. I could have it use types under the hood instead of removing it, but that's just more maintenance burden. It's time to go.

Cannot create custom `block` type with `annotations` a typescript support

Instead of doing:

{
  name: 'wysiwyg',
  title: 'WYSIWYG',
  type: s.array({
    of: [{ s.block() }]
  })
}

I wanted to add a few more custom types to all WYSIWYG editors in my Sanity CMS. Namely, I want to add a custom objectNamed called youtube (code posted below), and an s.image() as well. In addition, I wanted to add 3 annotations: highlight, link, and button. I've managed to do all of this, but the way I create a wysiwyg editor is awkward and doesn't have complete type support:

  1. I couldn't figure out how to get it to be an objectNamed. Since it's actually an array, and not an object to begin with, the array function does not expose a ref, making this impossible.
  2. I could only figure out how to get the wysiwyg editor to render when I do this:
import wysiwyg from '../shared/wysiwyg';

...
wysiwyg({
  name: 'wysiwyg',
  title: 'The editor',
})
...

What would be desirable is to address it like any other objectNamed:

import wysiwyg from '../shared/wysiwyg';

...
{
  name: 'wysiwyg',
  title: 'The editor',
  type: wysiwyg.ref()
}
...
  1. The definition of my image and youtube types were really awkward. I cannot do:
{
  name: 'image',
  title: 'Image',
  type: s.image()
},
{
  name: 'youtube',
  title: 'YouTube',
  type: youtube.ref()
}

as you would expect. Instead, I need to do:

{
  ...s.image(),
  name: 'image',
  title: 'Image',
},
{
  ...youtube.ref(),
  name: 'youtube',
  title: 'YouTube',
},

This isn't a huge deal, and it works, but it's a big awkward.

  1. I could not properly type my annotations. This is what I had to do:
...
s.block({
  marks: {
    annotations: [
      {
        name: 'highlight',
        title: 'Highlight',
        type: 'color',
        icon: StringIcon,
      },
    ]
  }
})
...

Instead of:

import color from '../shared/color';

...
s.block({
  marks: {
    annotations: [
      {
        name: 'highlight',
        title: 'Highlight',
        type: color.ref()
        icon: StringIcon,
      },
    ]
  }
})
...

Here's the full source of the working (but certainly not "correct") WYSIWYG editor:

import { LinkIcon, StringIcon, UnpublishIcon } from '@sanity/icons';
import { s } from '@sanity-typed/schema-builder';

import youtube from './youtube';

export default (props: any) => ({
  name: 'wysiwyg',
  title: 'WYSIWYG',
  ...props,
  type: s.array({
    of: [
      s.block({
        marks: {
          annotations: [
            {
              name: 'highlight',
              title: 'Highlight',
              type: 'color',
              icon: StringIcon,
            },
            {
              name: 'link',
              title: 'Link',
              icon: LinkIcon,
              type: 'object',
              fields: [
                {
                  name: 'href',
                  title: 'URL',
                  type: 'string',
                },
              ],
            },
            {
              name: 'button',
              title: 'Button',
              icon: UnpublishIcon,
              type: 'object',
              fields: [
                {
                  name: 'link',
                  title: 'Link',
                  type: 'string',
                },
                {
                  name: 'isCentered',
                  title: 'Centered',
                  type: 'boolean',
                },
              ],
            },
          ],
        },
      }),
      {
        ...s.image(),
        name: 'image',
        title: 'Image',
      },
      {
        ...youtube.ref(),
        name: 'youtube',
        title: 'YouTube',
      },
    ],
  }),
});

// export default defineField({
//   name: 'wysiwyg',
//   title: 'WYSIWYG',
//   type: 'array',
//   of: [
//     defineArrayMember({
//       type: 'block',
//       marks: {
//         annotations: [
//           defineField({
//             name: 'highlight',
//             title: 'Highlight',
//             type: 'color',
//             icon: StringIcon,
//           }),
//           defineField({
//             name: 'link',
//             title: 'Link',
//             type: 'object',
//             icon: LinkIcon,
//             fields: [
//               defineField({
//                 name: 'href',
//                 title: 'URL',
//                 type: 'string',
//               }),
//             ],
//           }),
//           defineField({
//             name: 'button',
//             title: 'Button',
//             type: 'object',
//             icon: UnpublishIcon,
//             fields: [
//               defineField({
//                 name: 'link',
//                 title: 'Link',
//                 type: 'string',
//               }),
//               defineField({
//                 name: 'isCentered',
//                 title: 'Centered',
//                 type: 'boolean',
//               }),
//             ],
//           }),
//         ],
//       },
//     }),
//     defineArrayMember({
//       name: 'image',
//       title: 'Image',
//       type: 'image',
//     }),
//     defineArrayMember({
//       name: 'youtube',
//       title: 'YouTube',
//       type: 'youtube',
//     }),
//   ],
// });

I would love any help you can provide to get this in a better shape. I'm really committed to this library and am moving all my schemas to it. I'm just facing some problems in the migration.

`@sanity-typed/faker`

Generating a faker instance from @sanity-typed/types, similar in functionality to @sanity-typed/schema-builder. Ideally, it would be given a Config and just work. Giving it options for specific types/fields/members will be tough, without adding to their specific define* methods (which I don't want to do, I want to keep those pure).

InferSchemaValues should handle non-object types

Hey guys!

I have a schema file for blockContent as in this template: blockContent.js

I define it like this:

import { defineArrayMember, defineType } from '@sanity-typed/types';

export const blockContent = defineType({
  title: 'Block Content',
  name: 'blockContent',
  type: 'array',
  of: [
    defineArrayMember({
      title: 'Block',
      type: 'block',
    }),
    defineArrayMember({
      type: 'image',
      options: { hotspot: true },
    }),
  ],
});

Later I want to get the types via

export type BlockContent = Extract<Values, { _type: 'blockContent' }>;

resolving to never.
Also, the fields of type blockContent have the type unknown for the working inferred types.

Is it a bug or am I missing something?

How to define an array of custom types?

I'm trying to create a 'Page Builder' type that takes an array of other custom types ('hero', 'form', etc.). I keep getting errors when trying to configure it and I'm not sure what I'm doing wrong since there isn't an example of this being done in the README. Can anyone give me a pointer? Here is my current solution which results in every type except PageSchemaType to be never.

const hero = defineType({
  name: 'hero',
  type: 'object',
  title: 'Hero',
  fields: [
    defineField({
      name: 'heading',
      type: 'string',
      title: 'Heading',
      validation: (Rule) => Rule.required(),
    })
  ],
});

const form = defineType({
  name: 'form',
  type: 'object',
  title: 'Form',
  fields: [
    defineField({
      name: 'form',
      type: 'string',
      title: 'Form',
      description: 'Select form type',
      options: {
        list: ['newsletter', 'register', 'contact'],
      },
      validation: (Rule) => Rule.required(),
    }),
  ],
});

const page = defineType({
  name: 'page',
  type: 'document',
  title: 'Page',
  fields: [
    defineField({
      name: 'pageBuilder',
      type: 'array',
      title: 'Page builder',
      of: [
        defineField({ type: 'hero', name: 'hero', title: 'Hero' }),
        defineField({ type: 'form', name: 'form', title: 'Form' }),
      ],
    }),
  ],
});

const config = defineConfig({
  // ...
  schema: {
    types: [
      hero,
      form,
      page
    ],
  },
  // ...
});

type Values = InferSchemaValues<typeof config>;
export type PageSchemaType = Extract<Values, { _type: 'page' }>;
export type HeroSchemaType = Extract<Values, { _type: 'hero' }>; // becomes type 'never'
export type FormSchemaType = Extract<Values, { _type: 'form' }>; // becomes type 'never'

Fix object array unions

Hi and thank you @saiichihashimoto for your fantastic work with this project!

Here is my use case: I have a document named booklet that has an array of sections. Each section can be either frontCover or textContent.

// schema.js
const booklet = defineType({
  name: "booklet",
  type: "document",
  fields: [
    defineField({
      name: "sections",
      type: "array",
      of: [
        defineField({
          type: "object",
          name: "frontCover",
          fields: [
            defineField({
              name: "title",
              type: "string",
            }),
          ],
        }),
        defineField({
          type: "object",
          name: "textContent",
          fields: [
            defineField({
              name: "text",
              type: "string",
            }),
          ],
        }),
      ],
    }),
  ],
});

Later when I'm using the data, the following doesn't work:

type Booklet = Extract<SchemaValues, { _type: "booklet" }>;
const myBooklet: Booklet = ...;
// ...
for(const section of myBooklet.sections) {
  if(section._type === "frontCover") {
    console.log(section.title); // Error! Property "title" does not exist
  }
  if(section._type === "textContent") {
    console.log(section.text); // Error! Property "text" does not exist
  }
}

Maybe this is related to #96?!

String union types

Couldn't tell if this is possible, but how would I get a string union from a string type that has an options.list array?

My current work around is to use an enum and then send that along for my frontend to use.

I looked in to adding it, the test was easy enough to make, but I couldn't figure out where to crack into the system to peel out the values from the options.list array ๐Ÿคท LMK if I could be of help!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.