refactor: decouple @calcom/prisma from @calcom/features, @calcom/ee, and @calcom/lib (#24802)

* refactor: decouple @calcom/prisma from @calcom/features, @calcom/ee, and @calcom/lib

- Inline helper functions in zod-utils.ts (emailSchema, slugify, getValidRhfFieldName, isPasswordValid, intervalLimitsType, zodAttributesQueryValue)
- Update schema.prisma @zod.import comments to reference zod-utils instead of @calcom/lib
- Inline idempotency key generation in booking-idempotency-key extension using uuid v5
- Move usage-tracking extension to @calcom/ee/prisma-extensions/
- Remove usage-tracking extension from packages/prisma/index.ts
- Move Prisma DI module from @calcom/prisma to @calcom/features/di/modules/Prisma.ts
- Update 20 import paths in @calcom/features to use new Prisma DI module
- Remove @calcom/lib dependency from @calcom/prisma package.json
- Add uuid dependency to @calcom/prisma package.json

This reduces the dependency footprint of @calcom/prisma and breaks circular dependencies between packages.

Co-Authored-By: Volnei Munhoz <volnei.munhoz@gmail.com>

* feat: add EE-specific Prisma DI module with usage tracking

Create packages/ee/di/modules/PrismaEE.ts to apply the usage-tracking extension in EE contexts. This module:
- Imports the base prisma client from @calcom/prisma
- Applies the usageTrackingExtention from @calcom/ee/prisma-extensions/usage-tracking
- Exports the same DI tokens as the base Prisma module for override in EE containers

This ensures usage tracking functionality is preserved in EE builds while keeping the base @calcom/prisma package free of EE dependencies.

Note: EE containers should load this module after the base Prisma module to override the bindings.
Co-Authored-By: Volnei Munhoz <volnei.munhoz@gmail.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Volnei Munhoz
2025-11-04 06:51:13 -03:00
committed by GitHub
co-authored by Volnei Munhoz Devin AI
parent 896f8dbd3d
commit c6ad767565
28 changed files with 238 additions and 42 deletions
+23
View File
@@ -0,0 +1,23 @@
import { type Container, createModule } from "@calcom/features/di/di";
import { DI_TOKENS } from "@calcom/features/di/tokens";
import { prisma, readonlyPrisma } from "@calcom/prisma";
import { usageTrackingExtention } from "../../prisma-extensions/usage-tracking";
export const prismaEEModule = createModule();
const token = DI_TOKENS.PRISMA_CLIENT;
const readOnlyToken = DI_TOKENS.READ_ONLY_PRISMA_CLIENT;
const moduleToken = DI_TOKENS.PRISMA_MODULE;
const prismaWithUsageTracking = prisma.$extends(usageTrackingExtention(prisma));
prismaEEModule.bind(token).toFactory(() => prismaWithUsageTracking, "singleton");
prismaEEModule.bind(readOnlyToken).toFactory(() => readonlyPrisma, "singleton");
export const moduleLoader = {
token,
readOnlyToken,
loadModule: (container: Container) => {
container.load(moduleToken, prismaEEModule);
},
};
@@ -1,7 +1,7 @@
import { BookingCancelService } from "@calcom/features/bookings/lib/handleCancelBooking";
import { bindModuleToClassOnToken, createModule } from "@calcom/lib/di/di";
import { DI_TOKENS } from "@calcom/lib/di/tokens";
import { moduleLoader as prismaModuleLoader } from "@calcom/prisma/prisma.module";
import { moduleLoader as prismaModuleLoader } from "@calcom/features/di/modules/Prisma";
const thisModule = createModule();
const token = DI_TOKENS.BOOKING_CANCEL_SERVICE;
@@ -1,7 +1,7 @@
import { InstantBookingCreateService } from "@calcom/features/bookings/lib/service/InstantBookingCreateService";
import { createModule, bindModuleToClassOnToken } from "@calcom/features/di/di";
import { DI_TOKENS } from "@calcom/features/di/tokens";
import { moduleLoader as prismaModuleLoader } from "@calcom/prisma/prisma.module";
import { moduleLoader as prismaModuleLoader } from "@calcom/features/di/modules/Prisma";
export const instantBookingCreateServiceModule = createModule();
const token = DI_TOKENS.INSTANT_BOOKING_CREATE_SERVICE;
@@ -7,7 +7,7 @@ import { moduleLoader as luckyUserServiceModuleLoader } from "@calcom/features/d
import { moduleLoader as userRepositoryModuleLoader } from "@calcom/features/di/modules/User";
import { DI_TOKENS } from "@calcom/features/di/tokens";
import { moduleLoader as hashedLinkServiceModuleLoader } from "@calcom/features/hashedLink/di/HashedLinkService.module";
import { moduleLoader as prismaModuleLoader } from "@calcom/prisma/prisma.module";
import { moduleLoader as prismaModuleLoader } from "@calcom/features/di/modules/Prisma";
const thisModule = createModule();
const token = DI_TOKENS.REGULAR_BOOKING_SERVICE;
@@ -1,6 +1,6 @@
import { DI_TOKENS } from "@calcom/features/di/tokens";
import { redisModule } from "@calcom/features/redis/di/redisModule";
import { prismaModule } from "@calcom/prisma/prisma.module";
import { prismaModule } from "@calcom/features/di/modules/Prisma";
import type { AvailableSlotsService } from "@calcom/trpc/server/routers/viewer/slots/util";
import { membershipRepositoryModule } from "@calcom/features/users/di/MembershipRepository.module";
import { createContainer } from "../di";
@@ -1,6 +1,6 @@
import type { CheckBookingLimitsService } from "@calcom/features/bookings/lib/checkBookingLimits";
import { DI_TOKENS } from "@calcom/features/di/tokens";
import { prismaModule } from "@calcom/prisma/prisma.module";
import { prismaModule } from "@calcom/features/di/modules/Prisma";
import type { CheckBookingAndDurationLimitsService } from "../../bookings/lib/handleNewBooking/checkBookingAndDurationLimits";
import { createContainer } from "../di";
+1 -1
View File
@@ -1,6 +1,6 @@
import type { BusyTimesService } from "@calcom/features/busyTimes/services/getBusyTimes";
import { DI_TOKENS } from "@calcom/features/di/tokens";
import { prismaModule } from "@calcom/prisma/prisma.module";
import { prismaModule } from "@calcom/features/di/modules/Prisma";
import { createContainer } from "../di";
import { bookingRepositoryModule } from "../modules/Booking";
+1 -1
View File
@@ -1,5 +1,5 @@
import { DI_TOKENS } from "@calcom/features/di/tokens";
import { prismaModule } from "@calcom/prisma/prisma.module";
import { prismaModule } from "@calcom/features/di/modules/Prisma";
import type { CacheService } from "../../../features/calendar-cache/lib/getShouldServeCache";
import { createContainer } from "../di";
@@ -1,6 +1,6 @@
import type { FilterHostsService } from "@calcom/features/bookings/lib/host-filtering/filterHostsBySameRoundRobinHost";
import { DI_TOKENS } from "@calcom/features/di/tokens";
import { prismaModule } from "@calcom/prisma/prisma.module";
import { prismaModule } from "@calcom/features/di/modules/Prisma";
import { createContainer } from "../di";
import { bookingRepositoryModule } from "../modules/Booking";
@@ -1,7 +1,7 @@
import type { UserAvailabilityService } from "@calcom/features/availability/lib/getUserAvailability";
import { DI_TOKENS } from "@calcom/features/di/tokens";
import { redisModule } from "@calcom/features/redis/di/redisModule";
import { prismaModule } from "@calcom/prisma/prisma.module";
import { prismaModule } from "@calcom/features/di/modules/Prisma";
import { createContainer } from "../di";
import { bookingRepositoryModule } from "../modules/Booking";
@@ -5,7 +5,7 @@ import type {
InsightsBookingBaseService,
} from "@calcom/features/insights/services/InsightsBookingBaseService";
import type { InsightsBookingService } from "@calcom/features/insights/services/InsightsBookingDIService";
import { prismaModule } from "@calcom/prisma/prisma.module";
import { prismaModule } from "@calcom/features/di/modules/Prisma";
import { createContainer } from "../di";
import { insightsBookingModule } from "../modules/InsightsBooking";
@@ -5,7 +5,7 @@ import type {
InsightsRoutingBaseService,
} from "@calcom/features/insights/services/InsightsRoutingBaseService";
import type { InsightsRoutingService } from "@calcom/features/insights/services/InsightsRoutingDIService";
import { prismaModule } from "@calcom/prisma/prisma.module";
import { prismaModule } from "@calcom/features/di/modules/Prisma";
import { createContainer } from "../di";
import { insightsRoutingModule } from "../modules/InsightsRouting";
@@ -1,7 +1,7 @@
import { DI_TOKENS } from "@calcom/features/di/tokens";
import { redisModule } from "@calcom/features/redis/di/redisModule";
import { membershipRepositoryModule } from "@calcom/features/users/di/MembershipRepository.module";
import { prismaModule } from "@calcom/prisma/prisma.module";
import { prismaModule } from "@calcom/features/di/modules/Prisma";
import type { NoSlotsNotificationService } from "@calcom/trpc/server/routers/viewer/slots/handleNotificationWhenNoSlots";
import { createContainer } from "../di";
@@ -1,6 +1,6 @@
import type { QualifiedHostsService } from "@calcom/features/bookings/lib/host-filtering/findQualifiedHostsWithDelegationCredentials";
import { DI_TOKENS } from "@calcom/features/di/tokens";
import { prismaModule } from "@calcom/prisma/prisma.module";
import { prismaModule } from "@calcom/features/di/modules/Prisma";
import { createContainer } from "../di";
import { bookingRepositoryModule } from "../modules/Booking";
+1 -1
View File
@@ -1,6 +1,6 @@
import { DI_TOKENS } from "@calcom/features/di/tokens";
import { PrismaAttributeRepository } from "@calcom/lib/server/repository/PrismaAttributeRepository";
import { moduleLoader as prismaModuleLoader } from "@calcom/prisma/prisma.module";
import { moduleLoader as prismaModuleLoader } from "@calcom/features/di/modules/Prisma";
import { createModule, bindModuleToClassOnToken, type ModuleLoader } from "../di";
+1 -1
View File
@@ -1,6 +1,6 @@
import { DI_TOKENS } from "@calcom/features/di/tokens";
import { HostRepository } from "@calcom/lib/server/repository/host";
import { moduleLoader as prismaModuleLoader } from "@calcom/prisma/prisma.module";
import { moduleLoader as prismaModuleLoader } from "@calcom/features/di/modules/Prisma";
import { createModule, bindModuleToClassOnToken, type ModuleLoader } from "../di";
+1 -1
View File
@@ -1,6 +1,6 @@
import { DI_TOKENS } from "@calcom/features/di/tokens";
import { PrismaOOORepository } from "@calcom/lib/server/repository/ooo";
import { moduleLoader as prismaModuleLoader } from "@calcom/prisma/prisma.module";
import { moduleLoader as prismaModuleLoader } from "@calcom/features/di/modules/Prisma";
import { createModule, bindModuleToClassOnToken, type ModuleLoader } from "../di";
@@ -1,7 +1,6 @@
import { type Container, createModule } from "@calcom/features/di/di";
import { DI_TOKENS } from "@calcom/features/di/tokens";
import { prisma, readonlyPrisma } from "./index";
import { prisma, readonlyPrisma } from "@calcom/prisma";
export const prismaModule = createModule();
const token = DI_TOKENS.PRISMA_CLIENT;
+1 -1
View File
@@ -1,6 +1,6 @@
import { DI_TOKENS } from "@calcom/features/di/tokens";
import { UserRepository } from "@calcom/features/users/repositories/UserRepository";
import { moduleLoader as prismaModuleLoader } from "@calcom/prisma/prisma.module";
import { moduleLoader as prismaModuleLoader } from "@calcom/features/di/modules/Prisma";
import { createModule, bindModuleToClassOnToken, type ModuleLoader } from "../di";
@@ -13,7 +13,7 @@ import type { GlobalBlockingService } from "@calcom/features/watchlist/lib/servi
import type { OrganizationBlockingService } from "@calcom/features/watchlist/lib/service/OrganizationBlockingService";
import type { WatchlistAuditService } from "@calcom/features/watchlist/lib/service/WatchlistAuditService";
import type { WatchlistService } from "@calcom/features/watchlist/lib/service/WatchlistService";
import { moduleLoader as prismaModuleLoader } from "@calcom/prisma/prisma.module";
import { moduleLoader as prismaModuleLoader } from "@calcom/features/di/modules/Prisma";
import { WATCHLIST_DI_TOKENS } from "../Watchlist.tokens";
import { watchlistModule } from "../modules/Watchlist.module";
@@ -1,6 +1,6 @@
import { bindModuleToClassOnToken, createModule, type ModuleLoader } from "@calcom/features/di/di";
import { DI_TOKENS } from "@calcom/features/di/tokens";
import { moduleLoader as prismaModuleLoader } from "@calcom/prisma/prisma.module";
import { moduleLoader as prismaModuleLoader } from "@calcom/features/di/modules/Prisma";
import { HashedLinkRepository } from "../lib/repository/HashedLinkRepository";
@@ -1,7 +1,7 @@
import { bindModuleToClassOnToken, createModule, type ModuleLoader } from "@calcom/features/di/di";
import { DI_TOKENS } from "@calcom/features/di/tokens";
import { MembershipRepository } from "@calcom/features/membership/repositories/MembershipRepository";
import { moduleLoader as prismaModuleLoader } from "@calcom/prisma/prisma.module";
import { moduleLoader as prismaModuleLoader } from "@calcom/features/di/modules/Prisma";
export const membershipRepositoryModule = createModule();
const token = DI_TOKENS.MEMBERSHIP_REPOSITORY;
@@ -1,14 +1,32 @@
import { IdempotencyKeyService } from "@calcom/lib/idempotencyKey/idempotencyKeyService";
import { v5 as uuidv5 } from "uuid";
import { Prisma } from "@calcom/prisma/client";
import { BookingStatus } from "@calcom/prisma/enums";
function generateIdempotencyKey({
startTime,
endTime,
userId,
reassignedById,
}: {
startTime: Date | string;
endTime: Date | string;
userId?: number;
reassignedById?: number | null;
}) {
return uuidv5(
`${startTime.valueOf()}.${endTime.valueOf()}.${userId}${reassignedById ? `.${reassignedById}` : ""}`,
uuidv5.URL
);
}
export function bookingIdempotencyKeyExtension() {
return Prisma.defineExtension({
query: {
booking: {
async create({ args, query }) {
if (args.data.status === BookingStatus.ACCEPTED) {
const idempotencyKey = IdempotencyKeyService.generate({
const idempotencyKey = generateIdempotencyKey({
startTime: args.data.startTime,
endTime: args.data.endTime,
userId: args.data.user?.connect?.id,
-3
View File
@@ -5,7 +5,6 @@ import { bookingIdempotencyKeyExtension } from "./extensions/booking-idempotency
import { disallowUndefinedDeleteUpdateManyExtension } from "./extensions/disallow-undefined-delete-update-many";
import { excludeLockedUsersExtension } from "./extensions/exclude-locked-users";
import { excludePendingPaymentsExtension } from "./extensions/exclude-pending-payment-teams";
import { usageTrackingExtention } from "./extensions/usage-tracking";
import { PrismaClient, type Prisma } from "./generated/prisma/client";
const connectionString = process.env.DATABASE_URL || "";
@@ -66,7 +65,6 @@ export const customPrisma = (options?: Prisma.PrismaClientOptions) => {
}
return new PrismaClient(finalOptions)
.$extends(usageTrackingExtention(baseClient))
.$extends(excludeLockedUsersExtension())
.$extends(excludePendingPaymentsExtension())
.$extends(bookingIdempotencyKeyExtension())
@@ -79,7 +77,6 @@ export const customPrisma = (options?: Prisma.PrismaClientOptions) => {
// Explanation why we cast as PrismaClient. When we leave Prisma to its devices it tries to infer logic based on the extensions, but this is not a simple extends.
// this makes the PrismaClient export type-hint impossible and it also is a massive hit on Prisma type hinting performance.
export const prisma: PrismaClient = baseClient
.$extends(usageTrackingExtention(baseClient))
.$extends(excludeLockedUsersExtension())
.$extends(excludePendingPaymentsExtension())
.$extends(bookingIdempotencyKeyExtension())
+1 -1
View File
@@ -23,7 +23,6 @@
"seed-pbac": "ts-node --transpile-only ../../scripts/seed-pbac-organization.ts"
},
"dependencies": {
"@calcom/lib": "workspace:*",
"@prisma/adapter-pg": "^6.16.0",
"@prisma/client": "^6.16.0",
"@prisma/extension-accelerate": "^1.3.0",
@@ -31,6 +30,7 @@
"prisma": "^6.16.0",
"prisma-kysely": "2.2.0",
"ts-node": "^10.9.1",
"uuid": "^8.3.2",
"zod": "^3.22.4",
"zod-prisma-types": "^3.2.4"
},
+6 -6
View File
@@ -223,7 +223,7 @@ model EventType {
hideOrganizerEmail Boolean @default(false)
maxActiveBookingsPerBooker Int?
maxActiveBookingPerBookerOfferReschedule Boolean @default(false)
/// @zod.import(["import { emailSchema } from '@calcom/lib/emailSchema'"]).custom.use(emailSchema)
/// @zod.import(["import { emailSchema } from '../../zod-utils'"]).custom.use(emailSchema)
customReplyToEmail String?
calVideoSettings CalVideoSettings?
@@ -300,7 +300,7 @@ model DestinationCalendar {
id Int @id @default(autoincrement())
integration String
externalId String
/// @zod.import(["import { emailSchema } from '@calcom/lib/emailSchema'"]).custom.use(emailSchema)
/// @zod.import(["import { emailSchema } from '../../zod-utils'"]).custom.use(emailSchema)
primaryEmail String?
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
userId Int? @unique
@@ -352,7 +352,7 @@ model User {
uuid String @unique @default(uuid()) @db.Uuid
username String?
name String?
/// @zod.import(["import { emailSchema } from '@calcom/lib/emailSchema'"]).custom.use(emailSchema)
/// @zod.import(["import { emailSchema } from '../../zod-utils'"]).custom.use(emailSchema)
email String
emailVerified DateTime?
password UserPassword?
@@ -796,7 +796,7 @@ model Booking {
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
userId Int?
// User's email at the time of booking
/// @zod.import(["import { emailSchema } from '@calcom/lib/emailSchema'"]).custom.use(emailSchema)
/// @zod.import(["import { emailSchema } from '../../zod-utils'"]).custom.use(emailSchema)
userPrimaryEmail String?
references BookingReference[]
eventType EventType? @relation(fields: [eventTypeId], references: [id])
@@ -842,9 +842,9 @@ model Booking {
noShowHost Boolean? @default(false)
scheduledTriggers WebhookScheduledTriggers[]
oneTimePassword String? @unique @default(uuid())
/// @zod.import(["import { emailSchema } from '@calcom/lib/emailSchema'"]).custom.use(emailSchema)
/// @zod.import(["import { emailSchema } from '../../zod-utils'"]).custom.use(emailSchema)
cancelledBy String?
/// @zod.import(["import { emailSchema } from '@calcom/lib/emailSchema'"]).custom.use(emailSchema)
/// @zod.import(["import { emailSchema } from '../../zod-utils'"]).custom.use(emailSchema)
rescheduledBy String?
// Ah, made a typo here. Should have been routedFromRoutingFormRe"s"ponse. Live with it :(
routedFromRoutingFormReponse App_RoutingForms_FormResponse?
+167 -8
View File
@@ -11,16 +11,177 @@ import type {
ZodTypeAny,
} from "zod";
import { isPasswordValid } from "@calcom/lib/auth/isPasswordValid";
import { emailSchema as emailRegexSchema, emailRegex } from "@calcom/lib/emailSchema";
import { getValidRhfFieldName } from "@calcom/lib/getValidRhfFieldName";
import type { IntervalLimit } from "@calcom/lib/intervalLimits/intervalLimitSchema";
import { zodAttributesQueryValue } from "@calcom/lib/raqb/zod";
import { slugify } from "@calcom/lib/slugify";
import { EventTypeCustomInputType } from "@calcom/prisma/enums";
import type { Prisma } from "./client";
/** @see https://github.com/colinhacks/zod/issues/3155#issuecomment-2060045794 */
export const emailRegex =
/* eslint-disable-next-line no-useless-escape */
/^(?!\.)(?!.*\.\.)([A-Z0-9_+-\.']*)[A-Z0-9_+'-]@([A-Z0-9][A-Z0-9\-]*\.)+[A-Z]{2,}$/i;
/**
* RFC 5321 Section 4.5.3.1.3 specifies:
* - Maximum email address length: 254 characters
* - Local part (before @): max 64 characters
* - Domain part (after @): max 253 characters
*/
const MAX_EMAIL_LENGTH = 254;
const emailRegexSchema = z
.string()
.max(MAX_EMAIL_LENGTH, { message: "Email address is too long" })
.regex(emailRegex);
const slugify = (str: string, forDisplayingInput?: boolean) => {
if (!str) {
return "";
}
const s = str
.toLowerCase() // Convert to lowercase
.trim() // Remove whitespace from both sides
.normalize("NFD") // Normalize to decomposed form for handling accents
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
.replace(/\p{Diacritic}/gu, "") // Remove any diacritics (accents) from characters
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
.replace(/[^.\p{L}\p{N}\p{Zs}\p{Emoji}]+/gu, "-") // Replace any non-alphanumeric characters (including Unicode and except "." period) with a dash
.replace(/[\s_#]+/g, "-") // Replace whitespace, # and underscores with a single dash
.replace(/^-+/, "") // Remove dashes from start
.replace(/\.{2,}/g, ".") // Replace consecutive periods with a single period
.replace(/^\.+/, "") // Remove periods from the start
.replace(
/([\u2700-\u27BF]|[\uE000-\uF8FF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDFFF]|[\u2011-\u26FF]|\uD83E[\uDD10-\uDDFF])/g,
""
) // Removes emojis
.replace(/\s+/g, " ")
.replace(/-+/g, "-"); // Replace consecutive dashes with a single dash
return forDisplayingInput ? s : s.replace(/-+$/, "").replace(/\.*$/, ""); // Remove dashes and period from end
};
const getValidRhfFieldName = (fieldName: string) => {
// Remember that any transformation that you do here would run on System Field names as well. So, be careful and avoiding doing anything here that would modify the SystemField names.
// e.g. SystemField name currently have uppercases in them. So, no need to lowercase unless absolutely needed.
return fieldName.replace(/[^a-zA-Z0-9-_]/g, "-");
};
function isPasswordValid(password: string): boolean;
function isPasswordValid(
password: string,
breakdown: boolean,
strict?: boolean
): { caplow: boolean; num: boolean; min: boolean; admin_min: boolean };
function isPasswordValid(password: string, breakdown?: boolean, strict?: boolean) {
let cap = false, // Has uppercase characters
low = false, // Has lowercase characters
num = false, // At least one number
min = false, // Eight characters, or fifteen in strict mode.
admin_min = false;
if (password.length >= 7 && (!strict || password.length > 14)) min = true;
if (strict && password.length > 14) admin_min = true;
if (password.match(/\d/)) num = true;
if (password.match(/[a-z]/)) low = true;
if (password.match(/[A-Z]/)) cap = true;
if (!breakdown) return cap && low && num && min && (strict ? admin_min : true);
let errors: Record<string, boolean> = { caplow: cap && low, num, min };
// Only return the admin key if strict mode is enabled.
if (strict) errors = { ...errors, admin_min };
return errors;
}
export type IntervalLimitUnit = "day" | "week" | "month" | "year";
export type IntervalLimit = Partial<Record<`PER_${Uppercase<IntervalLimitUnit>}`, number | undefined>>;
export type IntervalLimitKey = keyof IntervalLimit;
export const intervalLimitsType: z.Schema<IntervalLimit | null> = z
.object({
PER_DAY: z.number().optional(),
PER_WEEK: z.number().optional(),
PER_MONTH: z.number().optional(),
PER_YEAR: z.number().optional(),
})
.nullable();
const raqbChildSchema = z.object({
type: z.string().optional(),
properties: z
.object({
field: z.any().optional(),
operator: z.any().optional(),
value: z.any().optional(),
valueSrc: z.any().optional(),
valueError: z.array(z.union([z.string(), z.null()])).optional(),
valueType: z.any().optional(),
})
.optional(),
});
const raqbChildren1Schema = z
.record(raqbChildSchema)
.superRefine((children1, ctx) => {
if (!children1) return;
const isObject = (value: unknown): value is Record<string, unknown> =>
typeof value === "object" && value !== null;
Object.entries(children1).forEach(([, _rule]) => {
const rule = _rule as unknown;
if (!isObject(rule) || rule.type !== "rule") return;
if (!isObject(rule.properties)) return;
const value = rule.properties.value || [];
const valueSrc = rule.properties.valueSrc;
if (!(value instanceof Array) || !(valueSrc instanceof Array)) {
return;
}
if (!valueSrc.length) {
// If valueSrc is empty, value could be empty for operators like is_empty, is_not_empty
return;
}
// MultiSelect array can be 2D array
const flattenedValues = value.flat();
const validValues = flattenedValues.filter((value: unknown) => {
// Might want to restrict it to filter out null and empty string as well. But for now we know that Prisma errors only for undefined values when saving it in JSON field
// Also, it is possible that RAQB has some requirements to support null or empty string values.
if (value === undefined) return false;
return true;
});
if (!validValues.length) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Looks like you are trying to create a rule with no value",
});
}
});
});
const raqbQueryValueSchema = z.union([
z.object({
id: z.string().optional(),
type: z.literal("group"),
children1: raqbChildren1Schema.optional(),
properties: z.any(),
}),
z.object({
id: z.string().optional(),
type: z.literal("switch_group"),
children1: raqbChildren1Schema.optional(),
properties: z.any(),
}),
]);
const zodAttributesQueryValue = raqbQueryValueSchema;
// Let's not import 118kb just to get an enum
export enum Frequency {
YEARLY = 0,
@@ -220,8 +381,6 @@ export const eventTypeColor = z
export type IntervalLimitsType = IntervalLimit | null;
export { intervalLimitsType } from "@calcom/lib/intervalLimits/intervalLimitSchema";
export const eventTypeSlug = z
.string()
.trim()