Skip to content

Async import of the appStore packages#1

Open
ShashankFC wants to merge 1 commit into
appstore-sync-refactor-basefrom
appstore-async-improvements
Open

Async import of the appStore packages#1
ShashankFC wants to merge 1 commit into
appstore-sync-refactor-basefrom
appstore-async-improvements

Conversation

@ShashankFC

@ShashankFC ShashankFC commented Dec 31, 2025

Copy link
Copy Markdown
Collaborator

Test 2nn

Summary by CodeRabbit

Release Notes

  • Refactor
    • Improved reliability of calendar event operations during booking creation, updates, and cancellations.
    • Enhanced video meeting provisioning for better performance and stability.
    • Streamlined payment processing to ensure proper app loading before transactions.

✏️ Tip: You can customize this high-level summary in your review settings.

nn---n*Replicated from [ai-code-review-evaluation/cal.com-coderabbit#2](https://github.com/ai-code-review-evaluation/cal.com-coderabbit/pull/2)*

@ShashankFC ShashankFC marked this pull request as ready for review January 6, 2026 12:35
@AI-Code-Review-Evals AI-Code-Review-Evals deleted a comment from coderabbitai Bot Jan 6, 2026
@AI-Code-Review-Evals AI-Code-Review-Evals deleted a comment from coderabbitai Bot Jan 6, 2026
@ShashankFC

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jan 6, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jan 6, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This pull request converts synchronous calendar and video adapter retrieval operations to asynchronous throughout the codebase. Key changes include: making getCalendar async with Promise return type, converting appStore to use dynamic imports, and updating all call sites across calendar management, event handling, booking operations, and video infrastructure to use await for these now-async operations.

Changes

Cohort / File(s) Summary
App Store Infrastructure
packages/app-store/_utils/getCalendar.ts, packages/app-store/index.ts
getCalendar converted to async function returning Promise<Calendar | null>; appStore transitions from static imports to dynamic imports via import() calls for each module property
Core Calendar Operations
packages/core/CalendarManager.ts, packages/core/EventManager.ts
Multiple exported functions updated to await getCalendar calls; deleteEvent converted to async; calendar retrieval and deletion operations now awaited before proceeding
Video Infrastructure
packages/core/videoClient.ts
getVideoAdapters refactored from synchronous reduce-based factory to async function; all call sites (getBusyVideoTimes, createMeeting, updateMeeting, deleteMeeting, createMeetingWithCalVideo, recording functions) updated to await adapter provisioning
Booking & Payment Operations
packages/features/bookings/lib/handleCancelBooking.ts, packages/features/bookings/lib/handleNewBooking.ts, packages/lib/payment/deletePayment.ts, packages/lib/payment/handlePayment.ts, packages/trpc/server/routers/viewer/bookings.tsx
Calendar and payment app retrieval converted to await-based access; forEach loops refactored to async callbacks or for-of loops to properly handle awaited operations; calendar credential lookups now awaited before deletion/update
Integration Reschedule Logic
packages/app-store/vital/lib/reschedule.ts, packages/app-store/wipemycalother/lib/reschedule.ts
Booking reference iteration callbacks converted to async; calendar retrieval now awaited within loops, but outer try/catch does not await all async iterations

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 With async/await we now embrace,
The calendar app store finds its place,
Dynamic imports, promises galore,
Awaiting operations forevermore,
Where sync once rushed, now caution reigns—
Promise-chains flow through the app's veins!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Async import of the appStore packages' directly reflects the main change: converting appStore from static imports to dynamic/async imports.
✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
packages/app-store/wipemycalother/lib/reschedule.ts (1)

125-134: Critical: forEach with async callback creates unhandled promises.

Array.prototype.forEach does not await async callbacks. The promises returned by each iteration are discarded, meaning:

  1. Calendar/video deletions run as fire-and-forget operations
  2. The outer try-catch won't catch errors from these async callbacks
  3. The function may return before deletions complete
🔎 Proposed fix using Promise.all with map
     try {
-      bookingRefsFiltered.forEach(async (bookingRef) => {
+      await Promise.all(bookingRefsFiltered.map(async (bookingRef) => {
         if (bookingRef.uid) {
           if (bookingRef.type.endsWith("_calendar")) {
             const calendar = await getCalendar(credentialsMap.get(bookingRef.type));
             return calendar?.deleteEvent(bookingRef.uid, builder.calendarEvent);
           } else if (bookingRef.type.endsWith("_video")) {
             return deleteMeeting(credentialsMap.get(bookingRef.type), bookingRef.uid);
           }
         }
-      });
+      }));
     } catch (error) {
packages/app-store/vital/lib/reschedule.ts (1)

125-134: Critical: forEach with async callback creates unhandled promises.

Same issue as in packages/app-store/wipemycalother/lib/reschedule.ts - forEach does not await async callbacks, resulting in fire-and-forget deletions and the try-catch not catching errors from these callbacks.

🔎 Proposed fix using Promise.all with map
     try {
-      bookingRefsFiltered.forEach(async (bookingRef) => {
+      await Promise.all(bookingRefsFiltered.map(async (bookingRef) => {
         if (bookingRef.uid) {
           if (bookingRef.type.endsWith("_calendar")) {
             const calendar = await getCalendar(credentialsMap.get(bookingRef.type));
             return calendar?.deleteEvent(bookingRef.uid, builder.calendarEvent);
           } else if (bookingRef.type.endsWith("_video")) {
             return deleteMeeting(credentialsMap.get(bookingRef.type), bookingRef.uid);
           }
         }
-      });
+      }));
     } catch (error) {

Additionally, this file contains nearly identical logic to packages/app-store/wipemycalother/lib/reschedule.ts. Consider extracting shared reschedule logic into a common utility to reduce duplication.

packages/trpc/server/routers/viewer/bookings.tsx (1)

553-567: Critical: forEach with async callback creates unhandled promises.

Same issue as in the reschedule files - forEach does not await async callbacks. The calendar/video deletions will run as fire-and-forget operations, and errors won't propagate.

🔎 Proposed fix using Promise.all with map
-        bookingRefsFiltered.forEach(async (bookingRef) => {
+        await Promise.all(bookingRefsFiltered.map(async (bookingRef) => {
           if (bookingRef.uid) {
             if (bookingRef.type.endsWith("_calendar")) {
               const calendar = await getCalendar(credentialsMap.get(bookingRef.type));
 
               return calendar?.deleteEvent(
                 bookingRef.uid,
                 builder.calendarEvent,
                 bookingRef.externalCalendarId
               );
             } else if (bookingRef.type.endsWith("_video")) {
               return deleteMeeting(credentialsMap.get(bookingRef.type), bookingRef.uid);
             }
           }
-        });
+        }));
packages/features/bookings/lib/handleCancelBooking.ts (1)

458-470: Critical: forEach with async callback does not await operations.

The .forEach(async ...) pattern does not wait for async callbacks to complete. This creates a race condition where apiDeletes may not contain all delete promises when Promise.all(apiDeletes) is called later (line 652), potentially leaving calendar events orphaned.

🔎 Proposed fix using for...of loop
-        bookingToDelete.user.credentials
-          .filter((credential) => credential.type.endsWith("_calendar"))
-          .forEach(async (credential) => {
-            const calendar = await getCalendar(credential);
-            for (const updBooking of updatedBookings) {
-              const bookingRef = updBooking.references.find((ref) => ref.type.includes("_calendar"));
-              if (bookingRef) {
-                const { uid, externalCalendarId } = bookingRef;
-                const deletedEvent = await calendar?.deleteEvent(uid, evt, externalCalendarId);
-                apiDeletes.push(deletedEvent);
-              }
-            }
-          });
+        const calendarCredentials = bookingToDelete.user.credentials.filter((credential) =>
+          credential.type.endsWith("_calendar")
+        );
+        for (const credential of calendarCredentials) {
+          const calendar = await getCalendar(credential);
+          for (const updBooking of updatedBookings) {
+            const bookingRef = updBooking.references.find((ref) => ref.type.includes("_calendar"));
+            if (bookingRef) {
+              const { uid, externalCalendarId } = bookingRef;
+              if (calendar) {
+                apiDeletes.push(calendar.deleteEvent(uid, evt, externalCalendarId));
+              }
+            }
+          }
+        }

Note: The fix also changes from pushing the awaited result to pushing the Promise, which is the correct pattern for later use with Promise.all.

🧹 Nitpick comments (2)
packages/core/videoClient.ts (1)

21-36: LGTM - async adapter retrieval is correctly implemented.

The conversion to async is properly done with awaited appStore lookups.

The sequential for loop could potentially be parallelized using Promise.all for better performance when loading multiple adapters:

🔎 Optional: Parallel adapter loading
 const getVideoAdapters = async (withCredentials: CredentialPayload[]): Promise<VideoApiAdapter[]> => {
-  const videoAdapters: VideoApiAdapter[] = [];
-
-  for (const cred of withCredentials) {
+  const adapterPromises = withCredentials.map(async (cred) => {
     const appName = cred.type.split("_").join(""); // Transform `zoom_video` to `zoomvideo`;
     const app = await appStore[appName as keyof typeof appStore];
-
     if (app && "lib" in app && "VideoApiAdapter" in app.lib) {
       const makeVideoApiAdapter = app.lib.VideoApiAdapter as VideoApiAdapterFactory;
-      const videoAdapter = makeVideoApiAdapter(cred);
-      videoAdapters.push(videoAdapter);
+      return makeVideoApiAdapter(cred);
     }
-  }
-
-  return videoAdapters;
+    return undefined;
+  });
+  const adapters = await Promise.all(adapterPromises);
+  return adapters.filter((adapter): adapter is VideoApiAdapter => adapter !== undefined);
 };
packages/core/CalendarManager.ts (1)

27-32: Consider adding explicit type annotation for clarity.

Since getCalendar is now async, the calendar property in the returned objects is now Promise<Calendar | null> rather than Calendar | null. While this works (as getConnectedCalendars awaits it at line 48), the implicit Promise type may confuse future maintainers.

🔎 Suggested type annotation
 const credentials = app.credentials.flatMap((credential) => {
-  const calendar = getCalendar(credential);
+  const calendar: Promise<Calendar | null> = getCalendar(credential);
   return app.variant === "calendar" ? [{ integration: app, credential, calendar }] : [];
 });
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bff28e5 and e043a5e.

📒 Files selected for processing (12)
  • packages/app-store/_utils/getCalendar.ts
  • packages/app-store/index.ts
  • packages/app-store/vital/lib/reschedule.ts
  • packages/app-store/wipemycalother/lib/reschedule.ts
  • packages/core/CalendarManager.ts
  • packages/core/EventManager.ts
  • packages/core/videoClient.ts
  • packages/features/bookings/lib/handleCancelBooking.ts
  • packages/features/bookings/lib/handleNewBooking.ts
  • packages/lib/payment/deletePayment.ts
  • packages/lib/payment/handlePayment.ts
  • packages/trpc/server/routers/viewer/bookings.tsx
🧰 Additional context used
🧬 Code graph analysis (8)
packages/app-store/vital/lib/reschedule.ts (1)
packages/app-store/_utils/getCalendar.ts (1)
  • getCalendar (9-23)
packages/features/bookings/lib/handleNewBooking.ts (1)
packages/app-store/_utils/getCalendar.ts (1)
  • getCalendar (9-23)
packages/app-store/wipemycalother/lib/reschedule.ts (1)
packages/app-store/_utils/getCalendar.ts (1)
  • getCalendar (9-23)
packages/trpc/server/routers/viewer/bookings.tsx (1)
packages/app-store/_utils/getCalendar.ts (1)
  • getCalendar (9-23)
packages/core/videoClient.ts (2)
packages/types/Credential.d.ts (1)
  • CredentialPayload (8-17)
packages/types/VideoApiAdapter.d.ts (2)
  • VideoApiAdapter (14-28)
  • VideoApiAdapterFactory (30-30)
packages/core/EventManager.ts (1)
packages/app-store/_utils/getCalendar.ts (1)
  • getCalendar (9-23)
packages/core/CalendarManager.ts (2)
packages/app-store/_utils/getCalendar.ts (1)
  • getCalendar (9-23)
packages/app-store/googlecalendar/lib/CalendarService.ts (1)
  • deleteEvent (284-318)
packages/features/bookings/lib/handleCancelBooking.ts (1)
packages/app-store/_utils/getCalendar.ts (1)
  • getCalendar (9-23)
🔇 Additional comments (17)
packages/lib/payment/handlePayment.ts (1)

26-30: LGTM - async appStore lookup is correctly awaited.

The async pattern is properly implemented. The subsequent check on line 27 handles the case where the payment app is not found or doesn't have the expected structure.

packages/lib/payment/deletePayment.ts (1)

16-20: LGTM - async appStore lookup is correctly awaited.

The await pattern is properly implemented and consistent with handlePayment.ts.

packages/core/videoClient.ts (1)

142-152: LGTM - deleteMeeting correctly awaits adapter retrieval.

The async pattern is properly implemented.

packages/trpc/server/routers/viewer/bookings.tsx (1)

967-978: LGTM - async appStore lookup is correctly awaited.

The payment app retrieval correctly uses await for the dynamic import.

packages/app-store/index.ts (1)

1-31: Dynamic imports pattern is correct.

The conversion to dynamic import() calls enables lazy-loading of app modules. All identified consumers of appStore have been properly updated to await the Promises:

  • packages/lib/payment/handlePayment.ts
  • packages/lib/payment/deletePayment.ts
  • packages/core/videoClient.ts
  • packages/trpc/server/routers/viewer/bookings.tsx
  • packages/features/bookings/lib/handleCancelBooking.ts
  • packages/app-store/_utils/getCalendar.ts

Each usage follows the consistent pattern: const app = await appStore[key as keyof typeof appStore];, maintaining type safety while properly handling the Promise resolution.

packages/features/bookings/lib/handleNewBooking.ts (1)

887-894: LGTM!

The async getCalendar call is properly awaited within the async lastAttendeeDeleteBooking function, and the null check before calling deleteEvent is correctly in place.

packages/core/EventManager.ts (1)

488-490: LGTM!

The async getCalendar call is properly awaited, and the optional chaining on deleteEvent correctly handles the case where calendar might be null.

packages/app-store/_utils/getCalendar.ts (1)

9-22: LGTM!

The conversion to async is properly implemented. The function correctly awaits the dynamic import from appStore and the return type accurately reflects the Promise-based contract.

packages/features/bookings/lib/handleCancelBooking.ts (4)

243-249: LGTM!

The async getCalendar call is properly awaited within the for...of loop, and the null check before operations is correctly in place.


264-271: LGTM!

Proper async handling with null check before calling updateEvent.


477-484: LGTM!

The refactor from forEach to for...of with proper await is correctly implemented, ensuring calendar deletions complete before proceeding.


589-596: LGTM!

The async dynamic import for the payment app is properly awaited, and the subsequent type checking guards are correctly in place.

packages/core/CalendarManager.ts (5)

47-56: LGTM!

The restructured code correctly awaits the calendar Promise from item.calendar, and the null check is properly handled.


141-146: LGTM!

Efficient parallel fetching of calendars using Promise.all with proper await.


233-234: LGTM!

The async getCalendar call is properly awaited in createEvent.


284-285: LGTM!

The async getCalendar call is properly awaited in updateEvent.


330-341: LGTM!

The deleteEvent function is properly converted to async with correct await on getCalendar and appropriate return type.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants