refactor: optimize credit-service imports with lazy loading (#25091)
* refactor: optimize credit-service imports with lazy loading - Remove top-level imports of heavy modules (reminderScheduler, email services, i18n, billing services) - Implement dynamic imports for modules only when needed: - reminderScheduler: loaded only when SMS credit limit reached - email services: loaded only when sending credit notifications - getTranslation: loaded only when handling low credit balance - InternalTeamBilling: loaded only in getMonthlyCredits method - billing singleton: loaded only when calculating warning limits - Break circular dependency: credit-service → reminderScheduler → ... → credit-service - Update tests to mock StripeBillingService for dynamic imports - All 30 tests passing, no type errors, lint clean This reduces baseline import cost by deferring: - Stripe SDK initialization (loaded twice before) - 557KB+ i18n English translation file - Email template classes - Workflow reminder scheduler Verified with madge: circular dependency successfully resolved Co-Authored-By: morgan@cal.com <morgan@cal.com> * fix: add null checks for billing.getPrice() return value Co-Authored-By: morgan@cal.com <morgan@cal.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
morgan@cal.com <morgan@cal.com>
morgan@cal.com <morgan@cal.com>
Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent
a7cb71726a
commit
ad4b5957fc
@@ -101,6 +101,20 @@ vi.mock("../workflows/lib/reminders/reminderScheduler", () => ({
|
||||
vi.mock("@calcom/lib/getOrgIdFromMemberOrTeamId", () => ({
|
||||
default: vi.fn().mockResolvedValue(null),
|
||||
}));
|
||||
vi.mock("@calcom/features/ee/billing/stripe-billing-service", () => {
|
||||
return {
|
||||
StripeBillingService: vi.fn().mockImplementation(() => ({
|
||||
getPrice: async (priceId: string) => {
|
||||
const stripe = (await import("@calcom/features/ee/payments/server/stripe")).default;
|
||||
return stripe.prices.retrieve(priceId);
|
||||
},
|
||||
checkoutSessionIsPaid: vi.fn(),
|
||||
handleSubscriptionCancel: vi.fn(),
|
||||
handleSubscriptionCreation: vi.fn(),
|
||||
handleSubscriptionUpdate: vi.fn(),
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
const creditService = new CreditService();
|
||||
|
||||
|
||||
@@ -1,19 +1,11 @@
|
||||
import type { TFunction } from "i18next";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import {
|
||||
sendCreditBalanceLimitReachedEmails,
|
||||
sendCreditBalanceLowWarningEmails,
|
||||
} from "@calcom/emails/billing-email-service";
|
||||
import { StripeBillingService } from "@calcom/features/ee/billing/stripe-billing-service";
|
||||
import { InternalTeamBilling } from "@calcom/features/ee/billing/teams/internal-team-billing";
|
||||
import { TeamRepository } from "@calcom/features/ee/teams/repositories/TeamRepository";
|
||||
import { cancelScheduledMessagesAndScheduleEmails } from "@calcom/features/ee/workflows/lib/reminders/reminderScheduler";
|
||||
import { MembershipRepository } from "@calcom/features/membership/repositories/MembershipRepository";
|
||||
import { IS_SMS_CREDITS_ENABLED } from "@calcom/lib/constants";
|
||||
import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
import { CreditsRepository } from "@calcom/lib/server/repository/credits";
|
||||
import { prisma, type PrismaTransaction } from "@calcom/prisma";
|
||||
import { CreditUsageType, CreditType } from "@calcom/prisma/enums";
|
||||
@@ -474,9 +466,9 @@ export class CreditService {
|
||||
const { totalMonthlyCredits } = await this._getAllCreditsForTeam({ teamId, tx });
|
||||
warningLimit = totalMonthlyCredits * 0.2;
|
||||
} else if (userId) {
|
||||
const billingService = new StripeBillingService();
|
||||
const teamMonthlyPrice = await billingService.getPrice(process.env.STRIPE_TEAM_MONTHLY_PRICE_ID || "");
|
||||
const pricePerSeat = teamMonthlyPrice.unit_amount ?? 0;
|
||||
const billing = (await import("@calcom/features/ee/billing")).default;
|
||||
const teamMonthlyPrice = await billing.getPrice(process.env.STRIPE_TEAM_MONTHLY_PRICE_ID || "");
|
||||
const pricePerSeat = teamMonthlyPrice?.unit_amount ?? 0;
|
||||
warningLimit = (pricePerSeat / 2) * 0.2;
|
||||
}
|
||||
|
||||
@@ -495,6 +487,8 @@ export class CreditService {
|
||||
return null; // user has limit already reached or team has already reached limit this month
|
||||
}
|
||||
|
||||
const { getTranslation } = await import("@calcom/lib/server/i18n");
|
||||
|
||||
const teamWithAdmins = creditBalance?.team
|
||||
? {
|
||||
...creditBalance.team,
|
||||
@@ -591,6 +585,10 @@ export class CreditService {
|
||||
|
||||
try {
|
||||
if (result.type === "LIMIT_REACHED") {
|
||||
const { sendCreditBalanceLimitReachedEmails } = await import(
|
||||
"@calcom/emails/billing-email-service"
|
||||
);
|
||||
|
||||
const promises: Promise<unknown>[] = [
|
||||
sendCreditBalanceLimitReachedEmails({
|
||||
team: result.team,
|
||||
@@ -602,6 +600,9 @@ export class CreditService {
|
||||
];
|
||||
|
||||
if (!result.creditFor || result.creditFor === CreditUsageType.SMS) {
|
||||
const { cancelScheduledMessagesAndScheduleEmails } = await import(
|
||||
"@calcom/features/ee/workflows/lib/reminders/reminderScheduler"
|
||||
);
|
||||
promises.push(
|
||||
cancelScheduledMessagesAndScheduleEmails({ teamId: result.teamId, userId: result.userId }).catch(
|
||||
(error) => {
|
||||
@@ -613,6 +614,7 @@ export class CreditService {
|
||||
|
||||
await Promise.all(promises);
|
||||
} else if (result.type === "WARNING") {
|
||||
const { sendCreditBalanceLowWarningEmails } = await import("@calcom/emails/billing-email-service");
|
||||
await sendCreditBalanceLowWarningEmails({
|
||||
balance: result.balance,
|
||||
team: result.team,
|
||||
@@ -653,6 +655,7 @@ export class CreditService {
|
||||
|
||||
if (!team) return 0;
|
||||
|
||||
const { InternalTeamBilling } = await import("@calcom/features/ee/billing/teams/internal-team-billing");
|
||||
const teamBillingService = new InternalTeamBilling(team);
|
||||
const subscriptionStatus = await teamBillingService.getSubscriptionStatus();
|
||||
|
||||
@@ -668,7 +671,7 @@ export class CreditService {
|
||||
return activeMembers * creditsPerSeat;
|
||||
}
|
||||
|
||||
const billingService = new StripeBillingService();
|
||||
const billing = (await import("@calcom/features/ee/billing")).default;
|
||||
const priceId = process.env.STRIPE_TEAM_MONTHLY_PRICE_ID;
|
||||
|
||||
if (!priceId) {
|
||||
@@ -676,7 +679,11 @@ export class CreditService {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const monthlyPrice = await billingService.getPrice(priceId);
|
||||
const monthlyPrice = await billing.getPrice(priceId);
|
||||
if (!monthlyPrice) {
|
||||
log.warn("Failed to retrieve monthly price", { teamId, priceId });
|
||||
return 0;
|
||||
}
|
||||
const pricePerSeat = monthlyPrice.unit_amount ?? 0;
|
||||
const creditsPerSeat = pricePerSeat * 0.5;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user