-
Notifications
You must be signed in to change notification settings - Fork 0
Async import of the appStore packages #10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: appstore-sync-refactor-base
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -6,13 +6,13 @@ import appStore from ".."; | |||||||||||||||||
|
|
||||||||||||||||||
| const log = logger.getChildLogger({ prefix: ["CalendarManager"] }); | ||||||||||||||||||
|
|
||||||||||||||||||
| export const getCalendar = (credential: CredentialPayload | null): Calendar | null => { | ||||||||||||||||||
| export const getCalendar = async (credential: CredentialPayload | null): Promise<Calendar | null> => { | ||||||||||||||||||
| if (!credential || !credential.key) return null; | ||||||||||||||||||
| let { type: calendarType } = credential; | ||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡
--- a/packages/app-store/_utils/getCalendar.ts
+++ b/packages/app-store/_utils/getCalendar.ts
@@ -9,7 +9,14 @@ export const getCalendar = async (credential: CredentialPayload | null): Promise
if (!credential || !credential.key) return null;
let { type: calendarType } = credential;
if (calendarType?.endsWith("_other_calendar")) {
calendarType = calendarType.split("_other_calendar")[0];
}
- const calendarApp = await appStore[calendarType.split("_").join("") as keyof typeof appStore];
+ // Wrap the dynamic import in try/catch so that a missing or corrupted app
+ // module does not abort callers that iterate over multiple credentials
+ // (e.g. getCachedResults, getConnectedCalendars). Consistent with the
+ // null-return pattern used below for unimplemented calendar types.
+ let calendarApp: Awaited<(typeof appStore)[keyof typeof appStore]> | undefined;
+ try {
+ calendarApp = await appStore[calendarType.split("_").join("") as keyof typeof appStore];
+ } catch (e) {
+ log.warn(`Failed to load calendar app for type ${calendarType}`, e);
+ return null;
+ }
if (!(calendarApp && "lib" in calendarApp && "CalendarService" in calendarApp.lib)) {
log.warn(`calendar of type ${calendarType} is not implemented`);
return null;🤖 Grapple PR auto-fix • minor • Review this diff before applying |
||||||||||||||||||
| if (calendarType?.endsWith("_other_calendar")) { | ||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 MINOR — Error handling (confidence: 93%) If a dynamic Evidence:
Agent: logic There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✅
Suggested change
🤖 Grapple PR auto-fix • minor • confidence: 93% |
||||||||||||||||||
| calendarType = calendarType.split("_other_calendar")[0]; | ||||||||||||||||||
| } | ||||||||||||||||||
| const calendarApp = appStore[calendarType.split("_").join("") as keyof typeof appStore]; | ||||||||||||||||||
| const calendarApp = await appStore[calendarType.split("_").join("") as keyof typeof appStore]; | ||||||||||||||||||
| if (!(calendarApp && "lib" in calendarApp && "CalendarService" in calendarApp.lib)) { | ||||||||||||||||||
| log.warn(`calendar of type ${calendarType} is not implemented`); | ||||||||||||||||||
| return null; | ||||||||||||||||||
|
|
||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,63 +1,33 @@ | ||
| // import * as example from "./_example"; | ||
| import * as applecalendar from "./applecalendar"; | ||
| import * as caldavcalendar from "./caldavcalendar"; | ||
| import * as closecom from "./closecom"; | ||
| import * as dailyvideo from "./dailyvideo"; | ||
| import * as exchange2013calendar from "./exchange2013calendar"; | ||
| import * as exchange2016calendar from "./exchange2016calendar"; | ||
| import * as exchangecalendar from "./exchangecalendar"; | ||
| import * as facetime from "./facetime"; | ||
| import * as giphy from "./giphy"; | ||
| import * as googlecalendar from "./googlecalendar"; | ||
| import * as googlevideo from "./googlevideo"; | ||
| import * as hubspot from "./hubspot"; | ||
| import * as huddle01video from "./huddle01video"; | ||
| import * as jitsivideo from "./jitsivideo"; | ||
| import * as larkcalendar from "./larkcalendar"; | ||
| import * as office365calendar from "./office365calendar"; | ||
| import * as office365video from "./office365video"; | ||
| import * as plausible from "./plausible"; | ||
| import * as salesforce from "./salesforce"; | ||
| import * as sendgrid from "./sendgrid"; | ||
| import * as stripepayment from "./stripepayment"; | ||
| import * as sylapsvideo from "./sylapsvideo"; | ||
| import * as tandemvideo from "./tandemvideo"; | ||
| import * as vital from "./vital"; | ||
| import * as wipemycalother from "./wipemycalother"; | ||
| import * as zapier from "./zapier"; | ||
| import * as zohocrm from "./zohocrm"; | ||
| import * as zoomvideo from "./zoomvideo"; | ||
|
|
||
| const appStore = { | ||
| // example, | ||
| applecalendar, | ||
| caldavcalendar, | ||
| closecom, | ||
| dailyvideo, | ||
| googlecalendar, | ||
| googlevideo, | ||
| hubspot, | ||
| huddle01video, | ||
| jitsivideo, | ||
| sylapsvideo, | ||
| larkcalendar, | ||
| office365calendar, | ||
| office365video, | ||
| plausible, | ||
| salesforce, | ||
| zohocrm, | ||
| sendgrid, | ||
| stripepayment, | ||
| tandemvideo, | ||
| vital, | ||
| zoomvideo, | ||
| wipemycalother, | ||
| giphy, | ||
| zapier, | ||
| exchange2013calendar, | ||
| exchange2016calendar, | ||
| exchangecalendar, | ||
| facetime, | ||
| // example: import("./example"), | ||
| applecalendar: import("./applecalendar"), | ||
| caldavcalendar: import("./caldavcalendar"), | ||
| closecom: import("./closecom"), | ||
| dailyvideo: import("./dailyvideo"), | ||
| googlecalendar: import("./googlecalendar"), | ||
| googlevideo: import("./googlevideo"), | ||
| hubspot: import("./hubspot"), | ||
| huddle01video: import("./huddle01video"), | ||
| jitsivideo: import("./jitsivideo"), | ||
| larkcalendar: import("./larkcalendar"), | ||
| office365calendar: import("./office365calendar"), | ||
| office365video: import("./office365video"), | ||
| plausible: import("./plausible"), | ||
| salesforce: import("./salesforce"), | ||
| zohocrm: import("./zohocrm"), | ||
| sendgrid: import("./sendgrid"), | ||
| stripepayment: import("./stripepayment"), | ||
| tandemvideo: import("./tandemvideo"), | ||
| vital: import("./vital"), | ||
| zoomvideo: import("./zoomvideo"), | ||
| wipemycalother: import("./wipemycalother"), | ||
| giphy: import("./giphy"), | ||
| zapier: import("./zapier"), | ||
| exchange2013calendar: import("./exchange2013calendar"), | ||
| exchange2016calendar: import("./exchange2016calendar"), | ||
| exchangecalendar: import("./exchangecalendar"), | ||
| facetime: import("./facetime"), | ||
| sylapsvideo: import("./sylapsvideo"), | ||
| }; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 MAJOR — No lazy loading benefit (confidence: 100%) The Evidence:
Agent: logic There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 MAJOR — No actual lazy loading benefit (confidence: 100%) The Evidence:
Agent: architecture There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 MAJOR — Missing app store entry (confidence: 100%) The Evidence:
Agent: architecture There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡
--- a/packages/app-store/index.ts
+++ b/packages/app-store/index.ts
@@ -1,31 +1,31 @@
const appStore = {
- // example: import("./example"),
- applecalendar: import("./applecalendar"),
- caldavcalendar: import("./caldavcalendar"),
- closecom: import("./closecom"),
- dailyvideo: import("./dailyvideo"),
- googlecalendar: import("./googlecalendar"),
- googlevideo: import("./googlevideo"),
- hubspot: import("./hubspot"),
- huddle01video: import("./huddle01video"),
- jitsivideo: import("./jitsivideo"),
- larkcalendar: import("./larkcalendar"),
- office365calendar: import("./office365calendar"),
- office365video: import("./office365video"),
- plausible: import("./plausible"),
- salesforce: import("./salesforce"),
- zohocrm: import("./zohocrm"),
- sendgrid: import("./sendgrid"),
- stripepayment: import("./stripepayment"),
- tandemvideo: import("./tandemvideo"),
- vital: import("./vital"),
- zoomvideo: import("./zoomvideo"),
- wipemycalother: import("./wipemycalother"),
- giphy: import("./giphy"),
- zapier: import("./zapier"),
- exchange2013calendar: import("./exchange2013calendar"),
- exchange2016calendar: import("./exchange2016calendar"),
- exchangecalendar: import("./exchangecalendar"),
- facetime: import("./facetime"),
- sylapsvideo: import("./sylapsvideo"),
+ // example: () => import("./example"),
+ applecalendar: () => import("./applecalendar"),
+ caldavcalendar: () => import("./caldavcalendar"),
+ closecom: () => import("./closecom"),
+ dailyvideo: () => import("./dailyvideo"),
+ googlecalendar: () => import("./googlecalendar"),
+ googlevideo: () => import("./googlevideo"),
+ hubspot: () => import("./hubspot"),
+ huddle01video: () => import("./huddle01video"),
+ jitsivideo: () => import("./jitsivideo"),
+ larkcalendar: () => import("./larkcalendar"),
+ office365calendar: () => import("./office365calendar"),
+ office365video: () => import("./office365video"),
+ plausible: () => import("./plausible"),
+ salesforce: () => import("./salesforce"),
+ zohocrm: () => import("./zohocrm"),
+ sendgrid: () => import("./sendgrid"),
+ stripepayment: () => import("./stripepayment"),
+ tandemvideo: () => import("./tandemvideo"),
+ vital: () => import("./vital"),
+ zoomvideo: () => import("./zoomvideo"),
+ wipemycalother: () => import("./wipemycalother"),
+ giphy: () => import("./giphy"),
+ zapier: () => import("./zapier"),
+ exchange2013calendar: () => import("./exchange2013calendar"),
+ exchange2016calendar: () => import("./exchange2016calendar"),
+ exchangecalendar: () => import("./exchangecalendar"),
+ facetime: () => import("./facetime"),
+ sylapsvideo: () => import("./sylapsvideo"),
};
export default appStore;🤖 Grapple PR auto-fix • major • Review this diff before applying There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡
--- a/packages/app-store/index.ts
+++ b/packages/app-store/index.ts
@@ -1,31 +1,31 @@
const appStore = {
// example: () => import("./example"),
- applecalendar: import("./applecalendar"),
- caldavcalendar: import("./caldavcalendar"),
- closecom: import("./closecom"),
- dailyvideo: import("./dailyvideo"),
- googlecalendar: import("./googlecalendar"),
- googlevideo: import("./googlevideo"),
- hubspot: import("./hubspot"),
- huddle01video: import("./huddle01video"),
- jitsivideo: import("./jitsivideo"),
- larkcalendar: import("./larkcalendar"),
- office365calendar: import("./office365calendar"),
- office365video: import("./office365video"),
- plausible: import("./plausible"),
- salesforce: import("./salesforce"),
- zohocrm: import("./zohocrm"),
- sendgrid: import("./sendgrid"),
- stripepayment: import("./stripepayment"),
- tandemvideo: import("./tandemvideo"),
- vital: import("./vital"),
- zoomvideo: import("./zoomvideo"),
- wipemycalother: import("./wipemycalother"),
- giphy: import("./giphy"),
- zapier: import("./zapier"),
- exchange2013calendar: import("./exchange2013calendar"),
- exchange2016calendar: import("./exchange2016calendar"),
- exchangecalendar: import("./exchangecalendar"),
- facetime: import("./facetime"),
- sylapsvideo: import("./sylapsvideo"),
+ applecalendar: () => import("./applecalendar"),
+ caldavcalendar: () => import("./caldavcalendar"),
+ closecom: () => import("./closecom"),
+ dailyvideo: () => import("./dailyvideo"),
+ googlecalendar: () => import("./googlecalendar"),
+ googlevideo: () => import("./googlevideo"),
+ hubspot: () => import("./hubspot"),
+ huddle01video: () => import("./huddle01video"),
+ jitsivideo: () => import("./jitsivideo"),
+ larkcalendar: () => import("./larkcalendar"),
+ office365calendar: () => import("./office365calendar"),
+ office365video: () => import("./office365video"),
+ plausible: () => import("./plausible"),
+ salesforce: () => import("./salesforce"),
+ zohocrm: () => import("./zohocrm"),
+ sendgrid: () => import("./sendgrid"),
+ stripepayment: () => import("./stripepayment"),
+ tandemvideo: () => import("./tandemvideo"),
+ vital: () => import("./vital"),
+ zoomvideo: () => import("./zoomvideo"),
+ wipemycalother: () => import("./wipemycalother"),
+ giphy: () => import("./giphy"),
+ zapier: () => import("./zapier"),
+ exchange2013calendar: () => import("./exchange2013calendar"),
+ exchange2016calendar: () => import("./exchange2016calendar"),
+ exchangecalendar: () => import("./exchangecalendar"),
+ facetime: () => import("./facetime"),
+ sylapsvideo: () => import("./sylapsvideo"),
};
export default appStore;🤖 Grapple PR auto-fix • major • Review this diff before applying There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡
--- a/packages/app-store/index.ts
+++ b/packages/app-store/index.ts
@@ -8,8 +8,8 @@
hubspot: import("./hubspot"),
huddle01video: import("./huddle01video"),
jitsivideo: import("./jitsivideo"),
+ sylapsvideo: import("./sylapsvideo"),
larkcalendar: import("./larkcalendar"),
office365calendar: import("./office365calendar"),
office365video: import("./office365video"),
plausible: import("./plausible"),
@@ -26,6 +26,5 @@
exchange2013calendar: import("./exchange2013calendar"),
exchange2016calendar: import("./exchange2016calendar"),
exchangecalendar: import("./exchangecalendar"),
facetime: import("./facetime"),
- sylapsvideo: import("./sylapsvideo"),
};🤖 Grapple PR auto-fix • major • Review this diff before applying There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 MAJOR — Lazy Loading Defeated (confidence: 100%) All dynamic Evidence:
Agent: architecture There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡
--- a/packages/app-store/index.ts
+++ b/packages/app-store/index.ts
@@ -1,31 +1,31 @@
const appStore = {
- // example: import("./example"),
- applecalendar: import("./applecalendar"),
- caldavcalendar: import("./caldavcalendar"),
- closecom: import("./closecom"),
- dailyvideo: import("./dailyvideo"),
- googlecalendar: import("./googlecalendar"),
- googlevideo: import("./googlevideo"),
- hubspot: import("./hubspot"),
- huddle01video: import("./huddle01video"),
- jitsivideo: import("./jitsivideo"),
- larkcalendar: import("./larkcalendar"),
- office365calendar: import("./office365calendar"),
- office365video: import("./office365video"),
- plausible: import("./plausible"),
- salesforce: import("./salesforce"),
- zohocrm: import("./zohocrm"),
- sendgrid: import("./sendgrid"),
- stripepayment: import("./stripepayment"),
- tandemvideo: import("./tandemvideo"),
- vital: import("./vital"),
- zoomvideo: import("./zoomvideo"),
- wipemycalother: import("./wipemycalother"),
- giphy: import("./giphy"),
- zapier: import("./zapier"),
- exchange2013calendar: import("./exchange2013calendar"),
- exchange2016calendar: import("./exchange2016calendar"),
- exchangecalendar: import("./exchangecalendar"),
- facetime: import("./facetime"),
- sylapsvideo: import("./sylapsvideo"),
+ // example: () => import("./example"),
+ applecalendar: () => import("./applecalendar"),
+ caldavcalendar: () => import("./caldavcalendar"),
+ closecom: () => import("./closecom"),
+ dailyvideo: () => import("./dailyvideo"),
+ googlecalendar: () => import("./googlecalendar"),
+ googlevideo: () => import("./googlevideo"),
+ hubspot: () => import("./hubspot"),
+ huddle01video: () => import("./huddle01video"),
+ jitsivideo: () => import("./jitsivideo"),
+ larkcalendar: () => import("./larkcalendar"),
+ office365calendar: () => import("./office365calendar"),
+ office365video: () => import("./office365video"),
+ plausible: () => import("./plausible"),
+ salesforce: () => import("./salesforce"),
+ zohocrm: () => import("./zohocrm"),
+ sendgrid: () => import("./sendgrid"),
+ stripepayment: () => import("./stripepayment"),
+ tandemvideo: () => import("./tandemvideo"),
+ vital: () => import("./vital"),
+ zoomvideo: () => import("./zoomvideo"),
+ wipemycalother: () => import("./wipemycalother"),
+ giphy: () => import("./giphy"),
+ zapier: () => import("./zapier"),
+ exchange2013calendar: () => import("./exchange2013calendar"),
+ exchange2016calendar: () => import("./exchange2016calendar"),
+ exchangecalendar: () => import("./exchangecalendar"),
+ facetime: () => import("./facetime"),
+ sylapsvideo: () => import("./sylapsvideo"),
};
export default appStore;🤖 Grapple PR auto-fix • major • Review this diff before applying There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 MAJOR — No lazy loading benefit (confidence: 99%) The dynamic import() expressions are invoked immediately as object property initializers when the module is loaded. This means all app store modules are still loaded eagerly at startup — the imports execute immediately and produce Promises that resolve with the already-loading modules. The stated performance goal of lazy/async loading is not achieved; instead, this adds Promise resolution overhead to every call site without any code-splitting benefit. Evidence:
Agent: logic There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 MINOR — Type safety (confidence: 95%) The type of Evidence:
Agent: logic There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡
--- a/packages/app-store/index.ts
+++ b/packages/app-store/index.ts
@@ -1,31 +1,31 @@
const appStore = {
- // example: import("./example"),
- applecalendar: import("./applecalendar"),
- caldavcalendar: import("./caldavcalendar"),
- closecom: import("./closecom"),
- dailyvideo: import("./dailyvideo"),
- googlecalendar: import("./googlecalendar"),
- googlevideo: import("./googlevideo"),
- hubspot: import("./hubspot"),
- huddle01video: import("./huddle01video"),
- jitsivideo: import("./jitsivideo"),
- larkcalendar: import("./larkcalendar"),
- office365calendar: import("./office365calendar"),
- office365video: import("./office365video"),
- plausible: import("./plausible"),
- salesforce: import("./salesforce"),
- zohocrm: import("./zohocrm"),
- sendgrid: import("./sendgrid"),
- stripepayment: import("./stripepayment"),
- tandemvideo: import("./tandemvideo"),
- vital: import("./vital"),
- zoomvideo: import("./zoomvideo"),
- wipemycalother: import("./wipemycalother"),
- giphy: import("./giphy"),
- zapier: import("./zapier"),
- exchange2013calendar: import("./exchange2013calendar"),
- exchange2016calendar: import("./exchange2016calendar"),
- exchangecalendar: import("./exchangecalendar"),
- facetime: import("./facetime"),
- sylapsvideo: import("./sylapsvideo"),
+ // example: () => import("./example"),
+ applecalendar: () => import("./applecalendar"),
+ caldavcalendar: () => import("./caldavcalendar"),
+ closecom: () => import("./closecom"),
+ dailyvideo: () => import("./dailyvideo"),
+ googlecalendar: () => import("./googlecalendar"),
+ googlevideo: () => import("./googlevideo"),
+ hubspot: () => import("./hubspot"),
+ huddle01video: () => import("./huddle01video"),
+ jitsivideo: () => import("./jitsivideo"),
+ larkcalendar: () => import("./larkcalendar"),
+ office365calendar: () => import("./office365calendar"),
+ office365video: () => import("./office365video"),
+ plausible: () => import("./plausible"),
+ salesforce: () => import("./salesforce"),
+ zohocrm: () => import("./zohocrm"),
+ sendgrid: () => import("./sendgrid"),
+ stripepayment: () => import("./stripepayment"),
+ tandemvideo: () => import("./tandemvideo"),
+ vital: () => import("./vital"),
+ zoomvideo: () => import("./zoomvideo"),
+ wipemycalother: () => import("./wipemycalother"),
+ giphy: () => import("./giphy"),
+ zapier: () => import("./zapier"),
+ exchange2013calendar: () => import("./exchange2013calendar"),
+ exchange2016calendar: () => import("./exchange2016calendar"),
+ exchangecalendar: () => import("./exchangecalendar"),
+ facetime: () => import("./facetime"),
+ sylapsvideo: () => import("./sylapsvideo"),
};
export default appStore;🤖 Grapple PR auto-fix • major • Review this diff before applying There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡
--- a/packages/app-store/index.ts
+++ b/packages/app-store/index.ts
@@ -1,33 +1,51 @@
const appStore = {
// example: import("./example"),
applecalendar: import("./applecalendar"),
caldavcalendar: import("./caldavcalendar"),
closecom: import("./closecom"),
dailyvideo: import("./dailyvideo"),
googlecalendar: import("./googlecalendar"),
googlevideo: import("./googlevideo"),
hubspot: import("./hubspot"),
huddle01video: import("./huddle01video"),
jitsivideo: import("./jitsivideo"),
larkcalendar: import("./larkcalendar"),
office365calendar: import("./office365calendar"),
office365video: import("./office365video"),
plausible: import("./plausible"),
salesforce: import("./salesforce"),
zohocrm: import("./zohocrm"),
sendgrid: import("./sendgrid"),
stripepayment: import("./stripepayment"),
tandemvideo: import("./tandemvideo"),
vital: import("./vital"),
zoomvideo: import("./zoomvideo"),
wipemycalother: import("./wipemycalother"),
giphy: import("./giphy"),
zapier: import("./zapier"),
exchange2013calendar: import("./exchange2013calendar"),
exchange2016calendar: import("./exchange2016calendar"),
exchangecalendar: import("./exchangecalendar"),
facetime: import("./facetime"),
sylapsvideo: import("./sylapsvideo"),
};
+export type AppStoreModules = typeof appStore;
+export type AppKeys = keyof AppStoreModules;
+
+/**
+ * Type-safe async accessor for appStore entries.
+ * Always use this instead of `appStore[key]` directly to ensure the Promise is
+ * awaited before use. Direct indexing yields a Promise object — accessing
+ * properties like `lib` on it will silently return undefined rather than
+ * throwing, because Promise objects are truthy and `'lib' in promise === false`.
+ *
+ * @example
+ * const app = await getAppStore("zoomvideo");
+ * if (app && "lib" in app) { ... }
+ */
+export async function getAppStore<T extends AppKeys>(name: T): Promise<Awaited<AppStoreModules[T]>> {
+ return appStore[name];
+}
+
export default appStore;🤖 Grapple PR auto-fix • minor • Review this diff before applying |
||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 CRITICAL — Unnecessary work (confidence: 100%) All dynamic import() calls are evaluated eagerly at module load time, not lazily. The entire point of dynamic imports for code-splitting is that they are called conditionally or on-demand. Placing all import() calls as top-level object property initializers means every app module is fetched immediately when this file is first imported — identical behavior to static imports, but with added Promise overhead on every access. No lazy loading or code-splitting benefit is achieved. Evidence:
Agent: performance There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡
--- a/packages/app-store/index.ts
+++ b/packages/app-store/index.ts
@@ -1,33 +1,33 @@
const appStore = {
- // example: import("./example"),
- applecalendar: import("./applecalendar"),
- caldavcalendar: import("./caldavcalendar"),
- closecom: import("./closecom"),
- dailyvideo: import("./dailyvideo"),
- googlecalendar: import("./googlecalendar"),
- googlevideo: import("./googlevideo"),
- hubspot: import("./hubspot"),
- huddle01video: import("./huddle01video"),
- jitsivideo: import("./jitsivideo"),
- larkcalendar: import("./larkcalendar"),
- office365calendar: import("./office365calendar"),
- office365video: import("./office365video"),
- plausible: import("./plausible"),
- salesforce: import("./salesforce"),
- zohocrm: import("./zohocrm"),
- sendgrid: import("./sendgrid"),
- stripepayment: import("./stripepayment"),
- tandemvideo: import("./tandemvideo"),
- vital: import("./vital"),
- zoomvideo: import("./zoomvideo"),
- wipemycalother: import("./wipemycalother"),
- giphy: import("./giphy"),
- zapier: import("./zapier"),
- exchange2013calendar: import("./exchange2013calendar"),
- exchange2016calendar: import("./exchange2016calendar"),
- exchangecalendar: import("./exchangecalendar"),
- facetime: import("./facetime"),
- sylapsvideo: import("./sylapsvideo"),
+ // example: () => import("./example"),
+ applecalendar: () => import("./applecalendar"),
+ caldavcalendar: () => import("./caldavcalendar"),
+ closecom: () => import("./closecom"),
+ dailyvideo: () => import("./dailyvideo"),
+ googlecalendar: () => import("./googlecalendar"),
+ googlevideo: () => import("./googlevideo"),
+ hubspot: () => import("./hubspot"),
+ huddle01video: () => import("./huddle01video"),
+ jitsivideo: () => import("./jitsivideo"),
+ larkcalendar: () => import("./larkcalendar"),
+ office365calendar: () => import("./office365calendar"),
+ office365video: () => import("./office365video"),
+ plausible: () => import("./plausible"),
+ salesforce: () => import("./salesforce"),
+ zohocrm: () => import("./zohocrm"),
+ sendgrid: () => import("./sendgrid"),
+ stripepayment: () => import("./stripepayment"),
+ tandemvideo: () => import("./tandemvideo"),
+ vital: () => import("./vital"),
+ zoomvideo: () => import("./zoomvideo"),
+ wipemycalother: () => import("./wipemycalother"),
+ giphy: () => import("./giphy"),
+ zapier: () => import("./zapier"),
+ exchange2013calendar: () => import("./exchange2013calendar"),
+ exchange2016calendar: () => import("./exchange2016calendar"),
+ exchangecalendar: () => import("./exchangecalendar"),
+ facetime: () => import("./facetime"),
+ sylapsvideo: () => import("./sylapsvideo"),
};
export default appStore;🤖 Grapple PR auto-fix • critical • Review this diff before applying |
||
| export default appStore; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -122,10 +122,10 @@ const Reschedule = async (bookingUid: string, cancellationReason: string) => { | |
| (ref) => !!credentialsMap.get(ref.type) | ||
| ); | ||
| try { | ||
| bookingRefsFiltered.forEach((bookingRef) => { | ||
| bookingRefsFiltered.forEach(async (bookingRef) => { | ||
| if (bookingRef.uid) { | ||
| if (bookingRef.type.endsWith("_calendar")) { | ||
| const calendar = getCalendar(credentialsMap.get(bookingRef.type)); | ||
| 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); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 CRITICAL — Concurrency issues (confidence: 100%) An async callback is passed to forEach(). forEach() does not await async callbacks — it fires each async function and discards the returned Promise. This means calendar.deleteEvent() and deleteMeeting() calls are fire-and-forget: errors are silently swallowed, the surrounding try/catch cannot catch rejections, and the function may return before deletions complete. Evidence:
Agent: performance There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡
--- a/packages/app-store/vital/lib/reschedule.ts
+++ b/packages/app-store/vital/lib/reschedule.ts
@@ -122,13 +122,15 @@ const Reschedule = async (bookingUid: string, cancellationReason: string) => {
(ref) => !!credentialsMap.get(ref.type)
);
try {
- bookingRefsFiltered.forEach(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);
+ await Promise.all(
+ bookingRefsFiltered.map(async (bookingRef) => {
+ if (bookingRef.uid) {
+ if (bookingRef.type.endsWith("_calendar")) {
+ const calendar = await getCalendar(credentialsMap.get(bookingRef.type));
+ await calendar?.deleteEvent(bookingRef.uid, builder.calendarEvent);
+ } else if (bookingRef.type.endsWith("_video")) {
+ await deleteMeeting(credentialsMap.get(bookingRef.type), bookingRef.uid);
+ }
}
- }
- });
+ })
+ );
} catch (error) {
if (error instanceof Error) {
logger.error(error.message);🤖 Grapple PR auto-fix • critical • Review this diff before applying |
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -122,10 +122,10 @@ const Reschedule = async (bookingUid: string, cancellationReason: string) => { | |
| (ref) => !!credentialsMap.get(ref.type) | ||
| ); | ||
| try { | ||
| bookingRefsFiltered.forEach((bookingRef) => { | ||
| bookingRefsFiltered.forEach(async (bookingRef) => { | ||
| if (bookingRef.uid) { | ||
| if (bookingRef.type.endsWith("_calendar")) { | ||
| const calendar = getCalendar(credentialsMap.get(bookingRef.type)); | ||
| 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); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 CRITICAL — Concurrency issues (confidence: 100%) Same async-in-forEach anti-pattern as vital/lib/reschedule.ts. Calendar delete operations are fire-and-forget with no error handling and no guarantee of completion before the function returns. Evidence:
Agent: performance There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡
--- a/packages/app-store/wipemycalother/lib/reschedule.ts
+++ b/packages/app-store/wipemycalother/lib/reschedule.ts
@@ -122,14 +122,16 @@ const Reschedule = async (bookingUid: string, cancellationReason: string) => {
(ref) => !!credentialsMap.get(ref.type)
);
try {
- bookingRefsFiltered.forEach(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);
+ 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) {
if (error instanceof Error) {
logger.error(error.message);🤖 Grapple PR auto-fix • critical • Review this diff before applying |
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -28,6 +28,7 @@ export const getCalendarCredentials = (credentials: Array<CredentialPayload>) => | |||||
| const calendar = getCalendar(credential); | ||||||
| return app.variant === "calendar" ? [{ integration: app, credential, calendar }] : []; | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 MAJOR — Unresolved Promise in return type (confidence: 100%) In Evidence:
Agent: logic There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡
--- a/packages/core/CalendarManager.ts
+++ b/packages/core/CalendarManager.ts
@@ -23,14 +23,17 @@ const log = logger.getChildLogger({ prefix: ["CalendarManager"] });
-export const getCalendarCredentials = (credentials: Array<CredentialPayload>) => {
- const calendarCredentials = getApps(credentials)
+export const getCalendarCredentials = async (credentials: Array<CredentialPayload>) => {
+ const calendarCredentials = await Promise.all(getApps(credentials)
.filter((app) => app.type.endsWith("_calendar"))
- .flatMap((app) => {
- const credentials = app.credentials.flatMap((credential) => {
- const calendar = getCalendar(credential);
- return app.variant === "calendar" ? [{ integration: app, credential, calendar }] : [];
- });
+ .flatMap((app) => {
+ return app.credentials
+ .filter(() => app.variant === "calendar")
+ .map(async (credential) => {
+ const calendar = await getCalendar(credential);
+ return { integration: app, credential, calendar };
+ });
+ }));
- return credentials.length ? credentials : [];
- });
-
return calendarCredentials;
};
@@ -38,7 +41,7 @@ export const getConnectedCalendars = async (
calendarCredentials: ReturnType<typeof getCalendarCredentials>,🤖 Grapple PR auto-fix • major • Review this diff before applying There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 MAJOR — Unresolved Promise stored in object (confidence: 100%) getCalendar() is now async and returns Promise, but in getCalendarCredentials() the .map() callback is not async and does not await the result. This means Evidence:
Agent: logic There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡
--- a/packages/core/CalendarManager.ts
+++ b/packages/core/CalendarManager.ts
@@ -26,7 +26,7 @@ export const getCalendarCredentials = (credentials: Array<CredentialPayload>) =>
const credentials = app.credentials.flatMap((credential) => {
const calendar = getCalendar(credential);
- return app.variant === "calendar" ? [{ integration: app, credential, calendar }] : [];
+ return app.variant === "calendar" ? [{ integration: app, credential, calendar: calendar as Promise<Calendar | null> }] : [];
});
return credentials.length ? credentials : [];
@@ -44,8 +44,8 @@ export const getConnectedCalendars = async (
const connectedCalendars = await Promise.all(
calendarCredentials.map(async (item) => {
try {
- const { integration, credential } = item;
- const calendar = await item.calendar;
+ const { integration, credential } = item;
+ const calendar = await item.calendar;
// Don't leak credentials to the client
const credentialId = credential.id;
if (!calendar) {
@@ -139,7 +139,7 @@ export const getCachedResults = async (
selectedCalendars: SelectedCalendar[]
): Promise<EventBusyDate[][]> => {
const calendarCredentials = withCredentials.filter((credential) => credential.type.endsWith("_calendar"));
- const calendars = calendarCredentials.map((credential) => getCalendar(credential));
+ const calendars = await Promise.all(calendarCredentials.map((credential) => getCalendar(credential)));
performance.mark("getBusyCalendarTimesStart");
const results = calendars.map(async (c, i) => {
/** Filter out nulls */
@@ -230,7 +230,7 @@ export const createEvent = async (
calEvent: CalendarEvent
): Promise<EventResult<NewCalendarEventType>> => {
const uid: string = getUid(calEvent);
- const calendar = getCalendar(credential);
+ const calendar = await getCalendar(credential);
let success = true;
let calError: string | undefined = undefined;
@@ -281,7 +281,7 @@ export const updateEvent = async (
externalCalendarId: string | null
): Promise<EventResult<NewCalendarEventType>> => {
const uid = getUid(calEvent);
- const calendar = getCalendar(credential);
+ const calendar = await getCalendar(credential);
let success = false;
let calError: string | undefined = undefined;
let calWarnings: string[] | undefined = [];
@@ -327,12 +327,12 @@ export const updateEvent = async (
};
};
-export const deleteEvent = (
+export const deleteEvent = async (
credential: CredentialPayload,
uid: string,
event: CalendarEvent
): Promise<unknown> => {
- const calendar = getCalendar(credential);
+ const calendar = await getCalendar(credential);
if (calendar) {
return calendar.deleteEvent(uid, event);
}🤖 Grapple PR auto-fix • major • Review this diff before applying |
||||||
| }); | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 MAJOR — API contract / hidden Promise in return type (confidence: 100%) The Evidence:
Agent: architecture There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡
--- a/packages/core/CalendarManager.ts
+++ b/packages/core/CalendarManager.ts
@@ -23,16 +23,16 @@ const log = logger.getChildLogger({ prefix: ["CalendarManager"] });
-export const getCalendarCredentials = (credentials: Array<CredentialPayload>) => {
- const calendarCredentials = getApps(credentials)
+export const getCalendarCredentials = async (credentials: Array<CredentialPayload>) => {
+ const calendarCredentials = await Promise.all(getApps(credentials)
.filter((app) => app.type.endsWith("_calendar"))
.flatMap((app) => {
- const credentials = app.credentials.flatMap((credential) => {
- const calendar = getCalendar(credential);
- return app.variant === "calendar" ? [{ integration: app, credential, calendar }] : [];
+ const credentials = app.credentials.flatMap((credential) => {
+ return app.variant === "calendar" ? [{ integration: app, credential }] : [];
});
return credentials.length ? credentials : [];
- });
+ })
+ .map(async (item) => ({
+ ...item,
+ calendar: await getCalendar(item.credential),
+ })));
return calendarCredentials;
};
@@ -43,8 +43,8 @@ export const getConnectedCalendars = async (
const connectedCalendars = await Promise.all(
calendarCredentials.map(async (item) => {
try {
- const { integration, credential } = item;
- const calendar = await item.calendar;
+ const { calendar, integration, credential } = item;
+
// Don't leak credentials to the client
const credentialId = credential.id;
if (!calendar) {
@@ -138,7 +138,7 @@ export const getCachedResults = async (
selectedCalendars: SelectedCalendar[]
): Promise<EventBusyDate[][]> => {
const calendarCredentials = withCredentials.filter((credential) => credential.type.endsWith("_calendar"));
- const calendars = await Promise.all(calendarCredentials.map((credential) => getCalendar(credential)));
+ const calendars = await Promise.all(calendarCredentials.map((credential) => getCalendar(credential)));
performance.mark("getBusyCalendarTimesStart");
const results = calendars.map(async (c, i) => {
/** Filter out nulls */
@@ -229,7 +229,7 @@ export const createEvent = async (
calEvent: CalendarEvent
): Promise<EventResult<NewCalendarEventType>> => {
const uid: string = getUid(calEvent);
- const calendar = await getCalendar(credential);
+ const calendar = await getCalendar(credential);
let success = true;
let calError: string | undefined = undefined;
@@ -281,7 +281,7 @@ export const updateEvent = async (
externalCalendarId: string | null
): Promise<EventResult<NewCalendarEventType>> => {
const uid = getUid(calEvent);
- const calendar = await getCalendar(credential);
+ const calendar = await getCalendar(credential);
let success = false;
let calError: string | undefined = undefined;
let calWarnings: string[] | undefined = [];
@@ -327,7 +327,7 @@ export const updateEvent = async (
-export const deleteEvent = async (
+export const deleteEvent = async (
credential: CredentialPayload,
uid: string,
event: CalendarEvent
): Promise<unknown> => {
- const calendar = await getCalendar(credential);
+ const calendar = await getCalendar(credential);
if (calendar) {
return calendar.deleteEvent(uid, event);
}🤖 Grapple PR auto-fix • major • Review this diff before applying |
||||||
|
|
||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 MAJOR — Unawaited Promise in Type Contract (confidence: 100%) In Evidence:
Agent: architecture There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡
--- a/packages/core/CalendarManager.ts
+++ b/packages/core/CalendarManager.ts
@@ -23,15 +23,18 @@ const log = logger.getChildLogger({ prefix: ["CalendarManager"] });
-export const getCalendarCredentials = (credentials: Array<CredentialPayload>) => {
- const calendarCredentials = getApps(credentials)
+export const getCalendarCredentials = async (credentials: Array<CredentialPayload>) => {
+ const calendarCredentials = await Promise.all(
+ getApps(credentials)
.filter((app) => app.type.endsWith("_calendar"))
- .flatMap((app) => {
- const credentials = app.credentials.flatMap((credential) => {
- const calendar = getCalendar(credential);
- return app.variant === "calendar" ? [{ integration: app, credential, calendar }] : [];
- });
-
+ .flatMap((app) =>
+ app.credentials.flatMap((credential) =>
+ app.variant === "calendar"
+ ? [getCalendar(credential).then((calendar) => ({ integration: app, credential, calendar }))]
+ : []
+ )
+ )
+ ).then((results) =>
+ results.filter((item): item is Exclude<typeof item, { calendar: null }> & { calendar: NonNullable<typeof item.calendar> } | typeof item =>
+ true
+ )
+ );
- return credentials.length ? credentials : [];
- });
-
return calendarCredentials;
};🤖 Grapple PR auto-fix • major • Review this diff before applying |
||||||
| return credentials.length ? credentials : []; | ||||||
| }); | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 INFO — Code patterns (confidence: 96%) getCalendarCredentials() returns an array with a Evidence:
Agent: style |
||||||
|
|
||||||
|
|
@@ -43,8 +44,8 @@ export const getConnectedCalendars = async ( | |||||
| const connectedCalendars = await Promise.all( | ||||||
| calendarCredentials.map(async (item) => { | ||||||
| try { | ||||||
| const { calendar, integration, credential } = item; | ||||||
|
|
||||||
| const { integration, credential } = item; | ||||||
| const calendar = await item.calendar; | ||||||
| // Don't leak credentials to the client | ||||||
| const credentialId = credential.id; | ||||||
| if (!calendar) { | ||||||
|
|
@@ -138,7 +139,7 @@ export const getCachedResults = async ( | |||||
| selectedCalendars: SelectedCalendar[] | ||||||
| ): Promise<EventBusyDate[][]> => { | ||||||
| const calendarCredentials = withCredentials.filter((credential) => credential.type.endsWith("_calendar")); | ||||||
| const calendars = calendarCredentials.map((credential) => getCalendar(credential)); | ||||||
| const calendars = await Promise.all(calendarCredentials.map((credential) => getCalendar(credential))); | ||||||
| performance.mark("getBusyCalendarTimesStart"); | ||||||
| const results = calendars.map(async (c, i) => { | ||||||
| /** Filter out nulls */ | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 MAJOR — Null/undefined guard after await (confidence: 94%) getCachedResults resolves calendars via Promise.all(map(getCalendar)), but getCalendar can return null. The subsequent Evidence:
Agent: logic There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✅
Suggested change
🤖 Grapple PR auto-fix • major • confidence: 94% |
||||||
|
|
@@ -229,7 +230,7 @@ export const createEvent = async ( | |||||
| calEvent: CalendarEvent | ||||||
| ): Promise<EventResult<NewCalendarEventType>> => { | ||||||
| const uid: string = getUid(calEvent); | ||||||
| const calendar = getCalendar(credential); | ||||||
| const calendar = await getCalendar(credential); | ||||||
| let success = true; | ||||||
| let calError: string | undefined = undefined; | ||||||
|
|
||||||
|
|
@@ -280,7 +281,7 @@ export const updateEvent = async ( | |||||
| externalCalendarId: string | null | ||||||
| ): Promise<EventResult<NewCalendarEventType>> => { | ||||||
| const uid = getUid(calEvent); | ||||||
| const calendar = getCalendar(credential); | ||||||
| const calendar = await getCalendar(credential); | ||||||
| let success = false; | ||||||
| let calError: string | undefined = undefined; | ||||||
| let calWarnings: string[] | undefined = []; | ||||||
|
|
@@ -326,12 +327,12 @@ export const updateEvent = async ( | |||||
| }; | ||||||
| }; | ||||||
|
|
||||||
| export const deleteEvent = ( | ||||||
| export const deleteEvent = async ( | ||||||
| credential: CredentialPayload, | ||||||
| uid: string, | ||||||
| event: CalendarEvent | ||||||
| ): Promise<unknown> => { | ||||||
| const calendar = getCalendar(credential); | ||||||
| const calendar = await getCalendar(credential); | ||||||
| if (calendar) { | ||||||
| return calendar.deleteEvent(uid, event); | ||||||
| } | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,21 +18,25 @@ const log = logger.getChildLogger({ prefix: ["[lib] videoClient"] }); | |
| const translator = short(); | ||
|
|
||
| // factory | ||
| const getVideoAdapters = (withCredentials: CredentialPayload[]): VideoApiAdapter[] => | ||
| withCredentials.reduce<VideoApiAdapter[]>((acc, cred) => { | ||
| const getVideoAdapters = async (withCredentials: CredentialPayload[]): Promise<VideoApiAdapter[]> => { | ||
| const videoAdapters: VideoApiAdapter[] = []; | ||
|
|
||
| for (const cred of withCredentials) { | ||
| const appName = cred.type.split("_").join(""); // Transform `zoom_video` to `zoomvideo`; | ||
| const app = appStore[appName as keyof typeof appStore]; | ||
| 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); | ||
| acc.push(videoAdapter); | ||
| return acc; | ||
| videoAdapters.push(videoAdapter); | ||
| } | ||
| return acc; | ||
| }, []); | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 MINOR — Sequential awaits where parallelism is possible (confidence: 100%) The refactored Evidence:
Agent: architecture There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡
--- a/packages/core/videoClient.ts
+++ b/packages/core/videoClient.ts
@@ -21,18 +21,16 @@ const translator = short();
// factory
const getVideoAdapters = async (withCredentials: CredentialPayload[]): Promise<VideoApiAdapter[]> => {
- const videoAdapters: VideoApiAdapter[] = [];
-
- for (const cred of withCredentials) {
+ const adapters = await Promise.all(
+ 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 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 videoAdapters;
+ return makeVideoApiAdapter(cred);
+ }
+ return null;
+ })
+ );
+ return adapters.filter((adapter): adapter is VideoApiAdapter => adapter !== null);
};🤖 Grapple PR auto-fix • minor • Review this diff before applying |
||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 MAJOR — Concurrency issues (confidence: 100%) getVideoAdapters() now uses a sequential for...of loop with await for each credential, serializing all appStore lookups. The previous reduce()-based implementation was synchronous; now N video credentials result in N sequential awaits on import Promises that are already resolved (since all imports fired at module load). While functionally correct, this adds unnecessary sequential microtask overhead when Promise.all would be both simpler and faster. Evidence:
Agent: performance There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡
--- a/packages/core/videoClient.ts
+++ b/packages/core/videoClient.ts
@@ -20,18 +20,15 @@ const translator = short();
// factory
const getVideoAdapters = async (withCredentials: CredentialPayload[]): Promise<VideoApiAdapter[]> => {
- const videoAdapters: VideoApiAdapter[] = [];
-
- for (const cred of withCredentials) {
+ const adapters = await Promise.all(
+ 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 videoAdapters;
+ if (app && "lib" in app && "VideoApiAdapter" in app.lib) {
+ const makeVideoApiAdapter = app.lib.VideoApiAdapter as VideoApiAdapterFactory;
+ return makeVideoApiAdapter(cred);
+ }
+ return null;
+ })
+ );
+ return adapters.filter((adapter): adapter is VideoApiAdapter => adapter !== null);
};🤖 Grapple PR auto-fix • major • Review this diff before applying |
||
| return videoAdapters; | ||
| }; | ||
|
|
||
| const getBusyVideoTimes = (withCredentials: CredentialPayload[]) => | ||
| Promise.all(getVideoAdapters(withCredentials).map((c) => c?.getAvailability())).then((results) => | ||
| const getBusyVideoTimes = async (withCredentials: CredentialPayload[]) => | ||
| Promise.all((await getVideoAdapters(withCredentials)).map((c) => c?.getAvailability())).then((results) => | ||
| results.reduce((acc, availability) => acc.concat(availability), [] as (EventBusyDate | undefined)[]) | ||
| ); | ||
|
|
||
|
|
@@ -45,7 +49,7 @@ const createMeeting = async (credential: CredentialWithAppName, calEvent: Calend | |
| ); | ||
| } | ||
|
|
||
| const videoAdapters = getVideoAdapters([credential]); | ||
| const videoAdapters = await getVideoAdapters([credential]); | ||
| const [firstVideoAdapter] = videoAdapters; | ||
| let createdMeeting; | ||
| let returnObject: { | ||
|
|
@@ -104,7 +108,7 @@ const updateMeeting = async ( | |
|
|
||
| let success = true; | ||
|
|
||
| const [firstVideoAdapter] = getVideoAdapters([credential]); | ||
| const [firstVideoAdapter] = await getVideoAdapters([credential]); | ||
| const updatedMeeting = | ||
| credential && bookingRef | ||
| ? await firstVideoAdapter?.updateMeeting(bookingRef, calEvent).catch(async (e) => { | ||
|
|
@@ -135,9 +139,9 @@ const updateMeeting = async ( | |
| }; | ||
| }; | ||
|
|
||
| const deleteMeeting = (credential: CredentialPayload, uid: string): Promise<unknown> => { | ||
| const deleteMeeting = async (credential: CredentialPayload, uid: string): Promise<unknown> => { | ||
| if (credential) { | ||
| const videoAdapter = getVideoAdapters([credential])[0]; | ||
| const videoAdapter = (await getVideoAdapters([credential]))[0]; | ||
| // There are certain video apps with no video adapter defined. e.g. riverby,whereby | ||
| if (videoAdapter) { | ||
| return videoAdapter.deleteMeeting(uid); | ||
|
|
@@ -155,7 +159,7 @@ const createMeetingWithCalVideo = async (calEvent: CalendarEvent) => { | |
| } catch (e) { | ||
| return; | ||
| } | ||
| const [videoAdapter] = getVideoAdapters([ | ||
| const [videoAdapter] = await getVideoAdapters([ | ||
| { | ||
| id: 0, | ||
| appId: "daily-video", | ||
|
|
@@ -178,7 +182,7 @@ const getRecordingsOfCalVideoByRoomName = async ( | |
| console.error("Error: Cal video provider is not installed."); | ||
| return; | ||
| } | ||
| const [videoAdapter] = getVideoAdapters([ | ||
| const [videoAdapter] = await getVideoAdapters([ | ||
| { | ||
| id: 0, | ||
| appId: "daily-video", | ||
|
|
@@ -199,7 +203,7 @@ const getDownloadLinkOfCalVideoByRecordingId = async (recordingId: string) => { | |
| console.error("Error: Cal video provider is not installed."); | ||
| return; | ||
| } | ||
| const [videoAdapter] = getVideoAdapters([ | ||
| const [videoAdapter] = await getVideoAdapters([ | ||
| { | ||
| id: 0, | ||
| appId: "daily-video", | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -240,7 +240,7 @@ async function handler(req: CustomRequest) { | |
| integrationsToDelete.push(deleteMeeting(credential, reference.uid)); | ||
| } | ||
| if (reference.type.includes("_calendar")) { | ||
| const calendar = getCalendar(credential); | ||
| const calendar = await getCalendar(credential); | ||
| if (calendar) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 MINOR — Inconsistent Async Pattern (confidence: 79%) The Evidence:
Agent: architecture There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡
--- a/packages/features/bookings/lib/handleCancelBooking.ts
+++ b/packages/features/bookings/lib/handleCancelBooking.ts
@@ -240,7 +240,7 @@ async function handler(req: CustomRequest) {
integrationsToDelete.push(deleteMeeting(credential, reference.uid));
}
if (reference.type.includes("_calendar")) {
- const calendar = getCalendar(credential);
+ const calendar = await getCalendar(credential);
if (calendar) {
integrationsToDelete.push(
calendar?.deleteEvent(reference.uid, evt, reference.externalCalendarId)
@@ -262,7 +262,7 @@ async function handler(req: CustomRequest) {
);
}
if (reference.type.includes("_calendar")) {
- const calendar = getCalendar(credential);
+ const calendar = await getCalendar(credential);
if (calendar) {
integrationsToDelete.push(
calendar?.updateEvent(reference.uid, updatedEvt, reference.externalCalendarId)
@@ -449,7 +449,7 @@ async function handler(req: CustomRequest) {
(credential) => credential.id === credentialId
);
if (calendarCredential) {
- const calendar = getCalendar(calendarCredential);
+ const calendar = await getCalendar(calendarCredential);
if (
bookingToDelete.eventType?.recurringEvent &&
bookingToDelete.recurringEventId &&
@@ -458,10 +458,10 @@ async function handler(req: CustomRequest) {
- bookingToDelete.user.credentials
- .filter((credential) => credential.type.endsWith("_calendar"))
- .forEach(async (credential) => {
- const calendar = getCalendar(credential);
+ const calendarCredentialsForRecurring = bookingToDelete.user.credentials.filter((credential) =>
+ credential.type.endsWith("_calendar")
+ );
+ for (const credential of calendarCredentialsForRecurring) {
+ const calendar = await getCalendar(credential);
for (const updBooking of updatedBookings) {
const bookingRef = updBooking.references.find((ref) => ref.type.includes("_calendar"));
if (bookingRef) {
@@ -471,7 +471,7 @@ async function handler(req: CustomRequest) {
}
}
- });
+ }
}
} else {
// For bookings made before the refactor we go through the old behaviour of running through each calendar credential
- bookingToDelete.user.credentials
- .filter((credential) => credential.type.endsWith("_calendar"))
- .forEach((credential) => {
- const calendar = getCalendar(credential);
- apiDeletes.push(calendar?.deleteEvent(uid, evt, externalCalendarId) as Promise<unknown>);
- });
+ const calendarCredentials = bookingToDelete.user.credentials.filter((credential) =>
+ credential.type.endsWith("_calendar")
+ );
+ for (const credential of calendarCredentials) {
+ const calendar = await getCalendar(credential);
+ apiDeletes.push(calendar?.deleteEvent(uid, evt, externalCalendarId) as Promise<unknown>);
+ }
}
}
@@ -586,7 +586,7 @@ async function handler(req: CustomRequest) {
// Posible to refactor TODO:
- const paymentApp = appStore[paymentAppCredential?.app?.dirName as keyof typeof appStore];
+ const paymentApp = await appStore[paymentAppCredential?.app?.dirName as keyof typeof appStore];
if (!(paymentApp && "lib" in paymentApp && "PaymentService" in paymentApp.lib)) {
console.warn(`payment App service of type ${paymentApp} is not implemented`);
return null;🤖 Grapple PR auto-fix • minor • Review this diff before applying |
||
| integrationsToDelete.push( | ||
| calendar?.deleteEvent(reference.uid, evt, reference.externalCalendarId) | ||
|
|
@@ -262,7 +262,7 @@ async function handler(req: CustomRequest) { | |
| ); | ||
| } | ||
| if (reference.type.includes("_calendar")) { | ||
| const calendar = getCalendar(credential); | ||
| const calendar = await getCalendar(credential); | ||
| if (calendar) { | ||
| integrationsToDelete.push( | ||
| calendar?.updateEvent(reference.uid, updatedEvt, reference.externalCalendarId) | ||
|
|
@@ -449,7 +449,7 @@ async function handler(req: CustomRequest) { | |
| (credential) => credential.id === credentialId | ||
| ); | ||
| if (calendarCredential) { | ||
| const calendar = getCalendar(calendarCredential); | ||
| const calendar = await getCalendar(calendarCredential); | ||
| if ( | ||
| bookingToDelete.eventType?.recurringEvent && | ||
| bookingToDelete.recurringEventId && | ||
|
|
@@ -458,7 +458,7 @@ async function handler(req: CustomRequest) { | |
| bookingToDelete.user.credentials | ||
| .filter((credential) => credential.type.endsWith("_calendar")) | ||
| .forEach(async (credential) => { | ||
| const calendar = getCalendar(credential); | ||
| const calendar = await getCalendar(credential); | ||
| for (const updBooking of updatedBookings) { | ||
| const bookingRef = updBooking.references.find((ref) => ref.type.includes("_calendar")); | ||
| if (bookingRef) { | ||
|
|
@@ -474,12 +474,13 @@ async function handler(req: CustomRequest) { | |
| } | ||
| } else { | ||
| // For bookings made before the refactor we go through the old behaviour of running through each calendar credential | ||
| bookingToDelete.user.credentials | ||
| .filter((credential) => credential.type.endsWith("_calendar")) | ||
| .forEach((credential) => { | ||
| const calendar = getCalendar(credential); | ||
| apiDeletes.push(calendar?.deleteEvent(uid, evt, externalCalendarId) as Promise<unknown>); | ||
| }); | ||
| const calendarCredentials = bookingToDelete.user.credentials.filter((credential) => | ||
| credential.type.endsWith("_calendar") | ||
| ); | ||
| for (const credential of calendarCredentials) { | ||
| const calendar = await getCalendar(credential); | ||
| apiDeletes.push(calendar?.deleteEvent(uid, evt, externalCalendarId) as Promise<unknown>); | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -585,7 +586,7 @@ async function handler(req: CustomRequest) { | |
| } | ||
|
|
||
| // Posible to refactor TODO: | ||
| const paymentApp = appStore[paymentAppCredential?.app?.dirName as keyof typeof appStore]; | ||
| const paymentApp = await appStore[paymentAppCredential?.app?.dirName as keyof typeof appStore]; | ||
| if (!(paymentApp && "lib" in paymentApp && "PaymentService" in paymentApp.lib)) { | ||
| console.warn(`payment App service of type ${paymentApp} is not implemented`); | ||
| return null; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,7 +13,7 @@ const deletePayment = async ( | |
| } | null; | ||
| } | ||
| ): Promise<boolean> => { | ||
| const paymentApp = appStore[paymentAppCredentials?.app?.dirName as keyof typeof appStore]; | ||
| const paymentApp = await appStore[paymentAppCredentials?.app?.dirName as keyof typeof appStore]; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 MINOR — Error Handling Gap (confidence: 93%) The Evidence:
Agent: architecture There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡
--- a/packages/lib/payment/deletePayment.ts
+++ b/packages/lib/payment/deletePayment.ts
@@ -13,7 +13,15 @@ const deletePayment = async (
} | null;
}
): Promise<boolean> => {
- const paymentApp = await appStore[paymentAppCredentials?.app?.dirName as keyof typeof appStore];
+ // Dynamic import may reject if the module is missing or has a syntax error;
+ // return false to signal failure without leaving the caller in an inconsistent state.
+ let paymentApp;
+ try {
+ paymentApp = await appStore[paymentAppCredentials?.app?.dirName as keyof typeof appStore];
+ } catch (e) {
+ console.warn(
+ `Payment app module for "${paymentAppCredentials?.app?.dirName}" could not be loaded:`,
+ e
+ );
+ return false;
+ }
if (!(paymentApp && "lib" in paymentApp && "PaymentService" in paymentApp.lib)) {
console.warn(`payment App service of type ${paymentApp} is not implemented`);
return false;🤖 Grapple PR auto-fix • minor • Review this diff before applying |
||
| if (!(paymentApp && "lib" in paymentApp && "PaymentService" in paymentApp.lib)) { | ||
| console.warn(`payment App service of type ${paymentApp} is not implemented`); | ||
| return false; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 MINOR — Error handling for dynamic imports (confidence: 93%)
If a dynamic
import()in the appStore fails (e.g., module not found for a corrupted or removed app), theawaiton line 11 will throw an unhandled rejection. SincegetCalendar()is called in many places including loops, a single failed import could crash the entire operation. Consider wrapping the await in a try/catch that returns null on failure, consistent with the existing null-return pattern.Evidence:
const calendarApp = await appStore[calendarType.split('_').join('') as keyof typeof appStore]Agent: architecture