chrishoermann / zod-prisma-types

Generator creates zod types for your prisma models with advanced validation
Other
574 stars 43 forks source link

Date values come out as string | Date | null | undefined rather than expected Date | null | undefined #119

Open andenacitelli opened 1 year ago

andenacitelli commented 1 year ago

I am attempting to use the generated TaskUncheckedCreateInputSchema schema in order to validate a type on my frontend. However, the value that comes out of this is potentially leaving my Dates (the start and end fields) as strings.

Relevant Frontend Bits (React + Mantine):

export const TaskMutationView = ({
  task,
  parent,
  project,
  epic,
  onSuccess,
}: {
  task?: Task;
  parent?: Task;
  project?: Project;
  epic?: Epic;
  onSuccess: () => void;
}) => {
  const initialValues: z.infer<typeof TaskUncheckedCreateInputSchema> = {
    title: "",
    description: "",
    projectId: project?.id,
    epicId: epic?.id,
    parentId: parent?.id,
  };

  const form = useForm({
    initialValues: task ?? initialValues,
  });

  return (
      <SimpleGrid cols={2} breakpoints={[{ maxWidth: 600, cols: 1 }]}>
        <DatePickerInput
          clearable
          value={form.values.start}
          onChange={(v) => form.setFieldValue("start", v)}
          label={"Start Date"}
          minDate={new Date()}
          popoverProps={{ withinPortal: true }}
          onKeyDown={getHotkeyHandler([["enter", onSubmit]])}
        />
)
}

The value={form.values.start} is giving me the following TypeScript error:

TS2322: Type 'string | Date | null | undefined' is not assignable to type 'DateValue | undefined'.   Type 'string' is not assignable to type 'Date'.  PickerBaseProps.d.ts(6, 5): The expected type comes from property 'value' which is declared here on type 'IntrinsicAttributes & DatePickerInputProps<"default">'

My suspicion is that this has to do with the .coerce() that zod parses dates through, but I'm not quite sure what the recommended approach to dealing with this is outside of type assertions and other escape hatches.

Prisma Model

model Task {
  // Important Metadata
  id          String    @id @default(auto()) @map("_id") @db.ObjectId
  completed   Boolean   @default(false)
  title       String /// @zod.string.min(1)
  size        TaskSize  @default(MEDIUM)
  description String
  number      Int /// @zod.custom.omit(["input"])

  start       DateTime? // Date at which this task can be worked on
  end         DateTime? // Day this task is due
  url         String? // Page that triggered this task to be created

  // Subtasks
  parentId String? @db.ObjectId
  parent   Task?   @relation(name: "TaskToTask-Subtasks", fields: [parentId], references: [id], onDelete: NoAction, onUpdate: NoAction)
  subtasks Task[]  @relation(name: "TaskToTask-Subtasks")

  // Dependents & Dependencies
  dependencies    Task[]   @relation("TaskToTask-Dependencies", fields: [dependenciesIds], references: [id])
  dependenciesIds String[] @db.ObjectId
  dependents      Task[]   @relation("TaskToTask-Dependencies", fields: [dependentsIds], references: [id])
  dependentsIds   String[] @db.ObjectId

  // Location
  location         Location?       @relation(fields: [locationId], references: [id])
  locationId       String?         @db.ObjectId

  // Parent epic
  Epic             Epic?           @relation(fields: [epicId], references: [id])
  epicId           String?         @unique @db.ObjectId

  // Parent project
  Project          Project?        @relation(fields: [projectId], references: [id])
  projectId        String?         @unique @db.ObjectId

  // Ingestion event that triggered this task to be created, if any
  ingestionEvent   IngestionEvent? @relation(fields: [ingestionEventId], references: [id])
  ingestionEventId String?         @unique @db.ObjectId

  User             User            @relation(fields: [userEmail], references: [email])
  userEmail        String          @unique /// @zod.custom.omit(["input"])
  createdAt        DateTime        @default(now())
  updatedAt        DateTime        @updatedAt
}

EDIT: Adding the following parses before my render code fixes things. Doesn't feel like a solid fix for me, as now it's just a fatal error rather than undefined behavior, but hopefully helps diagnose what's going on.

form.values.start = z.date().optional().parse(form.values.start);
form.values.end = z.date().optional().parse(form.values.end);

Love the work you've done - so much that I actually started sponsoring you on GitHub! This project saves me a bunch of boilerplate when it comes to tRPC handlers for simple (and even slightly complicated) CRUD handlers.

cimchd commented 1 year ago

Probably a bit late, but we had the same problem. We solved it by using superjson as transformer in trpc:

import { initTRPC } from '@trpc/server';
import superjson from 'superjson';

import type { Context } from './context';

const t = initTRPC.context<Context>().create({ transformer: superjson });
// ...