perf: move to disable prisma client extension inference (#23692)

* perf: move to disable prisma client extension inference

* Prisma doesn't like it when you pass Record<string, unknown>

* API v1 type fixes

* Missed one

* Fix unit test fail due to faulty expect

* Assigning to prisma InputJsonValue/Array must be done as object, not interface

* Fix @calcom/web ts error, teams not defined

* Run eslint formatter

* fixed the routingFormHelpers file causing a failing app store e2e test
This commit is contained in:
Alex van Andel
2025-09-09 10:56:58 +00:00
committed by GitHub
parent 38bc5fb71e
commit aa51218428
24 changed files with 295 additions and 253 deletions
@@ -3,7 +3,8 @@ import type { NextApiRequest } from "next";
import { OAuth2UniversalSchema } from "@calcom/app-store/_utils/oauth/universalSchema";
import { symmetricDecrypt } from "@calcom/lib/crypto";
import { defaultResponder } from "@calcom/lib/server/defaultResponder";
import prisma from "@calcom/prisma";
import { prisma } from "@calcom/prisma";
import type { Prisma } from "@calcom/prisma/client";
import { schemaCredentialPatchParams, schemaCredentialPatchBody } from "~/lib/validations/credential-sync";
@@ -71,7 +72,7 @@ async function handler(req: NextApiRequest) {
userId,
},
data: {
key,
key: key as unknown as Prisma.InputJsonValue,
},
select: {
id: true,
@@ -6,7 +6,8 @@ import { appStoreMetadata } from "@calcom/app-store/appStoreMetaData";
import { symmetricDecrypt } from "@calcom/lib/crypto";
import { HttpError } from "@calcom/lib/http-error";
import { defaultResponder } from "@calcom/lib/server/defaultResponder";
import prisma from "@calcom/prisma";
import { prisma } from "@calcom/prisma";
import type { Prisma } from "@calcom/prisma/client";
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
import { schemaCredentialPostBody, schemaCredentialPostParams } from "~/lib/validations/credential-sync";
@@ -92,7 +93,7 @@ async function handler(req: NextApiRequest) {
data: {
userId,
appId: appSlug,
key,
key: key as unknown as Prisma.InputJsonValue,
type: appMetadata.type,
},
select: credentialForCalendarServiceSelect,
+1 -1
View File
@@ -77,7 +77,7 @@ describe("POST /api/attendees", () => {
const userBooking = { id: 1 };
prismaMock.booking.findFirst.mockResolvedValue(userBooking);
prismaMock.booking.findFirst.mockResolvedValue(userBooking as any);
const attendeeData = {
id: 1,
@@ -25,7 +25,7 @@ beforeEach(() => {
id: userId,
email: "test@example.com",
name: "Test User",
});
} as any);
});
afterEach(() => {
@@ -20,7 +20,7 @@ beforeEach(() => {
id: userId,
email: "test@example.com",
name: "Test User",
});
} as any);
});
afterEach(() => {
+3 -2
View File
@@ -30,12 +30,13 @@ beforeEach(() => {
id: userId,
email: "test@example.com",
name: "Test User",
});
} as any);
(getAccessibleUsers as any).mockResolvedValue([userId]);
(retrieveOrgScopedAccessibleUsers as any).mockResolvedValue([userId]);
prismaMock.membership.findMany.mockResolvedValue([
{
// @ts-expect-error Will be fixed by Prisma 6.7.0 upgrade mock changes - which uses vitest-mock-extended
team: {
id: 1,
isOrganization: true,
@@ -48,7 +49,7 @@ beforeEach(() => {
id: userId,
email: "test@example.com",
},
]);
] as any);
});
afterEach(() => {
+12 -4
View File
@@ -11,9 +11,9 @@ import { getEventTypesFromDB } from "@calcom/features/bookings/lib/handleNewBook
import sendPayload from "@calcom/features/webhooks/lib/sendOrSchedulePayload";
import { ErrorCode } from "@calcom/lib/errorCodes";
import { buildBooking, buildEventType, buildWebhook, buildUser } from "@calcom/lib/test/builder";
import prisma from "@calcom/prisma";
import { prisma } from "@calcom/prisma";
import type { Booking } from "@calcom/prisma/client";
import { CreationSource } from "@calcom/prisma/enums";
import { CreationSource, BookingStatus } from "@calcom/prisma/enums";
import handler from "../../../pages/api/bookings/_post";
@@ -303,6 +303,7 @@ describe("POST /api/bookings", () => {
prismaMock.eventType.findUniqueOrThrow.mockResolvedValue({
...buildEventType({ recurringEvent: { freq: 2, count: 12, interval: 1 } }),
// @ts-expect-error requires mockDeep which will be introduced in the Prisma 6.7.0 upgrade, ignore for now.
profile: { organizationId: null },
hosts: [],
users: [buildUser()],
@@ -340,6 +341,7 @@ describe("POST /api/bookings", () => {
prismaMock.eventType.findUniqueOrThrow.mockResolvedValue({
...buildEventType({ recurringEvent: { freq: 2, count: 12, interval: 1 } }),
// @ts-expect-error requires mockDeep which will be introduced in the Prisma 6.7.0 upgrade, ignore for now.
profile: { organizationId: null },
hosts: [],
users: [buildUser()],
@@ -376,6 +378,7 @@ describe("POST /api/bookings", () => {
prismaMock.eventType.findUniqueOrThrow.mockResolvedValue({
...buildEventType({ profileId: null }),
// @ts-expect-error requires mockDeep which will be introduced in the Prisma 6.7.0 upgrade, ignore for now.
profile: { organizationId: null },
hosts: [],
users: [buildUser()],
@@ -417,6 +420,7 @@ describe("POST /api/bookings", () => {
prismaMock.eventType.findUniqueOrThrow.mockResolvedValue({
...buildEventType({ profileId: null, length: 15 }),
// @ts-expect-error requires mockDeep which will be introduced in the Prisma 6.7.0 upgrade, ignore for now.
profile: { organizationId: null },
hosts: [],
users: [buildUser()],
@@ -468,7 +472,7 @@ describe("POST /api/bookings", () => {
prismaMock.booking.findUnique.mockResolvedValue({
...originalBooking,
status: "cancelled",
status: "CANCELLED",
});
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
@@ -492,6 +496,7 @@ describe("POST /api/bookings", () => {
prismaMock.eventType.findUniqueOrThrow.mockResolvedValue({
...buildEventType({ profileId: null, length: 15 }),
// @ts-expect-error requires mockDeep which will be introduced in the Prisma 6.7.0 upgrade, ignore for now.
profile: { organizationId: null },
hosts: [],
users: [buildUser()],
@@ -532,7 +537,7 @@ describe("POST /api/bookings", () => {
const previousBooking = await prisma.booking.findUnique({
where: { uid: "original-booking-uid" },
});
expect(previousBooking?.status).toBe("cancelled");
expect(previousBooking?.status).toBe(BookingStatus.CANCELLED);
});
test("Creates source as api_v1", async () => {
@@ -556,6 +561,7 @@ describe("POST /api/bookings", () => {
prismaMock.eventType.findUniqueOrThrow.mockResolvedValue({
...buildEventType({ profileId: null, length: 15 }),
// @ts-expect-error requires mockDeep which will be introduced in the Prisma 6.7.0 upgrade, ignore for now.
profile: { organizationId: null },
hosts: [],
users: [buildUser()],
@@ -627,6 +633,7 @@ describe("POST /api/bookings", () => {
prismaMock.eventType.findUniqueOrThrow.mockResolvedValue({
...buildEventType({ profileId: null, length: 15 }),
// @ts-expect-error requires mockDeep which will be introduced in the Prisma 6.7.0 upgrade, ignore for now.
profile: { organizationId: null },
hosts: [],
users: [buildUser()],
@@ -684,6 +691,7 @@ describe("POST /api/bookings", () => {
prismaMock.eventType.findUniqueOrThrow.mockResolvedValue({
...buildEventType({ recurringEvent: { freq: 2, count: 12, interval: 1 } }),
// @ts-expect-error requires mockDeep which will be introduced in the Prisma 6.7.0 upgrade, ignore for now.
profile: { organizationId: null },
hosts: [],
users: [buildUser()],
@@ -50,6 +50,7 @@ describe("DELETE /api/event-types/[id]", () => {
// Mocking team.findUnique
prismaMock.team.findUnique.mockResolvedValue({
id: teamId,
// @ts-expect-error requires mockDeep which will be introduced in the Prisma 6.7.0 upgrade, ignore for now.
members: [
{ userId: memberUser, role: MembershipRole.MEMBER, teamId: teamId },
{ userId: adminUser, role: MembershipRole.ADMIN, teamId: teamId },
@@ -84,6 +84,7 @@ describe("GET /api/event-types/[id]", () => {
prismaMock.team.findFirst.mockResolvedValue({
id: teamId,
// @ts-expect-error requires mockDeep which will be introduced in the Prisma 6.7.0 upgrade, ignore for now.
members: [
{
userId,
@@ -29,8 +29,11 @@ interface RouteConfig {
value: string;
};
isFallback?: boolean;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
queryValue?: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
attributesQueryValue?: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
fallbackAttributesQueryValue?: any;
}
@@ -129,6 +132,7 @@ export async function createRoutingForm(config: CreateRoutingFormConfig) {
const teamEventsConfig = config.attributeRouting.teamEvents;
// Create team events
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const teamEvents: { [key: string]: any } = {};
for (const eventConfig of teamEventsConfig) {
const eventType = await createRoundRobinTeamEventType({
@@ -149,10 +153,8 @@ export async function createRoutingForm(config: CreateRoutingFormConfig) {
throw new Error("Attributes not found");
}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const skillAttribute = attributes.find((attr) => attr.name === "Skills")!;
const locationAttribute = attributes.find((attr) => attr.name === "Location")!;
console.log("Attributes", JSON.stringify(attributes, null, 2));
const javascriptEventRoute = {
id: "8a898988-89ab-4cde-b012-31823f708642",
@@ -316,41 +318,44 @@ export async function createRoutingForm(config: CreateRoutingFormConfig) {
queryValue: { id: "814899aa-4567-489a-bcde-f1823f708646", type: "group" },
},
],
fields: config.form?.fields || [
{
id: form.formFieldLocation.id,
type: "select",
label: "Location",
options: formFieldLocationOptions,
required: true,
},
{
id: form.formFieldSkills.id,
type: "multiselect",
label: "skills",
options: formFieldSkillsOptions,
required: true,
},
{
id: form.formFieldEmail.id,
type: "email",
label: "Email",
required: true,
},
{
id: form.formFieldName.id,
identifier: "name",
type: "text",
label: "Name",
required: false,
},
{
id: form.formFieldRating.id,
type: "number",
label: "Rating",
required: false,
},
],
fields: (
config.form?.fields ||
([
{
id: form.formFieldLocation.id,
type: "select",
label: "Location",
options: formFieldLocationOptions,
required: true,
},
{
id: form.formFieldSkills.id,
type: "multiselect",
label: "skills",
options: formFieldSkillsOptions,
required: true,
},
{
id: form.formFieldEmail.id,
type: "email",
label: "Email",
required: true,
},
{
id: form.formFieldName.id,
identifier: "name",
type: "text",
label: "Name",
required: false,
},
{
id: form.formFieldRating.id,
type: "number",
label: "Rating",
required: false,
},
] satisfies FieldConfig[])
).map((field) => ({ ...field })),
team: {
connect: {
id: teamId,
@@ -379,172 +384,178 @@ export async function createRoutingForm(config: CreateRoutingFormConfig) {
const form = await prisma.app_RoutingForms_Form.create({
data: {
routes: config.form?.routes || [
{
id: "8a898988-89ab-4cde-b012-31823f708642",
action: { type: "eventTypeRedirectUrl", value: "pro/30min" },
queryValue: {
routes: (
config.form?.routes ||
([
{
id: "8a898988-89ab-4cde-b012-31823f708642",
type: "group",
children1: {
"8988bbb8-0123-4456-b89a-b1823f70c5ff": {
type: "rule",
properties: {
field: "c4296635-9f12-47b1-8153-c3a854649182",
value: ["event-routing"],
operator: "equal",
valueSrc: ["value"],
valueType: ["text"],
action: { type: "eventTypeRedirectUrl", value: "pro/30min" },
queryValue: {
id: "8a898988-89ab-4cde-b012-31823f708642",
type: "group",
children1: {
"8988bbb8-0123-4456-b89a-b1823f70c5ff": {
type: "rule",
properties: {
field: "c4296635-9f12-47b1-8153-c3a854649182",
value: ["event-routing"],
operator: "equal",
valueSrc: ["value"],
valueType: ["text"],
},
},
},
},
},
},
{
id: "aa8aaba9-cdef-4012-b456-71823f70f7ef",
action: { type: "customPageMessage", value: "Custom Page Result" },
queryValue: {
{
id: "aa8aaba9-cdef-4012-b456-71823f70f7ef",
type: "group",
children1: {
"b99b8a89-89ab-4cde-b012-31823f718ff5": {
type: "rule",
properties: {
field: "c4296635-9f12-47b1-8153-c3a854649182",
value: ["custom-page"],
operator: "equal",
valueSrc: ["value"],
valueType: ["text"],
action: { type: "customPageMessage", value: "Custom Page Result" },
queryValue: {
id: "aa8aaba9-cdef-4012-b456-71823f70f7ef",
type: "group",
children1: {
"b99b8a89-89ab-4cde-b012-31823f718ff5": {
type: "rule",
properties: {
field: "c4296635-9f12-47b1-8153-c3a854649182",
value: ["custom-page"],
operator: "equal",
valueSrc: ["value"],
valueType: ["text"],
},
},
},
},
},
},
{
id: "a8ba9aab-4567-489a-bcde-f1823f71b4ad",
action: { type: "externalRedirectUrl", value: `${WEBAPP_URL}/pro` },
queryValue: {
{
id: "a8ba9aab-4567-489a-bcde-f1823f71b4ad",
type: "group",
children1: {
"998b9b9a-0123-4456-b89a-b1823f7232b9": {
type: "rule",
properties: {
field: "c4296635-9f12-47b1-8153-c3a854649182",
value: ["external-redirect"],
operator: "equal",
valueSrc: ["value"],
valueType: ["text"],
action: { type: "externalRedirectUrl", value: `${WEBAPP_URL}/pro` },
queryValue: {
id: "a8ba9aab-4567-489a-bcde-f1823f71b4ad",
type: "group",
children1: {
"998b9b9a-0123-4456-b89a-b1823f7232b9": {
type: "rule",
properties: {
field: "c4296635-9f12-47b1-8153-c3a854649182",
value: ["external-redirect"],
operator: "equal",
valueSrc: ["value"],
valueType: ["text"],
},
},
},
},
},
},
{
id: "aa8ba8b9-0123-4456-b89a-b182623406d8",
action: { type: "customPageMessage", value: "Multiselect(Legacy) chosen" },
queryValue: {
{
id: "aa8ba8b9-0123-4456-b89a-b182623406d8",
type: "group",
children1: {
"b98a8abb-cdef-4012-b456-718262343d27": {
type: "rule",
properties: {
field: multiSelectLegacyFieldUuid,
value: [["Option-2"]],
operator: "multiselect_equals",
valueSrc: ["value"],
valueType: ["multiselect"],
action: { type: "customPageMessage", value: "Multiselect(Legacy) chosen" },
queryValue: {
id: "aa8ba8b9-0123-4456-b89a-b182623406d8",
type: "group",
children1: {
"b98a8abb-cdef-4012-b456-718262343d27": {
type: "rule",
properties: {
field: multiSelectLegacyFieldUuid,
value: [["Option-2"]],
operator: "multiselect_equals",
valueSrc: ["value"],
valueType: ["multiselect"],
},
},
},
},
},
},
{
id: "bb9ea8b9-0123-4456-b89a-b182623406d8",
action: { type: "customPageMessage", value: "Multiselect chosen" },
queryValue: {
id: "aa8ba8b9-0123-4456-b89a-b182623406d8",
type: "group",
children1: {
"b98a8abb-cdef-4012-b456-718262343d27": {
type: "rule",
properties: {
field: multiSelectFieldUuid,
value: [[multiSelectOption2Uuid]],
operator: "multiselect_equals",
valueSrc: ["value"],
valueType: ["multiselect"],
{
id: "bb9ea8b9-0123-4456-b89a-b182623406d8",
action: { type: "customPageMessage", value: "Multiselect chosen" },
queryValue: {
id: "aa8ba8b9-0123-4456-b89a-b182623406d8",
type: "group",
children1: {
"b98a8abb-cdef-4012-b456-718262343d27": {
type: "rule",
properties: {
field: multiSelectFieldUuid,
value: [[multiSelectOption2Uuid]],
operator: "multiselect_equals",
valueSrc: ["value"],
valueType: ["multiselect"],
},
},
},
},
},
},
{
id: "898899aa-4567-489a-bcde-f1823f708646",
action: { type: "customPageMessage", value: "Fallback Message" },
isFallback: true,
queryValue: { id: "898899aa-4567-489a-bcde-f1823f708646", type: "group" },
},
],
fields: config.form?.fields || [
{
id: "c4296635-9f12-47b1-8153-c3a854649182",
type: "text",
label: "Test field",
required: true,
},
{
id: multiSelectLegacyFieldUuid,
type: "multiselect",
label: "Multi Select(with Legacy `selectText`)",
identifier: "multi",
selectText: "Option-1\nOption-2",
required: false,
},
{
id: multiSelectFieldUuid,
type: "multiselect",
label: "Multi Select",
identifier: "multi-new-format",
options: [
{
id: multiSelectOption1Uuid,
label: "Option-1",
},
{
id: multiSelectOption2Uuid,
label: "Option-2",
},
],
required: false,
},
{
id: legacySelectFieldUuid,
type: "select",
label: "Legacy Select",
identifier: "test-select",
selectText: "Option-1\nOption-2",
required: false,
},
{
id: selectFieldUuid,
type: "select",
label: "Select",
identifier: "test-select-new-format",
options: [
{
id: selectOption1Uuid,
label: "Option-1",
},
{
id: selectOption2Uuid,
label: "Option-2",
},
],
required: false,
},
],
{
id: "898899aa-4567-489a-bcde-f1823f708646",
action: { type: "customPageMessage", value: "Fallback Message" },
isFallback: true,
queryValue: { id: "898899aa-4567-489a-bcde-f1823f708646", type: "group" },
},
] satisfies RouteConfig[])
).map((field) => ({ ...field })),
fields: (
config.form?.fields ||
([
{
id: "c4296635-9f12-47b1-8153-c3a854649182",
type: "text",
label: "Test field",
required: true,
},
{
id: multiSelectLegacyFieldUuid,
type: "multiselect",
label: "Multi Select(with Legacy `selectText`)",
identifier: "multi",
selectText: "Option-1\nOption-2",
required: false,
},
{
id: multiSelectFieldUuid,
type: "multiselect",
label: "Multi Select",
identifier: "multi-new-format",
options: [
{
id: multiSelectOption1Uuid,
label: "Option-1",
},
{
id: multiSelectOption2Uuid,
label: "Option-2",
},
],
required: false,
},
{
id: legacySelectFieldUuid,
type: "select",
label: "Legacy Select",
identifier: "test-select",
selectText: "Option-1\nOption-2",
required: false,
},
{
id: selectFieldUuid,
type: "select",
label: "Select",
identifier: "test-select-new-format",
options: [
{
id: selectOption1Uuid,
label: "Option-1",
},
{
id: selectOption2Uuid,
label: "Option-2",
},
],
required: false,
},
] satisfies FieldConfig[])
).map((field) => ({ ...field })),
user: {
connect: {
id: userId,
@@ -1,9 +1,9 @@
import type { Prisma } from "@prisma/client";
import type z from "zod";
import logger from "@calcom/lib/logger";
import { CredentialRepository } from "@calcom/lib/server/repository/credential";
import prisma from "@calcom/prisma";
import { prisma } from "@calcom/prisma";
import type { Prisma } from "@calcom/prisma/client";
import type { OAuth2UniversalSchemaWithCalcomBackwardCompatibility } from "./universalSchema";
@@ -23,7 +23,7 @@ export const updateTokenObject = async ({
id: credentialId,
},
data: {
key: tokenObject,
key: tokenObject as unknown as Prisma.InputJsonValue,
},
});
};
@@ -5,6 +5,7 @@ import { z } from "zod";
import logger from "@calcom/lib/logger";
import { safeStringify } from "@calcom/lib/safeStringify";
import prisma from "@calcom/prisma";
import type { Prisma } from "@calcom/prisma/client";
import type { CalendarEvent } from "@calcom/types/Calendar";
import type { CredentialPayload } from "@calcom/types/Credential";
import type { PartialReference } from "@calcom/types/EventManager";
@@ -102,7 +103,7 @@ const NextcloudTalkVideoApiAdapter = (credential: CredentialPayload): VideoApiAd
id: credential.id,
},
data: {
key: newTokenObject,
key: newTokenObject as unknown as Prisma.InputJsonValue,
},
});
},
@@ -10,7 +10,8 @@ import {
import logger from "@calcom/lib/logger";
import { getPiiFreeCalendarEvent } from "@calcom/lib/piiFreeData";
import { safeStringify } from "@calcom/lib/safeStringify";
import prisma from "@calcom/prisma";
import { prisma } from "@calcom/prisma";
import type { Prisma } from "@calcom/prisma/client";
import { Frequency } from "@calcom/prisma/zod-utils";
import type { CalendarEvent } from "@calcom/types/Calendar";
import type { CredentialPayload } from "@calcom/types/Credential";
@@ -316,7 +317,8 @@ const ZoomVideoApiAdapter = (credential: CredentialPayload): VideoApiAdapter =>
id: credential.id,
},
data: {
key: newTokenObject,
// z.passthrough() is not allowed in Prisma, but we know this is trusted.
key: newTokenObject as unknown as Prisma.InputJsonValue,
},
});
},
@@ -36,7 +36,6 @@ export class FeaturesRepository implements IFeaturesRepository {
const features = await this.prismaClient.feature.findMany({
orderBy: { slug: "asc" },
cacheStrategy: { swr: 300, ttl: 300 },
});
FeaturesRepository.featuresCache = {
@@ -12,7 +12,7 @@ import {
} from "@calcom/features/insights/server/raw-data.schema";
import { getInsightsBookingService } from "@calcom/lib/di/containers/InsightsBooking";
import { getInsightsRoutingService } from "@calcom/lib/di/containers/InsightsRouting";
import type { readonlyPrisma } from "@calcom/prisma";
import type { PrismaClient } from "@calcom/prisma";
import { BookingStatus } from "@calcom/prisma/enums";
import authedProcedure from "@calcom/trpc/server/procedures/authedProcedure";
import { router } from "@calcom/trpc/server/trpc";
@@ -31,7 +31,7 @@ const UserBelongsToTeamInput = z.object({
type BuildBaseWhereConditionCtxType = {
userIsOwnerAdminOfParentTeam: boolean;
userOrganizationId: number | null;
insightsDb: typeof readonlyPrisma;
insightsDb: PrismaClient;
};
interface BuildBaseWhereConditionType {
@@ -341,7 +341,7 @@ function createInsightsBookingService(
}
function createInsightsRoutingService(
ctx: { insightsDb: typeof readonlyPrisma; user: { id: number; organizationId: number | null } },
ctx: { insightsDb: PrismaClient; user: { id: number; organizationId: number | null } },
input: z.infer<typeof routingRepositoryBaseInputSchema>
) {
return getInsightsRoutingService({
@@ -1037,7 +1037,7 @@ export async function getEventTypeList({
isAll,
user,
}: {
prisma: typeof readonlyPrisma;
prisma: PrismaClient;
teamId: number | null | undefined;
userId: number | null | undefined;
isAll: boolean | undefined;
+24 -6
View File
@@ -1,16 +1,24 @@
import type { Prisma } from "@prisma/client";
import type { FormResponse } from "@calcom/app-store/routing-forms/types/types";
import { withReporting } from "@calcom/lib/sentryWrapper";
import type { PrismaClient } from "@calcom/prisma";
import { bookingMinimalSelect } from "@calcom/prisma";
import type { Booking } from "@calcom/prisma/client";
import { RRTimestampBasis } from "@calcom/prisma/enums";
import { BookingStatus } from "@calcom/prisma/enums";
import { RRTimestampBasis, BookingStatus } from "@calcom/prisma/enums";
import { bookingMinimalSelect } from "@calcom/prisma/selects/booking";
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
import { UserRepository } from "./user";
export type FormResponse = Record<
// Field ID
string,
{
value: number | string | string[];
label: string;
identifier?: string;
}
>;
type TeamBookingsParamsBase = {
user: { id: number; email: string };
teamId: number;
@@ -540,8 +548,10 @@ export class BookingRepository {
},
data: {
location,
metadata,
...(responses && { responses }),
// FIXME: metadata is untyped
metadata: metadata as unknown as Prisma.InputJsonValue,
// FIXME: responses is untyped
...(responses && { responses: responses as unknown as Prisma.InputJsonValue }),
...(iCalSequence !== undefined && { iCalSequence }),
references: {
create: referencesToCreate,
@@ -681,9 +691,13 @@ export class BookingRepository {
},
},
},
// eslint-disable-next-line @calcom/eslint/no-prisma-include-true
destinationCalendar: true,
// eslint-disable-next-line @calcom/eslint/no-prisma-include-true
payment: true,
// eslint-disable-next-line @calcom/eslint/no-prisma-include-true
references: true,
// eslint-disable-next-line @calcom/eslint/no-prisma-include-true
workflowReminders: true,
},
});
@@ -825,9 +839,13 @@ export class BookingRepository {
status: filterForUnconfirmed ? BookingStatus.PENDING : BookingStatus.ACCEPTED,
},
include: {
// eslint-disable-next-line @calcom/eslint/no-prisma-include-true
attendees: true,
// eslint-disable-next-line @calcom/eslint/no-prisma-include-true
references: true,
// eslint-disable-next-line @calcom/eslint/no-prisma-include-true
user: true,
// eslint-disable-next-line @calcom/eslint/no-prisma-include-true
payment: true,
},
});
@@ -97,7 +97,8 @@ export class DelegationCredentialRepository {
id: data.organizationId,
},
},
serviceAccountKey: encryptedKey,
// z.passthrough() is not allowed in Prisma, but we know this is trusted.
serviceAccountKey: encryptedKey as unknown as Prisma.InputJsonValue,
},
select: delegationCredentialSafeSelect,
});
+2 -2
View File
@@ -1,6 +1,6 @@
import { availabilityUserSelect, prisma, type PrismaTransaction, type PrismaClient } from "@calcom/prisma";
import { availabilityUserSelect, prisma, type PrismaTransaction } from "@calcom/prisma";
import { MembershipRole } from "@calcom/prisma/enums";
import type { Prisma, Membership } from "@calcom/prisma/client";
import type { Prisma, Membership, PrismaClient } from "@calcom/prisma/client";
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
import logger from "../../logger";
@@ -1,6 +1,5 @@
import { Prisma } from "@prisma/client";
import { prisma } from "@calcom/prisma";
import { Prisma } from "@calcom/prisma/client";
import type { TServiceAccountKeySchema } from "@calcom/prisma/zod-utils";
import { serviceAccountKeySchema } from "@calcom/prisma/zod-utils";
@@ -42,7 +41,7 @@ export class WorkspacePlatformRepository {
: _defaultServiceAccountKey;
return await prisma.workspacePlatform.create({
data: {
defaultServiceAccountKey,
defaultServiceAccountKey: defaultServiceAccountKey as unknown as Prisma.InputJsonValue,
...rest,
},
select: safeWorkspacePlatformSelect,
@@ -95,7 +94,10 @@ export class WorkspacePlatformRepository {
}) {
return await prisma.workspacePlatform.update({
where: { id },
data,
data: {
...data,
defaultServiceAccountKey: data.defaultServiceAccountKey as unknown as Prisma.InputJsonValue,
},
select: safeWorkspacePlatformSelect,
});
}
@@ -13,7 +13,7 @@ import {
isNumberFilterValue,
} from "@calcom/features/data-table/lib/utils";
import type { DateRange } from "@calcom/features/insights/server/insightsDateUtils";
import type { readonlyPrisma } from "@calcom/prisma";
import type { PrismaClient } from "@calcom/prisma";
import { MembershipRole } from "@calcom/prisma/enums";
import { MembershipRepository } from "../repository/membership";
@@ -142,7 +142,7 @@ const NOTHING_CONDITION = Prisma.sql`1=0`;
const bookingDataKeys = new Set(Object.keys(bookingDataSchema.shape));
export class InsightsBookingBaseService {
private prisma: typeof readonlyPrisma;
private prisma: PrismaClient;
private options: InsightsBookingServiceOptions | null;
private filters: InsightsBookingServiceFilterOptions | null;
private cachedAuthConditions?: Prisma.Sql;
@@ -153,7 +153,7 @@ export class InsightsBookingBaseService {
options,
filters,
}: {
prisma: typeof readonlyPrisma;
prisma: PrismaClient;
options: InsightsBookingServicePublicOptions;
filters?: InsightsBookingServiceFilterOptions;
}) {
@@ -12,7 +12,7 @@ import {
isSingleSelectFilterValue,
} from "@calcom/features/data-table/lib/utils";
import type { DateRange } from "@calcom/features/insights/server/insightsDateUtils";
import type { readonlyPrisma } from "@calcom/prisma";
import type { PrismaClient } from "@calcom/prisma";
import type { BookingStatus } from "@calcom/prisma/enums";
import { MembershipRole } from "@calcom/prisma/enums";
@@ -136,7 +136,7 @@ type GetConditionsOptions = {
};
export class InsightsRoutingBaseService {
private prisma: typeof readonlyPrisma;
private prisma: PrismaClient;
private options: InsightsRoutingServiceOptions | null;
private filters: InsightsRoutingServiceFilterOptions;
@@ -145,7 +145,7 @@ export class InsightsRoutingBaseService {
options,
filters,
}: {
prisma: typeof readonlyPrisma;
prisma: PrismaClient;
options: InsightsRoutingServicePublicOptions;
filters: InsightsRoutingServiceFilterOptions;
}) {
@@ -1,4 +1,4 @@
import type { readonlyPrisma } from "@calcom/prisma";
import type { PrismaClient } from "@calcom/prisma";
import {
InsightsRoutingBaseService,
@@ -7,7 +7,7 @@ import {
} from "./InsightsRoutingBaseService";
export interface IInsightsRoutingService {
prisma: typeof readonlyPrisma;
prisma: PrismaClient;
}
export class InsightsRoutingService {
+3 -3
View File
@@ -1,11 +1,11 @@
import { Prisma } from "@prisma/client";
import type { PrismaClient as PrismaClientWithoutExtensions } from "@prisma/client";
import type { PrismaClient } from "@prisma/client";
import { waitUntil } from "@vercel/functions";
import { UsageEvent, LicenseKeySingleton } from "@calcom/ee/common/server/LicenseKeyService";
import { DeploymentRepository } from "@calcom/lib/server/repository/deployment";
async function incrementUsage(prismaClient: PrismaClientWithoutExtensions, event?: UsageEvent) {
async function incrementUsage(prismaClient: PrismaClient, event?: UsageEvent) {
const deploymentRepo = new DeploymentRepository(prismaClient);
try {
const licenseKeyService = await LicenseKeySingleton.getInstance(deploymentRepo);
@@ -15,7 +15,7 @@ async function incrementUsage(prismaClient: PrismaClientWithoutExtensions, event
}
}
export function usageTrackingExtention(prismaClient: PrismaClientWithoutExtensions) {
export function usageTrackingExtention(prismaClient: PrismaClient) {
return Prisma.defineExtension({
query: {
booking: {
+20 -25
View File
@@ -1,6 +1,5 @@
import type { Prisma } from "@prisma/client";
import { PrismaClient as PrismaClientWithoutExtension } from "@prisma/client";
import { withAccelerate } from "@prisma/extension-accelerate";
import { PrismaClient } from "@prisma/client";
import { bookingIdempotencyKeyExtension } from "./extensions/booking-idempotency-key";
import { disallowUndefinedDeleteUpdateManyExtension } from "./extensions/disallow-undefined-delete-update-many";
@@ -12,8 +11,7 @@ import { bookingReferenceMiddleware } from "./middleware";
const prismaOptions: Prisma.PrismaClientOptions = {};
const globalForPrisma = global as unknown as {
prismaWithoutClientExtensions: PrismaClientWithoutExtension;
prismaWithClientExtensions: PrismaClientWithExtensions;
baseClient: PrismaClient;
};
const loggerLevel = parseInt(process.env.NEXT_PUBLIC_LOGGER_LEVEL ?? "", 10);
@@ -37,34 +35,32 @@ if (!isNaN(loggerLevel)) {
}
}
// Prevents flooding with idle connections
const prismaWithoutClientExtensions =
globalForPrisma.prismaWithoutClientExtensions || new PrismaClientWithoutExtension(prismaOptions);
const baseClient =
globalForPrisma.baseClient || new PrismaClient(prismaOptions);
export const customPrisma = (options?: Prisma.PrismaClientOptions) =>
new PrismaClientWithoutExtension({ ...prismaOptions, ...options })
.$extends(usageTrackingExtention(prismaWithoutClientExtensions))
new PrismaClient({ ...prismaOptions, ...options })
.$extends(usageTrackingExtention(baseClient))
.$extends(excludeLockedUsersExtension())
.$extends(excludePendingPaymentsExtension())
.$extends(bookingIdempotencyKeyExtension())
.$extends(disallowUndefinedDeleteUpdateManyExtension())
.$extends(withAccelerate());
.$extends(disallowUndefinedDeleteUpdateManyExtension()) as unknown as PrismaClient;
// If any changed on middleware server restart is required
// TODO: Migrate it to $extends
bookingReferenceMiddleware(prismaWithoutClientExtensions);
bookingReferenceMiddleware(baseClient);
// FIXME: Due to some reason, there are types failing in certain places due to the $extends. Fix it and then enable it
// Specifically we get errors like `Type 'string | Date | null | undefined' is not assignable to type 'Exact<string | Date | null | undefined, string | Date | null | undefined>'`
const prismaWithClientExtensions = prismaWithoutClientExtensions
.$extends(usageTrackingExtention(prismaWithoutClientExtensions))
// 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())
.$extends(disallowUndefinedDeleteUpdateManyExtension())
.$extends(withAccelerate());
export const prisma = globalForPrisma.prismaWithClientExtensions || prismaWithClientExtensions;
.$extends(disallowUndefinedDeleteUpdateManyExtension()) as unknown as PrismaClient;
// This prisma instance is meant to be used only for READ operations.
// If self hosting, feel free to leave INSIGHTS_DATABASE_URL as empty and `readonlyPrisma` will default to `prisma`.
@@ -75,24 +71,23 @@ export const readonlyPrisma = process.env.INSIGHTS_DATABASE_URL
: prisma;
if (process.env.NODE_ENV !== "production") {
globalForPrisma.prismaWithoutClientExtensions = prismaWithoutClientExtensions;
globalForPrisma.prismaWithClientExtensions = prisma;
globalForPrisma.baseClient = baseClient;
}
type PrismaClientWithExtensions = typeof prismaWithClientExtensions;
export type PrismaClient = PrismaClientWithExtensions;
type OmitPrismaClient = Omit<
PrismaClient,
"$connect" | "$disconnect" | "$on" | "$transaction" | "$use" | "$extends"
>;
// we cant pass tx to functions as types miss match since we have a custom prisma client https://github.com/prisma/prisma/discussions/20924#discussioncomment-10077649
export type PrismaTransaction = OmitPrismaClient;
export type {
OmitPrismaClient as PrismaTransaction,
// we re-export the native PrismaClient type for backwards-compatibility.
PrismaClient
};
/**
* @deprecated Use named export `prisma` instead
*/
export default prisma;
export * from "./selects";