From 37d6ea2a26175f3f909340b0c9049a89a745716e Mon Sep 17 00:00:00 2001 From: SandipBajracharya Date: Fri, 6 Feb 2026 17:17:31 +0545 Subject: [PATCH 1/2] chore(OUT-3058): add latest @assembly/node-sdk --- package.json | 2 +- pnpm-lock.yaml | 47 ++++++++++------------- src/errors/BaseServerError.ts | 7 ++++ src/features/sync/lib/MapFiles.service.ts | 8 ++-- src/features/sync/lib/Sync.service.ts | 10 ++--- src/lib/copilot/CopilotAPI.ts | 5 +-- 6 files changed, 41 insertions(+), 38 deletions(-) diff --git a/package.json b/package.json index 39d52ef..2409bdd 100644 --- a/package.json +++ b/package.json @@ -19,13 +19,13 @@ "supabase:dev": "supabase start --ignore-health-check" }, "dependencies": { + "@assembly-js/node-sdk": "^3.19.1", "@sentry/nextjs": "^10", "@trigger.dev/sdk": "4.0.6", "bottleneck": "^2.19.5", "camelcase-keys": "^10.0.1", "clsx": "^2.1.1", "copilot-design-system": "^2.2.6", - "copilot-node-sdk": "^3.16.0", "dayjs": "^1.11.19", "dotenv": "^17.2.1", "drizzle-orm": "^0.44.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 28b07c7..d2918b0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@assembly-js/node-sdk': + specifier: ^3.19.1 + version: 3.19.1(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@sentry/nextjs': specifier: ^10 version: 10.25.0(@opentelemetry/context-async-hooks@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(next@15.5.9(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(webpack@5.103.0(esbuild@0.25.9)) @@ -26,9 +29,6 @@ importers: copilot-design-system: specifier: ^2.2.6 version: 2.2.6(@types/react-dom@19.1.7(@types/react@19.1.11))(@types/react@19.1.11)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.9.2) - copilot-node-sdk: - specifier: ^3.16.0 - version: 3.16.0(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) dayjs: specifier: ^1.11.19 version: 1.11.19 @@ -175,6 +175,9 @@ packages: '@apm-js-collab/tracing-hooks@0.3.1': resolution: {integrity: sha512-Vu1CbmPURlN5fTboVuKMoJjbO5qcq9fA5YXpskx3dXe/zTBvjODFoerw+69rVBlRLrJpwPqSDqEuJDEKIrTldw==} + '@assembly-js/node-sdk@3.19.1': + resolution: {integrity: sha512-8td1ks3Oj3aPmFGUXdoTC28fEH3t5keyDt4vTewfOBSZW3sW0Rxw7LP9oP4clw29KIeLQtM5BTpsP3I5VM0edw==} + '@babel/code-frame@7.27.1': resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} @@ -2191,9 +2194,6 @@ packages: resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==} engines: {node: '>=16'} - caniuse-lite@1.0.30001737: - resolution: {integrity: sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw==} - caniuse-lite@1.0.30001756: resolution: {integrity: sha512-4HnCNKbMLkLdhJz3TToeVWHSnfJvPaq6vu/eRP0Ahub/07n484XHhBF5AJoSGHdVrS8tKFauUQz8Bp9P7LVx7A==} @@ -2305,9 +2305,6 @@ packages: react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - copilot-node-sdk@3.16.0: - resolution: {integrity: sha512-wqqeJuW+uge39nC1/+cqMWSSgCCNThtCR+JykPYFB2PTj0d0tfROCXofwtqZZccUUzk/xXXkJF53QfweVxmMLg==} - copy-anything@4.0.5: resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==} engines: {node: '>=18'} @@ -4059,6 +4056,20 @@ snapshots: transitivePeerDependencies: - supports-color + '@assembly-js/node-sdk@3.19.1(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + isomorphic-fetch: 3.0.0 + next: 14.2.32(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + transitivePeerDependencies: + - '@babel/core' + - '@opentelemetry/api' + - '@playwright/test' + - babel-plugin-macros + - encoding + - react + - react-dom + - sass + '@babel/code-frame@7.27.1': dependencies: '@babel/helper-validator-identifier': 7.27.1 @@ -6087,8 +6098,6 @@ snapshots: camelcase@8.0.0: {} - caniuse-lite@1.0.30001737: {} - caniuse-lite@1.0.30001756: {} chalk@5.6.0: {} @@ -6207,20 +6216,6 @@ snapshots: - supports-color - typescript - copilot-node-sdk@3.16.0(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): - dependencies: - isomorphic-fetch: 3.0.0 - next: 14.2.32(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - transitivePeerDependencies: - - '@babel/core' - - '@opentelemetry/api' - - '@playwright/test' - - babel-plugin-macros - - encoding - - react - - react-dom - - sass - copy-anything@4.0.5: dependencies: is-what: 5.5.0 @@ -7014,7 +7009,7 @@ snapshots: '@next/env': 14.2.32 '@swc/helpers': 0.5.5 busboy: 1.6.0 - caniuse-lite: 1.0.30001737 + caniuse-lite: 1.0.30001756 graceful-fs: 4.2.11 postcss: 8.4.31 react: 19.1.0 diff --git a/src/errors/BaseServerError.ts b/src/errors/BaseServerError.ts index fd6460a..e14fbf4 100644 --- a/src/errors/BaseServerError.ts +++ b/src/errors/BaseServerError.ts @@ -2,6 +2,13 @@ export interface StatusableError extends Error { status: number } +export interface CopilotApiError extends Error { + status: number + body?: { + message: string + } +} + /** * Base error class for Server components / actions / API routes */ diff --git a/src/features/sync/lib/MapFiles.service.ts b/src/features/sync/lib/MapFiles.service.ts index a43dcb8..3842f80 100644 --- a/src/features/sync/lib/MapFiles.service.ts +++ b/src/features/sync/lib/MapFiles.service.ts @@ -1,6 +1,5 @@ import { and, asc, eq, isNotNull, isNull, or, sql } from 'drizzle-orm' import httpStatus from 'http-status' -import { ApiError } from 'node_modules/copilot-node-sdk/dist/codegen/api' import z from 'zod' import db from '@/db' import { ObjectType } from '@/db/constants' @@ -17,6 +16,7 @@ import { fileFolderSync, } from '@/db/schema/fileFolderSync.schema' import APIError from '@/errors/APIError' +import type { StatusableError } from '@/errors/BaseServerError' import type { DropboxFileListFolderResultEntries, MapList, @@ -488,8 +488,10 @@ export class MapFilesService extends AuthenticatedDropboxService { logger.info('MapFilesService#formatChannelMap :: Formatted channel map', formattedChannelInfo) return formattedChannelInfo - } catch (error: unknown) { - if (error instanceof ApiError && error.status === httpStatus.BAD_REQUEST) { + } catch (err: unknown) { + const error = err as StatusableError // typecasting as Assembly doesn't export an error class + + if (error && error.status === httpStatus.BAD_REQUEST) { console.info('Soft delete channel map and make it inactive') await this.deleteChannelMapById(channelMap.id) } diff --git a/src/features/sync/lib/Sync.service.ts b/src/features/sync/lib/Sync.service.ts index d7c4888..80c3a9c 100644 --- a/src/features/sync/lib/Sync.service.ts +++ b/src/features/sync/lib/Sync.service.ts @@ -1,13 +1,13 @@ import { and, eq } from 'drizzle-orm' import { DropboxResponseError } from 'dropbox' import httpStatus from 'http-status' -import { ApiError as CopilotApiError } from 'node_modules/copilot-node-sdk/dist/codegen/api' import fetch from 'node-fetch' import z from 'zod' import { ObjectType, type ObjectTypeValue } from '@/db/constants' import type { DropboxConnectionTokens } from '@/db/schema/dropboxConnections.schema' import { type FileSyncCreateType, fileFolderSync } from '@/db/schema/fileFolderSync.schema' import APIError from '@/errors/APIError' +import type { CopilotApiError } from '@/errors/BaseServerError' import { DBX_URL_PATH } from '@/features/sync/constant' import { MapFilesService } from '@/features/sync/lib/MapFiles.service' import type { @@ -185,11 +185,11 @@ export class SyncService extends AuthenticatedDropboxService { `SyncService#createAndUploadFileToAssembly. Channel ID: ${assemblyChannelId}. File upload success. Type: ${tempFileType}. File ID: ${filePayload.assemblyFileId}. Dbx fileId: ${lastItem ? entry.id : null}`, ) await this.mapFilesService.updateChannelMapSyncedFilesCount(channelSyncId) - } catch (error: unknown) { + } catch (err: unknown) { + const error = err as CopilotApiError // typecasting as Assembly doesn't export an error class if ( - error instanceof CopilotApiError && - error.status === 400 && - error.body.message === 'Folder already exists' + error.status === httpStatus.BAD_REQUEST && + error.body?.message === 'Folder already exists' ) { console.info({ message: error.body.message, path: itemPath }) await this.handleFolderCreatedCase( diff --git a/src/lib/copilot/CopilotAPI.ts b/src/lib/copilot/CopilotAPI.ts index b43b31d..6009475 100644 --- a/src/lib/copilot/CopilotAPI.ts +++ b/src/lib/copilot/CopilotAPI.ts @@ -1,7 +1,6 @@ import 'server-only' -import type { CopilotAPI as SDK } from 'copilot-node-sdk' -import { copilotApi } from 'copilot-node-sdk' +import { assemblyApi, type AssemblyAPI as SDK } from '@assembly-js/node-sdk' import fetch from 'node-fetch' import z from 'zod' import env from '@/config/server.env' @@ -52,7 +51,7 @@ export class CopilotAPI { private readonly token: string, readonly customApiKey?: string, ) { - this.copilot = copilotApi({ + this.copilot = assemblyApi({ apiKey: customApiKey ?? env.COPILOT_API_KEY, token, }) From 280afdefac1d2df31cd16bcc95d4acfba6861871 Mon Sep 17 00:00:00 2001 From: SandipBajracharya Date: Fri, 6 Feb 2026 17:44:59 +0545 Subject: [PATCH 2/2] refactor(OUT-3058): implement type guard to check assembly api error --- src/errors/BaseServerError.ts | 7 ------- src/features/sync/lib/MapFiles.service.ts | 8 +++----- src/features/sync/lib/Sync.service.ts | 8 ++++---- src/utils/assemblyError.ts | 20 ++++++++++++++++++++ 4 files changed, 27 insertions(+), 16 deletions(-) create mode 100644 src/utils/assemblyError.ts diff --git a/src/errors/BaseServerError.ts b/src/errors/BaseServerError.ts index e14fbf4..fd6460a 100644 --- a/src/errors/BaseServerError.ts +++ b/src/errors/BaseServerError.ts @@ -2,13 +2,6 @@ export interface StatusableError extends Error { status: number } -export interface CopilotApiError extends Error { - status: number - body?: { - message: string - } -} - /** * Base error class for Server components / actions / API routes */ diff --git a/src/features/sync/lib/MapFiles.service.ts b/src/features/sync/lib/MapFiles.service.ts index 3842f80..50b0694 100644 --- a/src/features/sync/lib/MapFiles.service.ts +++ b/src/features/sync/lib/MapFiles.service.ts @@ -16,7 +16,6 @@ import { fileFolderSync, } from '@/db/schema/fileFolderSync.schema' import APIError from '@/errors/APIError' -import type { StatusableError } from '@/errors/BaseServerError' import type { DropboxFileListFolderResultEntries, MapList, @@ -30,6 +29,7 @@ import { } from '@/lib/copilot/types' import AuthenticatedDropboxService from '@/lib/dropbox/AuthenticatedDropbox.service' import logger from '@/lib/logger' +import { isAssemblyApiError } from '@/utils/assemblyError' export class MapFilesService extends AuthenticatedDropboxService { async getSingleFileMap(where: WhereClause): Promise { @@ -488,10 +488,8 @@ export class MapFilesService extends AuthenticatedDropboxService { logger.info('MapFilesService#formatChannelMap :: Formatted channel map', formattedChannelInfo) return formattedChannelInfo - } catch (err: unknown) { - const error = err as StatusableError // typecasting as Assembly doesn't export an error class - - if (error && error.status === httpStatus.BAD_REQUEST) { + } catch (error: unknown) { + if (isAssemblyApiError(error) && error.status === httpStatus.BAD_REQUEST) { console.info('Soft delete channel map and make it inactive') await this.deleteChannelMapById(channelMap.id) } diff --git a/src/features/sync/lib/Sync.service.ts b/src/features/sync/lib/Sync.service.ts index 80c3a9c..9672843 100644 --- a/src/features/sync/lib/Sync.service.ts +++ b/src/features/sync/lib/Sync.service.ts @@ -7,7 +7,6 @@ import { ObjectType, type ObjectTypeValue } from '@/db/constants' import type { DropboxConnectionTokens } from '@/db/schema/dropboxConnections.schema' import { type FileSyncCreateType, fileFolderSync } from '@/db/schema/fileFolderSync.schema' import APIError from '@/errors/APIError' -import type { CopilotApiError } from '@/errors/BaseServerError' import { DBX_URL_PATH } from '@/features/sync/constant' import { MapFilesService } from '@/features/sync/lib/MapFiles.service' import type { @@ -23,6 +22,7 @@ import type { CopilotFileRetrieve } from '@/lib/copilot/types' import AuthenticatedDropboxService from '@/lib/dropbox/AuthenticatedDropbox.service' import logger from '@/lib/logger' import { bidirectionalMasterSync } from '@/trigger/processFileSync' +import { isAssemblyApiError } from '@/utils/assemblyError' import { appendDateTimeToFilePath, buildPathArray, getPathFromRoot } from '@/utils/filePath' export class SyncService extends AuthenticatedDropboxService { @@ -185,11 +185,11 @@ export class SyncService extends AuthenticatedDropboxService { `SyncService#createAndUploadFileToAssembly. Channel ID: ${assemblyChannelId}. File upload success. Type: ${tempFileType}. File ID: ${filePayload.assemblyFileId}. Dbx fileId: ${lastItem ? entry.id : null}`, ) await this.mapFilesService.updateChannelMapSyncedFilesCount(channelSyncId) - } catch (err: unknown) { - const error = err as CopilotApiError // typecasting as Assembly doesn't export an error class + } catch (error: unknown) { if ( + isAssemblyApiError(error) && error.status === httpStatus.BAD_REQUEST && - error.body?.message === 'Folder already exists' + error.body.message === 'Folder already exists' ) { console.info({ message: error.body.message, path: itemPath }) await this.handleFolderCreatedCase( diff --git a/src/utils/assemblyError.ts b/src/utils/assemblyError.ts new file mode 100644 index 0000000..ab7f86e --- /dev/null +++ b/src/utils/assemblyError.ts @@ -0,0 +1,20 @@ +export interface AssemblyApiError extends Error { + status: number + body: { + message: string + } +} + +export function isAssemblyApiError(err: unknown): err is AssemblyApiError { + if (!(err instanceof Error)) return false + + // @ts-expect-error + const error = err as Record + + if (typeof error.status !== 'number') return false + if (!error.body || typeof error.body !== 'object') return false + + const body = error.body as Record + + return typeof body.message === 'string' +}