Gathering detailed insights and metrics for @sanity-typed/schema-builder
Gathering detailed insights and metrics for @sanity-typed/schema-builder
Gathering detailed insights and metrics for @sanity-typed/schema-builder
Gathering detailed insights and metrics for @sanity-typed/schema-builder
sanity-typed-schema-builder
Build Sanity schemas declaratively and get typescript types of schema values for free!
groq-builder
A **schema-aware**, strongly-typed GROQ query builder. It enables you to build GROQ queries using **auto-completion**, **type-checking**, and **runtime validation**.
@danteissaias/groq-builder
A **schema-aware**, strongly-typed GROQ query builder. It enables you to build GROQ queries using **auto-completion**, **type-checking**, and **runtime validation**.
Completing sanity's developer experience with typescript (and more)!
npm install @sanity-typed/schema-builder
Typescript
Module System
Node Version
NPM Version
@sanity-typed/next-sanity@4.0.2
Updated on May 27, 2025
@sanity-typed/groq@3.0.2
Updated on May 27, 2025
@sanity-typed/faker@4.0.2
Updated on May 27, 2025
@sanity-typed/client-mock@4.0.2
Updated on May 27, 2025
@sanity-typed/client@5.0.2
Updated on May 27, 2025
@sanity-typed/groq-js@3.0.2
Updated on May 27, 2025
TypeScript (99.94%)
JavaScript (0.06%)
Total Downloads
0
Last Day
0
Last Week
0
Last Month
0
Last Year
0
MIT License
158 Stars
1,706 Commits
8 Forks
3 Watchers
5 Branches
5 Contributors
Updated on Jul 10, 2025
Latest Version
3.0.1
Package Id
@sanity-typed/schema-builder@3.0.1
Unpacked Size
115.40 kB
Size
17.37 kB
File Count
5
NPM Version
8.19.4
Node Version
18.16.1
Published on
Jun 30, 2023
Cumulative downloads
Total Downloads
Last Day
0%
NaN
Compared to previous day
Last Week
0%
NaN
Compared to previous week
Last Month
0%
NaN
Compared to previous month
Last Year
0%
NaN
Compared to previous year
5
2
@sanity-typed/types
It's approach is as close as possible to sanity's native schema definitions and will solve this problem in a more maintainable way. With the introduction of the
defineType
,defineField
, anddefineArrayMember
methods, direct inference of typed values is possible.
Build Sanity schemas declaratively and get typescript types of schema values for free!
datetime
values into javascript Date
)!1npm install @sanity-typed/schema-builder sanity
1import { s } from "@sanity-typed/schema-builder"; 2 3const foo = s.document({ 4 name: "foo", 5 fields: [ 6 { 7 name: "foo", 8 type: s.string(), 9 }, 10 { 11 name: "bar", 12 type: s.array({ 13 of: [s.datetime(), s.number({ readOnly: true })], 14 }), 15 }, 16 { 17 name: "hello", 18 optional: true, 19 type: s.object({ 20 fields: [ 21 { 22 name: "world", 23 type: s.number(), 24 }, 25 ], 26 }), 27 }, 28 ], 29}); 30 31// Use schemas in Sanity https://www.sanity.io/docs/schema-types 32export default defineConfig({ 33 schema: { 34 types: [foo.schema()], 35 }, 36});
Your sanity client's return values can be typed with s.infer
:
1import { createClient } from "@sanity/client"; 2 3const client = createClient(/* ... */); 4 5// results are automatically typed from the schema! 6const result: s.infer<typeof foo> = await client.fetch(`* [_type == "foo"][0]`); 7 8/** 9 * typeof result === { 10 * _createdAt: string; 11 * _id: string; 12 * _rev: string; 13 * _type: "foo"; 14 * _updatedAt: string; 15 * bar: (string | number)[]; 16 * foo: string; 17 * hello?: { 18 * world: number; 19 * }; 20 * }; 21 **/
Because sanity returns JSON values, some values require conversion (ie changing most date strings into Date
s). This is available with .parse
:
1const parsedValue: s.output<typeof foo> = foo.parse(result); 2 3/** 4 * typeof parsedValue === { 5 * _createdAt: Date; 6 * _id: string; 7 * _rev: string; 8 * _type: "foo"; 9 * _updatedAt: Date; 10 * bar: (Date | number)[]; 11 * foo: string; 12 * hello?: { 13 * world: number; 14 * }; 15 * }; 16 **/
Mocks that match your schema can be generated with .mock
:
1// Use @faker-js/faker to create mocks for tests! 2import { faker } from "@faker-js/faker"; 3 4const mock = foo.mock(faker); 5 6/** 7 * Same type as s.infer<typeof foo> 8 * 9 * typeof mock === { 10 * _createdAt: string; 11 * _id: string; 12 * _rev: string; 13 * _type: "foo"; 14 * _updatedAt: string; 15 * bar: (string | number)[]; 16 * foo: string; 17 * hello?: { 18 * world: number; 19 * }; 20 * }; 21 **/
All methods correspond to a Schema Type and pass through their corresponding Schema Type Properties as-is. For example, s.string(def)
takes the usual properties of the sanity string type. Sanity's types documentation should "just work" with these types.
The notable difference is between how the sanity schema, the type
property, and the name
/ title
/ description
property are defined. The differentiator is that the s.*
methods replace type
, not the entire field:
1// This is how schemas are defined in sanity 2const schema = { 3 type: "document", 4 name: "foo", 5 fields: [ 6 { 7 name: "bar", 8 title: "Bar", 9 description: "The Bar", 10 type: "string", 11 }, 12 ], 13}; 14 15// This is the corresponding type in @sanity-typed/schema-builder 16const type = s.document({ 17 name: "foo", 18 fields: [ 19 { 20 name: "bar", 21 title: "Bar", 22 description: "The Bar", 23 type: s.string(), 24 }, 25 ], 26}); 27 28// INVALID!!! 29const invalidType = s.document({ 30 name: "foo", 31 fields: [ 32 // This is invalid. s.string is a type, not an entire field. 33 s.string({ 34 name: "bar", 35 title: "Bar", 36 description: "The Bar", 37 }), 38 ], 39});
The only types with names directly in the type are s.document
(because all documents are named and not nested) and s.objectNamed
(because named objects have unique behavior from nameless objects).
For types with fields
(ie s.document
, s.object
, s.objectNamed
, s.file
, and s.image
) all fields
are required by default (rather than sanity's default, which is optional by default). You can set it to optional: true
.
1const type = s.object({ 2 fields: [ 3 { 4 name: "foo", 5 type: s.number(), 6 }, 7 { 8 name: "bar", 9 optional: true, 10 type: s.number(), 11 }, 12 ], 13}); 14 15type Value = s.infer<typeof type>; 16 17/** 18 * type Value === { 19 * foo: number; 20 * bar?: number; 21 * } 22 */ 23 24const parsedValue: s.output<typeof type> = type.parse(value); 25 26/** 27 * typeof parsedValue === { 28 * foo: number; 29 * bar?: number; 30 * } 31 */ 32 33const schema = type.schema(); 34 35/** 36 * const schema = { 37 * type: "object", 38 * fields: [ 39 * { 40 * name: "foo", 41 * type: "number", 42 * validation: (Rule) => Rule.validation(), 43 * }, 44 * { 45 * name: "bar", 46 * type: "number", 47 * }, 48 * ], 49 * }; 50 */
All array type properties pass through with the exceptions noted in Types.
Other exceptions include min
, max
, and length
. These values are used in the zod validations, the sanity validations, and the inferred types.
1const type = s.array({ 2 of: [s.boolean(), s.datetime()], 3}); 4 5type Value = s.infer<typeof type>; 6 7/** 8 * type Value === (boolean | string)[]; 9 */ 10 11const parsedValue: s.output<typeof type> = type.parse(value); 12 13/** 14 * typeof parsedValue === (boolean | Date)[]; 15 */ 16 17const schema = type.schema(); 18 19/** 20 * const schema = { 21 * type: "array", 22 * of: [{ type: "boolean" }, { type: "datetime" }], 23 * ... 24 * }; 25 */
1const type = s.array({ 2 min: 1, 3 of: [s.boolean()], 4}); 5 6type Value = s.infer<typeof type>; 7 8/** 9 * type Value === [boolean, ...boolean[]]; 10 */
1const type = s.array({ 2 max: 2, 3 of: [s.boolean()], 4}); 5 6type Value = s.infer<typeof type>; 7 8/** 9 * type Value === [] | [boolean] | [boolean, boolean]; 10 */
1const type = s.array({ 2 min: 1, 3 max: 2, 4 of: [s.boolean()], 5}); 6 7type Value = s.infer<typeof type>; 8 9/** 10 * type Value === [boolean] | [boolean, boolean]; 11 */
1const type = s.array({ 2 length: 3, 3 of: [s.boolean()], 4}); 5 6type Value = s.infer<typeof type>; 7 8/** 9 * type Value === [boolean, boolean, boolean]; 10 */
All block type properties pass through with the exceptions noted in Types.
1const type = s.block(); 2 3type Value = s.infer<typeof type>; 4 5/** 6 * type Value === PortableTextBlock; 7 */ 8 9const parsedValue: s.output<typeof type> = type.parse(value); 10 11/** 12 * typeof parsedValue === PortableTextBlock; 13 */ 14 15const schema = type.schema(); 16 17/** 18 * const schema = { 19 * type: "block", 20 * ... 21 * }; 22 */
All boolean type properties pass through with the exceptions noted in Types.
1const type = s.boolean(); 2 3type Value = s.infer<typeof type>; 4 5/** 6 * type Value === boolean; 7 */ 8 9const parsedValue: s.output<typeof type> = type.parse(value); 10 11/** 12 * typeof parsedValue === boolean; 13 */ 14 15const schema = type.schema(); 16 17/** 18 * const schema = { 19 * type: "boolean", 20 * ... 21 * }; 22 */
All date type properties pass through with the exceptions noted in Types.
1const type = s.date(); 2 3type Value = s.infer<typeof type>; 4 5/** 6 * type Value === string; 7 */ 8 9const parsedValue: s.output<typeof type> = type.parse(value); 10 11/** 12 * typeof parsedValue === string; 13 */ 14 15const schema = type.schema(); 16 17/** 18 * const schema = { 19 * type: "date", 20 * ... 21 * }; 22 */
All datetime type properties pass through with the exceptions noted in Types.
Other exceptions include min
and max
. These values are used in the zod validations and the sanity validations.
Datetime parses into a javascript Date
.
1const type = s.datetime(); 2 3type Value = s.infer<typeof type>; 4 5/** 6 * type Value === string; 7 */ 8 9const parsedValue: s.output<typeof type> = type.parse(value); 10 11/** 12 * typeof parsedValue === Date; 13 */ 14 15const schema = type.schema(); 16 17/** 18 * const schema = { 19 * type: "datetime", 20 * ... 21 * }; 22 */
All document type properties pass through with the exceptions noted in Types and Types with Fields.
1const type = s.document({ 2 name: "foo", 3 fields: [ 4 { 5 name: "foo", 6 type: s.number(), 7 }, 8 { 9 name: "bar", 10 optional: true, 11 type: s.number(), 12 }, 13 ], 14}); 15 16type Value = s.infer<typeof type>; 17 18/** 19 * type Value === { 20 * _createdAt: string; 21 * _id: string; 22 * _rev: string; 23 * _type: "foo"; 24 * _updatedAt: string; 25 * foo: number; 26 * bar?: number; 27 * }; 28 */ 29 30const parsedValue: s.output<typeof type> = type.parse(value); 31 32/** 33 * typeof parsedValue === { 34 * _createdAt: Date; 35 * _id: string; 36 * _rev: string; 37 * _type: "foo"; 38 * _updatedAt: Date; 39 * foo: number; 40 * bar?: number; 41 * }; 42 */ 43 44const schema = type.schema(); 45 46/** 47 * const schema = { 48 * name: "foo", 49 * type: "document", 50 * fields: [...], 51 * ... 52 * }; 53 */
All file type properties pass through with the exceptions noted in Types and Types with Fields.
1const type = s.file({ 2 fields: [ 3 { 4 name: "foo", 5 type: s.number(), 6 }, 7 { 8 name: "bar", 9 optional: true, 10 type: s.number(), 11 }, 12 ], 13}); 14 15type Value = s.infer<typeof type>; 16 17/** 18 * type Value === { 19 * _type: "file"; 20 * asset: { 21 * _type: "reference"; 22 * _ref: string; 23 * }; 24 * foo: number; 25 * bar?: number; 26 * }; 27 */ 28 29const parsedValue: s.output<typeof type> = type.parse(value); 30 31/** 32 * typeof parsedValue === { 33 * _type: "file"; 34 * asset: { 35 * _type: "reference"; 36 * _ref: string; 37 * }; 38 * foo: number; 39 * bar?: number; 40 * }; 41 */ 42 43const schema = type.schema(); 44 45/** 46 * const schema = { 47 * name: "foo", 48 * type: "file", 49 * fields: [...], 50 * ... 51 * }; 52 */
All geopoint type properties pass through with the exceptions noted in Types.
1const type = s.geopoint(); 2 3type Value = s.infer<typeof type>; 4 5/** 6 * type Value === { 7 * _type: "geopoint"; 8 * alt: number; 9 * lat: number; 10 * lng: number; 11 * }; 12 */ 13 14const parsedValue: s.output<typeof type> = type.parse(value); 15 16/** 17 * typeof parsedValue === { 18 * _type: "geopoint"; 19 * alt: number; 20 * lat: number; 21 * lng: number; 22 * }; 23 */ 24 25const schema = type.schema(); 26 27/** 28 * const schema = { 29 * type: "geopoint", 30 * ... 31 * }; 32 */
All image type properties pass through with the exceptions noted in Types and Types with Fields.
Other exceptions include hotspot
. Including hotspot: true
adds the crop
and hotspot
properties in the infer types.
1const type = s.image({ 2 fields: [ 3 { 4 name: "foo", 5 type: s.number(), 6 }, 7 { 8 name: "bar", 9 optional: true, 10 type: s.number(), 11 }, 12 ], 13}); 14 15type Value = s.infer<typeof type>; 16 17/** 18 * type Value === { 19 * _type: "image"; 20 * asset: { 21 * _type: "reference"; 22 * _ref: string; 23 * }; 24 * foo: number; 25 * bar?: number; 26 * }; 27 */ 28 29const parsedValue: s.output<typeof type> = type.parse(value); 30 31/** 32 * typeof parsedValue === { 33 * _type: "image"; 34 * asset: { 35 * _type: "reference"; 36 * _ref: string; 37 * }; 38 * foo: number; 39 * bar?: number; 40 * }; 41 */ 42 43const schema = type.schema(); 44 45/** 46 * const schema = { 47 * name: "foo", 48 * type: "image", 49 * fields: [...], 50 * ... 51 * }; 52 */
All number type properties pass through with the exceptions noted in Types.
Other exceptions include greaterThan
, integer
, lessThan
, max
, min
, negative
, positive
, and precision
. These values are used in the zod validations and the sanity validations.
1const type = s.number(); 2 3type Value = s.infer<typeof type>; 4 5/** 6 * type Value === number; 7 */ 8 9const parsedValue: s.output<typeof type> = type.parse(value); 10 11/** 12 * typeof parsedValue === number; 13 */ 14 15const schema = type.schema(); 16 17/** 18 * const schema = { 19 * type: "number", 20 * ... 21 * }; 22 */
All object type properties pass through with the exceptions noted in Types and Types with Fields.
1const type = s.object({ 2 fields: [ 3 { 4 name: "foo", 5 type: s.number(), 6 }, 7 { 8 name: "bar", 9 optional: true, 10 type: s.number(), 11 }, 12 ], 13}); 14 15type Value = s.infer<typeof type>; 16 17/** 18 * type Value === { 19 * foo: number; 20 * bar?: number; 21 * }; 22 */ 23 24const parsedValue: s.output<typeof type> = type.parse(value); 25 26/** 27 * typeof parsedValue === { 28 * foo: number; 29 * bar?: number; 30 * }; 31 */ 32 33const schema = type.schema(); 34 35/** 36 * const schema = { 37 * name: "foo", 38 * type: "object", 39 * fields: [...], 40 * ... 41 * }; 42 */
All object type properties pass through with the exceptions noted in Types and Types with Fields.
This is separate from s.object
because, when objects are named in sanity, there are significant differences:
_type
field equal to the object's name.1const type = s.objectNamed({ 2 name: "aNamedObject", 3 fields: [ 4 { 5 name: "foo", 6 type: s.number(), 7 }, 8 { 9 name: "bar", 10 optional: true, 11 type: s.number(), 12 }, 13 ], 14}); 15 16type Value = s.infer<typeof type>; 17 18/** 19 * type Value === { 20 * _type: "aNamedObject"; 21 * foo: number; 22 * bar?: number; 23 * }; 24 */ 25 26const parsedValue: s.output<typeof type> = type.parse(value); 27 28/** 29 * typeof parsedValue === { 30 * _type: "aNamedObject"; 31 * foo: number; 32 * bar?: number; 33 * }; 34 */ 35 36const schema = type.schema(); 37 38/** 39 * const schema = { 40 * name: "foo", 41 * type: "object", 42 * fields: [...], 43 * ... 44 * }; 45 */
1// Use `.namedType()` to reference it in another schema. 2const someOtherType = s.array({ of: [type.namedType()] }); 3 4// The reference value is used directly. 5type SomeOtherValue = s.infer<typeof someOtherType>; 6 7/** 8 * type SomeOtherValue = [{ 9 * _type: "aNamedObject"; 10 * foo: number; 11 * bar?: number; 12 * }]; 13 */ 14 15// The schema is made within the referencing schema 16const someOtherTypeSchema = someOtherType.schema(); 17 18/** 19 * const someOtherTypeSchema = { 20 * type: "array", 21 * of: [{ type: "" }], 22 * ... 23 * }; 24 */ 25 26defineConfig({ 27 schema: { 28 types: [type.schema(), someOtherType.schema()], 29 }, 30});
All reference type properties pass through with the exceptions noted in Types.
Reference resolves into the referenced document's mock.
Other exceptions include weak
. Including weak: true
adds the _weak: true
properties in the infer types.
1const type = s.reference({ 2 to: [someDocumentType, someOtherDocumentType], 3}); 4 5type Value = s.infer<typeof type>; 6 7/** 8 * type Value === { 9 * _ref: string; 10 * _type: "reference"; 11 * _weak?: boolean; 12 * }; 13 */ 14 15const parsedValue: s.output<typeof type> = type.parse(value); 16 17/** 18 * typeof parsedValue === { 19 * _ref: string; 20 * _type: "reference"; 21 * _weak?: boolean; 22 * }; 23 */ 24 25const schema = type.schema(); 26 27/** 28 * const schema = { 29 * type: "reference", 30 * to: [...], 31 * ... 32 * }; 33 */
1const type = s.reference({ 2 weak: true, 3 to: [someDocumentType, someOtherDocumentType], 4}); 5 6type Value = s.infer<typeof type>; 7 8/** 9 * type Value === { 10 * _ref: string; 11 * _type: "reference"; 12 * _weak: true; 13 * }; 14 */
All slug type properties pass through with the exceptions noted in Types.
Slug parses into a string.
1const type = s.slug(); 2 3type Value = s.infer<typeof type>; 4 5/** 6 * type Value === { 7 * _type: "slug"; 8 * current: string; 9 * }; 10 */ 11 12const parsedValue: s.output<typeof type> = type.parse(value); 13 14/** 15 * typeof parsedValue === string; 16 */ 17 18const schema = type.schema(); 19 20/** 21 * const schema = { 22 * type: "slug", 23 * ... 24 * }; 25 */
All string type properties pass through with the exceptions noted in Types.
Other exceptions include min
, max
, and length
. These values are used in the zod validations and the sanity validations.
1const type = s.string(); 2 3type Value = s.infer<typeof type>; 4 5/** 6 * type Value === string; 7 */ 8 9const parsedValue: s.output<typeof type> = type.parse(value); 10 11/** 12 * typeof parsedValue === string; 13 */ 14 15const schema = type.schema(); 16 17/** 18 * const schema = { 19 * type: "string", 20 * ... 21 * }; 22 */
All text type properties pass through with the exceptions noted in Types.
Other exceptions include min
, max
, and length
. These values are used in the zod validations and the sanity validations.
1const type = s.text(); 2 3type Value = s.infer<typeof type>; 4 5/** 6 * type Value === string; 7 */ 8 9const parsedValue: s.output<typeof type> = type.parse(value); 10 11/** 12 * typeof parsedValue === string; 13 */ 14 15const schema = type.schema(); 16 17/** 18 * const schema = { 19 * type: "text", 20 * ... 21 * }; 22 */
All url type properties pass through with the exceptions noted in Types.
1const type = s.url(); 2 3type Value = s.infer<typeof type>; 4 5/** 6 * type Value === string; 7 */ 8 9const parsedValue: s.output<typeof type> = type.parse(value); 10 11/** 12 * typeof parsedValue === string; 13 */ 14 15const schema = type.schema(); 16 17/** 18 * const schema = { 19 * type: "url", 20 * ... 21 * }; 22 */
In addition to the default sanity schema types, you may have nonstandard types (custom asset sources like MUX Input or unique inputs like code input).
s.createType
allows for creation of a custom type. It returns an object of type s.SanityType<Definition, Value, ParsedValue, ResolvedValue>
. All provided s.*
methods use this, so it should be fully featured for any use case.
An example using Mux Input (not including installing the plugin):
1import { faker } from "@faker-js/faker"; 2import { z } from "zod"; 3 4import { s } from "@sanity-typed/schema-builder"; 5 6const muxVideo = () => 7 s.createType({ 8 // `schema` returns the sanity schema type 9 schema: () => ({ type: "mux.video" } as const), 10 11 // `mock` returns an instance of the native sanity value 12 // `faker` will have a stable `seed` value 13 mock: (faker) => 14 ({ 15 _type: "mux.video", 16 asset: { 17 _type: "reference", 18 _ref: faker.datatype.uuid(), 19 }, 20 } as const), 21 22 // `zod` is used for parsing this type 23 zod: z.object({ 24 _type: z.literal("mux.video"), 25 asset: z.object({ 26 _type: z.literal("reference"), 27 _ref: z.string(), 28 }), 29 }), 30 31 // `zodResolved` is used for parsing into the resolved value 32 // defaults to reusing `zod` 33 zodResolved: z 34 .object({ 35 _type: z.literal("mux.video"), 36 asset: z.object({ 37 _type: z.literal("reference"), 38 _ref: z.string(), 39 }), 40 }) 41 .transform( 42 ({ asset: { _ref: playbackId } }) => resolvedValues[playbackId] 43 ), 44 }); 45 46const type = document({ 47 name: "foo", 48 fields: [ 49 { 50 name: "video", 51 type: muxVideo(), 52 }, 53 ], 54}); 55 56const value = type.mock(faker); 57 58/** 59 * typeof value === { 60 * _createdAt: string; 61 * _id: string; 62 * _rev: string; 63 * _type: "foo"; 64 * _updatedAt: string; 65 * video: { 66 * _type: "mux.video"; 67 * asset: { 68 * _ref: string; 69 * _type: "reference"; 70 * }; 71 * }; 72 * }; 73 */ 74 75const parsedValue: s.output<typeof type> = type.parse(value); 76 77/** 78 * typeof parsedValue === { 79 * _createdAt: Date; 80 * _id: string; 81 * _rev: string; 82 * _type: "foo"; 83 * _updatedAt: Date; 84 * video: { 85 * _type: "mux.video"; 86 * asset: { 87 * _ref: string; 88 * _type: "reference"; 89 * }; 90 * }; 91 * }; 92 */ 93 94const resolvedValue: s.resolved<typeof type> = type.resolve(value); 95 96/** 97 * typeof resolvedValue === { 98 * _createdAt: Date; 99 * _id: string; 100 * _rev: string; 101 * _type: "foo"; 102 * _updatedAt: Date; 103 * video: (typeof resolvedValues)[string]; 104 * }; 105 */ 106 107const schema = type.schema(); 108 109/** 110 * const schema = { 111 * name: "foo", 112 * type: "document", 113 * fields: [ 114 * { 115 * name: "video", 116 * type: "mux.video", 117 * }, 118 * ], 119 * }; 120 */
Due to sanity's transport layer being JSON (and whatever reason slug
has for being wrapped in an object), some of sanity's return values require some transformation in application logic. Every type includes a .parse(value)
method that transforms values to a more convenient value.
We accomplish that using Zod, a powerful schema validation library with full typescript support. A few of the types have default transformations (most notably s.datetime
parsing into a javascript Date
object). The zod types are available for customization, allowing your own transformations.
1const type = s.document({ 2 name: "foo", 3 // If you dislike the dangling underscore on `_id`, this transforms it to `id`: 4 zod: (zod) => zod.transform(({ _id: id, ...doc }) => ({ id, ...doc })), 5 fields: [ 6 { 7 name: "aString", 8 type: s.string(), 9 }, 10 { 11 name: "aStringLength", 12 type: s.string({ 13 // For whatever reason, if you want the length of the string instead of the string itself: 14 zod: (zod) => zod.transform((value) => value.length), 15 }), 16 }, 17 { 18 name: "aDateTime", 19 type: s.datetime(), 20 }, 21 { 22 name: "aSlug", 23 type: s.slug(), 24 }, 25 ], 26}); 27 28const value: type Value === { 29 /* ... */ 30}; 31 32/** 33 * This remains the same: 34 * 35 * typeof value === { 36 * _createdAt: string; 37 * _id: string; 38 * _rev: string; 39 * _type: "foo"; 40 * _updatedAt: string; 41 * aString: string; 42 * aStringLength: string; 43 * aDateTime: string; 44 * aSlug: { 45 * _type: "slug"; 46 * current: string; 47 * }; 48 * } 49 */ 50 51const parsedValue: s.output<typeof type> = type.parse(value); 52 53/** 54 * Notice the changes: 55 * 56 * typeof parsedValue === { 57 * _createdAt: string; 58 * _rev: string; 59 * _type: "foo"; 60 * _updatedAt: string; 61 * id: string; 62 * aString: string; 63 * aStringLength: number; 64 * aDateTime: Date; 65 * aSlug: string; 66 * } 67 */
Sanity values are used directly in react components or application code that needs to be tested. While tests tend to need mocks that are specific to isolated tests, autogenerated mocks are extremely helpful. Every type includes a .mock(faker)
method that generates mocks of that type.
We accomplish that using Faker, a powerful mocking library with full typescript support. All of the types have default mocks. The mock methods are available for customization, allowing your own mocks.
Note: Each type will create it's own instance of Faker
with a seed
based on it's path in the document, so mocked values for any field should remain consistent as long as it remains in the same position.
1import { faker } from "@faker-js/faker"; 2 3const type = s.document({ 4 name: "foo", 5 fields: [ 6 { 7 name: "aString", 8 type: s.string(), 9 }, 10 { 11 name: "aFirstName", 12 type: s.string({ 13 mock: (faker) => faker.name.firstName(), 14 }), 15 }, 16 ], 17}); 18 19const value = type.mock(faker); 20 21/** 22 * typeof value === { 23 * _createdAt: string; 24 * _id: string; 25 * _rev: string; 26 * _type: "foo"; 27 * _updatedAt: string; 28 * aString: string; 29 * aFirstName: string; 30 * } 31 * 32 * value.aString === "Seamless" 33 * value.aFirstName === "Katelynn" 34 */
Sanity values often reference something outside of itself, most notably s.reference
referencing other documents. Applications determine how those resolutions happen (in the case of s.reference
, usually via groq queries) but tests that require resolved values shouldn't rebuild that logic. Every type includes a .resolve(value)
method that resolves mocks of that type.
We accomplish that using Zod, a powerful schema validation library with full typescript support. All of the types have default resolutions. The resolution methods are available for customization, allowing your own resolution.
1import { faker } from "@faker-js/faker"; 2 3const barType = s.document({ 4 name: "bar", 5 fields: [ 6 { 7 name: "value", 8 type: s.string(), 9 }, 10 ], 11}); 12 13const nonSanityMocks: Record<string, NonSanity> = { 14 /* ... */ 15}; 16 17const type = s.document({ 18 name: "foo", 19 fields: [ 20 { 21 name: "bar", 22 type: s.reference({ to: [barType] }), 23 }, 24 { 25 name: "aString", 26 type: s.string(), 27 }, 28 { 29 name: "nonSanity", 30 type: s.string({ 31 zodResolved: (zod) => zod.transform((value) => nonSanityMocks[value]!), 32 }), 33 }, 34 ], 35}); 36 37const value = type.resolve(type.mock(faker)); 38 39/** 40 * typeof value === { 41 * _createdAt: Date; 42 * _id: string; 43 * _rev: string; 44 * _type: "foo"; 45 * _updatedAt: Date; 46 * bar: { 47 * _createdAt: Date; 48 * _id: string; 49 * _rev: string; 50 * _type: "bar"; 51 * _updatedAt: Date; 52 * value: string; 53 * }; 54 * aString: string; 55 * nonSanity: NonSanity; 56 * } 57 */
No vulnerabilities found.
No security vulnerabilities found.