saiichihashimoto / sanity-typed

Completing sanity's developer experience with typescript (and more)!
MIT License
149 stars 7 forks source link

Issues building dynamic fields; `SchemaTypeDefinition` equivalent? #703

Open coreyward opened 7 months ago

coreyward commented 7 months ago

I'm exploring using sanity-typed, but with a relatively simple studio I'm running into some issues. One I am not sure how to proceed on is figuring out how to handle dynamically defined schemas. For example, I have a list of object types that are used like content modules (called "blocks" in this case). The definitions for each are exported in an array from a "blocks/index.ts" file. With the vanilla TS studio, I can do the following to define a contentBlocks type that I can use as a field elsewhere:

import { SchemaTypeDefinition, defineField } from "sanity"
import blockDefs from "../blocks"

const extractNameFromBlockDef = (
  blockDef: SchemaTypeDefinition | SchemaTypeDefinition[],
) => {
  const block = Array.isArray(blockDef)
    ? blockDef.find((def) => def.name.endsWith("Block"))
    : blockDef

  if (!block) {
    throw new Error("No block found")
  }
  return block.name
}

export default defineField({
  title: "Content Blocks",
  name: "contentBlocks",
  type: "array",
  of: blockDefs.map((block) => ({
    type: extractNameFromBlockDef(block),
  })),
})

If I convert this over to @sanity-typed/types, I wind up with a slew of TS errors, most obviously from SchemaTypeDefinition not matching what @sanity-typed/types is doing. I tried looking for an equivalent, but it seems like all of the utility types require a long list of generics to be passed in. I saw elsewhere that using as const on array members inside of might be necessary, but that doesn't really work in a scenario where the value is not actually constant like this.

Any direction or guidance on how to approach this?

coreyward commented 7 months ago

It's seeming like @sanity-typed really doesn't work in situations where types cannot be inferred:

defineField({
  name: "example",
  type: "array",
  of: [{ type: "string" }].map((f) => defineArrayMember(f)),
})

This yields errors on of:

Type 'ArrayMemberDefinition<string, string, IntrinsicTypeName, StrictDefinition, number, string, string, false, BlockStyleDefault, BlockListItemDefault, BlockMarkDecoratorDefault, never, false, never, never, false>[]' is not assignable to type '[ArrayMemberDefinition<string, string, IntrinsicTypeName, StrictDefinition, number, string, string, false, BlockStyleDefault, BlockListItemDefault, BlockMarkDecoratorDefault, never, false, never, never, false>, ...ArrayMemberDefinition<...>[]]'.ts(2322) internal.d.ts(126, 5): The expected type comes from property 'of' which is declared here on type 'FieldDefinition<"array", "example", IntrinsicTypeName, StrictDefinition, number, string, string, false, BlockStyleDefault, BlockListItemDefault, BlockMarkDecoratorDefault, never, false, never, ArrayMemberDefinition<string, ... 14 more ..., false>,

Normally I'd explicitly type the array to resolve this, but without the means to do so it seems like my hands are tied.

saiichihashimoto commented 2 weeks ago

SO. I'm going to remove this restriction in the next version. I think that use cases like this are unavoidable and having sanity-typed be this strict on the of field seems like more of an obstacle than a benefit. This DOES mean that you'll often get the string type where you assumed you'd get a string literal because, somewhere, you didn't do as const.

saiichihashimoto commented 2 weeks ago

When #801 gets deployed, that may help with your issue!