From 2504cab06cbba73ee6796fdec00eb677f1423fa7 Mon Sep 17 00:00:00 2001 From: SandipBajracharya Date: Fri, 10 Apr 2026 16:06:27 +0545 Subject: [PATCH] fix(OUT-3578): add afterIfAvailable utility for non-request contexts Next.js after() throws when called outside a request scope (Trigger.dev, CLI scripts). Add afterIfAvailable() that tries after() and falls back to direct execution. Use it in getQBPortalConnection's notification path which runs in both Vercel serverless and Trigger.dev contexts. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/app/api/core/utils/afterIfAvailable.ts | 21 +++++++++++++++++++++ src/app/api/quickbooks/auth/auth.service.ts | 7 ++++++- 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 src/app/api/core/utils/afterIfAvailable.ts diff --git a/src/app/api/core/utils/afterIfAvailable.ts b/src/app/api/core/utils/afterIfAvailable.ts new file mode 100644 index 0000000..e434f6d --- /dev/null +++ b/src/app/api/core/utils/afterIfAvailable.ts @@ -0,0 +1,21 @@ +import { after } from 'next/server' + +/** + * Runs the callback via Next.js `after()` if inside a request scope, + * otherwise executes it directly. This allows shared service code to + * work in both Vercel serverless (request context) and Trigger.dev / + * CLI (no request context) environments. + * + * `after()` is a pure registration call — the only errors it can throw + * are scope/context errors. Real callback errors are handled separately + * via `.catch()` in the fallback path. + */ +export function afterIfAvailable(callback: () => Promise): void { + try { + after(callback) + } catch { + void callback().catch((err) => { + console.error('[afterIfAvailable] callback failed:', err) + }) + } +} diff --git a/src/app/api/quickbooks/auth/auth.service.ts b/src/app/api/quickbooks/auth/auth.service.ts index 48eecc0..3a40713 100644 --- a/src/app/api/quickbooks/auth/auth.service.ts +++ b/src/app/api/quickbooks/auth/auth.service.ts @@ -31,6 +31,7 @@ import IntuitAPI, { IntuitAPITokensType } from '@/utils/intuitAPI' import dayjs from 'dayjs' import { and, eq, SQL } from 'drizzle-orm' import httpStatus from 'http-status' +import { afterIfAvailable } from '@/app/api/core/utils/afterIfAvailable' import { after } from 'next/server' export class AuthService extends BaseService { @@ -344,7 +345,11 @@ export class AuthService extends BaseService { await tokenService.turnOffSync(intuitRealmId) // send notification to IU - after(async () => { + afterIfAvailable(async () => { + console.info( + 'AuthService#handleConnectionError | running after() .. | Sending notification to IU', + ) + const notificationService = new NotificationService(this.user) await notificationService.sendNotificationToIU( intiatedBy,