From 851cca25aa8b7d3e65a7a4eebb1fa6f9d200d769 Mon Sep 17 00:00:00 2001 From: Keith Williams Date: Sun, 21 Dec 2025 23:15:04 -0300 Subject: [PATCH] perf: add z.ZodType annotations to high-impact schemas (#26114) * perf: add z.ZodType annotations to high-impact schemas Add explicit type annotations to reduce TypeScript type inference overhead and .d.ts file bloat in the tRPC package. Changes: - packages/prisma/zod-utils.ts: Add EventTypeLocation type and annotate eventTypeLocations schema - packages/features/eventtypes/lib/schemas.ts: Add TEventTypeDuplicateInput and TCreateEventTypeInput types with annotations - packages/app-store/routing-forms/zod.ts: Add FieldOption, TNonRouterField, TRouterField, TField, TFields types with annotations for zodField and zodFields These schemas feed into multiple tRPC routers and were identified as high-impact targets for reducing type checking time and declaration file sizes. Co-Authored-By: keith@cal.com * fix: replace z.infer with explicit types and define EventTypeLocation locally Address PR feedback: - Replace z.infer with explicit CalVideoSettings type - Define EventTypeLocation type locally instead of importing from prisma - Add z.ZodType annotation to calVideoSettingsSchema Note: Still importing Zod schemas from @calcom/prisma/zod-utils for validation. Awaiting guidance on whether to move those to @calcom/lib. Co-Authored-By: keith@cal.com --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- packages/app-store/routing-forms/zod.ts | 32 ++++++++++- packages/features/eventtypes/lib/schemas.ts | 61 ++++++++++++++++++++- packages/prisma/zod-utils.ts | 13 ++++- 3 files changed, 100 insertions(+), 6 deletions(-) diff --git a/packages/app-store/routing-forms/zod.ts b/packages/app-store/routing-forms/zod.ts index 60145f3058..8054b8fd95 100644 --- a/packages/app-store/routing-forms/zod.ts +++ b/packages/app-store/routing-forms/zod.ts @@ -4,6 +4,26 @@ import { raqbQueryValueSchema } from "@calcom/lib/raqb/zod"; import { routingFormAppDataSchemas } from "./appDataSchemas"; +export type FieldOption = { + label: string; + id: string | null; +}; + +export type TNonRouterField = { + id: string; + label: string; + identifier?: string; + placeholder?: string; + type: string; + /** @deprecated in favour of `options` */ + selectText?: string; + required?: boolean; + deleted?: boolean; + options?: FieldOption[]; +}; + +// Note: zodNonRouterField is NOT annotated with z.ZodType because it uses .extend() below +// which requires the full ZodObject type to be preserved export const zodNonRouterField = z.object({ id: z.string(), label: z.string(), @@ -29,13 +49,21 @@ export const zodNonRouterField = z.object({ .optional(), }); +export type TRouterField = TNonRouterField & { + routerId: string; +}; + +// Note: zodRouterField is NOT annotated with z.ZodType because it uses .extend() below export const zodRouterField = zodNonRouterField.extend({ routerId: z.string(), }); +export type TField = TNonRouterField | TRouterField; +export type TFields = TField[] | undefined; + // This ordering is important - If routerId is present then it should be in the parsed object. Moving zodNonRouterField to first position doesn't do that -export const zodField = z.union([zodRouterField, zodNonRouterField]); -export const zodFields = z.array(zodField).optional(); +export const zodField: z.ZodType = z.union([zodRouterField, zodNonRouterField]); +export const zodFields: z.ZodType = z.array(zodField).optional(); export const zodNonRouterFieldView = zodNonRouterField; export const zodRouterFieldView = zodRouterField.extend({ diff --git a/packages/features/eventtypes/lib/schemas.ts b/packages/features/eventtypes/lib/schemas.ts index 82350a7036..30b467797d 100644 --- a/packages/features/eventtypes/lib/schemas.ts +++ b/packages/features/eventtypes/lib/schemas.ts @@ -3,7 +3,21 @@ import { z } from "zod"; import { SchedulingType } from "@calcom/prisma/enums"; import { eventTypeLocations, EventTypeMetaDataSchema, eventTypeSlug } from "@calcom/prisma/zod-utils"; -const calVideoSettingsSchema = z +type CalVideoSettings = + | { + disableRecordingForGuests?: boolean | null; + disableRecordingForOrganizer?: boolean | null; + enableAutomaticTranscription?: boolean | null; + enableAutomaticRecordingForOrganizer?: boolean | null; + disableTranscriptionForGuests?: boolean | null; + disableTranscriptionForOrganizer?: boolean | null; + redirectUrlOnExit?: string | null; + requireEmailForGuests?: boolean | null; + } + | null + | undefined; + +const calVideoSettingsSchema: z.ZodType = z .object({ disableRecordingForGuests: z.boolean().nullish(), disableRecordingForOrganizer: z.boolean().nullish(), @@ -17,7 +31,29 @@ const calVideoSettingsSchema = z .optional() .nullable(); -export const EventTypeDuplicateInput = z +type EventTypeLocation = { + type: string; + address?: string; + link?: string; + displayLocationPublicly?: boolean; + hostPhoneNumber?: string; + credentialId?: number; + teamName?: string; + customLabel?: string; +}; + +type EventTypeMetadata = z.infer; + +export type TEventTypeDuplicateInput = { + id: number; + slug: string; + title: string; + description: string; + length: number; + teamId?: number | null; +}; + +export const EventTypeDuplicateInput: z.ZodType = z .object({ id: z.number(), slug: z.string(), @@ -28,7 +64,26 @@ export const EventTypeDuplicateInput = z }) .strict(); -export const createEventTypeInput = z +export type TCreateEventTypeInput = { + title: string; + slug: string; + description?: string | null; + length: number; + hidden?: boolean; + teamId?: number | null; + schedulingType?: SchedulingType | null; + locations?: EventTypeLocation[]; + metadata?: EventTypeMetadata; + disableGuests?: boolean; + slotInterval?: number | null; + minimumBookingNotice?: number; + beforeEventBuffer?: number; + afterEventBuffer?: number; + scheduleId?: number; + calVideoSettings?: CalVideoSettings; +}; + +export const createEventTypeInput: z.ZodType = z .object({ title: z.string().trim().min(1), slug: eventTypeSlug, diff --git a/packages/prisma/zod-utils.ts b/packages/prisma/zod-utils.ts index deb1498707..88c880c32e 100644 --- a/packages/prisma/zod-utils.ts +++ b/packages/prisma/zod-utils.ts @@ -327,7 +327,18 @@ export const bookingResponses = z export type BookingResponses = z.infer; -export const eventTypeLocations = z.array( +export type EventTypeLocation = { + type: string; + address?: string; + link?: string; + displayLocationPublicly?: boolean; + hostPhoneNumber?: string; + credentialId?: number; + teamName?: string; + customLabel?: string; +}; + +export const eventTypeLocations: z.ZodType = z.array( z.object({ // TODO: Couldn't find a way to make it a union of types from App Store locations // Creating a dynamic union by iterating over the object doesn't seem to make TS happy