npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@sanity-typed/types

v7.0.1

Published

Infer Sanity Document Types from Sanity Schemas

Downloads

7,256

Readme

@sanity-typed/types

NPM Downloads GitHub commit activity (branch) GitHub Repo stars GitHub contributors GitHub issues by-label Minified Size License

GitHub Sponsors

Infer Sanity Document Types from Sanity Schemas

Watch How to Type Your Sanity Document and Client

Page Contents

Install

npm install sanity @sanity-typed/types

Usage

Use defineConfig, defineType, defineField, and defineArrayMember from this library exactly as you would from sanity. Then, use InferSchemaValues to get the typescript types!

product.ts:

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

/** No changes using defineType, defineField, and defineArrayMember */
export const product = defineType({
  name: "product",
  type: "document",
  title: "Product",
  fields: [
    defineField({
      name: "productName",
      type: "string",
      title: "Product name",
      validation: (Rule) => Rule.required(),
    }),
    defineField({
      name: "tags",
      type: "array",
      title: "Tags for item",
      of: [
        defineArrayMember({
          type: "object",
          name: "tag",
          fields: [
            defineField({ type: "string", name: "label" }),
            defineField({ type: "string", name: "value" }),
          ],
        }),
      ],
    }),
  ],
});

sanity.config.ts:

import { structureTool } from "sanity/structure";

// import { defineConfig } from "sanity";
import { defineConfig } from "@sanity-typed/types";
import type { InferSchemaValues } from "@sanity-typed/types";

import { post } from "./schemas/post";
import { product } from "./schemas/product";

/** No changes using defineConfig */
const config = defineConfig({
  projectId: "59t1ed5o",
  dataset: "production",
  plugins: [structureTool()],
  schema: {
    types: [
      product,
      // ...
      post,
    ],
  },
});

export default config;

/** Typescript type of all types! */
export type SanityValues = InferSchemaValues<typeof config>;
/**
 *  SanityValues === {
 *    product: {
 *      _createdAt: string;
 *      _id: string;
 *      _rev: string;
 *      _type: "product";
 *      _updatedAt: string;
 *      productName: string;
 *      tags?: {
 *        _key: string;
 *        _type: "tag";
 *        label?: string;
 *        value?: string;
 *      }[];
 *    };
 *    // ... all your types!
 *  }
 */

DocumentValues

While InferSchemaValues gives you all the types for a given config keyed by type, sometimes you just want a union of all the SanityDocuments. Drop it into DocumentValues:

import type { DocumentValues, InferSchemaValues } from "@sanity-typed/types";

const config = defineConfig({
  /* ... */
});

type SanityValues = InferSchemaValues<typeof config>;
/**
 *  SanityValues === { [type: string]: TypeValueButSomeTypesArentDocuments }
 */

type SanityDocuments = DocumentValues<SanityValues>;
/**
 *  SanityDocuments === Each | Document | In | A | Union
 */

Plugins

Writing typed plugins

Use definePlugin from this library exactly as you would from sanity's own exports.

my-plugin.ts:

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

/** No changes using definePlugin */
export const myPlugin = definePlugin({
  name: "plugin",
  schema: {
    types: [
      defineType({
        name: "myPlugin",
        type: "object",
        fields: [
          defineField({
            name: "baz",
            type: "boolean",
          }),
        ],
      }),
    ],
  },
});

sanity.config.ts:

// import { defineConfig, defineField, defineType } from "sanity";
import { defineConfig, defineField, defineType } from "@sanity-typed/types";
import type { InferSchemaValues } from "@sanity-typed/types";

import { myPlugin } from "./my-plugin";

const foo = defineType({
  name: "foo",
  type: "document",
  fields: [
    defineField({
      name: "bar",
      type: "myPlugin",
    }),
  ],
});

const config = defineConfig({
  schema: {
    types: [foo],
  },
  plugins: [myPlugin()],
});

export default config;

type SanityValues = InferSchemaValues<typeof config>;

export type Foo = SanityValues["foo"];
/**
 *  Foo === {
 *    _createdAt: string;
 *    _id: string;
 *    _rev: string;
 *    _type: "foo";
 *    _updatedAt: string;
 *    bar?: {
 *      _type: "myPlugin";
 *      baz?: boolean;
 *    };
 *  };
 **/

However, this export won't work for users who are using sanity's default methods. So that you won't have to define your plugin twice, we provide a castFromTyped method, which converts the outputs of any define* method to their native sanity counterparts:

import { castFromTyped, definePlugin } from "@sanity-typed/types";

export const myTypedPlugin = definePlugin({
  name: "plugin",
  schema: {
    types: [
      // ...
    ],
  },
});

// You'll likely want this as a default export as well!
export const myUntypedPlugin = castFromTyped(myTypedPlugin);

Using external untyped plugins

sanity-typed also works directly with untyped definePlugin directly, so you can import and use plugins directly (although they type as unknown values). It doesn't handle untyped defineField/defineArrayMember/defineType though, and some plugins export some for convenience. castToTyped similarly converts untyped define* methods to sanity-typed versions with unknown values:

import { orderRankField } from "@sanity/orderable-document-list";

import { castToTyped } from "@sanity-typed/types";

const nav = defineType({
  name: "nav",
  type: "document",
  title: "Navigation",
  fields: [
    castToTyped(orderRankField({ type: "nav" })),
    defineField({
      name: "name",
      type: "string",
      title: "Name",
      validation: (Rule) => Rule.required(),
    }),
  ],
});

Considerations

Types match config but not actual documents

