get-convex / convex-helpers

A collection of useful code to complement the official packages.
MIT License
66 stars 9 forks source link

zodToConvexFields doesn't return the correct type when using zod transforms #70

Open ericdew opened 4 months ago

ericdew commented 4 months ago

When creating table fields using zod, using a transform on a zod schema results in:

  1. The return value of the transform is not used as the schema return type - instead the input type is always used
  2. Intellisense/autocomplete breaks when trying to get schema fields from the getEntDefinitions helper

Example implementation/use case:

import { defineEnt, defineEntSchema, getEntDefinitions } from "convex-ents";
import { zodToConvexFields } from "convex-helpers/server/zod";
import { z } from "zod";

const message = {
  sentTimestamp: z.string().transform((val) => new Date(val).getTime()),
  // ^ sentTimestamp: v.string()
  // expected: v.number()
};

const schema = defineEntSchema({
  messages: defineEnt(zodToConvexFields(message)),
});

const definitions = getEntDefinitions(schema);

type SentTimestamp = typeof definitions.messages.document.sentTimestamp;
// ^ "Value" / no type
// expected: number
// also no intellisense/autocomplete after typing "document."

// Zod is able so succesfully return the correct type for a string input transformed into a number
const result = z.object(message).parse({ sentTimestamp: "2024-01-01T00:00:00Z" });
type SentTimestamp = typeof result.sentTimestamp;
// ^ number (expected)
ianmacartney commented 4 months ago

Related to #69 the zodToConvexFields is intended to turn user input into values. It doesn't yet support generating validators for the output type, as you may want for your table. I have a branch (zod) where I offer a second parameter to zodToConvexFields as "output" or "input" but I haven't figured out how to keep TypeScript from giving up on the recursive types. I believe this may be the issue with (2) as well - running into the TypeScript inference capability, though I haven't debugged it. I would suggest duplicating the table definition from the zod input validators. Until there's a zod library to wrap database reads & writes to transform values, it's a bit unclear what the guarantees are when using zod validators in this way, since zodToConvexFields just reduces to bare v.string() etc and doesn't run the zod code at all at runtime for db read/write. See here for more details. If you really want to share configs for frontend validation and db types, I would suggest manually overriding that types that have transforms / defaults:

const messageTableFields = {
  ...zodToConvexFields(message),
  sentTimestamp: v.number()
};