saiichihashimoto / sanity-typed Goto Github PK
View Code? Open in Web Editor NEWCompleting sanity's developer experience with typescript (and more)!
License: MIT License
Completing sanity's developer experience with typescript (and more)!
License: MIT License
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
@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.
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
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
@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).
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.
https://www.sanity.io/docs/array-type#c1a07c0b34ec
Maybe we can do this like s.number and s.string where it narrows the inferred types.
test-utils is a nice library! Split it out into it's own library.
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.
"object"
infers a different "_type"
based on how it's involved in the schema. Here's a gist including the different permutations:
https://gist.github.com/saiichihashimoto/e0165c09898b5f50628958a05b3c51bf
This isn't including anything else that produces objects, like "file"
, "image"
, or plugins.
As per discussion here: https://sanity-io-land.slack.com/archives/CT03N628J/p1687895922428849
It would be useful to be able to infer types for objects.
It appears the InferSchemaValues call successfully infers the object types, they are just not obviously extractable.
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".
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.
peerDependency
. I don't really like forcing users to maintain a version of zod if they aren't really using it.z
and we'd do all our operations off of that. Not my favorite again.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).
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.
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
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.
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;
Migrating from saiichihashimoto/sanity-typed-schema-builder#187 (@karlomedallo).
There's been no work in this repo for typing plugins. I haven't thought at all about all the implications, so there's probably a lot of work here. Now that sanity v3 is "just javascript", maybe this is more doable!
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]);
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.
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>;
/**
* 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`
});
The extraction fails and returns never
when trying to use a type defined in a plugin. Both cases below fails.
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
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
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',
// }),
// ],
// }),
// ],
// }),
// ],
// });
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:
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>[]]
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:
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:
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.
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.
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.
Migrating from saiichihashimoto/sanity-typed-schema-builder#155 (@mckelveygreg)
For whatever reason, the types that are generated weren't publishable. Might be related to this issue which was in the 4.8 milestone, but is now in the 5.1.0 milestone.
when passing config created by this library the component complains while it works when using the plain sanity define functions.
<NextStudio config={config} />
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 }) => {
// ...
}
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.
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
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:
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.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.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.
The docs aren't great. A few things:
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)
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.
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 ๐คท
Migrating from saiichihashimoto/sanity-typed-schema-builder#113 (@han-tyumi, @trevorpfiz, @Waltari10)
For whatever reason, this package needing zod
ended up requiring the end user to include an mjs loader in their webpack config, which is undocument and obnoxious.
I'd like to, either:
Migrated from saiichihashimoto/sanity-typed-schema-builder#218 (@m-bp).
initialValue
needs some love:
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.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.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.
s.crossDatasetReference
https://www.sanity.io/docs/cross-dataset-reference-type
I've never used it (it's an enterprise feature) but it's there.
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:
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.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()
}
...
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.
...
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.
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).
Migrating from saiichihashimoto/sanity-typed-schema-builder#210 and saiichihashimoto/sanity-typed-schema-builder#211 (@Kolby-Udacity, @schietegal).
Everything needing or providing a zod type returns the least narrow type, ZodType
. This works, but doesn't let us use specific methods, for example .pick
or .omit
on objects.
Migrated from saiichihashimoto/sanity-typed-schema-builder#167 (@benjick, @miklschmidt)
Typing the preview
field. I believe right now it tries some defaults which aren't perfect. It gets a bit strange with the select
, which ultimately introduces some magic.
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?
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'
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?!
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!
https://sanity-io-land.slack.com/archives/CT03N628J/p1686618799606549
@sanity-typed/types doesn't type named or custom types at all.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.