Discussion about merging changes to @sanity-typed/types into Sanity.io and improving dev story for custom types.

18 replies
Last updated: Aug 9, 2023
Hey! I built
@sanity-typed/types
(link ) in an effort to get proper sanity types out of
defineType
,
defineField
, and
defineArrayMember
. I'd like to merge my changes as much as possible into sanity itself. My question is, would I just start making PRs to the repo? To get some of these changes in, I'd have to make some big changes and I'm hoping to get some confirmation that this is the direction sanity wants to go before I invest the time to do that.
Jun 13, 2023, 1:13 AM
// From sanity's docs: <https://www.sanity.io/docs/schema-field-types#e5642a3e8506>
// import { defineField, defineType } from "sanity";
import { defineField, defineType } from "@sanity-typed/types";
// This is where the magic happens
import { InferValue } from "@sanity-typed/types";

// Import this into sanity's createSchema, as usual.
export const product = defineType({
  name: "product",
  type: "document",
  title: "Product",
  fields: [
    defineField({
      name: "productName",
      type: "string",
      title: "Product name",
    }),
    defineField({
      name: "tags",
      type: "array",
      title: "Tags for item",
      of: [
        defineArrayMember({
          type: "object",
          name: "tag",
          fields: [
            { type: "string", name: "label" },
            { type: "string", name: "value" },
          ],
        }),
      ],
    }),
  ],
});

// Import this into your application!
export type Product = InferValue<typeof product>;

/**
 *  Product === {
 *    _createdAt: string;
 *    _id: string;
 *    _rev: string;
 *    _type: string;
 *    _updatedAt: string;
 *    productName?: string;
 *    tags?: ({
 *      label?: string;
 *      value: string;
 *    })[];
 *  };
 **/

Jun 13, 2023, 8:15 AM
Cool solution! I’ve forwarded this to the team to look at 🙇‍♂️
Jun 13, 2023, 8:16 AM
Hey, very cool. What is the dev story for working with custom types (alias/global/user defined types)? Ie how is "mytype" inferred?
Jun 13, 2023, 9:34 AM
user Q
I have one of a few ideas for this:1.
InferValue<typeof schema>
becomes
InferValue<typeof schema, { [type]: typeof customSchema }>
and it can bring in those custom schemas where they're used.2. Create a
MakeInferValue
, so there's the usual
InferValue<typeof schema>
, but then you can build your own
type MyInferValue<Value> = MakeInferValue<Value, { [type]: typeof customSchema }>
.3. Somehow, infer the values directly off of sanity's
createSchema
instead of using
InferValue
per schema. It'll have all of the
defineType
, so getting the custom types might be more feasible that way.4. Create a global
SanityCustomTypes
that
InferValue
uses and it's the user's job to augment with their new types.Ultimately, the `defineType`s from the "root" level have to somehow make their way back into where those custom types are referenced, so it can get weird (especially if things become cyclical). Would love some input.
Jun 13, 2023, 2:27 PM
Manually registrering custom types in intrinsic types lookup interface will work using module augmentation and interface merging, but I fear it will require a lot of boilerplate.
As you say, the cyclic nature of this makes it though. When we looked at this previously the dx story for this bit seems hard to get right.
Jun 13, 2023, 3:41 PM
Can you explain why custom types exist? It seems like it's for object reuse but, since all the schemas are just JS, it would be easier to just reuse the same object somewhere.
Instead of:

{ name: 'customName', type: 'object', fields: [...] }

{ name: 'somewhereElse, type: 'object', fields: [..., { name: 'foo', type: 'customName' }] }
Couldn't we do:

const customName = { name: 'customName', type: 'object', fields: [...] };

{ name: 'somewhereElse, type: 'object', fields: [..., customName] }
It feels a lot like reimplementing JS variables, but I'm assuming there's more to it.
Jun 13, 2023, 4:14 PM
But to my original question: I'd love to figure out what pieces of this can make its way into sanity itself. My goal is for it to all get in there but I'm imagining it'll happen in pieces. For example, to infer objects properly, a field's typescript definition needs to carry whether or not it's required, so the object can know whether it's
{ key?: value }
or
{ key: value }
. Should I just go for it with PRs against sanity for things like this?
Jun 13, 2023, 4:17 PM
Uhhh... idk if this is because an updated version of typescript did some magic, but this POC I wrote up for custom AND cyclic types worked immediately. It's unfortunate to have a breaking change for a library I released yesterday, but I think this solves all use cases right away.
Jun 13, 2023, 7:41 PM
Wow if this actually works, thats awesome. Are you sure that defining Foo and Bar here is not the reason the "cycle is broken"?
Keep in mind that plugins will complicate this, since they add to available types. But those could possible register using interface merging.

On named types:
You need them for type reuse. Code, color (plugins) for instance, and it is very common to have reusable types for portable text fields ect.
We encourage hoisting types like this, and is an actual requirement for our GraphQL api.

Im optimistic about where you are heading with this, but the devil is in the details if the full api surface
😅
Jun 14, 2023, 6:58 AM
I'll have to test it out, but it's looking promising. Even if the definitions are what do it, we can do this in two steps: one, that infers that intermediate step, and another that expands it all. If we want, we can have another generic that also expands
reference
into the document it references, for convenience.
I was imagining that plugin types could fit into the second parameter of that
ExpandNamedTypes<TypeToExpand, NamedTypes | PluginTypes | Etc>
. I'm imagining there's a DX that makes sense here.
I can see why the named types are needed for plugins, but a lot of what I see them used for is just for the
object
type registered as a schema and that one is a bit strange to me.
Jun 14, 2023, 7:10 PM
I was just experimenting with the new const type parameters , and that may also help tighten things down as we infer more and more!
Jun 14, 2023, 7:49 PM
I still have to push this with documentation but
user Q
, this just... works. No issues. Since the new infer type runs on the config directly we should theoretically be able to get types directly from
definePlugin
, eventually. I'm using documents instead of objects mainly because I don't know when `_type` is populated but, once I get that answer, this should work!
Jun 15, 2023, 8:03 AM
This looks.. amazing!Would this be drop-in to the or breaking change?
Jun 16, 2023, 1:22 PM
https://github.com/saiichihashimoto/sanity-typed/tree/main/packages/types
It's all pushed! I got it to include
definePlugin
types as well, so there's that! There's still some gaps but nothing new is broken, only things that weren't typed before continue to remain untyped. Regardless, it works, and it works well! There's a lot of tests, so you can browse through those and see how unchanged the schema API is.
I had to deviate a bit from sanity's own types to get these working, but not by much. I did my best to import
sanity
types directly so that any updates to sanity's types would make it in. I could start making PRs to sanity's codebase incrementally to get these in there, if I get the go ahead that that's welcome. I've tried to make the changes in a way that merging them in wouldn't break existing things, but I also don't want to invest the work if it's not something that sanity's interested in. Happy to run through it with whoever.
Jun 17, 2023, 8:06 AM
user J
great plugin :saluting_face: Can this plugin like creatw types to use on the front-end? Currently manually defining all the types. Thanks!
Jun 17, 2023, 9:44 PM
user Q
that’s what it’s for!
Jun 17, 2023, 9:45 PM
Will take a look and refactor docs and fields nice one!
Jun 17, 2023, 9:45 PM
Anyone following this thread, the front end client and groqs are now typed via @sanity-typed/client and @sanity-typed/groq.
Aug 9, 2023, 11:58 PM

Sanity– build remarkable experiences at scale

Sanity is a modern headless CMS that treats content as data to power your digital business. Free to get started, and pay-as-you-go on all plans.

Was this answer helpful?