perf: convert services to factory functions and add TypeScript project references (#26117)

* perf(app-store): convert services to factory functions to narrow SDK type exports

This change converts CRM, Calendar, Payment, and Analytics services from
exporting classes to exporting factory functions that return interface types.
This prevents SDK types (HubSpot, Stripe, etc.) from leaking into the type
system when TypeScript emits .d.ts files.

Services modified:
- CRM: hubspot, salesforce, closecom, pipedrive-crm, zoho-bigin, zohocrm
- Calendar: all 13 calendar services
- Payment: stripe, paypal, alby, btcpayserver, hitpay, mock-payment-app
- Analytics: dub

Video adapters were audited and already use factory pattern.

WIP: Salesforce CRM service has a type error that needs fixing.
Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* fix(salesforce): add type assertion for appOptions in factory function

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* fix: update remaining consumers to use factory functions

- getAnalytics.ts: call factory function instead of new
- getConnectedApps.ts: call factory function instead of new
- salesforce/CrmService.ts: fix type assertion with default value
- salesforce/routingForm/incompleteBookingAction.ts: use factory function
- salesforce/routingFormBookingFormHandler.ts: use factory function

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* fix(salesforce): add SalesforceCRM interface for Salesforce-specific methods

- Create SalesforceCRM interface extending CRM with findUserEmailFromLookupField and incompleteBookingWriteToRecord methods
- Add createSalesforceCrmServiceWithSalesforceType factory function for internal Salesforce modules
- Update routing form files to use the new factory function with proper typing

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* fix(salesforce): correct return type in SalesforceCRM interface

The findUserEmailFromLookupField method returns { email: string; recordType: RoutingReasons } | undefined,
not string | undefined. Updated the interface to match the actual implementation.

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* fix: update all call sites to use factory functions

- Update calendar API add files to use factory functions (7 files)
- Update Google Calendar test files to use factory functions (3 files)
- Update Salesforce test files to use SalesforceCRM interface
- Add testMode parameter to createSalesforceCrmServiceWithSalesforceType
- Remove unused @ts-expect-error directive in bookingScenario.ts

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* fix: add GoogleCalendar interface and update factory functions for service-specific methods

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* fix: add type annotation to attendee parameter in google-calendar.e2e.ts

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* fix: update selected-calendars/route.ts to use GoogleCalendar factory function

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* fix: update remaining GoogleCalendarService call sites to use factory function

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* fix: remove explicit type annotation in google-calendar.e2e.ts to fix type error

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* fix: update test mocks to use factory function pattern for calendar and payment services

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* fix: convert MockPaymentService to factory function in setupVitest.ts

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* fix(api-v2): call calendar service factory functions instead of using new

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* perf(trpc): add TypeScript project references to reduce build file count

- Add tsconfig.build.json to app-store with composite settings for declaration emit
- Add tsconfig.server.build.json to trpc with project references
- Add types and typesVersions to app-store package.json to redirect type resolution to dist-types
- Add build:types script to app-store for generating declarations
- Add dist-types to .gitignore

This reduces the tRPC build file count from 7,733 to 5,618 files (27% reduction)
by having TypeScript resolve to prebuilt .d.ts files instead of source files.

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* fix(app-store): add fallback to typesVersions for CI compatibility

The typesVersions now prefers dist-types but falls back to source files
when dist-types don't exist. This fixes CI type-check failures while
still allowing the optimization when dist-types are built.

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* Added build dependency for tRPC on app-store

* fix(app-store): exclude test directories from tsconfig.build.json

Exclude __tests__, __mocks__, and tests directories from the build
config to prevent test utility files (which import from outside rootDir)
from being included in the declaration emit.

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* fix(turbo): add build:types task definition for app-store

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* fix(turbo): remove circular build dependency between trpc and app-store

The build:types task for app-store transitively imports from @calcom/features,
which imports from @calcom/trpc. This creates a circular build dependency when
@calcom/trpc#build depends on @calcom/app-store#build:types.

Solution: Remove the turbo dependency. The typesVersions fallback pattern
['./dist-types/*', './*'] will use source files when dist-types/ doesn't exist,
and use prebuilt declarations when they do exist (for faster builds).

Also exclude dist-types from regular tsconfig.json to prevent 'Cannot write file'
errors when dist-types/ exists.

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* refactor(app-store): rename dist-types to types for consistency

Rename the prebuilt TypeScript declarations folder from dist-types to types
to match the convention used by @calcom/trpc and other packages in the monorepo.

Updated files:
- tsconfig.build.json: declarationDir and exclude
- tsconfig.json: exclude
- package.json: typesVersions paths
- turbo.json: build:types outputs
- .gitignore: ignore path

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* fix(api-v2): update ICS calendar test to mock factory function instead of class prototype

The IcsFeedCalendarService is now a factory function, not a class, so
jest.spyOn(IcsFeedCalendarService.prototype, 'listCalendars') no longer works.

Updated to use jest.spyOn(appStore, 'IcsFeedCalendarService').mockImplementation()
to mock the factory function return value instead.

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* refactor(app-store): rename factory functions to Build* naming convention

- Rename Calendar factory functions from create*CalendarService to BuildCalendarService
- Rename CRM factory functions from create*CrmService to BuildCrmService
- Rename Payment factory functions from PaymentService to BuildPaymentService
- Rename Analytics factory function from createDubAnalyticsService to BuildAnalyticsService
- Update all index.ts re-exports to use new names
- Update all call sites throughout the codebase
- Update test mocks to use new factory function names

This makes it clear that these are factory functions, not class constructors.

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* fix(app-store): update remaining CalendarService and PaymentService imports to Build* names

- Update api/add.ts files for applecalendar, caldavcalendar, exchange2013calendar, exchange2016calendar, exchangecalendar, ics-feedcalendar
- Update mock-payment-app index.ts to export BuildPaymentService
- Fix googlecalendar test import alias for createInMemoryDelegationCredentialForBuildCalendarService

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* fix(tests): update payment service mocks to use BuildPaymentService

- Update setupVitest.ts to use BuildPaymentService instead of PaymentService
- Update handlePayment.test.ts mock to use BuildPaymentService

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* refactor: address PR review comments - rename to Build* and remove unused code

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* refactor: remove explanatory comments and fix indentation

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* chore: update .gitignore to use *.tsbuildinfo pattern

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* fix(api-v2): update IcsFeedCalendarService to BuildIcsFeedCalendarService in e2e test

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* style: format CalendarService.test.ts

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* Delete apps/ui-playground/next-env.d.ts

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Keith Williams
2026-01-15 11:28:05 -03:00
committed by GitHub
co-authored by Devin AI
parent 511cc87d34
commit 3a8fe8c7af
93 changed files with 760 additions and 409 deletions
+3 -2
View File
@@ -60,7 +60,7 @@ yarn-error.log*
.history/
# Typescript
tsconfig.tsbuildinfo
*.tsbuildinfo
# turbo
.turbo
@@ -75,6 +75,7 @@ packages/prisma/enums
# Builds
dist
packages/app-store/types
# Linting
lint-results
@@ -107,4 +108,4 @@ packages/**/.yarn/ci-cache/
.claude
# trigger.dev
.trigger
.trigger
@@ -1,15 +1,3 @@
import {
GOOGLE_CALENDAR,
GOOGLE_CALENDAR_ID,
GOOGLE_CALENDAR_TYPE,
OFFICE_365_CALENDAR,
OFFICE_365_CALENDAR_ID,
OFFICE_365_CALENDAR_TYPE,
SUCCESS_STATUS,
} from "@calcom/platform-constants";
import { ICS_CALENDAR, ICS_CALENDAR_TYPE } from "@calcom/platform-constants/apps";
import { IcsFeedCalendarService } from "@calcom/platform-libraries/app-store";
import type { Credential, PlatformOAuthClient, Team, User } from "@calcom/prisma/client";
import { INestApplication } from "@nestjs/common";
import { NestExpressApplication } from "@nestjs/platform-express";
import { Test } from "@nestjs/testing";
@@ -22,6 +10,29 @@ import { UserRepositoryFixture } from "test/fixtures/repository/users.repository
import { CalendarsServiceMock } from "test/mocks/calendars-service-mock";
import { IcsCalendarServiceMock } from "test/mocks/ics-calendar-service-mock";
import { randomString } from "test/utils/randomString";
import {
GOOGLE_CALENDAR,
GOOGLE_CALENDAR_ID,
GOOGLE_CALENDAR_TYPE,
OFFICE_365_CALENDAR,
OFFICE_365_CALENDAR_ID,
OFFICE_365_CALENDAR_TYPE,
SUCCESS_STATUS,
} from "@calcom/platform-constants";
import { ICS_CALENDAR, ICS_CALENDAR_TYPE } from "@calcom/platform-constants/apps";
import type { Credential, PlatformOAuthClient, Team, User } from "@calcom/prisma/client";
// Mock the BuildIcsFeedCalendarService factory function
const mockBuildIcsFeedCalendarService = jest.fn();
jest.mock("@calcom/platform-libraries/app-store", () => {
const actual = jest.requireActual("@calcom/platform-libraries/app-store");
return {
...actual,
BuildIcsFeedCalendarService: (...args: unknown[]) => mockBuildIcsFeedCalendarService(...args),
};
});
import { AppModule } from "@/app.module";
import { bootstrap } from "@/bootstrap";
import { CreateIcsFeedOutput, CreateIcsFeedOutputResponseDto } from "@/ee/calendars/input/create-ics.output";
@@ -220,9 +231,13 @@ describe("Platform Calendars Endpoints", () => {
urls: ["https://cal.com/ics/feed.ics"],
readOnly: false,
};
jest
.spyOn(IcsFeedCalendarService.prototype, "listCalendars")
.mockImplementation(IcsCalendarServiceMock.prototype.listCalendars);
mockBuildIcsFeedCalendarService.mockReturnValue({
listCalendars: new IcsCalendarServiceMock().listCalendars,
createEvent: jest.fn(),
deleteEvent: jest.fn(),
updateEvent: jest.fn(),
getAvailability: jest.fn(),
});
await request(app.getHttpServer())
.post(`/v2/calendars/${ICS_CALENDAR}/save`)
.set("Authorization", `Bearer ${accessTokenSecret}`)
@@ -6,7 +6,7 @@ import { Injectable } from "@nestjs/common";
import { SUCCESS_STATUS, APPLE_CALENDAR_TYPE, APPLE_CALENDAR_ID } from "@calcom/platform-constants";
import { symmetricEncrypt, symmetricDecrypt } from "@calcom/platform-libraries";
import { CalendarService } from "@calcom/platform-libraries/app-store";
import { BuildCalendarService } from "@calcom/platform-libraries/app-store";
import type { Credential } from "@calcom/prisma/client";
@Injectable()
@@ -122,7 +122,7 @@ export class AppleCalendarService implements CredentialSyncCalendarApp {
delegationCredentialId: null,
};
const dav = new CalendarService({
const dav = BuildCalendarService({
id: 0,
...data,
user: { email: userEmail },
@@ -9,7 +9,7 @@ import { Injectable } from "@nestjs/common";
import { SUCCESS_STATUS, ICS_CALENDAR_TYPE, ICS_CALENDAR } from "@calcom/platform-constants";
import { symmetricEncrypt } from "@calcom/platform-libraries";
import { IcsFeedCalendarService } from "@calcom/platform-libraries/app-store";
import { BuildIcsFeedCalendarService } from "@calcom/platform-libraries/app-store";
@Injectable()
export class IcsFeedService implements ICSFeedCalendarApp {
@@ -43,7 +43,7 @@ export class IcsFeedService implements ICSFeedCalendarApp {
};
try {
const dav = new IcsFeedCalendarService({
const dav = BuildIcsFeedCalendarService({
id: 0,
...data,
user: { email: userEmail },
@@ -7,10 +7,14 @@ import { CalendarAppDelegationCredentialInvalidGrantError } from "@calcom/lib/Ca
import { handleCreateSelectedCalendars, isSameEmail } from "../route";
// Mock GoogleCalendarService
// Mock GoogleCalendarService factory function
const getPrimaryCalendarMock = vi.fn();
vi.mock("@calcom/app-store/googlecalendar/lib/CalendarService", () => {
return {
__esModule: true,
createGoogleCalendarServiceWithGoogleType: vi.fn().mockImplementation(function() { return {
getPrimaryCalendar: getPrimaryCalendarMock,
}; }),
default: vi.fn().mockImplementation(function() { return {
getPrimaryCalendar: getPrimaryCalendarMock,
}; }),
@@ -7,7 +7,10 @@ import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
import { findUniqueDelegationCalendarCredential } from "@calcom/app-store/delegationCredential";
import GoogleCalendarService from "@calcom/app-store/googlecalendar/lib/CalendarService";
import {
createGoogleCalendarServiceWithGoogleType,
type GoogleCalendar,
} from "@calcom/app-store/googlecalendar/lib/CalendarService";
import { CredentialRepository } from "@calcom/features/credentials/repositories/CredentialRepository";
import { CalendarAppDelegationCredentialInvalidGrantError } from "@calcom/lib/CalendarAppError";
import { HttpError } from "@calcom/lib/http-error";
@@ -135,7 +138,7 @@ async function getCalendarService(delegationUserCredential: DelegationUserCreden
return null;
}
const googleCalendarService = new GoogleCalendarService(
const googleCalendarService = createGoogleCalendarServiceWithGoogleType(
credentialForCalendarService as CredentialForCalendarServiceWithEmail
);
@@ -146,7 +149,7 @@ async function fetchPrimaryCalendarId({
googleCalendarService,
delegationUserCredential,
}: {
googleCalendarService: GoogleCalendarService;
googleCalendarService: GoogleCalendar;
delegationUserCredential: DelegationUserCredentialWithEnsuredUser;
}) {
let primaryCalendarId;
+3 -3
View File
@@ -25,12 +25,12 @@ export const getAnalyticsService = async ({
const analyticsApp = await analyticsAppImportFn;
const AnalyticsService = analyticsApp.default;
const createAnalyticsService = analyticsApp.default;
if (!AnalyticsService || typeof AnalyticsService !== "function") {
if (!createAnalyticsService || typeof createAnalyticsService !== "function") {
log.warn(`analytics of type ${analyticsType} is not implemented`);
return null;
}
return new AnalyticsService(credential);
return createAnalyticsService(credential);
};
+4 -3
View File
@@ -38,9 +38,9 @@ export const getCalendar = async (
const calendarApp = await calendarAppImportFn;
const CalendarService = calendarApp.default;
const createCalendarService = calendarApp.default;
if (!CalendarService || typeof CalendarService !== "function") {
if (!createCalendarService || typeof createCalendarService !== "function") {
log.warn(`calendar of type ${calendarType} is not implemented`);
return null;
}
@@ -83,7 +83,8 @@ export const getCalendar = async (
const isCacheSupported = CalendarCacheEventService.isCalendarTypeSupported(calendarType);
const originalCalendar = new CalendarService(credential as any);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const originalCalendar = createCalendarService(credential as any);
// Determine if we should use cache
const useCache = isCacheSupported && shouldServeCache;
@@ -183,9 +183,9 @@ export async function getConnectedApps({
const paymentAppImportFn = PaymentServiceMap[app.dirName as keyof typeof PaymentServiceMap];
if (paymentAppImportFn) {
const paymentApp = await paymentAppImportFn;
if (paymentApp && "PaymentService" in paymentApp && paymentApp?.PaymentService) {
const PaymentService = paymentApp.PaymentService;
const paymentInstance = new PaymentService(credential);
if (paymentApp && "BuildPaymentService" in paymentApp && paymentApp?.BuildPaymentService) {
const createPaymentService = paymentApp.BuildPaymentService;
const paymentInstance = createPaymentService(credential);
isSetupAlready = paymentInstance.isSetupAlready();
}
}
+6 -4
View File
@@ -4,7 +4,7 @@ import type { CredentialPayload } from "@calcom/types/Credential";
import { CrmServiceMap } from "../crm.apps.generated";
const log = logger.getSubLogger({ prefix: ["CrmManager"] });
export const getCrm = async (credential: CredentialPayload, appOptions: any) => {
export const getCrm = async (credential: CredentialPayload, appOptions?: Record<string, unknown>) => {
if (!credential || !credential.key) return null;
const { type: crmType } = credential;
@@ -17,14 +17,16 @@ export const getCrm = async (credential: CredentialPayload, appOptions: any) =>
return null;
}
const CrmService = crmServiceImportFn.default;
const createCrmService = crmServiceImportFn.default;
if (!CrmService) {
if (!createCrmService) {
log.warn(`crm of type ${crmType} is not implemented`);
return null;
}
return new CrmService(credential, appOptions);
// CRM services now export factory functions instead of classes
// to prevent SDK types from leaking into the type system
return createCrmService(credential, appOptions);
};
export default getCrm;
+10 -1
View File
@@ -14,7 +14,7 @@ import { albyCredentialKeysSchema } from "./albyCredentialKeysSchema";
const log = logger.getSubLogger({ prefix: ["payment-service:alby"] });
export class PaymentService implements IAbstractPaymentService {
class AlbyPaymentService implements IAbstractPaymentService {
private credentials: z.infer<typeof albyCredentialKeysSchema> | null;
constructor(credentials: { key: Prisma.JsonValue }) {
@@ -138,3 +138,12 @@ export class PaymentService implements IAbstractPaymentService {
return !!this.credentials;
}
}
/**
* Factory function that creates an Alby Payment service instance.
* This is exported instead of the class to prevent internal types
* from leaking into the emitted .d.ts file.
*/
export function BuildPaymentService(credentials: { key: Prisma.JsonValue }): IAbstractPaymentService {
return new AlbyPaymentService(credentials);
}
+2 -2
View File
@@ -5,7 +5,7 @@ import logger from "@calcom/lib/logger";
import prisma from "@calcom/prisma";
import getInstalledAppPath from "../../_utils/getInstalledAppPath";
import { CalendarService } from "../lib";
import { BuildCalendarService } from "../lib";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === "POST") {
@@ -56,7 +56,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
};
try {
const dav = new CalendarService({
const dav = BuildCalendarService({
id: 0,
...data,
user: { email: user.email },
@@ -1,8 +1,18 @@
import BaseCalendarService from "@calcom/lib/CalendarService";
import type { Calendar } from "@calcom/types/Calendar";
import type { CredentialPayload } from "@calcom/types/Credential";
export default class AppleCalendarService extends BaseCalendarService {
class AppleCalendarService extends BaseCalendarService {
constructor(credential: CredentialPayload) {
super(credential, "apple_calendar", "https://caldav.icloud.com");
}
}
/**
* Factory function that creates an Apple Calendar service instance.
* This is exported instead of the class to prevent internal types
* from leaking into the emitted .d.ts file.
*/
export default function BuildCalendarService(credential: CredentialPayload): Calendar {
return new AppleCalendarService(credential);
}
@@ -1 +1 @@
export { default as CalendarService } from "./CalendarService";
export { default as BuildCalendarService } from "./CalendarService";
@@ -30,7 +30,7 @@ function getFileExtension(url: string): string {
return fileName.substring(fileName.lastIndexOf(".") + 1);
}
export default class BasecampCalendarService implements Calendar {
class BasecampCalendarService implements Calendar {
private credentials: Record<string, string> = {};
private auth: Promise<{ configureToken: () => Promise<void> }>;
private headers: Record<string, string> = {};
@@ -92,7 +92,7 @@ export default class BasecampCalendarService implements Calendar {
});
const baseString = `<div>Event title: ${event.title}<br/>Date and time: ${date}, ${startTime} - ${endTime} ${timeZone}<br/>View on Cal.com: <a target="_blank" rel="noreferrer" class="autolinked" data-behavior="truncate" href="https://app.cal.com/booking/${event.uid}">https://app.cal.com/booking/${event.uid}</a> `;
const guestString = `<br/>Guests: ${event.attendees.reduce((acc, attendee) => {
return `${acc}<br/><a target=\"_blank\" rel=\"noreferrer\" class=\"autolinked\" data-behavior=\"truncate\" href=\"mailto:${attendee.email}\">${attendee.email}</a>`;
return `${acc}<br/><a target="_blank" rel="noreferrer" class="autolinked" data-behavior="truncate" href="mailto:${attendee.email}">${attendee.email}</a>`;
}, "")}`;
const videoString = event.videoCallData
@@ -256,3 +256,12 @@ export default class BasecampCalendarService implements Calendar {
return Promise.resolve([]);
}
}
/**
* Factory function that creates a Basecamp Calendar service instance.
* This is exported instead of the class to prevent internal types
* from leaking into the emitted .d.ts file.
*/
export default function BuildCalendarService(credential: CredentialPayload): Calendar {
return new BasecampCalendarService(credential);
}
+1 -1
View File
@@ -1 +1 @@
export { default as CalendarService } from "./CalendarService";
export { default as BuildCalendarService } from "./CalendarService";
@@ -36,7 +36,7 @@ interface BTCPayInvoice {
[key: string]: any;
}
export class PaymentService implements IAbstractPaymentService {
class BTCPayServerPaymentService implements IAbstractPaymentService {
private credentials: z.infer<typeof btcpayCredentialKeysSchema> | null;
private bookingPaymentRepository: IBookingPaymentRepository;
@@ -203,3 +203,12 @@ export class PaymentService implements IAbstractPaymentService {
return !!this.credentials;
}
}
/**
* Factory function that creates a BTCPay Server Payment service instance.
* This is exported instead of the class to prevent internal types
* from leaking into the emitted .d.ts file.
*/
export function BuildPaymentService(credentials: { key: Prisma.JsonValue }): IAbstractPaymentService {
return new BTCPayServerPaymentService(credentials);
}
+2 -2
View File
@@ -5,7 +5,7 @@ import logger from "@calcom/lib/logger";
import prisma from "@calcom/prisma";
import getInstalledAppPath from "../../_utils/getInstalledAppPath";
import { CalendarService } from "../lib";
import { BuildCalendarService } from "../lib";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === "POST") {
@@ -35,7 +35,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
};
try {
const dav = new CalendarService({
const dav = BuildCalendarService({
id: 0,
...data,
user: { email: user.email },
@@ -1,8 +1,18 @@
import BaseCalendarService from "@calcom/lib/CalendarService";
import type { Calendar } from "@calcom/types/Calendar";
import type { CredentialPayload } from "@calcom/types/Credential";
export default class CalDavCalendarService extends BaseCalendarService {
class CalDavCalendarService extends BaseCalendarService {
constructor(credential: CredentialPayload) {
super(credential, "caldav_calendar");
}
}
/**
* Factory function that creates a CalDAV Calendar service instance.
* This is exported instead of the class to prevent internal types
* from leaking into the emitted .d.ts file.
*/
export default function BuildCalendarService(credential: CredentialPayload): Calendar {
return new CalDavCalendarService(credential);
}
@@ -1 +1 @@
export { default as CalendarService } from "./CalendarService";
export { default as BuildCalendarService } from "./CalendarService";
+13 -2
View File
@@ -57,7 +57,7 @@ const calComCustomActivityFields: CloseComFieldOptions = [
* Close.com as part of this integration, a new generic Lead will be created in order
* to assign every contact created by this process, and it is named "From Cal.com"
*/
export default class CloseComCRMService implements CRM {
class CloseComCRMService implements CRM {
private integrationName = "";
private closeCom: CloseCom;
private log: typeof logger;
@@ -133,7 +133,6 @@ export default class CloseComCRMService implements CRM {
});
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async updateEvent(uid: string, event: CalendarEvent): Promise<CrmEvent> {
const updatedEvent = await this.closeComUpdateCustomActivity(uid, event);
return {
@@ -210,3 +209,15 @@ export default class CloseComCRMService implements CRM {
console.log("Not implemented");
}
}
/**
* Factory function that creates a Close.com CRM service instance.
* This is exported instead of the class to prevent internal types
* from leaking into the emitted .d.ts file.
*/
export default function BuildCrmService(
credential: CredentialPayload,
_appOptions?: Record<string, unknown>
): CRM {
return new CloseComCRMService(credential);
}
+1 -1
View File
@@ -1 +1 @@
export { default as CrmService } from "./CrmService";
export { default as BuildCrmService } from "./CrmService";
+10 -1
View File
@@ -9,7 +9,7 @@ import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
import refreshOAuthTokens from "../../_utils/oauth/refreshOAuthTokens";
import type { DubOAuthToken } from "./type";
export default class DubService implements AnalyticsService {
class DubService implements AnalyticsService {
private dubClient?: Dub;
private client_id = "";
private client_secret = "";
@@ -120,3 +120,12 @@ export default class DubService implements AnalyticsService {
});
}
}
/**
* Factory function that creates a Dub Analytics service instance.
* This is exported instead of the class to prevent SDK types (Dub)
* from leaking into the emitted .d.ts file.
*/
export default function BuildAnalyticsService(credential: CredentialPayload): AnalyticsService {
return new DubService(credential);
}
+1 -1
View File
@@ -1 +1 @@
export { default as AnalyticsService } from "./AnalyticsService";
export { default as BuildAnalyticsService } from "./AnalyticsService";
@@ -8,7 +8,7 @@ import { defaultResponder } from "@calcom/lib/server/defaultResponder";
import prisma from "@calcom/prisma";
import getInstalledAppPath from "../../_utils/getInstalledAppPath";
import { CalendarService } from "../lib";
import { BuildCalendarService } from "../lib";
const bodySchema = z
.object({
@@ -42,7 +42,7 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) {
};
try {
const dav = new CalendarService({
const dav = BuildCalendarService({
id: 0,
...data,
user: { email: user.email },
@@ -34,7 +34,7 @@ import type {
} from "@calcom/types/Calendar";
import type { CredentialPayload } from "@calcom/types/Credential";
export default class ExchangeCalendarService implements Calendar {
class ExchangeCalendarService implements Calendar {
private url = "";
private integrationName = "";
private log: typeof logger;
@@ -236,3 +236,12 @@ export default class ExchangeCalendarService implements Calendar {
return exch1;
}
}
/**
* Factory function that creates an Exchange 2013 Calendar service instance.
* This is exported instead of the class to prevent SDK types (like ews-javascript-api types)
* from leaking into the emitted .d.ts file.
*/
export default function BuildCalendarService(credential: CredentialPayload): Calendar {
return new ExchangeCalendarService(credential);
}
@@ -1 +1 @@
export { default as CalendarService } from "./CalendarService";
export { default as BuildCalendarService } from "./CalendarService";
@@ -8,7 +8,7 @@ import { defaultResponder } from "@calcom/lib/server/defaultResponder";
import prisma from "@calcom/prisma";
import getInstalledAppPath from "../../_utils/getInstalledAppPath";
import { CalendarService } from "../lib";
import { BuildCalendarService } from "../lib";
const bodySchema = z
.object({
@@ -42,7 +42,7 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) {
};
try {
const dav = new CalendarService({
const dav = BuildCalendarService({
id: 0,
user: { email: user.email },
...data,
@@ -34,7 +34,7 @@ import type {
} from "@calcom/types/Calendar";
import type { CredentialPayload } from "@calcom/types/Credential";
export default class ExchangeCalendarService implements Calendar {
class ExchangeCalendarService implements Calendar {
private url = "";
private integrationName = "";
private log: typeof logger;
@@ -237,3 +237,12 @@ export default class ExchangeCalendarService implements Calendar {
return exch1;
}
}
/**
* Factory function that creates an Exchange 2016 Calendar service instance.
* This is exported instead of the class to prevent SDK types (like ews-javascript-api types)
* from leaking into the emitted .d.ts file.
*/
export default function BuildCalendarService(credential: CredentialPayload): Calendar {
return new ExchangeCalendarService(credential);
}
@@ -1 +1 @@
export { default as CalendarService } from "./CalendarService";
export { default as BuildCalendarService } from "./CalendarService";
@@ -10,7 +10,7 @@ import prisma from "@calcom/prisma";
import checkSession from "../../_utils/auth";
import { ExchangeAuthentication, ExchangeVersion } from "../enums";
import { CalendarService } from "../lib";
import { BuildCalendarService } from "../lib";
const formSchema = z
.object({
@@ -38,7 +38,7 @@ export async function getHandler(req: NextApiRequest, res: NextApiResponse) {
};
try {
const service = new CalendarService({ id: 0, user: { email: session.user.email || "" }, ...data });
const service = BuildCalendarService({ id: 0, user: { email: session.user.email || "" }, ...data });
await service?.listCalendars();
await prisma.credential.create({ data });
} catch (reason) {
@@ -41,7 +41,7 @@ import type { CredentialPayload } from "@calcom/types/Credential";
import { ExchangeAuthentication } from "../enums";
export default class ExchangeCalendarService implements Calendar {
class ExchangeCalendarService implements Calendar {
private integrationName = "";
private log: typeof logger;
private payload;
@@ -208,3 +208,15 @@ export default class ExchangeCalendarService implements Calendar {
return service;
}
}
/**
* Factory function that creates an Exchange Calendar service instance.
* This is exported instead of the class to prevent SDK types (like ews-javascript-api types)
* from leaking into the emitted .d.ts file, which would cause TypeScript to load
* all EWS SDK declaration files when type-checking dependent packages.
*/
export default function BuildCalendarService(
credential: CredentialPayload
): Calendar {
return new ExchangeCalendarService(credential);
}
@@ -1 +1 @@
export { default as CalendarService } from "./CalendarService";
export { default as BuildCalendarService } from "./CalendarService";
@@ -32,7 +32,7 @@ function parseEventTime2Timestamp(eventTime: string): string {
return String(+new Date(eventTime) / 1000);
}
export default class FeishuCalendarService implements Calendar {
class FeishuCalendarService implements Calendar {
private url = `https://${FEISHU_HOST}/open-apis`;
private integrationName = "";
private log: typeof logger;
@@ -117,7 +117,7 @@ export default class FeishuCalendarService implements Calendar {
let accessToken = "";
try {
accessToken = await this.auth.getToken();
} catch (error) {
} catch {
throw new Error("get access token error");
}
@@ -423,3 +423,12 @@ export default class FeishuCalendarService implements Calendar {
return attendeeArray;
};
}
/**
* Factory function that creates a Feishu Calendar service instance.
* This is exported instead of the class to prevent internal types
* from leaking into the emitted .d.ts file.
*/
export default function BuildCalendarService(credential: CredentialPayload): Calendar {
return new FeishuCalendarService(credential);
}
@@ -1 +1 @@
export { default as CalendarService } from "./CalendarService";
export { default as BuildCalendarService } from "./CalendarService";
@@ -2,7 +2,7 @@ import { calendar_v3 } from "@googleapis/calendar";
import { OAuth2Client } from "googleapis-common";
import type { NextApiRequest, NextApiResponse } from "next";
import GoogleCalendarService from "@calcom/app-store/googlecalendar/lib/CalendarService";
import { createGoogleCalendarServiceWithGoogleType } from "@calcom/app-store/googlecalendar/lib/CalendarService";
import { CredentialRepository } from "@calcom/features/credentials/repositories/CredentialRepository";
import { renewSelectedCalendarCredentialId } from "@calcom/lib/connectedCalendar";
import {
@@ -78,7 +78,7 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) {
type: "google_calendar",
});
const gCalService = new GoogleCalendarService({
const gCalService = createGoogleCalendarServiceWithGoogleType({
...gcalCredential,
user: null,
delegatedTo: null,
@@ -29,6 +29,25 @@ type FreeBusyArgs = { timeMin: string; timeMax: string; items: { id: string }[]
const log = logger.getSubLogger({ prefix: ["app-store/googlecalendar/lib/CalendarService"] });
/**
* Extended interface for Google Calendar service that includes Google-specific methods.
* This interface is used by internal Google Calendar modules (callback, tests, etc.)
* that need access to Google-specific functionality beyond the generic Calendar interface.
*/
export interface GoogleCalendar extends Calendar {
getPrimaryCalendar(calendar?: unknown): Promise<{ id?: string | null; timeZone?: string | null } | null>;
upsertSelectedCalendar(
data: Omit<Prisma.SelectedCalendarUncheckedCreateInput, "integration" | "credentialId" | "userId">
): Promise<unknown>;
createSelectedCalendar(
data: Omit<Prisma.SelectedCalendarUncheckedCreateInput, "integration" | "credentialId">
): Promise<unknown>;
authedCalendar(): Promise<calendar_v3.Calendar>;
}
interface GoogleCalError extends Error {
code?: number;
}
@@ -36,7 +55,7 @@ interface GoogleCalError extends Error {
const isGaxiosResponse= (error: unknown): error is GaxiosResponse<calendar_v3.Schema$Event> =>
typeof error === "object" && !!error && Object.prototype.hasOwnProperty.call(error, "config");
export default class GoogleCalendarService implements Calendar {
class GoogleCalendarService implements Calendar {
private integrationName = "";
private auth: CalendarAuth;
private log: typeof logger;
@@ -806,3 +825,26 @@ export default class GoogleCalendarService implements Calendar {
}
}
}
/**
* Factory function that creates a Google Calendar service instance.
* This is exported instead of the class to prevent SDK types (like calendar_v3.Calendar)
* from leaking into the emitted .d.ts file, which would cause TypeScript to load
* all Google API SDK declaration files when type-checking dependent packages.
*/
export default function BuildCalendarService(
credential: CredentialForCalendarServiceWithEmail
): Calendar {
return new GoogleCalendarService(credential);
}
/**
* Factory function that creates a Google Calendar service instance with the extended GoogleCalendar type.
* This is used by internal Google Calendar modules (callback, tests, etc.) that need access to
* Google-specific methods beyond the generic Calendar interface.
*/
export function createGoogleCalendarServiceWithGoogleType(
credential: CredentialForCalendarServiceWithEmail
): GoogleCalendar {
return new GoogleCalendarService(credential);
}
@@ -19,10 +19,10 @@ import "vitest-fetch-mock";
import type { CredentialForCalendarServiceWithEmail } from "@calcom/types/Credential";
import CalendarService from "../CalendarService";
import BuildCalendarService from "../CalendarService";
import {
createMockJWTInstance,
createInMemoryDelegationCredentialForCalendarService,
createInMemoryDelegationCredentialForCalendarService as createInMemoryDelegationCredentialForBuildCalendarService,
defaultDelegatedCredential,
createCredentialForCalendarService,
} from "./utils";
@@ -70,7 +70,7 @@ async function expectCredentialsInDb(credentials: CredentialForCalendarServiceWi
describe("GoogleCalendarService credential handling", () => {
describe("Delegation Credential", () => {
test("uses JWT auth with impersonation when listCalendars is called and creates a new credential in DB with the key as JWT token if it doesn't exist", async () => {
const credentialWithDelegation = await createInMemoryDelegationCredentialForCalendarService({
const credentialWithDelegation = await createInMemoryDelegationCredentialForBuildCalendarService({
user: { email: "user@example.com" },
delegatedTo: defaultDelegatedCredential,
delegationCredentialId: "delegation-credential-id-1",
@@ -78,7 +78,7 @@ describe("GoogleCalendarService credential handling", () => {
mockSuccessfulCalendarListFetch();
expectNoCredentialsInDb();
const calendarService = new CalendarService(credentialWithDelegation);
const calendarService = BuildCalendarService(credentialWithDelegation);
await calendarService.listCalendars();
expectJWTInstanceToBeCreated();
await expectCredentialsInDb([
@@ -91,7 +91,7 @@ describe("GoogleCalendarService credential handling", () => {
});
test("uses JWT auth with impersonation when listCalendars is called and updates the credential in DB with the new JWT token if it exists", async () => {
const credentialWithDelegation = await createInMemoryDelegationCredentialForCalendarService({
const credentialWithDelegation = await createInMemoryDelegationCredentialForBuildCalendarService({
user: { email: "user@example.com" },
delegatedTo: defaultDelegatedCredential,
delegationCredentialId: "delegation-credential-id-1",
@@ -108,7 +108,7 @@ describe("GoogleCalendarService credential handling", () => {
},
},
});
const calendarService = new CalendarService(credentialWithDelegation);
const calendarService = BuildCalendarService(credentialWithDelegation);
await calendarService.listCalendars();
expectJWTInstanceToBeCreated();
await expectCredentialsInDb([
@@ -129,7 +129,7 @@ describe("GoogleCalendarService credential handling", () => {
createMockJWTInstance({
tokenExpiryDate: jwtTokenThatHasNotExpired.expiry_date,
});
const credentialWithDelegation = await createInMemoryDelegationCredentialForCalendarService({
const credentialWithDelegation = await createInMemoryDelegationCredentialForBuildCalendarService({
user: { email: "user@example.com" },
delegatedTo: defaultDelegatedCredential,
delegationCredentialId: "delegation-credential-id-1",
@@ -140,7 +140,7 @@ describe("GoogleCalendarService credential handling", () => {
mockSuccessfulCalendarListFetch();
await expectNoCredentialsInDb();
console.log("TESTS: First instance of CalendarService");
const calendarService1 = new CalendarService({
const calendarService1 = BuildCalendarService({
...credentialWithDelegation,
});
await calendarService1.listCalendars();
@@ -169,7 +169,7 @@ describe("GoogleCalendarService credential handling", () => {
},
},
});
const calendarService2 = new CalendarService({
const calendarService2 = BuildCalendarService({
...credentialWithDelegation,
});
await calendarService2.listCalendars();
@@ -188,7 +188,7 @@ describe("GoogleCalendarService credential handling", () => {
test("uses OAuth2 auth when listCalendars is called", async () => {
const regularCredential = await createCredentialForCalendarService();
mockSuccessfulCalendarListFetch();
const calendarService = new CalendarService(regularCredential);
const calendarService = BuildCalendarService(regularCredential);
await calendarService.listCalendars();
expectOAuth2InstanceToBeCreated();
@@ -207,7 +207,7 @@ describe("GoogleCalendarService credential handling", () => {
describe("Delegation Credential Error handling", () => {
test("handles clientId not added to Google Workspace Admin Console error", async () => {
const credentialWithDelegation = await createInMemoryDelegationCredentialForCalendarService({
const credentialWithDelegation = await createInMemoryDelegationCredentialForBuildCalendarService({
user: { email: "user@example.com" },
delegatedTo: defaultDelegatedCredential,
delegationCredentialId: "delegation-credential-id-1",
@@ -223,7 +223,7 @@ describe("GoogleCalendarService credential handling", () => {
},
});
const calendarService = new CalendarService(credentialWithDelegation);
const calendarService = BuildCalendarService(credentialWithDelegation);
await expect(calendarService.listCalendars()).rejects.toThrow(
"Make sure that the Client ID for the delegation credential is added to the Google Workspace Admin Console"
@@ -231,7 +231,7 @@ describe("GoogleCalendarService credential handling", () => {
});
test("handles DelegationCredential authorization errors appropriately", async () => {
const credentialWithDelegation = await createInMemoryDelegationCredentialForCalendarService({
const credentialWithDelegation = await createInMemoryDelegationCredentialForBuildCalendarService({
user: { email: "user@example.com" },
delegatedTo: defaultDelegatedCredential,
delegationCredentialId: "delegation-credential-id-1",
@@ -247,7 +247,7 @@ describe("GoogleCalendarService credential handling", () => {
},
});
const calendarService = new CalendarService(credentialWithDelegation);
const calendarService = BuildCalendarService(credentialWithDelegation);
await expect(calendarService.listCalendars()).rejects.toThrow(
"Make sure that the Client ID for the delegation credential is added to the Google Workspace Admin Console"
@@ -255,7 +255,7 @@ describe("GoogleCalendarService credential handling", () => {
});
test("handles invalid_grant error (user not in workspace) appropriately", async () => {
const credentialWithDelegation = await createInMemoryDelegationCredentialForCalendarService({
const credentialWithDelegation = await createInMemoryDelegationCredentialForBuildCalendarService({
user: { email: "user@example.com" },
delegatedTo: defaultDelegatedCredential,
delegationCredentialId: "delegation-credential-id-1",
@@ -271,7 +271,7 @@ describe("GoogleCalendarService credential handling", () => {
},
});
const calendarService = new CalendarService(credentialWithDelegation);
const calendarService = BuildCalendarService(credentialWithDelegation);
await expect(calendarService.listCalendars()).rejects.toThrow(
`User ${credentialWithDelegation.user?.email} might not exist in Google Workspace`
@@ -279,7 +279,7 @@ describe("GoogleCalendarService credential handling", () => {
});
test("handles DelegationCredential authorization errors appropriately", async () => {
const credentialWithDelegation = await createInMemoryDelegationCredentialForCalendarService({
const credentialWithDelegation = await createInMemoryDelegationCredentialForBuildCalendarService({
user: { email: "user@example.com" },
delegatedTo: defaultDelegatedCredential,
delegationCredentialId: "delegation-credential-id-1",
@@ -289,7 +289,7 @@ describe("GoogleCalendarService credential handling", () => {
authorizeError: new Error("Some unexpected error"),
});
const calendarService = new CalendarService(credentialWithDelegation);
const calendarService = BuildCalendarService(credentialWithDelegation);
await expect(calendarService.listCalendars()).rejects.toThrow(
"Error authorizing delegation credential"
@@ -297,13 +297,13 @@ describe("GoogleCalendarService credential handling", () => {
});
test("On missing user email for DelegationCredential, it should fallback to OAuth2 auth", async () => {
const credentialWithDelegation = await createInMemoryDelegationCredentialForCalendarService({
const credentialWithDelegation = await createInMemoryDelegationCredentialForBuildCalendarService({
user: { email: null },
delegatedTo: defaultDelegatedCredential,
delegationCredentialId: "delegation-credential-id-1",
});
const calendarService = new CalendarService(credentialWithDelegation);
const calendarService = BuildCalendarService(credentialWithDelegation);
mockSuccessfulCalendarListFetch();
await calendarService.listCalendars();
expectOAuth2InstanceToBeCreated();
@@ -20,8 +20,8 @@ import "vitest-fetch-mock";
import logger from "@calcom/lib/logger";
import { CredentialForCalendarServiceWithEmail } from "@calcom/types/Credential";
import CalendarService from "../CalendarService";
import { createMockJWTInstance } from "./utils";
import BuildCalendarService, { createGoogleCalendarServiceWithGoogleType } from "../CalendarService";
import { createMockJWTInstance, createCredentialForCalendarService } from "./utils";
const log = logger.getSubLogger({ prefix: ["CalendarService.test"] });
@@ -56,7 +56,7 @@ const mockCredential: CredentialForCalendarServiceWithEmail = {
describe("getAvailability", () => {
test("returns availability for selected calendars", async () => {
const calendarService = new CalendarService(mockCredential);
const calendarService = BuildCalendarService(mockCredential);
setFullMockOAuthManagerRequest();
const mockedBusyTimes1 = [
{
@@ -125,7 +125,7 @@ describe("getAvailability", () => {
describe("getPrimaryCalendar", () => {
test("should fetch primary calendar using 'primary' keyword", async () => {
const calendarService = new CalendarService(mockCredential);
const calendarService = createGoogleCalendarServiceWithGoogleType(mockCredential);
setFullMockOAuthManagerRequest();
const mockPrimaryCalendar = {
id: "user@example.com",
@@ -327,7 +327,7 @@ describe("Date Optimization Benchmarks", () => {
});
test("fetchAvailabilityData should handle both single API call and chunked scenarios correctly", async () => {
const calendarService = new CalendarService(mockCredential);
const calendarService = BuildCalendarService(mockCredential);
setFullMockOAuthManagerRequest();
const mockBusyData = [
@@ -371,7 +371,7 @@ describe("Date Optimization Benchmarks", () => {
describe("createEvent", () => {
test("should create event with correct input/output format and handle all expected properties", async () => {
const calendarService = new CalendarService(mockCredential);
const calendarService = BuildCalendarService(mockCredential);
setFullMockOAuthManagerRequest();
// Mock Google Calendar API response
@@ -570,7 +570,7 @@ describe("createEvent", () => {
});
test("should handle recurring events correctly", async () => {
const calendarService = new CalendarService(mockCredential);
const calendarService = BuildCalendarService(mockCredential);
setFullMockOAuthManagerRequest();
// Mock recurring event response
@@ -672,7 +672,7 @@ describe("createEvent", () => {
});
test("should use default reminders when no custom reminder is configured", async () => {
const calendarService = new CalendarService(mockCredential);
const calendarService = BuildCalendarService(mockCredential);
setFullMockOAuthManagerRequest();
const mockGoogleEvent = {
@@ -736,7 +736,7 @@ describe("createEvent", () => {
});
test("should handle 'just in time' reminder (0 minutes) correctly", async () => {
const calendarService = new CalendarService(mockCredential);
const calendarService = BuildCalendarService(mockCredential);
setFullMockOAuthManagerRequest();
// Mock the getReminderDuration method to return 0 (just in time)
@@ -1 +1 @@
export { default as CalendarService } from "./CalendarService";
export { default as BuildCalendarService } from "./CalendarService";
@@ -79,11 +79,11 @@ test.describe("Google Calendar", async () => {
test.skip(!qaUsername, "QA username not found");
const googleCalendarService = new GoogleCalendarService(qaGCalCredential);
const googleCalendarService = GoogleCalendarService(qaGCalCredential);
const calendars = await googleCalendarService.listCalendars();
const primaryCalendarName = calendars.find((calendar) => calendar.primary)?.name;
const primaryCalendarName = calendars.find((calendar: { primary?: boolean; name?: string }) => calendar.primary)?.name;
assertValueExists(primaryCalendarName, "primaryCalendarName");
await prisma.destinationCalendar.upsert({
@@ -7,7 +7,7 @@ import type { Prisma } from "@calcom/prisma/client";
import { bookTimeSlot, selectSecondAvailableTimeSlotNextMonth } from "@calcom/web/playwright/lib/testUtils";
import metadata from "../_metadata";
import GoogleCalendarService from "../lib/CalendarService";
import { createGoogleCalendarServiceWithGoogleType } from "../lib/CalendarService";
/**
* Creates the booking on Cal.com and makes the GCal call to fetch the event.
@@ -92,7 +92,7 @@ export const createBookingAndFetchGCalEvent = async (
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
const googleCalendarService = new GoogleCalendarService(refreshedCredential);
const googleCalendarService = createGoogleCalendarServiceWithGoogleType(refreshedCredential);
const authedCalendar = await googleCalendarService.authedCalendar();
@@ -19,7 +19,7 @@ import type { PaidBooking } from "./types";
const log = logger.getSubLogger({ prefix: ["payment-service:hitpay"] });
export class PaymentService implements IAbstractPaymentService {
class HitPayPaymentService implements IAbstractPaymentService {
private credentials: z.infer<typeof hitpayCredentialKeysSchema> | null;
constructor(credentials: { key: Prisma.JsonValue }) {
@@ -240,3 +240,12 @@ export class PaymentService implements IAbstractPaymentService {
return !!this.credentials;
}
}
/**
* Factory function that creates a HitPay Payment service instance.
* This is exported instead of the class to prevent internal types
* from leaking into the emitted .d.ts file.
*/
export function BuildPaymentService(credentials: { key: Prisma.JsonValue }): IAbstractPaymentService {
return new HitPayPaymentService(credentials);
}
@@ -2,6 +2,7 @@ import type { TFunction } from "i18next";
import type { CalendarEvent } from "@calcom/types/Calendar";
import type { CredentialPayload } from "@calcom/types/Credential";
import type { CRM } from "@calcom/types/CrmService";
import { setupAndTeardown } from "@calcom/testing/lib/bookingScenario/setupAndTeardown";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { z } from "zod";
@@ -15,6 +16,8 @@ type AppOptions = z.infer<typeof appDataSchema>;
const {
mockHubspotClient,
mockGetAppKeysFromSlug,
mockTrackingRepository,
mockBookingRepository,
}: {
mockHubspotClient: {
crm: {
@@ -39,6 +42,8 @@ const {
setAccessToken: ReturnType<typeof vi.fn>;
};
mockGetAppKeysFromSlug: ReturnType<typeof vi.fn>;
mockTrackingRepository: { findByBookingUid: ReturnType<typeof vi.fn> };
mockBookingRepository: { findBookingByUid: ReturnType<typeof vi.fn> };
} = vi.hoisted(() => {
const mockHubspotClient = {
crm: {
@@ -88,7 +93,15 @@ const {
client_secret: "mock_client_secret",
});
return { mockHubspotClient, mockGetAppKeysFromSlug };
const mockTrackingRepository = {
findByBookingUid: vi.fn(),
};
const mockBookingRepository = {
findBookingByUid: vi.fn(),
};
return { mockHubspotClient, mockGetAppKeysFromSlug, mockTrackingRepository, mockBookingRepository };
});
vi.mock("@hubspot/api-client", () => {
@@ -113,16 +126,22 @@ vi.mock("@calcom/prisma", () => ({
},
}));
import HubspotCalendarService from "./CrmService";
vi.mock("@calcom/lib/server/repository/PrismaTrackingRepository", () => ({
PrismaTrackingRepository: class {
findByBookingUid = mockTrackingRepository.findByBookingUid;
},
}));
vi.mock("@calcom/features/bookings/repositories/BookingRepository", () => ({
BookingRepository: class {
findBookingByUid = mockBookingRepository.findBookingByUid;
},
}));
import BuildCrmService from "./CrmService";
describe("HubspotCalendarService", () => {
let service: HubspotCalendarService;
let mockTrackingRepository: {
findByBookingUid: ReturnType<typeof vi.fn>;
};
let mockBookingRepository: {
findBookingByUid: ReturnType<typeof vi.fn>;
};
let service: CRM & { getAppOptions: () => AppOptions };
setupAndTeardown();
@@ -136,14 +155,6 @@ describe("HubspotCalendarService", () => {
client_secret: "mock_client_secret",
});
mockTrackingRepository = {
findByBookingUid: vi.fn(),
};
mockBookingRepository = {
findBookingByUid: vi.fn(),
};
const mockCredential: CredentialPayload = {
id: 1,
type: "hubspot_other_calendar",
@@ -163,16 +174,7 @@ describe("HubspotCalendarService", () => {
delegationCredentialId: null,
};
service = new HubspotCalendarService(mockCredential, {});
// @ts-expect-error - Injecting mock repositories for testing
service.trackingRepository = mockTrackingRepository;
// @ts-expect-error - Injecting mock repositories for testing
service.bookingRepository = mockBookingRepository;
// @ts-expect-error - Injecting mock hubspot client for testing
service.hubspotClient = mockHubspotClient;
// @ts-expect-error - Mocking auth promise to prevent unhandled rejections
service.auth = Promise.resolve({ getToken: vi.fn() });
service = BuildCrmService(mockCredential, {}) as CRM & { getAppOptions: () => AppOptions };
});
afterEach(() => {
+14 -1
View File
@@ -26,7 +26,7 @@ import { PrismaTrackingRepository } from "@calcom/lib/server/repository/PrismaTr
import type { HubspotToken } from "../api/callback";
import type { appDataSchema } from "../zod";
export default class HubspotCalendarService implements CRM {
class HubspotCalendarService implements CRM {
private url = "";
private integrationName = "";
private auth: Promise<{ getToken: () => Promise<HubspotToken | void | never[]> }>;
@@ -567,3 +567,16 @@ export default class HubspotCalendarService implements CRM {
console.log("Not implemented");
}
}
/**
* Factory function that creates a HubSpot CRM service instance.
* This is exported instead of the class to prevent SDK types (like hubspot.Client)
* from leaking into the emitted .d.ts file, which would cause TypeScript to load
* all 1,800+ HubSpot SDK declaration files when type-checking dependent packages.
*/
export default function BuildCrmService(
credential: CredentialPayload,
appOptions?: Record<string, unknown>
): CRM {
return new HubspotCalendarService(credential, appOptions);
}
+1 -1
View File
@@ -1 +1 @@
export { default as CrmService } from "./CrmService";
export { default as BuildCrmService } from "./CrmService";
@@ -6,7 +6,7 @@ import prisma from "@calcom/prisma";
import getInstalledAppPath from "../../_utils/getInstalledAppPath";
import appConfig from "../config.json";
import { CalendarService } from "../lib";
import { BuildCalendarService } from "../lib";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === "POST") {
@@ -33,7 +33,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
};
try {
const dav = new CalendarService({
const dav = BuildCalendarService({
id: 0,
...data,
user: { email: user.email },
@@ -39,7 +39,7 @@ const applyTravelDuration = (event: ICAL.Event, seconds: number) => {
const CALENDSO_ENCRYPTION_KEY = process.env.CALENDSO_ENCRYPTION_KEY || "";
export default class ICSFeedCalendarService implements Calendar {
class ICSFeedCalendarService implements Calendar {
private urls: string[] = [];
protected integrationName = "ics-feed_calendar";
@@ -306,3 +306,12 @@ export default class ICSFeedCalendarService implements Calendar {
});
}
}
/**
* Factory function that creates an ICS Feed Calendar service instance.
* This is exported instead of the class to prevent internal types
* from leaking into the emitted .d.ts file.
*/
export default function BuildCalendarService(credential: CredentialPayload): Calendar {
return new ICSFeedCalendarService(credential);
}
@@ -1 +1 @@
export { default as CalendarService } from "./CalendarService";
export { default as BuildCalendarService } from "./CalendarService";
@@ -32,7 +32,7 @@ function parseEventTime2Timestamp(eventTime: string): string {
return String(+new Date(eventTime) / 1000);
}
export default class LarkCalendarService implements Calendar {
class LarkCalendarService implements Calendar {
private url = `https://${LARK_HOST}/open-apis`;
private integrationName = "";
private log: typeof logger;
@@ -117,7 +117,7 @@ export default class LarkCalendarService implements Calendar {
let accessToken = "";
try {
accessToken = await this.auth.getToken();
} catch (error) {
} catch {
throw new Error("get access token error");
}
@@ -423,3 +423,12 @@ export default class LarkCalendarService implements Calendar {
return attendeeArray;
};
}
/**
* Factory function that creates a Lark Calendar service instance.
* This is exported instead of the class to prevent internal types
* from leaking into the emitted .d.ts file.
*/
export default function BuildCalendarService(credential: CredentialPayload): Calendar {
return new LarkCalendarService(credential);
}
+1 -1
View File
@@ -1 +1 @@
export { default as CalendarService } from "./CalendarService";
export { default as BuildCalendarService } from "./CalendarService";
@@ -4,7 +4,7 @@ import prisma from "@calcom/prisma";
import type { Booking, Payment, Prisma, PaymentOption } from "@calcom/prisma/client";
import type { IAbstractPaymentService } from "@calcom/types/PaymentService";
export class PaymentService implements IAbstractPaymentService {
class MockPaymentService implements IAbstractPaymentService {
async create(
payment: Pick<Prisma.PaymentUncheckedCreateInput, "amount" | "currency">,
bookingId: Booking["id"]
@@ -139,3 +139,12 @@ export class PaymentService implements IAbstractPaymentService {
return true;
}
}
/**
* Factory function that creates a Mock Payment service instance.
* This is exported instead of the class to prevent internal types
* from leaking into the emitted .d.ts file.
*/
export function BuildPaymentService(_credentials: { key: Prisma.JsonValue }): IAbstractPaymentService {
return new MockPaymentService();
}
@@ -1 +1 @@
export { PaymentService } from "./PaymentService";
export { BuildPaymentService } from "./PaymentService";
@@ -55,7 +55,7 @@ interface BodyValue {
start: { dateTime: string };
}
export default class Office365CalendarService implements Calendar {
class Office365CalendarService implements Calendar {
private url = "";
private integrationName = "";
private log: typeof logger;
@@ -785,3 +785,15 @@ export default class Office365CalendarService implements Calendar {
}
}
}
/**
* Factory function that creates an Office365 Calendar service instance.
* This is exported instead of the class to prevent SDK types (like Microsoft Graph types)
* from leaking into the emitted .d.ts file, which would cause TypeScript to load
* all Microsoft Graph SDK declaration files when type-checking dependent packages.
*/
export default function BuildCalendarService(
credential: CredentialForCalendarServiceWithTenantId
): Calendar {
return new Office365CalendarService(credential);
}
@@ -1 +1 @@
export { default as CalendarService } from "./CalendarService";
export { default as BuildCalendarService } from "./CalendarService";
+9
View File
@@ -4,10 +4,19 @@
"sideEffects": false,
"version": "0.0.0",
"main": "./index.ts",
"typesVersions": {
"*": {
"*": [
"./types/*",
"./*"
]
}
},
"files": [
"components.ts"
],
"scripts": {
"build:types": "tsc --project tsconfig.build.json",
"type-check": "tsc --pretty --noEmit",
"type-check:ci": "tsc-absolute --pretty --noEmit",
"lint": "biome lint .",
@@ -20,7 +20,7 @@ export const paypalCredentialKeysSchema = z.object({
webhook_id: z.string(),
});
export class PaymentService implements IAbstractPaymentService {
class PaypalPaymentService implements IAbstractPaymentService {
private credentials: z.infer<typeof paypalCredentialKeysSchema> | null;
constructor(credentials: { key: Prisma.JsonValue }) {
@@ -198,3 +198,12 @@ export class PaymentService implements IAbstractPaymentService {
return !!this.credentials;
}
}
/**
* Factory function that creates a PayPal Payment service instance.
* This is exported instead of the class to prevent internal types
* from leaking into the emitted .d.ts file.
*/
export function BuildPaymentService(credentials: { key: Prisma.JsonValue }): IAbstractPaymentService {
return new PaypalPaymentService(credentials);
}
@@ -33,7 +33,7 @@ type ContactCreateResult = {
};
};
export default class PipedriveCrmService implements CRM {
class PipedriveCrmService implements CRM {
private log: typeof logger;
private tenantId: string;
private revertApiKey: string;
@@ -52,7 +52,7 @@ export default class PipedriveCrmService implements CRM {
headers.append("x-revert-t-id", this.tenantId);
headers.append("Content-Type", "application/json");
const [firstname, lastname] = !!attendee.name ? attendee.name.split(" ") : [attendee.email, "-"];
const [firstname, lastname] = attendee.name ? attendee.name.split(" ") : [attendee.email, "-"];
const bodyRaw = JSON.stringify({
firstName: firstname,
lastName: lastname || "-",
@@ -99,7 +99,7 @@ export default class PipedriveCrmService implements CRM {
const response = await fetch(`${this.revertApiUrl}crm/contacts/search`, requestOptions);
const result = (await response.json()) as ContactSearchResult;
return result;
} catch (error) {
} catch {
return { status: "error", results: [] };
}
});
@@ -239,3 +239,15 @@ export default class PipedriveCrmService implements CRM {
console.log("Not implemented");
}
}
/**
* Factory function that creates a Pipedrive CRM service instance.
* This is exported instead of the class to prevent internal types
* from leaking into the emitted .d.ts file.
*/
export default function BuildCrmService(
credential: CredentialPayload,
_appOptions?: Record<string, unknown>
): CRM {
return new PipedriveCrmService(credential);
}
@@ -1 +1 @@
export { default as CrmService } from "./CrmService";
export { default as BuildCrmService } from "./CrmService";
@@ -6,7 +6,8 @@ import type { z } from "zod";
import type { CredentialPayload } from "@calcom/types/Credential";
import type { appDataSchema } from "../zod";
import SalesforceCRMService from "./CrmService";
import type { SalesforceCRM } from "./CrmService";
import { createSalesforceCrmServiceWithSalesforceType } from "./CrmService";
import { SalesforceRecordEnum } from "./enums";
type AppOptions = z.infer<typeof appDataSchema>;
@@ -87,7 +88,7 @@ vi.mock("./graphql/SalesforceGraphQLClient", () => ({
}));
describe("SalesforceCRMService", () => {
let service: SalesforceCRMService;
let service: SalesforceCRM;
let mockConnection: {
query: ReturnType<typeof vi.fn>;
sobject: ReturnType<typeof vi.fn>;
@@ -121,9 +122,9 @@ describe("SalesforceCRMService", () => {
delegationCredentialId: null,
};
service = new SalesforceCRMService(mockCredential, {}, true);
service = createSalesforceCrmServiceWithSalesforceType(mockCredential, {}, true);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
// @ts-ignore - accessing private property for testing
service.conn = Promise.resolve(mockConnection);
});
@@ -29,6 +29,26 @@ import {
DateFieldTypeData,
RoutingReasons,
} from "./enums";
/**
* Extended CRM interface with Salesforce-specific methods.
* This interface is used by internal Salesforce modules (routing forms, etc.)
* that need access to Salesforce-specific functionality beyond the generic CRM interface.
*/
export interface SalesforceCRM extends CRM {
findUserEmailFromLookupField(
attendeeEmail: string,
fieldName: string,
salesforceObject: SalesforceRecordEnum
): Promise<{ email: string; recordType: RoutingReasons } | undefined>;
incompleteBookingWriteToRecord(
email: string,
writeToRecordObject: z.infer<typeof writeToRecordDataSchema>
): Promise<void>;
getAllPossibleAccountWebsiteFromEmailDomain(emailDomain: string): string;
}
import { getSalesforceAppKeys } from "./getSalesforceAppKeys";
import { SalesforceGraphQLClient } from "./graphql/SalesforceGraphQLClient";
import getAllPossibleWebsiteValuesFromEmailDomain from "./utils/getAllPossibleWebsiteValuesFromEmailDomain";
@@ -92,7 +112,7 @@ const salesforceTokenSchema = z.object({
token_type: z.string(),
});
export default class SalesforceCRMService implements CRM {
class SalesforceCRMService implements CRM {
private integrationName = "";
private conn!: Promise<Connection>;
private log: typeof logger;
@@ -1699,3 +1719,29 @@ export default class SalesforceCRMService implements CRM {
}
}
}
/**
* Factory function that creates a Salesforce CRM service instance.
* This is exported instead of the class to prevent SDK types (like jsforce.Connection)
* from leaking into the emitted .d.ts file, which would cause TypeScript to load
* all jsforce SDK declaration files when type-checking dependent packages.
*/
export default function BuildCrmService(
credential: CredentialPayload,
appOptions?: Record<string, unknown>
): CRM {
return new SalesforceCRMService(credential, (appOptions ?? {}) as z.infer<typeof appDataSchema>);
}
/**
* Factory function that creates a Salesforce CRM service instance with the extended SalesforceCRM type.
* This is used by internal Salesforce modules (routing forms, etc.) that need access to
* Salesforce-specific methods beyond the generic CRM interface.
*/
export function createSalesforceCrmServiceWithSalesforceType(
credential: CredentialPayload,
appOptions?: Record<string, unknown>,
testMode = false
): SalesforceCRM {
return new SalesforceCRMService(credential, (appOptions ?? {}) as z.infer<typeof appDataSchema>, testMode);
}
@@ -9,7 +9,7 @@
import jsforce from "@jsforce/jsforce-node";
import { describe, it, expect, vi, beforeEach } from "vitest";
import SalesforceCRMService from "../CrmService";
import { createSalesforceCrmServiceWithSalesforceType } from "../CrmService";
import { SalesforceRecordEnum } from "../enums";
import {
mockValueOfAccountOwnershipQueryMatchingContact,
@@ -133,7 +133,7 @@ describe("SalesforceCRMService", () => {
const forRoundRobinSkip = true;
it("(Lookup-1) should return contact's account owner's email as ownerEmail if the contact is found directly by email", async () => {
mockUrqlQuery.mockResolvedValue(mockValueOfAccountOwnershipQueryMatchingContact());
const crmService = new SalesforceCRMService(credential, appOptions);
const crmService = createSalesforceCrmServiceWithSalesforceType(credential, appOptions);
const contactAccountOwnerEmail = "owner@test.com";
const lookingForEmail = "contact@email.com";
@@ -151,7 +151,7 @@ describe("SalesforceCRMService", () => {
it("(Lookup-2) should fallback to account(and use the account owner email as ownerEmail) matched by emailDomain if the contact is not found(i.e Lookup-1 fails)", async () => {
mockUrqlQuery.mockResolvedValue(mockValueOfAccountOwnershipQueryMatchingAccountWebsite());
const crmService = new SalesforceCRMService(credential, appOptions);
const crmService = createSalesforceCrmServiceWithSalesforceType(credential, appOptions);
const contactAccountOwnerEmail = "owner@test.com";
const emailDomain = "example.com";
const lookingForEmail = `test@${emailDomain}`;
@@ -172,7 +172,7 @@ describe("SalesforceCRMService", () => {
it("(Lookup-3) should fallback to account having most number of contacts matched by emailDomain when Lookup-1 and Lookup-2 fails", async () => {
mockUrqlQuery.mockResolvedValue(mockValueOfAccountOwnershipQueryMatchingRelatedContacts());
const crmService = new SalesforceCRMService(credential, appOptions);
const crmService = createSalesforceCrmServiceWithSalesforceType(credential, appOptions);
const accountOwnerEmail = "owner1@test.com";
const emailDomain = "example.com";
const lookingForEmail = `test@${emailDomain}`;
@@ -195,7 +195,7 @@ describe("SalesforceCRMService", () => {
describe("getContacts: forRoundRobinSkip=false i.e. how it is called when creating event", () => {
const forRoundRobinSkip = false;
it("should return lead object if the lead is found directly by email and contact is not found by that email", async () => {
const crmService = new SalesforceCRMService(credential, appOptions);
const crmService = createSalesforceCrmServiceWithSalesforceType(credential, appOptions);
const contactAccountOwnerEmail = "contact-account-owner@acme.com";
const contactOwnerEmail = "contact-owner@acme.com";
const leadOwnerEmail = "lead-owner@acme.com";
@@ -245,7 +245,7 @@ describe("SalesforceCRMService", () => {
});
it("should return contact object(if there is one by that email) even if lead is found by that email", async () => {
const crmService = new SalesforceCRMService(credential, appOptions);
const crmService = createSalesforceCrmServiceWithSalesforceType(credential, appOptions);
const contactAccountOwnerEmail = "contact-account-owner@acme.com";
const contactOwnerEmail = "contact-owner@acme.com";
const leadOwnerEmail = "lead-owner@acme.com";
@@ -297,7 +297,7 @@ describe("SalesforceCRMService", () => {
describe("createContact", () => {
it("should create a contact under an account if the attendee has an account", async () => {
const crmService = new SalesforceCRMService(credential, appOptions);
const crmService = createSalesforceCrmServiceWithSalesforceType(credential, appOptions);
const attendeeEmail = "test@booker.com";
const account = salesforceMock.addAccount({
Id: "test-account-id",
@@ -331,7 +331,7 @@ describe("SalesforceCRMService", () => {
});
it("should create a lead if the attendee has no account", async () => {
const crmService = new SalesforceCRMService(credential, appOptions);
const crmService = createSalesforceCrmServiceWithSalesforceType(credential, appOptions);
const attendeeEmail = "test@booker.com";
const result = await crmService.createContacts([
{
+1 -1
View File
@@ -1 +1 @@
export { default as CrmService } from "./CrmService";
export { default as BuildCrmService } from "./CrmService";
@@ -2,7 +2,7 @@ import { CredentialRepository } from "@calcom/features/credentials/repositories/
import logger from "@calcom/lib/logger";
import { routingFormIncompleteBookingDataSchema } from "../../zod";
import SalesforceCRMService from "../CrmService";
import { createSalesforceCrmServiceWithSalesforceType } from "../CrmService";
const log = logger.getSubLogger({ prefix: ["incomplete-booking: salesforce"] });
@@ -24,7 +24,7 @@ export const incompleteBookingAction = async (action: any, email: string) => {
}
if (writeToRecordObject) {
const crm = new SalesforceCRMService(credential, {});
const crm = createSalesforceCrmServiceWithSalesforceType(credential, {});
await crm.incompleteBookingWriteToRecord(email, writeToRecordObject);
}
@@ -1,7 +1,7 @@
import { CredentialRepository } from "@calcom/features/credentials/repositories/CredentialRepository";
import type { AttributeRoutingConfig } from "../../routing-forms/types/types";
import SalesforceCRMService from "./CrmService";
import { createSalesforceCrmServiceWithSalesforceType } from "./CrmService";
import { SalesforceRecordEnum, RoutingReasons } from "./enums";
import { EventTypeService } from "./eventTypeService";
@@ -27,7 +27,7 @@ const routingFormBookingFormHandler = async (
if (!credential) return { email: null, recordType: null, recordId: null };
const crm = new SalesforceCRMService(credential, {});
const crm = createSalesforceCrmServiceWithSalesforceType(credential, {});
const userLookupEmail = await crm.findUserEmailFromLookupField(
attendeeEmail,
@@ -27,7 +27,7 @@ const CALENDSO_ENCRYPTION_KEY = process.env.CALENDSO_ENCRYPTION_KEY || "";
* user and input it in our system. A Setup page was created when trying to install
* Sendgrid in order to instruct how to create such resource and to obtain it.
*/
export default class CloseComCalendarService implements Calendar {
class SendgridCalendarService implements Calendar {
private integrationName = "";
private sendgrid: Sendgrid;
private log: typeof logger;
@@ -96,3 +96,12 @@ export default class CloseComCalendarService implements Calendar {
return Promise.resolve([]);
}
}
/**
* Factory function that creates a Sendgrid Calendar service instance.
* This is exported instead of the class to prevent internal types
* from leaking into the emitted .d.ts file.
*/
export default function BuildCalendarService(credential: CredentialPayload): Calendar {
return new SendgridCalendarService(credential);
}
+1 -1
View File
@@ -1 +1 @@
export { default as CalendarService } from "./CalendarService";
export { default as BuildCalendarService } from "./CalendarService";
@@ -28,7 +28,7 @@ export const stripeCredentialKeysSchema = z.object({
stripe_publishable_key: z.string(),
});
export class PaymentService implements IAbstractPaymentService {
class StripePaymentService implements IAbstractPaymentService {
private stripe: Stripe;
private credentials: z.infer<typeof stripeCredentialKeysSchema> | null;
@@ -480,3 +480,12 @@ export class PaymentService implements IAbstractPaymentService {
};
}
}
/**
* Factory function that creates a Stripe Payment service instance.
* This is exported instead of the class to prevent SDK types (Stripe)
* from leaking into the emitted .d.ts file.
*/
export function BuildPaymentService(credentials: { key: Prisma.JsonValue }): IAbstractPaymentService {
return new StripePaymentService(credentials);
}
+37
View File
@@ -0,0 +1,37 @@
{
"extends": "@calcom/tsconfig/react-library.json",
"compilerOptions": {
"composite": true,
"declaration": true,
"emitDeclarationOnly": true,
"declarationDir": "types",
"rootDir": ".",
"baseUrl": ".",
"paths": {
"@components/*": ["../../apps/web/components/*"],
"@lib/*": ["../../apps/web/lib/*"],
"@prisma/client/*": ["@calcom/prisma/client/*"]
},
"resolveJsonModule": true,
"experimentalDecorators": true
},
"include": [
"next-env.d.ts",
"@calcom/types",
"../types/*.d.ts",
"../types/next-auth.d.ts",
"**/*.ts",
"**/*.tsx"
],
"exclude": [
"dist",
"types",
"build",
"**/node_modules/**",
"**/*.test.ts",
"**/*.spec.ts",
"**/__tests__/**",
"**/__mocks__/**",
"**/tests/**"
]
}
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,6 +1,6 @@
{
"extends": "@calcom/tsconfig/react-library.json",
"exclude": ["dist", "build", "**/node_modules/**"],
"exclude": ["dist", "types", "build", "**/node_modules/**"],
"compilerOptions": {
"baseUrl": ".",
"paths": {
@@ -33,7 +33,7 @@ export type BiginContact = {
Email: string;
};
export default class BiginCrmService implements CRM {
class BiginCrmService implements CRM {
private readonly integrationName = "zoho-bigin";
private readonly auth: { getToken: () => Promise<BiginToken> };
private log: typeof logger;
@@ -299,7 +299,19 @@ export default class BiginCrmService implements CRM {
}
}
const toISO8601String = (date: Date) => {
/**
* Factory function that creates a Zoho Bigin CRM service instance.
* This is exported instead of the class to prevent internal types
* from leaking into the emitted .d.ts file.
*/
export default function BuildCrmService(
credential: CredentialPayload,
_appOptions?: Record<string, unknown>
): CRM {
return new BiginCrmService(credential);
}
const toISO8601String= (date: Date) => {
const tzo = -date.getTimezoneOffset(),
dif = tzo >= 0 ? "+" : "-",
pad = function (num: number) {
+1 -1
View File
@@ -1 +1 @@
export { default as CrmService } from "./CrmService";
export { default as BuildCrmService } from "./CrmService";
@@ -19,7 +19,7 @@ import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
import type { ZohoAuthCredentials, FreeBusy, ZohoCalendarListResp } from "../types/ZohoCalendar";
import { appKeysSchema as zohoKeysSchema } from "../zod";
export default class ZohoCalendarService implements Calendar {
class ZohoCalendarService implements Calendar {
private integrationName = "";
private log: typeof logger;
auth: { getToken: () => Promise<ZohoAuthCredentials> };
@@ -479,3 +479,12 @@ export default class ZohoCalendarService implements Calendar {
return zohoEvent;
};
}
/**
* Factory function that creates a Zoho Calendar service instance.
* This is exported instead of the class to prevent internal types
* from leaking into the emitted .d.ts file.
*/
export default function BuildCalendarService(credential: CredentialPayload): Calendar {
return new ZohoCalendarService(credential);
}
+1 -1
View File
@@ -1 +1 @@
export { default as CalendarService } from "./CalendarService";
export { default as BuildCalendarService } from "./CalendarService";
+14 -2
View File
@@ -45,7 +45,7 @@ const toISO8601String = (date: Date) => {
Math.abs(tzo) % 60
)}`;
};
export default class ZohoCrmCrmService implements CRM {
class ZohoCrmCrmService implements CRM {
private integrationName = "";
private auth: Promise<{ getToken: () => Promise<void> }>;
private log: typeof logger;
@@ -63,7 +63,7 @@ export default class ZohoCrmCrmService implements CRM {
const auth = await this.auth;
await auth.getToken();
const contacts = contactsToCreate.map((contactToCreate) => {
const [firstname, lastname] = !!contactToCreate.name
const [firstname, lastname] = contactToCreate.name
? contactToCreate.name.split(" ")
: [contactToCreate.email, "-"];
return {
@@ -298,3 +298,15 @@ export default class ZohoCrmCrmService implements CRM {
console.log("Not implemented");
}
}
/**
* Factory function that creates a Zoho CRM service instance.
* This is exported instead of the class to prevent internal types
* from leaking into the emitted .d.ts file.
*/
export default function BuildCrmService(
credential: CredentialPayload,
_appOptions?: Record<string, unknown>
): CRM {
return new ZohoCrmCrmService(credential);
}
+1 -1
View File
@@ -1 +1 @@
export { default as CrmService } from "./CrmService";
export { default as BuildCrmService } from "./CrmService";
@@ -10,7 +10,10 @@ import EmailProvider from "next-auth/providers/email";
import GoogleProvider from "next-auth/providers/google";
import { updateProfilePhotoGoogle } from "@calcom/app-store/_utils/oauth/updateProfilePhotoGoogle";
import GoogleCalendarService from "@calcom/app-store/googlecalendar/lib/CalendarService";
import {
createGoogleCalendarServiceWithGoogleType,
type GoogleCalendar,
} from "@calcom/app-store/googlecalendar/lib/CalendarService";
import { LicenseKeySingleton } from "@calcom/ee/common/server/LicenseKeyService";
import { getBillingProviderService } from "@calcom/features/ee/billing/di/containers/Billing";
import { CredentialRepository } from "@calcom/features/credentials/repositories/CredentialRepository";
@@ -733,7 +736,7 @@ export const getOptions = ({
appId: "google-calendar",
type: "google_calendar",
});
const gCalService = new GoogleCalendarService({
const gCalService = createGoogleCalendarServiceWithGoogleType({
...gcalCredential,
user: null,
delegatedTo: null,
@@ -11,29 +11,21 @@ vi.mock("@calcom/app-store/zod-utils", () => ({
vi.mock("@calcom/app-store/payment.services.generated", () => ({
PaymentServiceMap: {
stripepayment: Promise.resolve({
PaymentService: class MockPaymentService {
async create(paymentData: { amount: number; currency: string }) {
return {
...paymentData,
id: "mock-payment-id",
externalId: "mock-external-id",
success: true,
};
}
async collectCard(paymentData: { amount: number; currency: string }) {
return {
...paymentData,
id: "mock-payment-id",
externalId: "mock-external-id",
success: true,
};
}
async afterPayment() {
return true;
}
},
BuildPaymentService: () => ({
create: async (paymentData: { amount: number; currency: string }) => ({
...paymentData,
id: "mock-payment-id",
externalId: "mock-external-id",
success: true,
}),
collectCard: async (paymentData: { amount: number; currency: string }) => ({
...paymentData,
id: "mock-payment-id",
externalId: "mock-external-id",
success: true,
}),
afterPayment: async () => true,
}),
}),
},
}));
@@ -8,8 +8,8 @@ import type { AppCategories, Prisma, EventType } from "@calcom/prisma/client";
import type { CalendarEvent } from "@calcom/types/Calendar";
import type { IAbstractPaymentService } from "@calcom/types/PaymentService";
const isPaymentService = (x: unknown): x is { PaymentService: any } =>
!!x && typeof x === "object" && "PaymentService" in x && typeof x.PaymentService === "function";
const isPaymentService = (x: unknown): x is { BuildPaymentService: (credentials: { key: unknown }) => unknown } =>
!!x && typeof x === "object" && "BuildPaymentService" in x && typeof x.BuildPaymentService === "function";
const handlePayment = async ({
evt,
@@ -61,8 +61,8 @@ const handlePayment = async ({
console.warn(`payment App service not found for key: ${key}`);
return null;
}
const PaymentService = paymentAppModule.PaymentService;
const paymentInstance = new PaymentService(paymentAppCredentials) as IAbstractPaymentService;
const createPaymentService = paymentAppModule.BuildPaymentService;
const paymentInstance = createPaymentService(paymentAppCredentials) as IAbstractPaymentService;
const apps = eventTypeMetaDataSchemaWithTypedApps.parse(selectedEventType?.metadata)?.apps;
@@ -21,12 +21,12 @@ const deletePayment = async (
}
const paymentAppModule = await paymentAppImportFn;
if (!paymentAppModule?.PaymentService) {
if (!paymentAppModule?.BuildPaymentService) {
console.warn(`payment App service not found for key: ${key}`);
return false;
}
const PaymentService = paymentAppModule.PaymentService;
const paymentInstance = new PaymentService(paymentAppCredentials) as IAbstractPaymentService;
const createPaymentService = paymentAppModule.BuildPaymentService;
const paymentInstance = createPaymentService(paymentAppCredentials) as IAbstractPaymentService;
const deleted = await paymentInstance.deletePayment(paymentId);
return deleted;
};
@@ -13,7 +13,7 @@ import { handleNoShowFee } from "./handleNoShowFee";
vi.mock("@calcom/app-store/payment.services.generated", () => ({
PaymentServiceMap: {
stripepayment: Promise.resolve({
PaymentService: vi.fn().mockImplementation(function () {
BuildPaymentService: vi.fn().mockImplementation(function () {
return {
chargeCard: vi.fn(),
};
@@ -71,7 +71,7 @@ describe("handleNoShowFee", () => {
};
const paymentServiceModule = await PaymentServiceMap.stripepayment;
vi.mocked(paymentServiceModule.PaymentService).mockImplementation(function() { return mockPaymentService; });
vi.mocked(paymentServiceModule.BuildPaymentService).mockImplementation(function() { return mockPaymentService; });
});
const mockBooking = {
@@ -146,12 +146,12 @@ export const handleNoShowFee = async ({
throw new Error("Payment app not implemented");
}
const paymentApp = await paymentAppImportFn;
if (!paymentApp?.PaymentService) {
if (!paymentApp?.BuildPaymentService) {
log.error(`Payment service not found for app ${key}`);
throw new Error("Payment service not found");
}
const PaymentService = paymentApp.PaymentService;
const paymentInstance = new PaymentService(paymentCredential) as IAbstractPaymentService;
const createPaymentService = paymentApp.BuildPaymentService;
const paymentInstance = createPaymentService(paymentCredential) as IAbstractPaymentService;
try {
const paymentData = await paymentInstance.chargeCard(payment, booking.id);
@@ -21,12 +21,12 @@ const handlePaymentRefund = async (
}
const paymentAppModule = await paymentAppImportFn;
if (!paymentAppModule?.PaymentService) {
if (!paymentAppModule?.BuildPaymentService) {
console.warn(`payment App service not found for key: ${key}`);
return false;
}
const PaymentService = paymentAppModule.PaymentService;
const paymentInstance = new PaymentService(paymentAppCredentials) as IAbstractPaymentService;
const createPaymentService = paymentAppModule.BuildPaymentService;
const paymentInstance = createPaymentService(paymentAppCredentials) as IAbstractPaymentService;
const refund = await paymentInstance.refund(paymentId);
return refund;
};
@@ -2,8 +2,6 @@ import "@calcom/testing/lib/__mocks__/prisma";
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
import GoogleCalendarService from "@calcom/app-store/googlecalendar/lib/CalendarService";
import OfficeCalendarService from "@calcom/app-store/office365calendar/lib/CalendarService";
import { symmetricDecrypt } from "@calcom/lib/crypto";
import logger from "@calcom/lib/logger";
import type { SelectedCalendar } from "@calcom/prisma/client";
@@ -21,79 +19,36 @@ vi.mock("@calcom/lib/crypto", () => ({
const mockedSymmetricDecrypt = vi.mocked(symmetricDecrypt);
const mockGoogleGetAvailability = vi.fn().mockResolvedValue([]);
const mockGoogleGetAvailabilityWithTimeZones = vi.fn().mockResolvedValue([]);
const mockOfficeGetAvailability = vi.fn().mockResolvedValue([]);
const mockOfficeGetAvailabilityWithTimeZones = vi.fn().mockResolvedValue([]);
vi.mock("@calcom/app-store/calendar.services.generated", () => {
class MockGoogleCalendarService {
constructor(credential: any) {
this.credential = credential;
}
getCredentialId() {
return this.credential.id;
}
async createEvent() {
return {};
}
async updateEvent() {
return {};
}
async deleteEvent() {
return {};
}
async getAvailability() {
return [];
}
async getAvailabilityWithTimeZones() {
return [];
}
async listCalendars() {
return [];
}
}
class MockOfficeCalendarService {
constructor(credential: any) {
this.credential = credential;
}
getCredentialId() {
return this.credential.id;
}
async createEvent() {
return {};
}
async updateEvent() {
return {};
}
async deleteEvent() {
return {};
}
async getAvailability() {
return [];
}
async getAvailabilityWithTimeZones() {
return [];
}
async listCalendars() {
return [];
}
}
return {
CalendarServiceMap: {
googlecalendar: vi.importActual("@calcom/app-store/googlecalendar/lib/CalendarService"),
office365calendar: vi.importActual("@calcom/app-store/office365calendar/lib/CalendarService"),
googlecalendar: Promise.resolve({
default: (credential: { id: number }) => ({
getCredentialId: () => credential.id,
createEvent: vi.fn().mockResolvedValue({}),
updateEvent: vi.fn().mockResolvedValue({}),
deleteEvent: vi.fn().mockResolvedValue({}),
getAvailability: mockGoogleGetAvailability,
getAvailabilityWithTimeZones: mockGoogleGetAvailabilityWithTimeZones,
listCalendars: vi.fn().mockResolvedValue([]),
}),
}),
office365calendar: Promise.resolve({
default: (credential: { id: number }) => ({
getCredentialId: () => credential.id,
createEvent: vi.fn().mockResolvedValue({}),
updateEvent: vi.fn().mockResolvedValue({}),
deleteEvent: vi.fn().mockResolvedValue({}),
getAvailability: mockOfficeGetAvailability,
getAvailabilityWithTimeZones: mockOfficeGetAvailabilityWithTimeZones,
listCalendars: vi.fn().mockResolvedValue([]),
}),
}),
},
};
});
@@ -250,9 +205,7 @@ describe("getCalendarsEvents", () => {
},
];
const getAvailabilitySpy = vi
.spyOn(GoogleCalendarService.prototype, "getAvailability")
.mockReturnValue(Promise.resolve(availability));
mockGoogleGetAvailability.mockResolvedValueOnce(availability);
const selectedCalendar: SelectedCalendar = buildSelectedCalendar({
credentialId: 100,
@@ -274,7 +227,7 @@ describe("getCalendarsEvents", () => {
"slots"
);
expect(getAvailabilitySpy).toHaveBeenCalledWith({
expect(mockGoogleGetAvailability).toHaveBeenCalledWith({
dateFrom: "2010-12-01",
dateTo: "2010-12-04",
selectedCalendars: [selectedCalendar],
@@ -303,12 +256,8 @@ describe("getCalendarsEvents", () => {
},
];
const getGoogleAvailabilitySpy = vi
.spyOn(GoogleCalendarService.prototype, "getAvailability")
.mockReturnValue(Promise.resolve(googleAvailability));
const getOfficeAvailabilitySpy = vi
.spyOn(OfficeCalendarService.prototype, "getAvailability")
.mockReturnValue(Promise.resolve(officeAvailability));
mockGoogleGetAvailability.mockResolvedValueOnce(googleAvailability);
mockOfficeGetAvailability.mockResolvedValueOnce(officeAvailability);
const selectedGoogleCalendar: SelectedCalendar = buildSelectedCalendar({
credentialId: 100,
@@ -346,14 +295,14 @@ describe("getCalendarsEvents", () => {
"slots"
);
expect(getGoogleAvailabilitySpy).toHaveBeenCalledWith({
expect(mockGoogleGetAvailability).toHaveBeenCalledWith({
dateFrom: "2010-12-01",
dateTo: "2010-12-04",
selectedCalendars: [selectedGoogleCalendar],
mode: "slots",
fallbackToPrimary: false,
});
expect(getOfficeAvailabilitySpy).toHaveBeenCalledWith({
expect(mockOfficeGetAvailability).toHaveBeenCalledWith({
dateFrom: "2010-12-01",
dateTo: "2010-12-04",
selectedCalendars: [selectedOfficeCalendar],
@@ -373,9 +322,7 @@ describe("getCalendarsEvents", () => {
});
it("should not call getAvailability if selectedCalendars is empty", async () => {
const getAvailabilitySpy = vi
.spyOn(GoogleCalendarService.prototype, "getAvailability")
.mockReturnValue(Promise.resolve([]));
mockGoogleGetAvailability.mockClear();
const result = await getCalendarsEvents(
[buildRegularCredential(credential)],
@@ -385,7 +332,7 @@ describe("getCalendarsEvents", () => {
"slots"
);
expect(getAvailabilitySpy).not.toHaveBeenCalled();
expect(mockGoogleGetAvailability).not.toHaveBeenCalled();
expect(result).toEqual([[]]);
});
});
@@ -396,13 +343,11 @@ describe("getCalendarsEvents", () => {
const endDate = "2010-12-02";
const delegationCredential: CredentialForCalendarService = buildDelegationCredential(credential);
const credentials = [delegationCredential];
const getAvailabilitySpy = vi
.spyOn(GoogleCalendarService.prototype, "getAvailability")
.mockReturnValue(Promise.resolve([]));
mockGoogleGetAvailability.mockResolvedValueOnce([]);
const result = await getCalendarsEvents(credentials, startDate, endDate, [], "slots");
expect(getAvailabilitySpy).toHaveBeenCalledWith({
expect(mockGoogleGetAvailability).toHaveBeenCalledWith({
dateFrom: startDate,
dateTo: endDate,
selectedCalendars: [],
@@ -515,9 +460,7 @@ describe("getCalendarsEventsWithTimezones", () => {
},
];
const getAvailabilityWithTimezonesSpy = vi
.spyOn(GoogleCalendarService.prototype, "getAvailabilityWithTimeZones")
.mockReturnValue(Promise.resolve(availability));
mockGoogleGetAvailabilityWithTimeZones.mockResolvedValueOnce(availability);
const selectedCalendar: SelectedCalendar = buildSelectedCalendar({
credentialId: 100,
@@ -538,7 +481,7 @@ describe("getCalendarsEventsWithTimezones", () => {
[selectedCalendar]
);
expect(getAvailabilityWithTimezonesSpy).toHaveBeenCalledWith({
expect(mockGoogleGetAvailabilityWithTimeZones).toHaveBeenCalledWith({
dateFrom: "2010-12-01",
dateTo: "2010-12-04",
selectedCalendars: [selectedCalendar],
@@ -553,9 +496,7 @@ describe("getCalendarsEventsWithTimezones", () => {
});
it("should not call getAvailabilityWithTimezones if selectedCalendars is empty", async () => {
const getAvailabilityWithTimezonesSpy = vi
.spyOn(GoogleCalendarService.prototype, "getAvailabilityWithTimeZones")
.mockReturnValue(Promise.resolve([]));
mockGoogleGetAvailabilityWithTimeZones.mockClear();
const result = await getCalendarsEventsWithTimezones(
[buildRegularCredential(credential)],
@@ -564,7 +505,7 @@ describe("getCalendarsEventsWithTimezones", () => {
[]
);
expect(getAvailabilityWithTimezonesSpy).not.toHaveBeenCalled();
expect(mockGoogleGetAvailabilityWithTimeZones).not.toHaveBeenCalled();
expect(result).toEqual([[]]);
});
});
@@ -575,13 +516,11 @@ describe("getCalendarsEventsWithTimezones", () => {
const endDate = "2010-12-02";
const delegationCredential: CredentialForCalendarService = buildDelegationCredential(credential);
const credentials = [delegationCredential];
const getAvailabilityWithTimezonesSpy = vi
.spyOn(GoogleCalendarService.prototype, "getAvailabilityWithTimeZones")
.mockReturnValue(Promise.resolve([]));
mockGoogleGetAvailabilityWithTimeZones.mockResolvedValueOnce([]);
const result = await getCalendarsEventsWithTimezones(credentials, startDate, endDate, []);
expect(getAvailabilityWithTimezonesSpy).toHaveBeenCalledWith({
expect(mockGoogleGetAvailabilityWithTimeZones).toHaveBeenCalledWith({
dateFrom: startDate,
dateTo: endDate,
selectedCalendars: [],
+2 -2
View File
@@ -6,11 +6,11 @@ import addDelegationCredential from "@calcom/trpc/server/routers/viewer/delegati
export type { TDependencyData } from "@calcom/app-store/_appRegistry";
export type { CredentialOwner } from "@calcom/app-store/types";
export { CalendarService as IcsFeedCalendarService } from "@calcom/app-store/ics-feedcalendar/lib";
export { BuildCalendarService as BuildIcsFeedCalendarService } from "@calcom/app-store/ics-feedcalendar/lib";
export { getAppFromSlug } from "@calcom/app-store/utils";
export type { CredentialDataWithTeamName, LocationOption } from "@calcom/app-store/utils";
export { CalendarService } from "@calcom/app-store/applecalendar/lib";
export { BuildCalendarService } from "@calcom/app-store/applecalendar/lib";
export { getApps };
+4
View File
@@ -0,0 +1,4 @@
import type { PrismaClient } from "@calcom/prisma";
declare const prismaMockProxy: PrismaClient;
export default prismaMockProxy;
//# sourceMappingURL=prisma.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"prisma.d.ts","sourceRoot":"","sources":["prisma.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAwBnD,QAAA,MAAM,eAAe,cA0CnB,CAAC;AA4CH,eAAe,eAAe,CAAC"}
@@ -1813,9 +1813,7 @@ export async function mockCalendar(
const calendarServicePromise = CalendarServiceMap[calendarServiceKey];
if (calendarServicePromise) {
const resolvedService = await calendarServicePromise;
vi.mocked(resolvedService.default).mockImplementation(
// @ts-expect-error - Mock implementation satisfies Calendar interface but TypeScript expects specific calendar service types
function MockCalendarService(credential) {
vi.mocked(resolvedService.default).mockImplementation(function MockCalendarService(credential) {
return {
createEvent: async function (
...rest: Parameters<Calendar["createEvent"]>
+76 -76
View File
@@ -58,107 +58,107 @@ vi.mock("@calcom/exchange2016calendar/lib/CalendarService", () => ({
const MOCK_PAYMENT_UID = "MOCK_PAYMENT_UID";
class MockPaymentService {
constructor(credentials?: any) {
this.credentials = credentials;
}
function createMockPaymentService(_credentials?: unknown) {
return {
async create(
payment: { amount: number; currency: string },
bookingId: number,
_userId: number,
_username: string | null,
_bookerName: string | null,
paymentOption: string,
_bookerEmail: string,
_bookerPhoneNumber?: string | null,
_selectedEventTypeTitle?: string,
_eventTitle?: string
) {
const { default: prismaMock } = await import("@calcom/testing/lib/__mocks__/prisma");
const externalId = "mock_payment_external_id";
private credentials: any;
const paymentCreateData = {
uid: MOCK_PAYMENT_UID,
appId: null,
bookingId,
fee: 10,
success: false,
refunded: false,
data: {},
externalId,
paymentOption,
amount: payment.amount,
currency: payment.currency,
};
async create(
payment: any,
bookingId: number,
userId: number,
username: string | null,
bookerName: string | null,
paymentOption: any,
bookerEmail: string,
bookerPhoneNumber?: string | null,
selectedEventTypeTitle?: string,
eventTitle?: string
) {
const { default: prismaMock } = await import("@calcom/testing/lib/__mocks__/prisma");
const externalId = "mock_payment_external_id";
const createdPayment = await prismaMock.payment.create({
data: paymentCreateData,
});
const paymentCreateData = {
uid: MOCK_PAYMENT_UID,
appId: null,
bookingId,
fee: 10,
success: false,
refunded: false,
data: {},
externalId,
paymentOption,
amount: payment.amount,
currency: payment.currency,
};
const createdPayment = await prismaMock.payment.create({
data: paymentCreateData,
});
return createdPayment;
}
async collectCard() {
return { success: true };
}
async chargeCard() {
return { success: true };
}
async refund() {
return { success: true };
}
async deletePayment() {
return { success: true };
}
async afterPayment(event: any, booking: any, paymentData: any) {
const { sendAwaitingPaymentEmailAndSMS } = await import("@calcom/emails/email-manager");
await sendAwaitingPaymentEmailAndSMS({
...event,
paymentInfo: {
link: "http://mock-payment.example.com/",
paymentOption: paymentData.paymentOption || "ON_BOOKING",
amount: paymentData.amount,
currency: paymentData.currency,
},
});
return { success: true };
}
return createdPayment;
},
async collectCard() {
return { success: true };
},
async chargeCard() {
return { success: true };
},
async refund() {
return { success: true };
},
async deletePayment() {
return { success: true };
},
async afterPayment(
event: Record<string, unknown>,
_booking: Record<string, unknown>,
paymentData: { paymentOption?: string; amount: number; currency: string }
) {
const { sendAwaitingPaymentEmailAndSMS } = await import("@calcom/emails/email-manager");
await sendAwaitingPaymentEmailAndSMS({
...event,
paymentInfo: {
link: "http://mock-payment.example.com/",
paymentOption: paymentData.paymentOption || "ON_BOOKING",
amount: paymentData.amount,
currency: paymentData.currency,
},
});
return { success: true };
},
};
}
vi.mock("@calcom/app-store/stripepayment/index", () => ({
PaymentService: MockPaymentService,
BuildPaymentService: createMockPaymentService,
}));
vi.mock("@calcom/app-store/paypal/index", () => ({
PaymentService: MockPaymentService,
BuildPaymentService: createMockPaymentService,
}));
vi.mock("@calcom/app-store/alby/index", () => ({
PaymentService: MockPaymentService,
BuildPaymentService: createMockPaymentService,
}));
vi.mock("@calcom/app-store/hitpay/index", () => ({
PaymentService: MockPaymentService,
BuildPaymentService: createMockPaymentService,
}));
vi.mock("@calcom/app-store/btcpayserver/index", () => ({
PaymentService: MockPaymentService,
BuildPaymentService: createMockPaymentService,
}));
vi.mock("@calcom/app-store/mock-payment-app/index", () => ({
PaymentService: MockPaymentService,
BuildPaymentService: createMockPaymentService,
}));
vi.mock("@calcom/app-store/payment.services.generated", () => ({
PaymentServiceMap: {
stripepayment: Promise.resolve({ PaymentService: MockPaymentService }),
paypal: Promise.resolve({ PaymentService: MockPaymentService }),
alby: Promise.resolve({ PaymentService: MockPaymentService }),
hitpay: Promise.resolve({ PaymentService: MockPaymentService }),
btcpayserver: Promise.resolve({ PaymentService: MockPaymentService }),
"mock-payment-app": Promise.resolve({ PaymentService: MockPaymentService }),
stripepayment: Promise.resolve({ BuildPaymentService: createMockPaymentService }),
paypal: Promise.resolve({ BuildPaymentService: createMockPaymentService }),
alby: Promise.resolve({ BuildPaymentService: createMockPaymentService }),
hitpay: Promise.resolve({ BuildPaymentService: createMockPaymentService }),
btcpayserver: Promise.resolve({ BuildPaymentService: createMockPaymentService }),
"mock-payment-app": Promise.resolve({ BuildPaymentService: createMockPaymentService }),
},
}));
+7
View File
@@ -0,0 +1,7 @@
{
"extends": "./tsconfig.server.json",
"compilerOptions": {
"disableSourceOfProjectReferenceRedirect": true
},
"references": [{ "path": "../app-store/tsconfig.build.json" }]
}
File diff suppressed because one or more lines are too long
+3
View File
@@ -353,6 +353,9 @@
"cache": false,
"dependsOn": ["@calcom/prisma#db-up"]
},
"@calcom/app-store#build:types": {
"outputs": ["types/**"]
},
"@calcom/trpc#build": {
"dependsOn": ["@calcom/prisma#post-install"],
"outputs": ["./types"]