refactor: use explicit type annotations for TUpdateInputSchema to reduce type inference complexity (#26133)
* refactor: use explicit type annotations for TUpdateInputSchema to reduce type inference complexity This change significantly reduces TypeScript type-checking time by: 1. Defining explicit TypeScript types instead of using z.infer<> on complex schema chains 2. Using z.ZodType<T> annotations on schemas to prevent TypeScript from inferring through .extend(), .partial(), .merge() chains 3. Removing the dependency on EventTypeSchema from @calcom/prisma/zod/modelSchema which pulls in many dependencies The schema still validates all fields at runtime - only the compile-time type inference is optimized. Co-Authored-By: keith@cal.com <keithwillcode@gmail.com> * fix: update BookingFieldInput and users types to fix type errors - BookingFieldInput now includes name, hidden, required properties with index signature - users type changed from (string | number)[] to number[] - Zod schema updated to use z.array(z.number()) for users field - Removed unused stringOrNumber import Co-Authored-By: keith@cal.com <keithwillcode@gmail.com> * fix: handle bookingFields null case with Prisma.DbNull for JSON field compatibility Co-Authored-By: keith@cal.com <keithwillcode@gmail.com> * fix: remove index signature from BookingFieldInput for API v2 DTO compatibility Co-Authored-By: keith@cal.com <keithwillcode@gmail.com> * chore: remove useless JSDoc comments per PR feedback Co-Authored-By: keith@cal.com <keithwillcode@gmail.com> * test: add unit tests for aiPhoneCallConfig transformation in ZUpdateInputSchema Co-Authored-By: keith@cal.com <keithwillcode@gmail.com> * test: add unit tests for bookingFields null to Prisma.DbNull transformation Co-Authored-By: keith@cal.com <keithwillcode@gmail.com> * chore: remove comments from update.handler.test.ts Co-Authored-By: keith@cal.com <keithwillcode@gmail.com> * refactor: scope test describe block to update.handler per PR feedback Co-Authored-By: keith@cal.com <keithwillcode@gmail.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
Devin AI
parent
55805f081d
commit
4128a9b4fb
@@ -0,0 +1,142 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
|
||||
import { ZUpdateInputSchema } from "../types";
|
||||
|
||||
describe("ZUpdateInputSchema", () => {
|
||||
describe("aiPhoneCallConfig transformation", () => {
|
||||
const baseValidInput = {
|
||||
id: 1,
|
||||
};
|
||||
|
||||
const baseAiPhoneCallConfig = {
|
||||
generalPrompt: "Test prompt",
|
||||
enabled: true,
|
||||
beginMessage: "Hello",
|
||||
yourPhoneNumber: "+1234567890",
|
||||
numberToCall: "+0987654321",
|
||||
templateType: "CUSTOM_TEMPLATE" as const,
|
||||
};
|
||||
|
||||
it("should pass validation with valid aiPhoneCallConfig", () => {
|
||||
const input = {
|
||||
...baseValidInput,
|
||||
aiPhoneCallConfig: baseAiPhoneCallConfig,
|
||||
};
|
||||
|
||||
const result = ZUpdateInputSchema.safeParse(input);
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it("should set yourPhoneNumber to empty string when falsy", () => {
|
||||
const input = {
|
||||
...baseValidInput,
|
||||
aiPhoneCallConfig: {
|
||||
...baseAiPhoneCallConfig,
|
||||
yourPhoneNumber: "",
|
||||
},
|
||||
};
|
||||
|
||||
const result = ZUpdateInputSchema.safeParse(input);
|
||||
expect(result.success).toBe(true);
|
||||
if (result.success) {
|
||||
expect(result.data.aiPhoneCallConfig?.yourPhoneNumber).toBe("");
|
||||
}
|
||||
});
|
||||
|
||||
it("should set numberToCall to empty string when falsy", () => {
|
||||
const input = {
|
||||
...baseValidInput,
|
||||
aiPhoneCallConfig: {
|
||||
...baseAiPhoneCallConfig,
|
||||
numberToCall: "",
|
||||
},
|
||||
};
|
||||
|
||||
const result = ZUpdateInputSchema.safeParse(input);
|
||||
expect(result.success).toBe(true);
|
||||
if (result.success) {
|
||||
expect(result.data.aiPhoneCallConfig?.numberToCall).toBe("");
|
||||
}
|
||||
});
|
||||
|
||||
it("should set guestName to undefined when null", () => {
|
||||
const input = {
|
||||
...baseValidInput,
|
||||
aiPhoneCallConfig: {
|
||||
...baseAiPhoneCallConfig,
|
||||
guestName: null,
|
||||
},
|
||||
};
|
||||
|
||||
const result = ZUpdateInputSchema.safeParse(input);
|
||||
expect(result.success).toBe(true);
|
||||
if (result.success) {
|
||||
expect(result.data.aiPhoneCallConfig?.guestName).toBeUndefined();
|
||||
}
|
||||
});
|
||||
|
||||
it("should set guestEmail to null when undefined", () => {
|
||||
const input = {
|
||||
...baseValidInput,
|
||||
aiPhoneCallConfig: {
|
||||
...baseAiPhoneCallConfig,
|
||||
guestEmail: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
const result = ZUpdateInputSchema.safeParse(input);
|
||||
expect(result.success).toBe(true);
|
||||
if (result.success) {
|
||||
expect(result.data.aiPhoneCallConfig?.guestEmail).toBeNull();
|
||||
}
|
||||
});
|
||||
|
||||
it("should set guestCompany to null when undefined", () => {
|
||||
const input = {
|
||||
...baseValidInput,
|
||||
aiPhoneCallConfig: {
|
||||
...baseAiPhoneCallConfig,
|
||||
guestCompany: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
const result = ZUpdateInputSchema.safeParse(input);
|
||||
expect(result.success).toBe(true);
|
||||
if (result.success) {
|
||||
expect(result.data.aiPhoneCallConfig?.guestCompany).toBeNull();
|
||||
}
|
||||
});
|
||||
|
||||
it("should preserve existing values when provided", () => {
|
||||
const input = {
|
||||
...baseValidInput,
|
||||
aiPhoneCallConfig: {
|
||||
...baseAiPhoneCallConfig,
|
||||
guestName: "John Doe",
|
||||
guestEmail: "john@example.com",
|
||||
guestCompany: "Acme Inc",
|
||||
},
|
||||
};
|
||||
|
||||
const result = ZUpdateInputSchema.safeParse(input);
|
||||
expect(result.success).toBe(true);
|
||||
if (result.success) {
|
||||
expect(result.data.aiPhoneCallConfig?.guestName).toBe("John Doe");
|
||||
expect(result.data.aiPhoneCallConfig?.guestEmail).toBe("john@example.com");
|
||||
expect(result.data.aiPhoneCallConfig?.guestCompany).toBe("Acme Inc");
|
||||
}
|
||||
});
|
||||
|
||||
it("should allow undefined aiPhoneCallConfig", () => {
|
||||
const input = {
|
||||
...baseValidInput,
|
||||
};
|
||||
|
||||
const result = ZUpdateInputSchema.safeParse(input);
|
||||
expect(result.success).toBe(true);
|
||||
if (result.success) {
|
||||
expect(result.data.aiPhoneCallConfig).toBeUndefined();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,59 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
|
||||
import { Prisma } from "@calcom/prisma/client";
|
||||
|
||||
describe("update.handler", () => {
|
||||
describe("bookingFields null to Prisma.DbNull transformation", () => {
|
||||
function transformBookingFields(
|
||||
bookingFields: null | undefined | Prisma.InputJsonValue
|
||||
): typeof Prisma.DbNull | Prisma.InputJsonValue | undefined {
|
||||
return bookingFields === null ? Prisma.DbNull : (bookingFields as Prisma.InputJsonValue | undefined);
|
||||
}
|
||||
|
||||
it("should convert null to Prisma.DbNull", () => {
|
||||
const result = transformBookingFields(null);
|
||||
expect(result).toBe(Prisma.DbNull);
|
||||
});
|
||||
|
||||
it("should pass through undefined as-is", () => {
|
||||
const result = transformBookingFields(undefined);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should pass through an array of booking fields as-is", () => {
|
||||
const bookingFieldsArray = [
|
||||
{
|
||||
name: "email",
|
||||
type: "email",
|
||||
label: "Email",
|
||||
required: true,
|
||||
hidden: false,
|
||||
},
|
||||
{
|
||||
name: "name",
|
||||
type: "name",
|
||||
label: "Name",
|
||||
required: true,
|
||||
hidden: false,
|
||||
},
|
||||
];
|
||||
|
||||
const result = transformBookingFields(bookingFieldsArray);
|
||||
expect(result).toEqual(bookingFieldsArray);
|
||||
});
|
||||
|
||||
it("should pass through an empty array as-is", () => {
|
||||
const result = transformBookingFields([]);
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it("should distinguish between null and empty array", () => {
|
||||
const nullResult = transformBookingFields(null);
|
||||
const emptyArrayResult = transformBookingFields([]);
|
||||
|
||||
expect(nullResult).toBe(Prisma.DbNull);
|
||||
expect(emptyArrayResult).toEqual([]);
|
||||
expect(nullResult).not.toEqual(emptyArrayResult);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -240,7 +240,8 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
|
||||
}),
|
||||
description: newDescription,
|
||||
title: newTitle,
|
||||
bookingFields,
|
||||
bookingFields:
|
||||
bookingFields === null ? Prisma.DbNull : (bookingFields as Prisma.InputJsonValue | undefined),
|
||||
maxActiveBookingsPerBooker,
|
||||
isRRWeightsEnabled,
|
||||
rrSegmentQueryValue:
|
||||
|
||||
@@ -1,18 +1,245 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import type { TemplateType } from "@calcom/features/calAIPhone/zod-utils";
|
||||
import { templateTypeEnum } from "@calcom/features/calAIPhone/zod-utils";
|
||||
import { MAX_SEATS_PER_TIME_SLOT } from "@calcom/lib/constants";
|
||||
import type { PeriodType, SchedulingType } from "@calcom/prisma/enums";
|
||||
import type {
|
||||
CustomInputSchema,
|
||||
EventTypeMetadata,
|
||||
IntervalLimit,
|
||||
EventTypeLocation,
|
||||
} from "@calcom/prisma/zod-utils";
|
||||
import {
|
||||
customInputSchema,
|
||||
EventTypeMetaDataSchema,
|
||||
stringOrNumber,
|
||||
rrSegmentQueryValueSchema,
|
||||
eventTypeLocations,
|
||||
recurringEventType,
|
||||
intervalLimitsType,
|
||||
eventTypeBookingFields,
|
||||
eventTypeColor,
|
||||
} from "@calcom/prisma/zod-utils";
|
||||
import { eventTypeBookingFields } from "@calcom/prisma/zod-utils";
|
||||
import { DestinationCalendarSchema } from "@calcom/prisma/zod/modelSchema/DestinationCalendarSchema";
|
||||
import { EventTypeSchema } from "@calcom/prisma/zod/modelSchema/EventTypeSchema";
|
||||
|
||||
const hashedLinkInputSchema = z
|
||||
// ============================================================================
|
||||
// EXPLICIT TYPE DEFINITIONS
|
||||
// ============================================================================
|
||||
// These explicit types prevent TypeScript from having to infer complex types
|
||||
// through chains of .extend(), .partial(), .merge() etc. This significantly
|
||||
// reduces type-checking time by giving TypeScript the answer directly.
|
||||
// ============================================================================
|
||||
|
||||
type HashedLinkInput = {
|
||||
link: string;
|
||||
expiresAt?: Date | null;
|
||||
maxUsageCount?: number | null;
|
||||
usageCount?: number | null;
|
||||
};
|
||||
|
||||
type AiPhoneCallConfig = {
|
||||
generalPrompt: string;
|
||||
enabled: boolean;
|
||||
beginMessage: string | null;
|
||||
yourPhoneNumber: string;
|
||||
numberToCall: string;
|
||||
guestName?: string | null;
|
||||
guestEmail?: string | null;
|
||||
guestCompany?: string | null;
|
||||
templateType: TemplateType;
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
type HostInput = {
|
||||
userId: number;
|
||||
profileId?: number | null;
|
||||
isFixed?: boolean;
|
||||
priority?: number | null;
|
||||
weight?: number | null;
|
||||
scheduleId?: number | null;
|
||||
groupId?: string | null;
|
||||
};
|
||||
|
||||
type HostGroupInput = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
type ChildInput = {
|
||||
owner: {
|
||||
id: number;
|
||||
name: string;
|
||||
email: string;
|
||||
eventTypeSlugs: string[];
|
||||
};
|
||||
hidden: boolean;
|
||||
};
|
||||
|
||||
type DestinationCalendarInput = {
|
||||
integration: string;
|
||||
externalId: string;
|
||||
} | null;
|
||||
|
||||
type RecurringEventInput = {
|
||||
dtstart?: Date;
|
||||
interval: number;
|
||||
count: number;
|
||||
freq: number;
|
||||
until?: Date;
|
||||
tzid?: string;
|
||||
} | null;
|
||||
|
||||
type EventTypeColorInput = {
|
||||
lightEventTypeColor: string;
|
||||
darkEventTypeColor: string;
|
||||
} | null;
|
||||
|
||||
/**
|
||||
* Booking field type - minimal type definition for fields that need to be accessed.
|
||||
* Only includes properties that are actually read in server code.
|
||||
* Does NOT use an index signature to maintain compatibility with API v2 DTO classes.
|
||||
*/
|
||||
type BookingFieldInput = {
|
||||
name: string;
|
||||
hidden?: boolean;
|
||||
required?: boolean;
|
||||
type?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* RR Segment query value - using index signature for complex RAQB structure.
|
||||
* The values need to be indexable (string keys) for downstream usage.
|
||||
*/
|
||||
type RRSegmentQueryValueInput = {
|
||||
[key: string]: unknown;
|
||||
} | null;
|
||||
|
||||
/**
|
||||
* Explicit type definition for event type update input.
|
||||
*
|
||||
* This type is defined explicitly rather than using z.infer<> on a complex
|
||||
* schema chain to significantly reduce TypeScript type-checking time.
|
||||
* The schema still validates all fields at runtime.
|
||||
*
|
||||
* All fields are optional (from .partial()) except `id` which is required.
|
||||
*/
|
||||
export type TUpdateInputSchema = {
|
||||
// Required field
|
||||
id: number;
|
||||
|
||||
// Fields from EventTypeSchema (all optional due to .partial())
|
||||
periodType?: PeriodType;
|
||||
schedulingType?: SchedulingType | null;
|
||||
title?: string;
|
||||
slug?: string;
|
||||
description?: string | null;
|
||||
interfaceLanguage?: string | null;
|
||||
position?: number;
|
||||
locations?: EventTypeLocation[] | null;
|
||||
length?: number;
|
||||
offsetStart?: number;
|
||||
hidden?: boolean;
|
||||
userId?: number | null;
|
||||
profileId?: number | null;
|
||||
teamId?: number | null;
|
||||
useEventLevelSelectedCalendars?: boolean;
|
||||
eventName?: string | null;
|
||||
parentId?: number | null;
|
||||
bookingFields?: BookingFieldInput[] | null;
|
||||
timeZone?: string | null;
|
||||
periodStartDate?: Date | null;
|
||||
periodEndDate?: Date | null;
|
||||
periodDays?: number | null;
|
||||
periodCountCalendarDays?: boolean | null;
|
||||
lockTimeZoneToggleOnBookingPage?: boolean;
|
||||
lockedTimeZone?: string | null;
|
||||
requiresConfirmation?: boolean;
|
||||
requiresConfirmationWillBlockSlot?: boolean;
|
||||
requiresConfirmationForFreeEmail?: boolean;
|
||||
requiresBookerEmailVerification?: boolean;
|
||||
canSendCalVideoTranscriptionEmails?: boolean;
|
||||
autoTranslateDescriptionEnabled?: boolean;
|
||||
autoTranslateInstantMeetingTitleEnabled?: boolean;
|
||||
recurringEvent?: RecurringEventInput;
|
||||
disableGuests?: boolean;
|
||||
hideCalendarNotes?: boolean;
|
||||
hideCalendarEventDetails?: boolean;
|
||||
minimumBookingNotice?: number;
|
||||
beforeEventBuffer?: number;
|
||||
afterEventBuffer?: number;
|
||||
seatsPerTimeSlot?: number | null;
|
||||
onlyShowFirstAvailableSlot?: boolean;
|
||||
showOptimizedSlots?: boolean | null;
|
||||
disableCancelling?: boolean | null;
|
||||
disableRescheduling?: boolean | null;
|
||||
minimumRescheduleNotice?: number | null;
|
||||
seatsShowAttendees?: boolean | null;
|
||||
seatsShowAvailabilityCount?: boolean | null;
|
||||
scheduleId?: number | null;
|
||||
allowReschedulingCancelledBookings?: boolean | null;
|
||||
price?: number;
|
||||
currency?: string;
|
||||
slotInterval?: number | null;
|
||||
metadata?: EventTypeMetadata;
|
||||
successRedirectUrl?: string | null;
|
||||
forwardParamsSuccessRedirect?: boolean | null;
|
||||
bookingLimits?: IntervalLimit | null;
|
||||
durationLimits?: IntervalLimit | null;
|
||||
isInstantEvent?: boolean;
|
||||
instantMeetingExpiryTimeOffsetInSeconds?: number;
|
||||
instantMeetingScheduleId?: number | null;
|
||||
instantMeetingParameters?: string[];
|
||||
assignAllTeamMembers?: boolean;
|
||||
assignRRMembersUsingSegment?: boolean;
|
||||
rrSegmentQueryValue?: RRSegmentQueryValueInput;
|
||||
useEventTypeDestinationCalendarEmail?: boolean;
|
||||
isRRWeightsEnabled?: boolean;
|
||||
maxLeadThreshold?: number | null;
|
||||
includeNoShowInRRCalculation?: boolean;
|
||||
allowReschedulingPastBookings?: boolean;
|
||||
hideOrganizerEmail?: boolean;
|
||||
maxActiveBookingsPerBooker?: number | null;
|
||||
maxActiveBookingPerBookerOfferReschedule?: boolean;
|
||||
customReplyToEmail?: string | null;
|
||||
eventTypeColor?: EventTypeColorInput;
|
||||
rescheduleWithSameRoundRobinHost?: boolean;
|
||||
secondaryEmailId?: number | null;
|
||||
useBookerTimezone?: boolean;
|
||||
restrictionScheduleId?: number | null;
|
||||
bookingRequiresAuthentication?: boolean;
|
||||
rrHostSubsetEnabled?: boolean;
|
||||
createdAt?: Date | null;
|
||||
updatedAt?: Date | null;
|
||||
|
||||
// Extended fields (all optional due to .partial())
|
||||
aiPhoneCallConfig?: AiPhoneCallConfig;
|
||||
calVideoSettings?: CalVideoSettings;
|
||||
calAiPhoneScript?: string;
|
||||
customInputs?: CustomInputSchema[];
|
||||
destinationCalendar?: DestinationCalendarInput;
|
||||
users?: number[];
|
||||
children?: ChildInput[];
|
||||
hosts?: HostInput[];
|
||||
schedule?: number | null;
|
||||
instantMeetingSchedule?: number | null;
|
||||
multiplePrivateLinks?: (string | HashedLinkInput)[];
|
||||
hostGroups?: HostGroupInput[];
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// ZOD SCHEMAS
|
||||
// ============================================================================
|
||||
|
||||
const hashedLinkInputSchema: z.ZodType<HashedLinkInput> = z
|
||||
.object({
|
||||
link: z.string(),
|
||||
expiresAt: z.date().nullish(),
|
||||
@@ -21,7 +248,7 @@ const hashedLinkInputSchema = z
|
||||
})
|
||||
.strict();
|
||||
|
||||
const aiPhoneCallConfig = z
|
||||
const aiPhoneCallConfigSchema: z.ZodType<AiPhoneCallConfig | undefined> = z
|
||||
.object({
|
||||
generalPrompt: z.string(),
|
||||
enabled: z.boolean(),
|
||||
@@ -35,7 +262,7 @@ const aiPhoneCallConfig = z
|
||||
})
|
||||
.optional();
|
||||
|
||||
const calVideoSettingsSchema = z
|
||||
const calVideoSettingsSchema: z.ZodType<CalVideoSettings | undefined> = z
|
||||
.object({
|
||||
disableRecordingForGuests: z.boolean().nullish(),
|
||||
disableRecordingForOrganizer: z.boolean().nullish(),
|
||||
@@ -49,7 +276,7 @@ const calVideoSettingsSchema = z
|
||||
.optional()
|
||||
.nullable();
|
||||
|
||||
const hostSchema = z.object({
|
||||
const hostSchema: z.ZodType<HostInput> = z.object({
|
||||
userId: z.number(),
|
||||
profileId: z.number().or(z.null()).optional(),
|
||||
isFixed: z.boolean().optional(),
|
||||
@@ -59,12 +286,12 @@ const hostSchema = z.object({
|
||||
groupId: z.string().optional().nullable(),
|
||||
});
|
||||
|
||||
const hostGroupSchema = z.object({
|
||||
const hostGroupSchema: z.ZodType<HostGroupInput> = z.object({
|
||||
id: z.string().uuid(),
|
||||
name: z.string(),
|
||||
});
|
||||
|
||||
const childSchema = z.object({
|
||||
const childSchema: z.ZodType<ChildInput> = z.object({
|
||||
owner: z.object({
|
||||
id: z.number(),
|
||||
name: z.string(),
|
||||
@@ -74,55 +301,132 @@ const childSchema = z.object({
|
||||
hidden: z.boolean(),
|
||||
});
|
||||
|
||||
/** Optional fields */
|
||||
const BaseEventTypeUpdateInput = EventTypeSchema.extend({
|
||||
isInstantEvent: z.boolean(),
|
||||
instantMeetingParameters: z.array(z.string()),
|
||||
instantMeetingExpiryTimeOffsetInSeconds: z.number(),
|
||||
aiPhoneCallConfig,
|
||||
calVideoSettings: calVideoSettingsSchema,
|
||||
calAiPhoneScript: z.string(),
|
||||
customInputs: z.array(customInputSchema),
|
||||
destinationCalendar: DestinationCalendarSchema
|
||||
.pick({
|
||||
integration: true,
|
||||
externalId: true,
|
||||
})
|
||||
.nullable(),
|
||||
users: z.array(stringOrNumber),
|
||||
children: z.array(childSchema),
|
||||
hosts: z.array(hostSchema),
|
||||
schedule: z.number().nullable(),
|
||||
instantMeetingSchedule: z.number().nullable(),
|
||||
multiplePrivateLinks: z.array(z.union([z.string(), hashedLinkInputSchema])),
|
||||
assignAllTeamMembers: z.boolean(),
|
||||
isRRWeightsEnabled: z.boolean(),
|
||||
metadata: EventTypeMetaDataSchema,
|
||||
bookingFields: eventTypeBookingFields,
|
||||
assignRRMembersUsingSegment: z.boolean().optional(),
|
||||
rrSegmentQueryValue: rrSegmentQueryValueSchema.optional(),
|
||||
useEventLevelSelectedCalendars: z.boolean().optional(),
|
||||
seatsPerTimeSlot: z.number().min(1).max(MAX_SEATS_PER_TIME_SLOT).nullable().optional(),
|
||||
hostGroups: z.array(hostGroupSchema).optional(),
|
||||
})
|
||||
.partial()
|
||||
.extend(EventTypeSchema.pick({ id: true }).shape);
|
||||
const destinationCalendarInputSchema: z.ZodType<DestinationCalendarInput> = z
|
||||
.object({
|
||||
integration: z.string(),
|
||||
externalId: z.string(),
|
||||
})
|
||||
.nullable();
|
||||
|
||||
export const ZUpdateInputSchema = BaseEventTypeUpdateInput.extend({
|
||||
aiPhoneCallConfig: aiPhoneCallConfig.refine(
|
||||
(data) => {
|
||||
if (!data) return true;
|
||||
data.yourPhoneNumber = data.yourPhoneNumber || "";
|
||||
data.numberToCall = data.numberToCall || "";
|
||||
data.guestName = data.guestName ?? undefined;
|
||||
data.guestEmail = data.guestEmail ?? null;
|
||||
data.guestCompany = data.guestCompany ?? null;
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message: "Applying default values and transformations",
|
||||
}
|
||||
),
|
||||
}).strict();
|
||||
// only run infer over the simple type, excluding refines/transforms.
|
||||
export type TUpdateInputSchema = z.infer<typeof BaseEventTypeUpdateInput>;
|
||||
/**
|
||||
* Base schema for event type updates.
|
||||
*
|
||||
* Uses z.ZodType<TUpdateInputSchema> annotation to prevent TypeScript from
|
||||
* inferring the complex type through the schema chain. This is a key
|
||||
* optimization that reduces type-checking time significantly.
|
||||
*/
|
||||
const BaseEventTypeUpdateInput: z.ZodType<TUpdateInputSchema> = z
|
||||
.object({
|
||||
// Required field
|
||||
id: z.number().int(),
|
||||
|
||||
// Fields from EventTypeSchema
|
||||
periodType: z.enum(["UNLIMITED", "ROLLING", "ROLLING_WINDOW", "RANGE"]).optional(),
|
||||
schedulingType: z.enum(["ROUND_ROBIN", "COLLECTIVE", "MANAGED"]).nullable().optional(),
|
||||
title: z.string().min(1).optional(),
|
||||
slug: z.string().optional(),
|
||||
description: z.string().nullable().optional(),
|
||||
interfaceLanguage: z.string().nullable().optional(),
|
||||
position: z.number().int().optional(),
|
||||
locations: eventTypeLocations.nullable().optional(),
|
||||
length: z.number().min(1).optional(),
|
||||
offsetStart: z.number().int().optional(),
|
||||
hidden: z.boolean().optional(),
|
||||
userId: z.number().int().nullable().optional(),
|
||||
profileId: z.number().int().nullable().optional(),
|
||||
teamId: z.number().int().nullable().optional(),
|
||||
useEventLevelSelectedCalendars: z.boolean().optional(),
|
||||
eventName: z.string().nullable().optional(),
|
||||
parentId: z.number().int().nullable().optional(),
|
||||
bookingFields: eventTypeBookingFields.nullable().optional(),
|
||||
timeZone: z.string().nullable().optional(),
|
||||
periodStartDate: z.coerce.date().nullable().optional(),
|
||||
periodEndDate: z.coerce.date().nullable().optional(),
|
||||
periodDays: z.number().int().nullable().optional(),
|
||||
periodCountCalendarDays: z.boolean().nullable().optional(),
|
||||
lockTimeZoneToggleOnBookingPage: z.boolean().optional(),
|
||||
lockedTimeZone: z.string().nullable().optional(),
|
||||
requiresConfirmation: z.boolean().optional(),
|
||||
requiresConfirmationWillBlockSlot: z.boolean().optional(),
|
||||
requiresConfirmationForFreeEmail: z.boolean().optional(),
|
||||
requiresBookerEmailVerification: z.boolean().optional(),
|
||||
canSendCalVideoTranscriptionEmails: z.boolean().optional(),
|
||||
autoTranslateDescriptionEnabled: z.boolean().optional(),
|
||||
autoTranslateInstantMeetingTitleEnabled: z.boolean().optional(),
|
||||
recurringEvent: recurringEventType.nullable().optional(),
|
||||
disableGuests: z.boolean().optional(),
|
||||
hideCalendarNotes: z.boolean().optional(),
|
||||
hideCalendarEventDetails: z.boolean().optional(),
|
||||
minimumBookingNotice: z.number().min(0).optional(),
|
||||
beforeEventBuffer: z.number().int().optional(),
|
||||
afterEventBuffer: z.number().int().optional(),
|
||||
seatsPerTimeSlot: z.number().min(1).max(MAX_SEATS_PER_TIME_SLOT).nullable().optional(),
|
||||
onlyShowFirstAvailableSlot: z.boolean().optional(),
|
||||
showOptimizedSlots: z.boolean().nullable().optional(),
|
||||
disableCancelling: z.boolean().nullable().optional(),
|
||||
disableRescheduling: z.boolean().nullable().optional(),
|
||||
minimumRescheduleNotice: z.number().min(0).nullable().optional(),
|
||||
seatsShowAttendees: z.boolean().nullable().optional(),
|
||||
seatsShowAvailabilityCount: z.boolean().nullable().optional(),
|
||||
scheduleId: z.number().int().nullable().optional(),
|
||||
allowReschedulingCancelledBookings: z.boolean().nullable().optional(),
|
||||
price: z.number().int().optional(),
|
||||
currency: z.string().optional(),
|
||||
slotInterval: z.number().int().nullable().optional(),
|
||||
metadata: EventTypeMetaDataSchema.optional(),
|
||||
successRedirectUrl: z.string().nullable().optional(),
|
||||
forwardParamsSuccessRedirect: z.boolean().nullable().optional(),
|
||||
bookingLimits: intervalLimitsType.nullable().optional(),
|
||||
durationLimits: intervalLimitsType.nullable().optional(),
|
||||
isInstantEvent: z.boolean().optional(),
|
||||
instantMeetingExpiryTimeOffsetInSeconds: z.number().int().optional(),
|
||||
instantMeetingScheduleId: z.number().int().nullable().optional(),
|
||||
instantMeetingParameters: z.string().array().optional(),
|
||||
assignAllTeamMembers: z.boolean().optional(),
|
||||
assignRRMembersUsingSegment: z.boolean().optional(),
|
||||
rrSegmentQueryValue: rrSegmentQueryValueSchema.nullable().optional(),
|
||||
useEventTypeDestinationCalendarEmail: z.boolean().optional(),
|
||||
isRRWeightsEnabled: z.boolean().optional(),
|
||||
maxLeadThreshold: z.number().int().nullable().optional(),
|
||||
includeNoShowInRRCalculation: z.boolean().optional(),
|
||||
allowReschedulingPastBookings: z.boolean().optional(),
|
||||
hideOrganizerEmail: z.boolean().optional(),
|
||||
maxActiveBookingsPerBooker: z.number().int().nullable().optional(),
|
||||
maxActiveBookingPerBookerOfferReschedule: z.boolean().optional(),
|
||||
customReplyToEmail: z.string().nullable().optional(),
|
||||
eventTypeColor: eventTypeColor.nullable().optional(),
|
||||
rescheduleWithSameRoundRobinHost: z.boolean().optional(),
|
||||
secondaryEmailId: z.number().int().nullable().optional(),
|
||||
useBookerTimezone: z.boolean().optional(),
|
||||
restrictionScheduleId: z.number().int().nullable().optional(),
|
||||
bookingRequiresAuthentication: z.boolean().optional(),
|
||||
rrHostSubsetEnabled: z.boolean().optional(),
|
||||
createdAt: z.coerce.date().nullable().optional(),
|
||||
updatedAt: z.coerce.date().nullable().optional(),
|
||||
|
||||
// Extended fields
|
||||
aiPhoneCallConfig: aiPhoneCallConfigSchema,
|
||||
calVideoSettings: calVideoSettingsSchema,
|
||||
calAiPhoneScript: z.string().optional(),
|
||||
customInputs: z.array(customInputSchema).optional(),
|
||||
destinationCalendar: destinationCalendarInputSchema.optional(),
|
||||
users: z.array(z.number()).optional(),
|
||||
children: z.array(childSchema).optional(),
|
||||
hosts: z.array(hostSchema).optional(),
|
||||
schedule: z.number().nullable().optional(),
|
||||
instantMeetingSchedule: z.number().nullable().optional(),
|
||||
multiplePrivateLinks: z.array(z.union([z.string(), hashedLinkInputSchema])).optional(),
|
||||
hostGroups: z.array(hostGroupSchema).optional(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const ZUpdateInputSchema = BaseEventTypeUpdateInput.superRefine((data, _ctx) => {
|
||||
// Apply transformations to aiPhoneCallConfig if present
|
||||
if (data.aiPhoneCallConfig) {
|
||||
data.aiPhoneCallConfig.yourPhoneNumber = data.aiPhoneCallConfig.yourPhoneNumber || "";
|
||||
data.aiPhoneCallConfig.numberToCall = data.aiPhoneCallConfig.numberToCall || "";
|
||||
data.aiPhoneCallConfig.guestName = data.aiPhoneCallConfig.guestName ?? undefined;
|
||||
data.aiPhoneCallConfig.guestEmail = data.aiPhoneCallConfig.guestEmail ?? null;
|
||||
data.aiPhoneCallConfig.guestCompany = data.aiPhoneCallConfig.guestCompany ?? null;
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user