As your sanity driven application grows over time, your config is likely to change. Keep in mind that you can only derive types of your current config, while documents in your Sanity Content Lake will have shapes from older configs. This can be a problem when adding new fields or changing the type of old fields, as the types won't can clash with the old documents.

Ultimately, there's nothing that can automatically solve this; we can't derive types from a no longer existing config. This is a consideration with or without types: your application needs to handle all existing documents. Be sure to make changes in a backwards compatible manner (ie, make new fields optional, don't change the type of old fields, etc).

Another solution would be to keep old configs around, just to derive their types:

const config = defineConfig({
  schema: {
    types: [foo],
  },
  plugins: [myPlugin()],
});

const oldConfig = defineConfig({
  schema: {
    types: [oldFoo],
  },
  plugins: [myPlugin()],
});

type SanityValues =
  | InferSchemaValues<typeof config>
  | InferSchemaValues<typeof oldConfig>;

This can get unwieldy although, if you're diligent about data migrations of your old documents to your new types, you may be able to deprecate old configs and remove them from your codebase.

Typescript Errors in IDEs

Often you'll run into an issue where you get typescript errors in your IDE but, when building workspace (either you studio or app using types), there are no errors. This only occurs because your IDE is using a different version of typescript than the one in your workspace. A few debugging steps:

VSCode

Breaking Changes

6 to 7

Typescript version from 5.4.2 <= x <= 5.6.3

The supported Typescript version is now 5.4.2 <= x <= 5.6.3. Older versions are no longer supported and newer versions will be added as we validate it.

as const needed for certain types to infer correctly

Like mentioned in 6 no longer forces as const, as const is no required anywhere excent for references (otherwise they wouldn't reference correctly), but you will still want them in many places. Literals where it narrows the type are the usual candidates (ie string and number lists). But there are a few others, ie options.hotspot for the image type needs it to be typed as true to infer the hotspot fields. Due to typescript quirks, sometimes you'll need to add true as const for it to infer correctly.

Until we get a proper understanding on how we can force typescript to infer the literals, we won't enforce it anywhere except for references. This is because it's a convenience everywhere else; references are rarely what you want without it.

6 no longer forces as const

Besides for references, as const is no longer needed for some of the types. While it will still type string literals when possible, it won't be required. You'll still need as const if you actually want the literal types, but it was breaking too many valid workflows to require it.

5 to 6

Block fields require as const

Similar to references, to get the right types out of a block, we'll need as const with styles[number].value and lists[number].value. Also, marks.annotations[number] now requires typing like other array members, ie defineArrayMember:

const foo = defineType({
  name: "foo",
  type: "array",
  of: [
    defineArrayMember({
      type: "block",
      styles: [
-       { title: "Foo", value: "foo" },
+       { title: "Foo", value: "foo" as const },
-       { title: "Bar", value: "bar" },
+       { title: "Bar", value: "bar" as const },
      ],
      lists: [
-       { title: "Foo", value: "foo" },
+       { title: "Foo", value: "foo" as const },
-       { title: "Bar", value: "bar" },
+       { title: "Bar", value: "bar" as const },
      ],
      marks: {
        annotations: [
-         {
+         defineArrayMember({
            name: "internalLink",
            type: "object",
            fields: [
-             {
+             defineField({
                name: "reference",
                type: "reference",
-               to: [{ type: "post" }],
+               to: [{ type: "post" as const }],
-             },
+             }),
            ],
-         },
+         }),
        ],
      },
    }),
  ],
});

4 to 5

Removed _InferValue and AliasValue

Use InferSchemaValues instead. Neither _InferValue nor AliasValue are directly usable, while InferSchemaValues is the only real world use case.

3 to 4

Referenced _type needs as const

For @sanity-typed/groq to infer the right types from references, the reference type needs to carry the type it's referencing along with it. Unfortunately, it isn't deriving the literal so an as const is needed.

const product = defineType({
  name: "product",
  type: "document",
  title: "Product",
  fields: [
    defineField({
      name: "foo",
      type: "reference",
-     to: [{ type: "referencedType" }],
+     to: [{ type: "referencedType" as const }],
    }),
  ],
});

Renamed DocumentValue to SanityDocument

- import type { DocumentValue } from "@sanity-typed/types";
+ import type { SanityDocument } from "@sanity-typed/types";

2 to 3

InferSchemaValues

InferSchemaValues<typeof config> used to return a union of all types but now returns an object keyed off by type. This is because using Extract to retrieve specific type was difficult. Object types would have a _type for easy extraction, but all the other types were less reliable (i.e. arrays and primitives).

export default config;

type Values = InferSchemaValues<typeof config>;

- export type Product = Extract<Values, { _type: "product" }>
+ export type Product = Values["product"];

InferValue

Types used to be inferred using InferValue<typeof type> for easy exporting. Now, InferSchemaValues<typeof config> needs to be used, and individual types keyed off of it. The reason for this is that only the config has context about aliased types, so InferValue was always going to be missing those values.

const product = defineType({
  name: "product",
  type: "document",
  title: "Product",
  fields: [
    // ...
  ],
});

- export type Product = InferValue<typeof product>;

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

export default config;

type Values = InferSchemaValues<typeof config>;

+ export type Product = Values["product"];

You can still use _InferValue but this is discouraged, because it will be missing the context from the config (and is removed in v5):

const product = defineType({
  name: "product",
  type: "document",
  title: "Product",
  fields: [
    // ...
  ],
});

- export type Product = InferValue<typeof product>;
+ export type Product = _InferValue<typeof product>;

Alternatives