fabian-hiller / valibot

The modular and type safe schema library for validating structural data 🤖
https://valibot.dev
MIT License
5.67k stars 170 forks source link

Empty array in optional as a default value turns the type into union of the type or never #593

Closed jansedlon closed 1 month ago

jansedlon commented 1 month ago

Given this schema

const ResourceSchema = variant("type", [
  object({
    type: literal("link"),
    url: pipe(string(), url()),
    label: pipe(string(), minLength(1)),
  }),
  object({
    type: literal("file"),
    filename: pipe(string(), minLength(1)),
    size: pipe(string(), minLength(1)),
    location: pipe(string(), userUploadValidation(true)),
    status: picklist(["uploading", "error", "done"]),
  }),
]);

export const CourseSectionSchema = object({
  title: pipe(string(), minLength(1)),
  content: string(),
  video: pipe(
    union([pipe(string(), url()), literal("")]),
    transform((val) => val || null),
  ),
  resources: optional(array(ResourceSchema), []),
});

export type CourseSectionSchemaData = InferOutput<typeof CourseSectionSchema>;

The resources type is inferred as never or the type. It shouldn't really be never. I'm on version 0.31

image
fabian-hiller commented 1 month ago

Thank you for creating this issue, I will try to find a solution soon!

fabian-hiller commented 1 month ago

This should be fixed now! Thank you!

jansedlon commented 1 month ago

@fabian-hiller Thank YOU for the wonderful work you're doing 🙏 One question, i updated the package and tried it again. Should it really infer to undefined if there's a provided default value?

image

Why am i insisting on it so much? I'm using @conform-to/react library for Form handling in Remix.run framework and it returns available functions based on return type of a given schema. Since it infers the type as the type or undefined, I don't have access to array methods.

PS: For now the workaround is this

// Infer the type of the inner schema
type ResourceSchemaData = InferOutput<typeof ResourceSchema>

// Cast the default value
resources: optional(array(ResourceSchema), [] as ResourceSchemaData[])

What's the difference?

Given simple example

const a = optional(array(string()), []);

The type of the default parameter is inferred as

Default<ArraySchema<StringSchema<undefined>, undefined>, undefined>

but when the default parameter is casted as as string[], the type for the default parameter is string[] and not the previous one

fabian-hiller commented 1 month ago

You are right, there is still a problem with empty arrays as default argument. I will fix it in the next few minutes and release another version. Thanks for your feedback!

fabian-hiller commented 1 month ago

Should be fixed now. You can check it in our playground.

jansedlon commented 1 month ago

Works great, thank you