From 4368e550755cb4f88607282d4f4acf1892b7408f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Mon, 24 Feb 2025 17:02:49 +0100 Subject: [PATCH 01/11] refactor(core): Migrate all errors in cli package to new hierarchy --- packages/cli/src/active-workflow-manager.ts | 4 +- .../collaboration/collaboration.service.ts | 4 +- packages/cli/src/commands/audit.ts | 4 +- packages/cli/src/commands/base-command.ts | 4 +- packages/cli/src/commands/execute-batch.ts | 4 +- packages/cli/src/commands/execute.ts | 8 ++-- .../cli/src/commands/export/credentials.ts | 4 +- packages/cli/src/commands/export/workflow.ts | 4 +- .../cli/src/commands/import/credentials.ts | 10 ++--- packages/cli/src/commands/import/workflow.ts | 12 +++--- packages/cli/src/commands/ldap/reset.ts | 16 +++---- packages/cli/src/commands/webhook.ts | 6 +-- packages/cli/src/config/index.ts | 4 +- packages/cli/src/config/utils.ts | 4 +- .../oauth/abstract-oauth.controller.ts | 8 ++-- packages/cli/src/credentials-helper.ts | 8 ++-- .../src/credentials/credentials.service.ts | 4 +- packages/cli/src/databases/config.ts | 4 +- packages/cli/src/databases/dsl/table.ts | 4 +- .../1700571993961-AddGlobalAdminRole.ts | 4 +- .../common/1714133768519-CreateProject.ts | 4 +- .../repositories/execution.repository.ts | 8 ++-- .../databases/subscribers/user-subscriber.ts | 6 +-- .../src/databases/utils/migration-helpers.ts | 10 ++--- .../cli/src/decorators/controller.registry.ts | 4 +- packages/cli/src/decorators/on-shutdown.ts | 6 +-- .../source-control-export.service.ee.ts | 20 +++++---- .../source-control-git.service.ee.ts | 42 +++++++++---------- .../source-control-helper.ee.ts | 4 +- .../source-control-import.service.ee.ts | 8 ++-- .../source-control-preferences.service.ee.ts | 12 +++--- .../source-control.service.ee.ts | 10 ++--- .../cli/src/executions/execution.service.ts | 8 ++-- .../parse-range-query.middleware.ts | 4 +- packages/cli/src/external-hooks.ts | 6 +-- .../external-secrets-manager.ee.ts | 4 +- .../providers/infisical.ts | 8 ++-- packages/cli/src/ldap.ee/ldap.service.ee.ts | 8 ++-- .../cli/src/load-nodes-and-credentials.ts | 6 +-- .../list-query/dtos/base.filter.dto.ts | 6 +-- .../list-query/dtos/base.select.dto.ts | 4 +- .../list-query/dtos/pagination.dto.ts | 6 +-- .../src/middlewares/list-query/pagination.ts | 4 +- .../cli/src/middlewares/list-query/sort-by.ts | 4 +- packages/cli/src/node-types.ts | 6 +-- .../cli/src/permissions.ee/check-access.ts | 4 +- packages/cli/src/push/websocket.push.ts | 4 +- packages/cli/src/scaling/job-processor.ts | 8 ++-- packages/cli/src/scaling/scaling.service.ts | 8 ++-- .../cli/src/services/cache/cache.service.ts | 6 +-- .../src/services/cache/redis.cache-manager.ts | 4 +- .../services/community-packages.service.ts | 20 ++++----- .../services/credentials-tester.service.ts | 4 +- .../dynamic-node-parameters.service.ts | 8 ++-- packages/cli/src/services/role.service.ts | 4 +- packages/cli/src/services/user.service.ts | 4 +- .../src/services/workflow-loader.service.ts | 4 +- packages/cli/src/shutdown/shutdown.service.ts | 12 +++--- .../cli/src/sso.ee/saml/saml.service.ee.ts | 6 +-- .../task-broker/task-broker-ws-server.ts | 4 +- .../task-broker/task-broker.service.ts | 13 ++---- packages/cli/src/wait-tracker.ts | 8 ++-- packages/cli/src/webhooks/webhook.service.ts | 4 +- .../src/workflow-execute-additional-data.ts | 6 +-- .../cli/src/workflows/workflow.service.ee.ts | 6 +-- .../cli/src/workflows/workflows.controller.ts | 12 ++---- 66 files changed, 231 insertions(+), 246 deletions(-) diff --git a/packages/cli/src/active-workflow-manager.ts b/packages/cli/src/active-workflow-manager.ts index 16500e635577c..305ad170c76ce 100644 --- a/packages/cli/src/active-workflow-manager.ts +++ b/packages/cli/src/active-workflow-manager.ts @@ -30,7 +30,7 @@ import { Workflow, WorkflowActivationError, WebhookPathTakenError, - ApplicationError, + UnexpectedError, } from 'n8n-workflow'; import { ActivationErrorsService } from '@/activation-errors.service'; @@ -238,7 +238,7 @@ export class ActiveWorkflowManager { }); if (workflowData === null) { - throw new ApplicationError('Could not find workflow', { extra: { workflowId } }); + throw new UnexpectedError('Could not find workflow', { extra: { workflowId } }); } const workflow = new Workflow({ diff --git a/packages/cli/src/collaboration/collaboration.service.ts b/packages/cli/src/collaboration/collaboration.service.ts index 9b2f3dec8ed65..34575de4cd7b0 100644 --- a/packages/cli/src/collaboration/collaboration.service.ts +++ b/packages/cli/src/collaboration/collaboration.service.ts @@ -2,7 +2,7 @@ import type { PushPayload } from '@n8n/api-types'; import { Service } from '@n8n/di'; import { ErrorReporter } from 'n8n-core'; import type { Workflow } from 'n8n-workflow'; -import { ApplicationError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; import { CollaborationState } from '@/collaboration/collaboration.state'; import type { User } from '@/databases/entities/user'; @@ -34,7 +34,7 @@ export class CollaborationService { await this.handleUserMessage(event.userId, event.msg); } catch (error) { this.errorReporter.error( - new ApplicationError('Error handling CollaborationService push message', { + new UnexpectedError('Error handling CollaborationService push message', { extra: { msg: event.msg, userId: event.userId, diff --git a/packages/cli/src/commands/audit.ts b/packages/cli/src/commands/audit.ts index 1bd76bc4ed54d..03069ac9d9a76 100644 --- a/packages/cli/src/commands/audit.ts +++ b/packages/cli/src/commands/audit.ts @@ -1,7 +1,7 @@ import { SecurityConfig } from '@n8n/config'; import { Container } from '@n8n/di'; import { Flags } from '@oclif/core'; -import { ApplicationError } from 'n8n-workflow'; +import { UserError } from 'n8n-workflow'; import { RISK_CATEGORIES } from '@/security-audit/constants'; import { SecurityAuditService } from '@/security-audit/security-audit.service'; @@ -48,7 +48,7 @@ export class SecurityAudit extends BaseCommand { const hint = `Valid categories are: ${RISK_CATEGORIES.join(', ')}`; - throw new ApplicationError([message, hint].join('. ')); + throw new UserError([message, hint].join('. ')); } const result = await Container.get(SecurityAuditService).run( diff --git a/packages/cli/src/commands/base-command.ts b/packages/cli/src/commands/base-command.ts index d9bc8dd29626e..d9e3356ac85d2 100644 --- a/packages/cli/src/commands/base-command.ts +++ b/packages/cli/src/commands/base-command.ts @@ -10,7 +10,7 @@ import { DataDeduplicationService, ErrorReporter, } from 'n8n-core'; -import { ApplicationError, ensureError, sleep } from 'n8n-workflow'; +import { ensureError, sleep, UserError } from 'n8n-workflow'; import type { AbstractServer } from '@/abstract-server'; import config from '@/config'; @@ -151,7 +151,7 @@ export abstract class BaseCommand extends Command { if (!isSelected && !isAvailable) return; if (isSelected && !isAvailable) { - throw new ApplicationError( + throw new UserError( 'External storage selected but unavailable. Please make external storage available by adding "s3" to `N8N_AVAILABLE_BINARY_DATA_MODES`.', ); } diff --git a/packages/cli/src/commands/execute-batch.ts b/packages/cli/src/commands/execute-batch.ts index 3116186025f1d..9d3b31f846e7f 100644 --- a/packages/cli/src/commands/execute-batch.ts +++ b/packages/cli/src/commands/execute-batch.ts @@ -5,7 +5,7 @@ import fs from 'fs'; import { diff } from 'json-diff'; import pick from 'lodash/pick'; import type { IRun, ITaskData, IWorkflowBase, IWorkflowExecutionDataProcess } from 'n8n-workflow'; -import { ApplicationError, jsonParse } from 'n8n-workflow'; +import { jsonParse, UnexpectedError } from 'n8n-workflow'; import os from 'os'; import { sep } from 'path'; @@ -472,7 +472,7 @@ export class ExecuteBatch extends BaseCommand { this.updateStatus(); } } else { - throw new ApplicationError('Wrong execution status - cannot proceed'); + throw new UnexpectedError('Wrong execution status - cannot proceed'); } }); } diff --git a/packages/cli/src/commands/execute.ts b/packages/cli/src/commands/execute.ts index 786b91f7fc55a..122169c901659 100644 --- a/packages/cli/src/commands/execute.ts +++ b/packages/cli/src/commands/execute.ts @@ -1,7 +1,7 @@ import { Container } from '@n8n/di'; import { Flags } from '@oclif/core'; import type { IWorkflowBase, IWorkflowExecutionDataProcess } from 'n8n-workflow'; -import { ApplicationError, ExecutionBaseError } from 'n8n-workflow'; +import { ExecutionBaseError, UnexpectedError, UserError } from 'n8n-workflow'; import { ActiveExecutions } from '@/active-executions'; import { WorkflowRepository } from '@/databases/repositories/workflow.repository'; @@ -44,7 +44,7 @@ export class Execute extends BaseCommand { } if (flags.file) { - throw new ApplicationError( + throw new UserError( 'The --file flag is no longer supported. Please first import the workflow and then execute it using the --id flag.', { level: 'warning' }, ); @@ -64,7 +64,7 @@ export class Execute extends BaseCommand { } if (!workflowData) { - throw new ApplicationError('Failed to retrieve workflow data for requested workflow'); + throw new UnexpectedError('Failed to retrieve workflow data for requested workflow'); } if (!isWorkflowIdValid(workflowId)) { @@ -87,7 +87,7 @@ export class Execute extends BaseCommand { const data = await activeExecutions.getPostExecutePromise(executionId); if (data === undefined) { - throw new ApplicationError('Workflow did not return any data'); + throw new UnexpectedError('Workflow did not return any data'); } if (data.data.resultData.error) { diff --git a/packages/cli/src/commands/export/credentials.ts b/packages/cli/src/commands/export/credentials.ts index ad0b5784c14ab..9ab38daf0f1c5 100644 --- a/packages/cli/src/commands/export/credentials.ts +++ b/packages/cli/src/commands/export/credentials.ts @@ -2,7 +2,7 @@ import { Container } from '@n8n/di'; import { Flags } from '@oclif/core'; import fs from 'fs'; import { Credentials } from 'n8n-core'; -import { ApplicationError } from 'n8n-workflow'; +import { UserError } from 'n8n-workflow'; import path from 'path'; import { CredentialsRepository } from '@/databases/repositories/credentials.repository'; @@ -123,7 +123,7 @@ export class ExportCredentialsCommand extends BaseCommand { } if (credentials.length === 0) { - throw new ApplicationError('No credentials found with specified filters'); + throw new UserError('No credentials found with specified filters'); } if (flags.separate) { diff --git a/packages/cli/src/commands/export/workflow.ts b/packages/cli/src/commands/export/workflow.ts index 3fa0f1e0468ce..15e6bcd2e4058 100644 --- a/packages/cli/src/commands/export/workflow.ts +++ b/packages/cli/src/commands/export/workflow.ts @@ -1,7 +1,7 @@ import { Container } from '@n8n/di'; import { Flags } from '@oclif/core'; import fs from 'fs'; -import { ApplicationError } from 'n8n-workflow'; +import { UserError } from 'n8n-workflow'; import path from 'path'; import { WorkflowRepository } from '@/databases/repositories/workflow.repository'; @@ -107,7 +107,7 @@ export class ExportWorkflowsCommand extends BaseCommand { }); if (workflows.length === 0) { - throw new ApplicationError('No workflows found with specified filters'); + throw new UserError('No workflows found with specified filters'); } if (flags.separate) { diff --git a/packages/cli/src/commands/import/credentials.ts b/packages/cli/src/commands/import/credentials.ts index aae6c7d23e941..0108a994fd48a 100644 --- a/packages/cli/src/commands/import/credentials.ts +++ b/packages/cli/src/commands/import/credentials.ts @@ -6,7 +6,7 @@ import glob from 'fast-glob'; import fs from 'fs'; import { Cipher } from 'n8n-core'; import type { ICredentialsEncrypted } from 'n8n-workflow'; -import { ApplicationError, jsonParse } from 'n8n-workflow'; +import { jsonParse, UserError } from 'n8n-workflow'; import { UM_FIX_INSTRUCTION } from '@/constants'; import { CredentialsEntity } from '@/databases/entities/credentials-entity'; @@ -66,7 +66,7 @@ export class ImportCredentialsCommand extends BaseCommand { } if (flags.projectId && flags.userId) { - throw new ApplicationError( + throw new UserError( 'You cannot use `--userId` and `--projectId` together. Use one or the other.', ); } @@ -81,7 +81,7 @@ export class ImportCredentialsCommand extends BaseCommand { const result = await this.checkRelations(credentials, flags.projectId, flags.userId); if (!result.success) { - throw new ApplicationError(result.message); + throw new UserError(result.message); } for (const credential of credentials) { @@ -202,7 +202,7 @@ export class ImportCredentialsCommand extends BaseCommand { ); if (!Array.isArray(credentialsUnchecked)) { - throw new ApplicationError( + throw new UserError( 'File does not seem to contain credentials. Make sure the credentials are contained in an array.', ); } @@ -252,7 +252,7 @@ export class ImportCredentialsCommand extends BaseCommand { if (!userId) { const owner = await this.transactionManager.findOneBy(User, { role: 'global:owner' }); if (!owner) { - throw new ApplicationError(`Failed to find owner. ${UM_FIX_INSTRUCTION}`); + throw new UserError(`Failed to find owner. ${UM_FIX_INSTRUCTION}`); } userId = owner.id; } diff --git a/packages/cli/src/commands/import/workflow.ts b/packages/cli/src/commands/import/workflow.ts index 6d33df6c022ce..0c6292c93b4aa 100644 --- a/packages/cli/src/commands/import/workflow.ts +++ b/packages/cli/src/commands/import/workflow.ts @@ -3,7 +3,7 @@ import { Flags } from '@oclif/core'; import glob from 'fast-glob'; import fs from 'fs'; import type { IWorkflowBase, WorkflowId } from 'n8n-workflow'; -import { ApplicationError, jsonParse } from 'n8n-workflow'; +import { jsonParse, UserError } from 'n8n-workflow'; import { UM_FIX_INSTRUCTION } from '@/constants'; import type { WorkflowEntity } from '@/databases/entities/workflow-entity'; @@ -19,7 +19,7 @@ import { BaseCommand } from '../base-command'; function assertHasWorkflowsToImport(workflows: unknown): asserts workflows is IWorkflowToImport[] { if (!Array.isArray(workflows)) { - throw new ApplicationError( + throw new UserError( 'File does not seem to contain workflows. Make sure the workflows are contained in an array.', ); } @@ -30,7 +30,7 @@ function assertHasWorkflowsToImport(workflows: unknown): asserts workflows is IW !Object.prototype.hasOwnProperty.call(workflow, 'nodes') || !Object.prototype.hasOwnProperty.call(workflow, 'connections') ) { - throw new ApplicationError('File does not seem to contain valid workflows.'); + throw new UserError('File does not seem to contain valid workflows.'); } } } @@ -81,7 +81,7 @@ export class ImportWorkflowsCommand extends BaseCommand { } if (flags.projectId && flags.userId) { - throw new ApplicationError( + throw new UserError( 'You cannot use `--userId` and `--projectId` together. Use one or the other.', ); } @@ -93,7 +93,7 @@ export class ImportWorkflowsCommand extends BaseCommand { const result = await this.checkRelations(workflows, flags.projectId, flags.userId); if (!result.success) { - throw new ApplicationError(result.message); + throw new UserError(result.message); } this.logger.info(`Importing ${workflows.length} workflows...`); @@ -220,7 +220,7 @@ export class ImportWorkflowsCommand extends BaseCommand { if (!userId) { const owner = await Container.get(UserRepository).findOneBy({ role: 'global:owner' }); if (!owner) { - throw new ApplicationError(`Failed to find owner. ${UM_FIX_INSTRUCTION}`); + throw new UserError(`Failed to find owner. ${UM_FIX_INSTRUCTION}`); } userId = owner.id; } diff --git a/packages/cli/src/commands/ldap/reset.ts b/packages/cli/src/commands/ldap/reset.ts index 8d3eb8da94522..c38781f6d66a8 100644 --- a/packages/cli/src/commands/ldap/reset.ts +++ b/packages/cli/src/commands/ldap/reset.ts @@ -2,7 +2,7 @@ import { Container } from '@n8n/di'; // eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import import { In } from '@n8n/typeorm'; import { Flags } from '@oclif/core'; -import { ApplicationError } from 'n8n-workflow'; +import { ApplicationError, UserError } from 'n8n-workflow'; import { UM_FIX_INSTRUCTION } from '@/constants'; import { CredentialsService } from '@/credentials/credentials.service'; @@ -56,7 +56,7 @@ export class Reset extends BaseCommand { Number(!!flags.deleteWorkflowsAndCredentials); if (numberOfOptions !== 1) { - throw new ApplicationError(wrongFlagsError); + throw new UserError(wrongFlagsError); } const owner = await this.getOwner(); @@ -71,13 +71,13 @@ export class Reset extends BaseCommand { // Migrate all workflows and credentials to another project. if (flags.projectId ?? flags.userId) { if (flags.userId && ldapIdentities.some((i) => i.userId === flags.userId)) { - throw new ApplicationError( + throw new UserError( `Can't migrate workflows and credentials to the user with the ID ${flags.userId}. That user was created via LDAP and will be deleted as well.`, ); } if (flags.projectId && personalProjectIds.includes(flags.projectId)) { - throw new ApplicationError( + throw new UserError( `Can't migrate workflows and credentials to the project with the ID ${flags.projectId}. That project is a personal project belonging to a user that was created via LDAP and will be deleted as well.`, ); } @@ -132,7 +132,7 @@ export class Reset extends BaseCommand { const project = await Container.get(ProjectRepository).findOneBy({ id: projectId }); if (project === null) { - throw new ApplicationError(`Could not find the project with the ID ${projectId}.`); + throw new UserError(`Could not find the project with the ID ${projectId}.`); } return project; @@ -142,7 +142,7 @@ export class Reset extends BaseCommand { const project = await Container.get(ProjectRepository).getPersonalProjectForUser(userId); if (project === null) { - throw new ApplicationError( + throw new UserError( `Could not find the user with the ID ${userId} or their personalProject.`, ); } @@ -150,7 +150,7 @@ export class Reset extends BaseCommand { return project; } - throw new ApplicationError(wrongFlagsError); + throw new UserError(wrongFlagsError); } async catch(error: Error): Promise { @@ -161,7 +161,7 @@ export class Reset extends BaseCommand { private async getOwner() { const owner = await Container.get(UserRepository).findOneBy({ role: 'global:owner' }); if (!owner) { - throw new ApplicationError(`Failed to find owner. ${UM_FIX_INSTRUCTION}`); + throw new UserError(`Failed to find owner. ${UM_FIX_INSTRUCTION}`); } return owner; diff --git a/packages/cli/src/commands/webhook.ts b/packages/cli/src/commands/webhook.ts index fd1e961b59d94..570db352fea3f 100644 --- a/packages/cli/src/commands/webhook.ts +++ b/packages/cli/src/commands/webhook.ts @@ -1,6 +1,6 @@ import { Container } from '@n8n/di'; import { Flags } from '@oclif/core'; -import { ApplicationError } from 'n8n-workflow'; +import { UserError } from 'n8n-workflow'; import { ActiveExecutions } from '@/active-executions'; import config from '@/config'; @@ -84,9 +84,7 @@ export class Webhook extends BaseCommand { async run() { if (this.globalConfig.multiMainSetup.enabled) { - throw new ApplicationError( - 'Webhook process cannot be started when multi-main setup is enabled.', - ); + throw new UserError('Webhook process cannot be started when multi-main setup is enabled.'); } const { ScalingService } = await import('@/scaling/scaling.service'); diff --git a/packages/cli/src/config/index.ts b/packages/cli/src/config/index.ts index d11fa795db7a1..3d32ce99316cd 100644 --- a/packages/cli/src/config/index.ts +++ b/packages/cli/src/config/index.ts @@ -5,7 +5,7 @@ import { flatten } from 'flat'; import { readFileSync } from 'fs'; import merge from 'lodash/merge'; import { Logger } from 'n8n-core'; -import { ApplicationError, setGlobalState } from 'n8n-workflow'; +import { setGlobalState, UserError } from 'n8n-workflow'; import assert from 'node:assert'; import colors from 'picocolors'; @@ -84,7 +84,7 @@ if (!inE2ETests && !inTest) { } catch (error) { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (error.code === 'ENOENT') { - throw new ApplicationError('File not found', { extra: { fileName } }); + throw new UserError('File not found', { extra: { fileName } }); } throw error; } diff --git a/packages/cli/src/config/utils.ts b/packages/cli/src/config/utils.ts index ad2c8300c3004..decc1be5e845f 100644 --- a/packages/cli/src/config/utils.ts +++ b/packages/cli/src/config/utils.ts @@ -1,10 +1,10 @@ import type { SchemaObj } from 'convict'; -import { ApplicationError } from 'n8n-workflow'; +import { UserError } from 'n8n-workflow'; import { NotStringArrayError } from '@/errors/not-string-array.error'; export const ensureStringArray = (values: string[], { env }: SchemaObj) => { - if (!env) throw new ApplicationError('Missing env', { extra: { env } }); + if (!env) throw new UserError('Missing env', { extra: { env } }); if (!Array.isArray(values)) throw new NotStringArrayError(env); diff --git a/packages/cli/src/controllers/oauth/abstract-oauth.controller.ts b/packages/cli/src/controllers/oauth/abstract-oauth.controller.ts index 2964437b4991e..ed23b050f999b 100644 --- a/packages/cli/src/controllers/oauth/abstract-oauth.controller.ts +++ b/packages/cli/src/controllers/oauth/abstract-oauth.controller.ts @@ -4,7 +4,7 @@ import Csrf from 'csrf'; import type { Response } from 'express'; import { Credentials, Logger } from 'n8n-core'; import type { ICredentialDataDecryptedObject, IWorkflowExecuteAdditionalData } from 'n8n-workflow'; -import { jsonParse, ApplicationError } from 'n8n-workflow'; +import { jsonParse, UnexpectedError } from 'n8n-workflow'; import { RESPONSE_ERROR_MESSAGES, Time } from '@/constants'; import { CredentialsHelper } from '@/credentials-helper'; @@ -171,7 +171,7 @@ export abstract class AbstractOAuthController { }); if (typeof decoded.cid !== 'string' || typeof decoded.token !== 'string') { - throw new ApplicationError(errorMessage); + throw new UnexpectedError(errorMessage); } if (decoded.userId !== req.user?.id) { @@ -201,7 +201,7 @@ export abstract class AbstractOAuthController { const state = this.decodeCsrfState(encodedState, req); const credential = await this.getCredentialWithoutUser(state.cid); if (!credential) { - throw new ApplicationError('OAuth callback failed because of insufficient permissions'); + throw new UnexpectedError('OAuth callback failed because of insufficient permissions'); } const additionalData = await this.getAdditionalData(); @@ -216,7 +216,7 @@ export abstract class AbstractOAuthController { ); if (!this.verifyCsrfState(decryptedDataOriginal, state)) { - throw new ApplicationError('The OAuth callback state is invalid!'); + throw new UnexpectedError('The OAuth callback state is invalid!'); } return [credential, decryptedDataOriginal, oauthCredentials]; diff --git a/packages/cli/src/credentials-helper.ts b/packages/cli/src/credentials-helper.ts index 2f9e6bfbd3118..a4aaaae25a92b 100644 --- a/packages/cli/src/credentials-helper.ts +++ b/packages/cli/src/credentials-helper.ts @@ -26,7 +26,7 @@ import type { IExecuteData, IDataObject, } from 'n8n-workflow'; -import { ICredentialsHelper, NodeHelpers, Workflow, ApplicationError } from 'n8n-workflow'; +import { ICredentialsHelper, NodeHelpers, Workflow, UnexpectedError } from 'n8n-workflow'; import { CredentialTypes } from '@/credential-types'; import { CredentialsOverwrites } from '@/credentials-overwrites'; @@ -65,7 +65,7 @@ const mockNodeTypes: INodeTypes = { }, getByNameAndVersion(nodeType: string, version?: number): INodeType { if (!mockNodesData[nodeType]) { - throw new ApplicationError(RESPONSE_ERROR_MESSAGES.NO_NODE, { + throw new UnexpectedError(RESPONSE_ERROR_MESSAGES.NO_NODE, { tags: { nodeType }, }); } @@ -245,7 +245,7 @@ export class CredentialsHelper extends ICredentialsHelper { type: string, ): Promise { if (!nodeCredential.id) { - throw new ApplicationError('Found credential with no ID.', { + throw new UnexpectedError('Found credential with no ID.', { extra: { credentialName: nodeCredential.name }, tags: { credentialType: type }, }); @@ -276,7 +276,7 @@ export class CredentialsHelper extends ICredentialsHelper { const credentialTypeData = this.credentialTypes.getByName(type); if (credentialTypeData === undefined) { - throw new ApplicationError('Unknown credential type', { tags: { credentialType: type } }); + throw new UnexpectedError('Unknown credential type', { tags: { credentialType: type } }); } if (credentialTypeData.extends === undefined) { diff --git a/packages/cli/src/credentials/credentials.service.ts b/packages/cli/src/credentials/credentials.service.ts index 8564a6528c0b1..bed039279ae1d 100644 --- a/packages/cli/src/credentials/credentials.service.ts +++ b/packages/cli/src/credentials/credentials.service.ts @@ -15,7 +15,7 @@ import type { ICredentialType, INodeProperties, } from 'n8n-workflow'; -import { ApplicationError, CREDENTIAL_EMPTY_VALUE, deepCopy, NodeHelpers } from 'n8n-workflow'; +import { CREDENTIAL_EMPTY_VALUE, deepCopy, NodeHelpers, UnexpectedError } from 'n8n-workflow'; import { CREDENTIAL_BLANKING_VALUE } from '@/constants'; import { CredentialTypes } from '@/credential-types'; @@ -405,7 +405,7 @@ export class CredentialsService { // Safe guard in case the personal project does not exist for whatever reason. if (project === null) { - throw new ApplicationError('No personal project found'); + throw new UnexpectedError('No personal project found'); } const newSharedCredential = this.sharedCredentialsRepository.create({ diff --git a/packages/cli/src/databases/config.ts b/packages/cli/src/databases/config.ts index 45348e0079a5d..46398df5caf99 100644 --- a/packages/cli/src/databases/config.ts +++ b/packages/cli/src/databases/config.ts @@ -6,7 +6,7 @@ import type { PostgresConnectionOptions } from '@n8n/typeorm/driver/postgres/Pos import type { SqliteConnectionOptions } from '@n8n/typeorm/driver/sqlite/SqliteConnectionOptions'; import type { SqlitePooledConnectionOptions } from '@n8n/typeorm/driver/sqlite-pooled/SqlitePooledConnectionOptions'; import { InstanceSettings } from 'n8n-core'; -import { ApplicationError } from 'n8n-workflow'; +import { UserError } from 'n8n-workflow'; import path from 'path'; import type { TlsOptions } from 'tls'; @@ -129,7 +129,7 @@ export function getConnectionOptions(): DataSourceOptions { case 'mysqldb': return getMysqlConnectionOptions(dbType); default: - throw new ApplicationError('Database type currently not supported', { extra: { dbType } }); + throw new UserError('Database type currently not supported', { extra: { dbType } }); } } diff --git a/packages/cli/src/databases/dsl/table.ts b/packages/cli/src/databases/dsl/table.ts index f598b674d7a88..76df89a33cc36 100644 --- a/packages/cli/src/databases/dsl/table.ts +++ b/packages/cli/src/databases/dsl/table.ts @@ -1,6 +1,6 @@ import type { TableForeignKeyOptions, TableIndexOptions, QueryRunner } from '@n8n/typeorm'; import { Table, TableColumn, TableForeignKey } from '@n8n/typeorm'; -import { ApplicationError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; import LazyPromise from 'p-lazy'; import { Column } from './column'; @@ -176,7 +176,7 @@ class ModifyNotNull extends TableOperation { async execute(queryRunner: QueryRunner) { const { tableName, prefix, columnName, isNullable } = this; const table = await queryRunner.getTable(`${prefix}${tableName}`); - if (!table) throw new ApplicationError('No table found', { extra: { tableName } }); + if (!table) throw new UnexpectedError('No table found', { extra: { tableName } }); const oldColumn = table.findColumnByName(columnName)!; const newColumn = oldColumn.clone(); newColumn.isNullable = isNullable; diff --git a/packages/cli/src/databases/migrations/common/1700571993961-AddGlobalAdminRole.ts b/packages/cli/src/databases/migrations/common/1700571993961-AddGlobalAdminRole.ts index b2bd406622969..29469a3738c26 100644 --- a/packages/cli/src/databases/migrations/common/1700571993961-AddGlobalAdminRole.ts +++ b/packages/cli/src/databases/migrations/common/1700571993961-AddGlobalAdminRole.ts @@ -1,4 +1,4 @@ -import { ApplicationError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; import type { MigrationContext, ReversibleMigration } from '@/databases/types'; @@ -41,7 +41,7 @@ export class AddGlobalAdminRole1700571993961 implements ReversibleMigration { const memberRoleId = memberRoleIdResult[0]?.id; if (!memberRoleId) { - throw new ApplicationError('Could not find global member role!'); + throw new UnexpectedError('Could not find global member role!'); } await runQuery( diff --git a/packages/cli/src/databases/migrations/common/1714133768519-CreateProject.ts b/packages/cli/src/databases/migrations/common/1714133768519-CreateProject.ts index d7b8a2d47c11a..32498fcf44c79 100644 --- a/packages/cli/src/databases/migrations/common/1714133768519-CreateProject.ts +++ b/packages/cli/src/databases/migrations/common/1714133768519-CreateProject.ts @@ -1,5 +1,5 @@ import type { ProjectRole } from '@n8n/api-types'; -import { ApplicationError } from 'n8n-workflow'; +import { UserError } from 'n8n-workflow'; import { nanoid } from 'nanoid'; import type { User } from '@/databases/entities/user'; @@ -249,7 +249,7 @@ export class CreateProject1714133768519 implements ReversibleMigration { const message = 'Down migration only possible when there are no projects. Please delete all projects that were created via the UI first.'; logger.error(message); - throw new ApplicationError(message); + throw new UserError(message); } // 1. create temp table for shared workflows diff --git a/packages/cli/src/databases/repositories/execution.repository.ts b/packages/cli/src/databases/repositories/execution.repository.ts index 9c24cea5c263d..2c2dba656cca7 100644 --- a/packages/cli/src/databases/repositories/execution.repository.ts +++ b/packages/cli/src/databases/repositories/execution.repository.ts @@ -23,7 +23,7 @@ import { DateUtils } from '@n8n/typeorm/util/DateUtils'; import { parse, stringify } from 'flatted'; import pick from 'lodash/pick'; import { BinaryDataService, ErrorReporter, Logger } from 'n8n-core'; -import { ExecutionCancelledError, ApplicationError } from 'n8n-workflow'; +import { ExecutionCancelledError, UnexpectedError } from 'n8n-workflow'; import type { AnnotationVote, ExecutionStatus, @@ -206,7 +206,7 @@ export class ExecutionRepository extends Repository { if (executions.length === 0) return; this.errorReporter.error( - new ApplicationError('Found executions without executionData', { + new UnexpectedError('Found executions without executionData', { extra: { executionIds: executions.map(({ id }) => id) }, }), ); @@ -434,7 +434,7 @@ export class ExecutionRepository extends Repository { }, ) { if (!deleteConditions?.deleteBefore && !deleteConditions?.ids) { - throw new ApplicationError( + throw new UnexpectedError( 'Either "deleteBefore" or "ids" must be present in the request body', ); } @@ -836,7 +836,7 @@ export class ExecutionRepository extends Repository { async findManyByRangeQuery(query: ExecutionSummaries.RangeQuery): Promise { if (query?.accessibleWorkflowIds?.length === 0) { - throw new ApplicationError('Expected accessible workflow IDs'); + throw new UnexpectedError('Expected accessible workflow IDs'); } // Due to performance reasons, we use custom query builder with raw SQL. diff --git a/packages/cli/src/databases/subscribers/user-subscriber.ts b/packages/cli/src/databases/subscribers/user-subscriber.ts index 485eeb6fc54ec..b579423424f02 100644 --- a/packages/cli/src/databases/subscribers/user-subscriber.ts +++ b/packages/cli/src/databases/subscribers/user-subscriber.ts @@ -2,7 +2,7 @@ import { Container } from '@n8n/di'; import type { EntitySubscriberInterface, UpdateEvent } from '@n8n/typeorm'; import { EventSubscriber } from '@n8n/typeorm'; import { ErrorReporter, Logger } from 'n8n-core'; -import { ApplicationError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; import { Project } from '../entities/project'; import { User } from '../entities/user'; @@ -47,7 +47,7 @@ export class UserSubscriber implements EntitySubscriberInterface { // that this could cause further data inconsistencies. const message = "Could not update the personal project's name"; Container.get(Logger).warn(message, event.entity); - const exception = new ApplicationError(message); + const exception = new UnexpectedError(message); this.eventReporter.warn(exception, event.entity); return; } @@ -69,7 +69,7 @@ export class UserSubscriber implements EntitySubscriberInterface { // that this could cause further data inconsistencies. const message = "Could not update the personal project's name"; Container.get(Logger).warn(message, event.entity); - const exception = new ApplicationError(message); + const exception = new UnexpectedError(message); this.eventReporter.warn(exception, event.entity); } } diff --git a/packages/cli/src/databases/utils/migration-helpers.ts b/packages/cli/src/databases/utils/migration-helpers.ts index e248f19c834cd..81e32b9eedc7a 100644 --- a/packages/cli/src/databases/utils/migration-helpers.ts +++ b/packages/cli/src/databases/utils/migration-helpers.ts @@ -4,7 +4,7 @@ import type { ObjectLiteral } from '@n8n/typeorm'; import type { QueryRunner } from '@n8n/typeorm/query-runner/QueryRunner'; import { readFileSync, rmSync } from 'fs'; import { InstanceSettings, Logger } from 'n8n-core'; -import { ApplicationError, jsonParse } from 'n8n-workflow'; +import { jsonParse, UnexpectedError } from 'n8n-workflow'; import { inTest } from '@/constants'; import { createSchemaBuilder } from '@/databases/dsl'; @@ -23,7 +23,7 @@ function loadSurveyFromDisk(): string | null { const personalizationSurvey = JSON.parse(surveyFile) as object; const kvPairs = Object.entries(personalizationSurvey); if (!kvPairs.length) { - throw new ApplicationError('personalizationSurvey is empty'); + throw new UnexpectedError('personalizationSurvey is empty'); } else { const emptyKeys = kvPairs.reduce((acc, [, value]) => { if (!value || (Array.isArray(value) && !value.length)) { @@ -32,7 +32,7 @@ function loadSurveyFromDisk(): string | null { return acc; }, 0); if (emptyKeys === kvPairs.length) { - throw new ApplicationError('incomplete personalizationSurvey'); + throw new UnexpectedError('incomplete personalizationSurvey'); } } return surveyFile; @@ -69,7 +69,7 @@ const runDisablingForeignKeys = async ( ) => { const { dbType, queryRunner } = context; if (dbType !== 'sqlite') - throw new ApplicationError('Disabling transactions only available in sqlite'); + throw new UnexpectedError('Disabling transactions only available in sqlite'); await queryRunner.query('PRAGMA foreign_keys=OFF'); await queryRunner.startTransaction(); try { @@ -195,7 +195,7 @@ export const wrapMigration = (migration: Migration) => { }, }); } else { - throw new ApplicationError(`Migration "${migration.name}" is missing the method \`up\`.`); + throw new UnexpectedError(`Migration "${migration.name}" is missing the method \`up\`.`); } if (down) { Object.assign(migration.prototype, { diff --git a/packages/cli/src/decorators/controller.registry.ts b/packages/cli/src/decorators/controller.registry.ts index 645a675011ce2..a1295cdc0df1f 100644 --- a/packages/cli/src/decorators/controller.registry.ts +++ b/packages/cli/src/decorators/controller.registry.ts @@ -3,7 +3,7 @@ import { Container, Service } from '@n8n/di'; import { Router } from 'express'; import type { Application, Request, Response, RequestHandler } from 'express'; import { rateLimit as expressRateLimit } from 'express-rate-limit'; -import { ApplicationError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; import type { ZodClass } from 'zod-class'; import { AuthService } from '@/auth/auth.service'; @@ -100,7 +100,7 @@ export class ControllerRegistry { return res.status(400).json(output.error.errors[0]); } } - } else throw new ApplicationError('Unknown arg type: ' + arg.type); + } else throw new UnexpectedError('Unknown arg type: ' + arg.type); } return await controller[handlerName](...args); }; diff --git a/packages/cli/src/decorators/on-shutdown.ts b/packages/cli/src/decorators/on-shutdown.ts index 3d170021051aa..5cd5211c67e16 100644 --- a/packages/cli/src/decorators/on-shutdown.ts +++ b/packages/cli/src/decorators/on-shutdown.ts @@ -1,5 +1,5 @@ import { Container } from '@n8n/di'; -import { ApplicationError } from 'n8n-workflow'; +import { UserError } from 'n8n-workflow'; import { DEFAULT_SHUTDOWN_PRIORITY } from '@/constants'; import { type ServiceClass, ShutdownService } from '@/shutdown/shutdown.service'; @@ -33,8 +33,6 @@ export const OnShutdown = Container.get(ShutdownService).register(priority, { serviceClass, methodName }); } else { const name = `${serviceClass.name}.${methodName}()`; - throw new ApplicationError( - `${name} must be a method on ${serviceClass.name} to use "OnShutdown"`, - ); + throw new UserError(`${name} must be a method on ${serviceClass.name} to use "OnShutdown"`); } }; diff --git a/packages/cli/src/environments.ee/source-control/source-control-export.service.ee.ts b/packages/cli/src/environments.ee/source-control/source-control-export.service.ee.ts index 9505f738a4183..3a2505cc757ef 100644 --- a/packages/cli/src/environments.ee/source-control/source-control-export.service.ee.ts +++ b/packages/cli/src/environments.ee/source-control/source-control-export.service.ee.ts @@ -2,7 +2,11 @@ import type { SourceControlledFile } from '@n8n/api-types'; import { Service } from '@n8n/di'; import { rmSync } from 'fs'; import { Credentials, InstanceSettings, Logger } from 'n8n-core'; -import { ApplicationError, type ICredentialDataDecryptedObject } from 'n8n-workflow'; +import { + ApplicationError, + UnexpectedError, + type ICredentialDataDecryptedObject, +} from 'n8n-workflow'; import { writeFile as fsWriteFile, rm as fsRm } from 'node:fs/promises'; import path from 'path'; @@ -120,7 +124,7 @@ export class SourceControlExportService { const project = sharedWorkflow.project; if (!project) { - throw new ApplicationError( + throw new UnexpectedError( `Workflow ${formatWorkflow(sharedWorkflow.workflow)} has no owner`, ); } @@ -130,7 +134,7 @@ export class SourceControlExportService { (pr) => pr.role === 'project:personalOwner', ); if (!ownerRelation) { - throw new ApplicationError( + throw new UnexpectedError( `Workflow ${formatWorkflow(sharedWorkflow.workflow)} has no owner`, ); } @@ -145,7 +149,7 @@ export class SourceControlExportService { teamName: project.name, }; } else { - throw new ApplicationError( + throw new UnexpectedError( `Workflow belongs to unknown project type: ${project.type as string}`, ); } @@ -165,7 +169,7 @@ export class SourceControlExportService { }; } catch (error) { if (error instanceof ApplicationError) throw error; - throw new ApplicationError('Failed to export workflows to work folder', { cause: error }); + throw new UnexpectedError('Failed to export workflows to work folder', { cause: error }); } } @@ -195,7 +199,7 @@ export class SourceControlExportService { ], }; } catch (error) { - throw new ApplicationError('Failed to export variables to work folder', { + throw new UnexpectedError('Failed to export variables to work folder', { cause: error, }); } @@ -237,7 +241,7 @@ export class SourceControlExportService { ], }; } catch (error) { - throw new ApplicationError('Failed to export variables to work folder', { cause: error }); + throw new UnexpectedError('Failed to export variables to work folder', { cause: error }); } } @@ -336,7 +340,7 @@ export class SourceControlExportService { missingIds, }; } catch (error) { - throw new ApplicationError('Failed to export credentials to work folder', { cause: error }); + throw new UnexpectedError('Failed to export credentials to work folder', { cause: error }); } } } diff --git a/packages/cli/src/environments.ee/source-control/source-control-git.service.ee.ts b/packages/cli/src/environments.ee/source-control/source-control-git.service.ee.ts index 01aaf78c68cea..892cd9c4258a7 100644 --- a/packages/cli/src/environments.ee/source-control/source-control-git.service.ee.ts +++ b/packages/cli/src/environments.ee/source-control/source-control-git.service.ee.ts @@ -1,7 +1,7 @@ import { Service } from '@n8n/di'; import { execSync } from 'child_process'; import { Logger } from 'n8n-core'; -import { ApplicationError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; import path from 'path'; import type { CommitResult, @@ -51,7 +51,7 @@ export class SourceControlGitService { }); this.logger.debug(`Git binary found: ${gitResult.toString()}`); } catch (error) { - throw new ApplicationError('Git binary not found', { cause: error }); + throw new UnexpectedError('Git binary not found', { cause: error }); } try { const sshResult = execSync('ssh -V', { @@ -59,7 +59,7 @@ export class SourceControlGitService { }); this.logger.debug(`SSH binary found: ${sshResult.toString()}`); } catch (error) { - throw new ApplicationError('SSH binary not found', { cause: error }); + throw new UnexpectedError('SSH binary not found', { cause: error }); } return true; } @@ -126,7 +126,7 @@ export class SourceControlGitService { private async checkRepositorySetup(): Promise { if (!this.git) { - throw new ApplicationError('Git is not initialized (async)'); + throw new UnexpectedError('Git is not initialized (async)'); } if (!(await this.git.checkIsRepo())) { return false; @@ -141,7 +141,7 @@ export class SourceControlGitService { private async hasRemote(remote: string): Promise { if (!this.git) { - throw new ApplicationError('Git is not initialized (async)'); + throw new UnexpectedError('Git is not initialized (async)'); } try { const remotes = await this.git.getRemotes(true); @@ -153,7 +153,7 @@ export class SourceControlGitService { return true; } } catch (error) { - throw new ApplicationError('Git is not initialized', { cause: error }); + throw new UnexpectedError('Git is not initialized', { cause: error }); } this.logger.debug(`Git remote not found: ${remote}`); return false; @@ -167,7 +167,7 @@ export class SourceControlGitService { user: User, ): Promise { if (!this.git) { - throw new ApplicationError('Git is not initialized (Promise)'); + throw new UnexpectedError('Git is not initialized (Promise)'); } if (sourceControlPreferences.initRepo) { try { @@ -231,7 +231,7 @@ export class SourceControlGitService { async setGitUserDetails(name: string, email: string): Promise { if (!this.git) { - throw new ApplicationError('Git is not initialized (setGitUserDetails)'); + throw new UnexpectedError('Git is not initialized (setGitUserDetails)'); } await this.git.addConfig('user.email', email); await this.git.addConfig('user.name', name); @@ -239,7 +239,7 @@ export class SourceControlGitService { async getBranches(): Promise<{ branches: string[]; currentBranch: string }> { if (!this.git) { - throw new ApplicationError('Git is not initialized (getBranches)'); + throw new UnexpectedError('Git is not initialized (getBranches)'); } try { @@ -256,13 +256,13 @@ export class SourceControlGitService { currentBranch: current, }; } catch (error) { - throw new ApplicationError('Could not get remote branches from repository', { cause: error }); + throw new UnexpectedError('Could not get remote branches from repository', { cause: error }); } } async setBranch(branch: string): Promise<{ branches: string[]; currentBranch: string }> { if (!this.git) { - throw new ApplicationError('Git is not initialized (setBranch)'); + throw new UnexpectedError('Git is not initialized (setBranch)'); } await this.git.checkout(branch); await this.git.branch([`--set-upstream-to=${SOURCE_CONTROL_ORIGIN}/${branch}`, branch]); @@ -271,7 +271,7 @@ export class SourceControlGitService { async getCurrentBranch(): Promise<{ current: string; remote: string }> { if (!this.git) { - throw new ApplicationError('Git is not initialized (getCurrentBranch)'); + throw new UnexpectedError('Git is not initialized (getCurrentBranch)'); } const currentBranch = (await this.git.branch()).current; return { @@ -282,7 +282,7 @@ export class SourceControlGitService { async diffRemote(): Promise { if (!this.git) { - throw new ApplicationError('Git is not initialized (diffRemote)'); + throw new UnexpectedError('Git is not initialized (diffRemote)'); } const currentBranch = await this.getCurrentBranch(); if (currentBranch.remote) { @@ -294,7 +294,7 @@ export class SourceControlGitService { async diffLocal(): Promise { if (!this.git) { - throw new ApplicationError('Git is not initialized (diffLocal)'); + throw new UnexpectedError('Git is not initialized (diffLocal)'); } const currentBranch = await this.getCurrentBranch(); if (currentBranch.remote) { @@ -306,7 +306,7 @@ export class SourceControlGitService { async fetch(): Promise { if (!this.git) { - throw new ApplicationError('Git is not initialized (fetch)'); + throw new UnexpectedError('Git is not initialized (fetch)'); } await this.setGitSshCommand(); return await this.git.fetch(); @@ -314,7 +314,7 @@ export class SourceControlGitService { async pull(options: { ffOnly: boolean } = { ffOnly: true }): Promise { if (!this.git) { - throw new ApplicationError('Git is not initialized (pull)'); + throw new UnexpectedError('Git is not initialized (pull)'); } await this.setGitSshCommand(); const params = {}; @@ -332,7 +332,7 @@ export class SourceControlGitService { ): Promise { const { force, branch } = options; if (!this.git) { - throw new ApplicationError('Git is not initialized ({)'); + throw new UnexpectedError('Git is not initialized ({)'); } await this.setGitSshCommand(); if (force) { @@ -343,7 +343,7 @@ export class SourceControlGitService { async stage(files: Set, deletedFiles?: Set): Promise { if (!this.git) { - throw new ApplicationError('Git is not initialized (stage)'); + throw new UnexpectedError('Git is not initialized (stage)'); } if (deletedFiles?.size) { try { @@ -359,7 +359,7 @@ export class SourceControlGitService { options: { hard: boolean; target: string } = { hard: true, target: 'HEAD' }, ): Promise { if (!this.git) { - throw new ApplicationError('Git is not initialized (Promise)'); + throw new UnexpectedError('Git is not initialized (Promise)'); } if (options?.hard) { return await this.git.raw(['reset', '--hard', options.target]); @@ -371,14 +371,14 @@ export class SourceControlGitService { async commit(message: string): Promise { if (!this.git) { - throw new ApplicationError('Git is not initialized (commit)'); + throw new UnexpectedError('Git is not initialized (commit)'); } return await this.git.commit(message); } async status(): Promise { if (!this.git) { - throw new ApplicationError('Git is not initialized (status)'); + throw new UnexpectedError('Git is not initialized (status)'); } const statusResult = await this.git.status(); return statusResult; diff --git a/packages/cli/src/environments.ee/source-control/source-control-helper.ee.ts b/packages/cli/src/environments.ee/source-control/source-control-helper.ee.ts index 45ca38ed9698f..43132018a2a74 100644 --- a/packages/cli/src/environments.ee/source-control/source-control-helper.ee.ts +++ b/packages/cli/src/environments.ee/source-control/source-control-helper.ee.ts @@ -3,7 +3,7 @@ import { Container } from '@n8n/di'; import { generateKeyPairSync } from 'crypto'; import { constants as fsConstants, mkdirSync, accessSync } from 'fs'; import { Logger } from 'n8n-core'; -import { ApplicationError } from 'n8n-workflow'; +import { UserError } from 'n8n-workflow'; import { ok } from 'node:assert/strict'; import path from 'path'; @@ -182,7 +182,7 @@ export function normalizeAndValidateSourceControlledFilePath( const normalizedPath = path.isAbsolute(filePath) ? filePath : path.join(gitFolderPath, filePath); if (!isContainedWithin(gitFolderPath, filePath)) { - throw new ApplicationError(`File path ${filePath} is invalid`); + throw new UserError(`File path ${filePath} is invalid`); } return normalizedPath; diff --git a/packages/cli/src/environments.ee/source-control/source-control-import.service.ee.ts b/packages/cli/src/environments.ee/source-control/source-control-import.service.ee.ts index 3c416c0b4ccda..aba8d16eaa876 100644 --- a/packages/cli/src/environments.ee/source-control/source-control-import.service.ee.ts +++ b/packages/cli/src/environments.ee/source-control/source-control-import.service.ee.ts @@ -4,7 +4,7 @@ import { Service } from '@n8n/di'; import { In } from '@n8n/typeorm'; import glob from 'fast-glob'; import { Credentials, ErrorReporter, InstanceSettings, Logger } from 'n8n-core'; -import { ApplicationError, jsonParse, ensureError } from 'n8n-workflow'; +import { jsonParse, ensureError, UserError, UnexpectedError } from 'n8n-workflow'; import { readFile as fsReadFile } from 'node:fs/promises'; import path from 'path'; @@ -250,7 +250,7 @@ export class SourceControlImportService { this.logger.debug(`Updating workflow id ${importedWorkflow.id ?? 'new'}`); const upsertResult = await this.workflowRepository.upsert({ ...importedWorkflow }, ['id']); if (upsertResult?.identifiers?.length !== 1) { - throw new ApplicationError('Failed to upsert workflow', { + throw new UnexpectedError('Failed to upsert workflow', { extra: { workflowId: importedWorkflow.id ?? 'new' }, }); } @@ -411,7 +411,7 @@ export class SourceControlImportService { select: ['id'], }); if (findByName && findByName.id !== tag.id) { - throw new ApplicationError( + throw new UserError( `A tag with the name ${tag.name} already exists locally.
Please either rename the local tag, or the remote one with the id ${tag.id} in the tags.json file.`, ); } @@ -570,7 +570,7 @@ export class SourceControlImportService { assertNever(owner); const errorOwner = owner as ResourceOwner; - throw new ApplicationError( + throw new UnexpectedError( `Unknown resource owner type "${ typeof errorOwner !== 'string' ? errorOwner.type : 'UNKNOWN' }" found when importing from source controller`, diff --git a/packages/cli/src/environments.ee/source-control/source-control-preferences.service.ee.ts b/packages/cli/src/environments.ee/source-control/source-control-preferences.service.ee.ts index d424a844b2866..5ae7068a5eb27 100644 --- a/packages/cli/src/environments.ee/source-control/source-control-preferences.service.ee.ts +++ b/packages/cli/src/environments.ee/source-control/source-control-preferences.service.ee.ts @@ -3,7 +3,7 @@ import type { ValidationError } from 'class-validator'; import { validate } from 'class-validator'; import { rm as fsRm } from 'fs/promises'; import { Cipher, InstanceSettings, Logger } from 'n8n-core'; -import { ApplicationError, jsonParse } from 'n8n-workflow'; +import { jsonParse, UnexpectedError } from 'n8n-workflow'; import { writeFile, chmod, readFile } from 'node:fs/promises'; import path from 'path'; @@ -78,7 +78,7 @@ export class SourceControlPreferencesService { private async getPrivateKeyFromDatabase() { const dbKeyPair = await this.getKeyPairFromDatabase(); - if (!dbKeyPair) throw new ApplicationError('Failed to find key pair in database'); + if (!dbKeyPair) throw new UnexpectedError('Failed to find key pair in database'); return this.cipher.decrypt(dbKeyPair.encryptedPrivateKey); } @@ -86,7 +86,7 @@ export class SourceControlPreferencesService { private async getPublicKeyFromDatabase() { const dbKeyPair = await this.getKeyPairFromDatabase(); - if (!dbKeyPair) throw new ApplicationError('Failed to find key pair in database'); + if (!dbKeyPair) throw new UnexpectedError('Failed to find key pair in database'); return dbKeyPair.publicKey; } @@ -147,7 +147,7 @@ export class SourceControlPreferencesService { loadOnStartup: true, }); } catch (error) { - throw new ApplicationError('Failed to write key pair to database', { cause: error }); + throw new UnexpectedError('Failed to write key pair to database', { cause: error }); } // update preferences only after generating key pair to prevent endless loop @@ -190,7 +190,7 @@ export class SourceControlPreferencesService { validationError: { target: false }, }); if (validationResult.length > 0) { - throw new ApplicationError('Invalid source control preferences', { + throw new UnexpectedError('Invalid source control preferences', { extra: { preferences: validationResult }, }); } @@ -218,7 +218,7 @@ export class SourceControlPreferencesService { { transaction: false }, ); } catch (error) { - throw new ApplicationError('Failed to save source control preferences', { cause: error }); + throw new UnexpectedError('Failed to save source control preferences', { cause: error }); } } return this.sourceControlPreferences; diff --git a/packages/cli/src/environments.ee/source-control/source-control.service.ee.ts b/packages/cli/src/environments.ee/source-control/source-control.service.ee.ts index 2bd040ee3a995..2a04a5835320a 100644 --- a/packages/cli/src/environments.ee/source-control/source-control.service.ee.ts +++ b/packages/cli/src/environments.ee/source-control/source-control.service.ee.ts @@ -6,7 +6,7 @@ import type { import { Service } from '@n8n/di'; import { writeFileSync } from 'fs'; import { Logger } from 'n8n-core'; -import { ApplicationError } from 'n8n-workflow'; +import { UnexpectedError, UserError } from 'n8n-workflow'; import path from 'path'; import type { PushResult } from 'simple-git'; @@ -90,7 +90,7 @@ export class SourceControlService { false, ); if (!foldersExisted) { - throw new ApplicationError('No folders exist'); + throw new UserError('No folders exist'); } if (!this.gitService.git) { await this.initGitService(); @@ -101,7 +101,7 @@ export class SourceControlService { branches.current !== this.sourceControlPreferencesService.sourceControlPreferences.branchName ) { - throw new ApplicationError('Branch is not set up correctly'); + throw new UserError('Branch is not set up correctly'); } } catch (error) { throw new BadRequestError( @@ -123,7 +123,7 @@ export class SourceControlService { this.gitService.resetService(); return this.sourceControlPreferencesService.sourceControlPreferences; } catch (error) { - throw new ApplicationError('Failed to disconnect from source control', { cause: error }); + throw new UnexpectedError('Failed to disconnect from source control', { cause: error }); } } @@ -202,7 +202,7 @@ export class SourceControlService { await this.gitService.pull(); } catch (error) { this.logger.error(`Failed to reset workfolder: ${(error as Error).message}`); - throw new ApplicationError( + throw new UserError( 'Unable to fetch updates from git - your folder might be out of sync. Try reconnecting from the Source Control settings page.', ); } diff --git a/packages/cli/src/executions/execution.service.ts b/packages/cli/src/executions/execution.service.ts index 84090bcb0a4c0..eca486289a5f5 100644 --- a/packages/cli/src/executions/execution.service.ts +++ b/packages/cli/src/executions/execution.service.ts @@ -12,8 +12,8 @@ import type { IWorkflowExecutionDataProcess, } from 'n8n-workflow'; import { - ApplicationError, ExecutionStatusList, + UnexpectedError, Workflow, WorkflowOperationError, } from 'n8n-workflow'; @@ -146,7 +146,7 @@ export class ExecutionService { if (!execution.data.executionData) throw new AbortedExecutionRetryError(); if (execution.finished) { - throw new ApplicationError('The execution succeeded, so it cannot be retried.'); + throw new UnexpectedError('The execution succeeded, so it cannot be retried.'); } const executionMode = 'retry'; @@ -188,7 +188,7 @@ export class ExecutionService { })) as IWorkflowBase; if (workflowData === undefined) { - throw new ApplicationError( + throw new UnexpectedError( 'Workflow could not be found and so the data not be loaded for the retry.', { extra: { workflowId } }, ); @@ -232,7 +232,7 @@ export class ExecutionService { const executionData = await this.activeExecutions.getPostExecutePromise(retriedExecutionId); if (!executionData) { - throw new ApplicationError('The retry did not start for an unknown reason.'); + throw new UnexpectedError('The retry did not start for an unknown reason.'); } return !!executionData.finished; diff --git a/packages/cli/src/executions/parse-range-query.middleware.ts b/packages/cli/src/executions/parse-range-query.middleware.ts index 608fb1056f8ce..762e4a679a1c4 100644 --- a/packages/cli/src/executions/parse-range-query.middleware.ts +++ b/packages/cli/src/executions/parse-range-query.middleware.ts @@ -1,7 +1,7 @@ import type { NextFunction, Response } from 'express'; import { validate } from 'jsonschema'; import type { JsonObject } from 'n8n-workflow'; -import { ApplicationError, jsonParse } from 'n8n-workflow'; +import { jsonParse, UnexpectedError } from 'n8n-workflow'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import * as ResponseHelper from '@/response-helper'; @@ -44,7 +44,7 @@ export const parseRangeQuery = ( if (jsonFilter.waitTill) jsonFilter.waitTill = Boolean(jsonFilter.waitTill); - if (!isValid(jsonFilter)) throw new ApplicationError('Query does not match schema'); + if (!isValid(jsonFilter)) throw new UnexpectedError('Query does not match schema'); req.rangeQuery = { ...req.rangeQuery, ...jsonFilter }; } diff --git a/packages/cli/src/external-hooks.ts b/packages/cli/src/external-hooks.ts index d369fae05f398..6f2990c0e9743 100644 --- a/packages/cli/src/external-hooks.ts +++ b/packages/cli/src/external-hooks.ts @@ -4,7 +4,7 @@ import { GlobalConfig } from '@n8n/config'; import { Service } from '@n8n/di'; import { ErrorReporter, Logger } from 'n8n-core'; import type { IRun, IWorkflowBase, Workflow, WorkflowExecuteMode } from 'n8n-workflow'; -import { ApplicationError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; import type clientOAuth1 from 'oauth-1.0a'; import type { AbstractServer } from '@/abstract-server'; @@ -125,7 +125,7 @@ export class ExternalHooks { } catch (e) { const error = e instanceof Error ? e : new Error(`${e}`); - throw new ApplicationError('Problem loading external hook file', { + throw new UnexpectedError('Problem loading external hook file', { extra: { errorMessage: error.message, hookFilePath }, cause: error, }); @@ -160,7 +160,7 @@ export class ExternalHooks { } catch (cause) { this.logger.error(`There was a problem running hook "${hookName}"`); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const error = new ApplicationError(`External hook "${hookName}" failed`, { cause }); + const error = new UnexpectedError(`External hook "${hookName}" failed`, { cause }); this.errorReporter.error(error, { level: 'fatal' }); // Throw original error, so that hooks control the error returned to use diff --git a/packages/cli/src/external-secrets.ee/external-secrets-manager.ee.ts b/packages/cli/src/external-secrets.ee/external-secrets-manager.ee.ts index 83579c3e8e8e3..1edaff137342d 100644 --- a/packages/cli/src/external-secrets.ee/external-secrets-manager.ee.ts +++ b/packages/cli/src/external-secrets.ee/external-secrets-manager.ee.ts @@ -1,6 +1,6 @@ import { Service } from '@n8n/di'; import { Cipher, Logger } from 'n8n-core'; -import { jsonParse, type IDataObject, ApplicationError, ensureError } from 'n8n-workflow'; +import { jsonParse, type IDataObject, ensureError, UnexpectedError } from 'n8n-workflow'; import { SettingsRepository } from '@/databases/repositories/settings.repository'; import { EventService } from '@/events/event.service'; @@ -95,7 +95,7 @@ export class ExternalSecretsManager { try { return jsonParse(decryptedData); } catch (e) { - throw new ApplicationError( + throw new UnexpectedError( 'External Secrets Settings could not be decrypted. The likely reason is that a different "encryptionKey" was used to encrypt the data.', ); } diff --git a/packages/cli/src/external-secrets.ee/providers/infisical.ts b/packages/cli/src/external-secrets.ee/providers/infisical.ts index e74ef921b76ff..17888182feb69 100644 --- a/packages/cli/src/external-secrets.ee/providers/infisical.ts +++ b/packages/cli/src/external-secrets.ee/providers/infisical.ts @@ -1,7 +1,7 @@ import InfisicalClient from 'infisical-node'; import { getServiceTokenData } from 'infisical-node/lib/api/serviceTokenData'; import { populateClientWorkspaceConfigsHelper } from 'infisical-node/lib/helpers/key'; -import { ApplicationError, type IDataObject, type INodeProperties } from 'n8n-workflow'; +import { UnexpectedError, type IDataObject, type INodeProperties } from 'n8n-workflow'; import type { SecretsProvider, SecretsProviderSettings, SecretsProviderState } from '@/interfaces'; @@ -77,10 +77,10 @@ export class InfisicalProvider implements SecretsProvider { async update(): Promise { if (!this.client) { - throw new ApplicationError('Updated attempted on Infisical when initialization failed'); + throw new UnexpectedError('Updated attempted on Infisical when initialization failed'); } if (!(await this.test())[0]) { - throw new ApplicationError('Infisical provider test failed during update'); + throw new UnexpectedError('Infisical provider test failed during update'); } const secrets = (await this.client.getAllSecrets({ environment: this.environment, @@ -123,7 +123,7 @@ export class InfisicalProvider implements SecretsProvider { if (serviceTokenData.scopes) { return serviceTokenData.scopes[0].environment; } - throw new ApplicationError("Couldn't find environment for Infisical"); + throw new UnexpectedError("Couldn't find environment for Infisical"); } async test(): Promise<[boolean] | [boolean, string]> { diff --git a/packages/cli/src/ldap.ee/ldap.service.ee.ts b/packages/cli/src/ldap.ee/ldap.service.ee.ts index a4d9cbc97d901..99c622b962ef1 100644 --- a/packages/cli/src/ldap.ee/ldap.service.ee.ts +++ b/packages/cli/src/ldap.ee/ldap.service.ee.ts @@ -4,7 +4,7 @@ import { QueryFailedError } from '@n8n/typeorm'; import type { Entry as LdapUser, ClientOptions } from 'ldapts'; import { Client } from 'ldapts'; import { Cipher, Logger } from 'n8n-core'; -import { ApplicationError, jsonParse } from 'n8n-workflow'; +import { jsonParse, UnexpectedError } from 'n8n-workflow'; import type { ConnectionOptions } from 'tls'; import config from '@/config'; @@ -89,7 +89,7 @@ export class LdapService { const { valid, message } = validateLdapConfigurationSchema(ldapConfig); if (!valid) { - throw new ApplicationError(message); + throw new UnexpectedError(message); } if (ldapConfig.loginEnabled && getCurrentAuthenticationMethod() === 'saml') { @@ -165,7 +165,7 @@ export class LdapService { */ private async getClient() { if (this.config === undefined) { - throw new ApplicationError('Service cannot be used without setting the property config'); + throw new UnexpectedError('Service cannot be used without setting the property config'); } if (this.client === undefined) { const url = formatUrl( @@ -304,7 +304,7 @@ export class LdapService { /** Schedule a synchronization job based on the interval set in the LDAP config */ private scheduleSync(): void { if (!this.config.synchronizationInterval) { - throw new ApplicationError('Interval variable has to be defined'); + throw new UnexpectedError('Interval variable has to be defined'); } this.syncTimer = setInterval(async () => { await this.runSync('live'); diff --git a/packages/cli/src/load-nodes-and-credentials.ts b/packages/cli/src/load-nodes-and-credentials.ts index dadf44c7ab69f..947a03a25dfc0 100644 --- a/packages/cli/src/load-nodes-and-credentials.ts +++ b/packages/cli/src/load-nodes-and-credentials.ts @@ -26,7 +26,7 @@ import type { IVersionedNodeType, INodeProperties, } from 'n8n-workflow'; -import { ApplicationError, NodeConnectionType } from 'n8n-workflow'; +import { NodeConnectionType, UnexpectedError, UserError } from 'n8n-workflow'; import path from 'path'; import picocolors from 'picocolors'; @@ -71,7 +71,7 @@ export class LoadNodesAndCredentials { ) {} async init() { - if (inTest) throw new ApplicationError('Not available in tests'); + if (inTest) throw new UnexpectedError('Not available in tests'); // Make sure the imported modules can resolve dependencies fine. const delimiter = process.platform === 'win32' ? ';' : ':'; @@ -293,7 +293,7 @@ export class LoadNodesAndCredentials { ) { const loader = new constructor(dir, this.excludeNodes, this.includeNodes); if (loader instanceof PackageDirectoryLoader && loader.packageName in this.loaders) { - throw new ApplicationError( + throw new UserError( picocolors.red( `nodes package ${loader.packageName} is already loaded.\n Please delete this second copy at path ${dir}`, ), diff --git a/packages/cli/src/middlewares/list-query/dtos/base.filter.dto.ts b/packages/cli/src/middlewares/list-query/dtos/base.filter.dto.ts index 9fa5c216774b3..e5c852c2464eb 100644 --- a/packages/cli/src/middlewares/list-query/dtos/base.filter.dto.ts +++ b/packages/cli/src/middlewares/list-query/dtos/base.filter.dto.ts @@ -1,13 +1,13 @@ import { plainToInstance, instanceToPlain } from 'class-transformer'; import { validate } from 'class-validator'; import { isObjectLiteral } from 'n8n-core'; -import { ApplicationError, jsonParse } from 'n8n-workflow'; +import { jsonParse, UnexpectedError } from 'n8n-workflow'; export class BaseFilter { protected static async toFilter(rawFilter: string, Filter: typeof BaseFilter) { const dto = jsonParse(rawFilter, { errorMessage: 'Failed to parse filter JSON' }); - if (!isObjectLiteral(dto)) throw new ApplicationError('Filter must be an object literal'); + if (!isObjectLiteral(dto)) throw new UnexpectedError('Filter must be an object literal'); const instance = plainToInstance(Filter, dto, { excludeExtraneousValues: true, // remove fields not in class @@ -23,6 +23,6 @@ export class BaseFilter { private async validate() { const result = await validate(this); - if (result.length > 0) throw new ApplicationError('Parsed filter does not fit the schema'); + if (result.length > 0) throw new UnexpectedError('Parsed filter does not fit the schema'); } } diff --git a/packages/cli/src/middlewares/list-query/dtos/base.select.dto.ts b/packages/cli/src/middlewares/list-query/dtos/base.select.dto.ts index 9a8cb395f0694..7a186c47eac70 100644 --- a/packages/cli/src/middlewares/list-query/dtos/base.select.dto.ts +++ b/packages/cli/src/middlewares/list-query/dtos/base.select.dto.ts @@ -1,4 +1,4 @@ -import { ApplicationError, jsonParse } from 'n8n-workflow'; +import { jsonParse, UnexpectedError } from 'n8n-workflow'; import { isStringArray } from '@/utils'; @@ -8,7 +8,7 @@ export class BaseSelect { protected static toSelect(rawFilter: string, Select: typeof BaseSelect) { const dto = jsonParse(rawFilter, { errorMessage: 'Failed to parse filter JSON' }); - if (!isStringArray(dto)) throw new ApplicationError('Parsed select is not a string array'); + if (!isStringArray(dto)) throw new UnexpectedError('Parsed select is not a string array'); return dto.reduce>((acc, field) => { if (!Select.selectableFields.has(field)) return acc; diff --git a/packages/cli/src/middlewares/list-query/dtos/pagination.dto.ts b/packages/cli/src/middlewares/list-query/dtos/pagination.dto.ts index a48a79e31db11..3b408dedddb9e 100644 --- a/packages/cli/src/middlewares/list-query/dtos/pagination.dto.ts +++ b/packages/cli/src/middlewares/list-query/dtos/pagination.dto.ts @@ -1,15 +1,15 @@ -import { ApplicationError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; import { isIntegerString } from '@/utils'; export class Pagination { static fromString(rawTake: string, rawSkip: string) { if (!isIntegerString(rawTake)) { - throw new ApplicationError('Parameter take is not an integer string'); + throw new UnexpectedError('Parameter take is not an integer string'); } if (!isIntegerString(rawSkip)) { - throw new ApplicationError('Parameter skip is not an integer string'); + throw new UnexpectedError('Parameter skip is not an integer string'); } const [take, skip] = [rawTake, rawSkip].map((o) => parseInt(o, 10)); diff --git a/packages/cli/src/middlewares/list-query/pagination.ts b/packages/cli/src/middlewares/list-query/pagination.ts index 8823cf8572763..da24db6d5c03e 100644 --- a/packages/cli/src/middlewares/list-query/pagination.ts +++ b/packages/cli/src/middlewares/list-query/pagination.ts @@ -1,5 +1,5 @@ import type { RequestHandler } from 'express'; -import { ApplicationError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; import type { ListQuery } from '@/requests'; import * as ResponseHelper from '@/response-helper'; @@ -16,7 +16,7 @@ export const paginationListQueryMiddleware: RequestHandler = ( try { if (!rawTake && req.query.skip) { - throw new ApplicationError('Please specify `take` when using `skip`'); + throw new UnexpectedError('Please specify `take` when using `skip`'); } if (!rawTake) return next(); diff --git a/packages/cli/src/middlewares/list-query/sort-by.ts b/packages/cli/src/middlewares/list-query/sort-by.ts index 01aca505af53b..f600b1fdca1b1 100644 --- a/packages/cli/src/middlewares/list-query/sort-by.ts +++ b/packages/cli/src/middlewares/list-query/sort-by.ts @@ -1,7 +1,7 @@ import { plainToInstance } from 'class-transformer'; import { validateSync } from 'class-validator'; import type { RequestHandler } from 'express'; -import { ApplicationError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; import type { ListQuery } from '@/requests'; import * as ResponseHelper from '@/response-helper'; @@ -27,7 +27,7 @@ export const sortByQueryMiddleware: RequestHandler = (req: ListQuery.Request, re if (validationResponse.length) { const validationError = validationResponse[0]; - throw new ApplicationError(validationError.constraints?.workflowSortBy ?? ''); + throw new UnexpectedError(validationError.constraints?.workflowSortBy ?? ''); } req.listQueryOptions = { ...req.listQueryOptions, sortBy }; diff --git a/packages/cli/src/node-types.ts b/packages/cli/src/node-types.ts index bb0a5fbbc7458..2572b93e4dc54 100644 --- a/packages/cli/src/node-types.ts +++ b/packages/cli/src/node-types.ts @@ -6,7 +6,7 @@ import { readdir } from 'fs/promises'; import { RoutingNode } from 'n8n-core'; import type { ExecuteContext } from 'n8n-core'; import type { INodeType, INodeTypeDescription, INodeTypes, IVersionedNodeType } from 'n8n-workflow'; -import { ApplicationError, NodeHelpers } from 'n8n-workflow'; +import { NodeHelpers, UnexpectedError, UserError } from 'n8n-workflow'; import { join, dirname } from 'path'; import { LoadNodesAndCredentials } from './load-nodes-and-credentials'; @@ -52,7 +52,7 @@ export class NodeTypes implements INodeTypes { const node = this.loadNodesAndCredentials.getNode(nodeType); const versionedNodeType = NodeHelpers.getVersionedNodeType(node.type, version); if (toolRequested && typeof versionedNodeType.supplyData === 'function') { - throw new ApplicationError('Node already has a `supplyData` method', { extra: { nodeType } }); + throw new UnexpectedError('Node already has a `supplyData` method', { extra: { nodeType } }); } if ( @@ -72,7 +72,7 @@ export class NodeTypes implements INodeTypes { if (!toolRequested) return versionedNodeType; if (!versionedNodeType.description.usableAsTool) - throw new ApplicationError('Node cannot be used as a tool', { extra: { nodeType } }); + throw new UserError('Node cannot be used as a tool', { extra: { nodeType } }); const { loadedNodes } = this.loadNodesAndCredentials; if (origType in loadedNodes) { diff --git a/packages/cli/src/permissions.ee/check-access.ts b/packages/cli/src/permissions.ee/check-access.ts index 062e3c1865759..6d45ddfe65b62 100644 --- a/packages/cli/src/permissions.ee/check-access.ts +++ b/packages/cli/src/permissions.ee/check-access.ts @@ -2,7 +2,7 @@ import { Container } from '@n8n/di'; import type { Scope } from '@n8n/permissions'; // eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import import { In } from '@n8n/typeorm'; -import { ApplicationError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; import type { User } from '@/databases/entities/user'; import { ProjectRepository } from '@/databases/repositories/project.repository'; @@ -71,7 +71,7 @@ export async function userHasScopes( if (projectId) return userProjectIds.includes(projectId); - throw new ApplicationError( + throw new UnexpectedError( "`@ProjectScope` decorator was used but does not have a `credentialId`, `workflowId`, or `projectId` in its URL parameters. This is likely an implementation error. If you're a developer, please check your URL is correct or that this should be using `@GlobalScope`.", ); } diff --git a/packages/cli/src/push/websocket.push.ts b/packages/cli/src/push/websocket.push.ts index 16e28f24530dc..6178d0cdee396 100644 --- a/packages/cli/src/push/websocket.push.ts +++ b/packages/cli/src/push/websocket.push.ts @@ -1,6 +1,6 @@ import { heartbeatMessageSchema } from '@n8n/api-types'; import { Service } from '@n8n/di'; -import { ApplicationError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; import type WebSocket from 'ws'; import type { User } from '@/databases/entities/user'; @@ -34,7 +34,7 @@ export class WebSocketPush extends AbstractPush { this.onMessageReceived(pushRef, msg); } catch (error) { this.errorReporter.error( - new ApplicationError('Error parsing push message', { + new UnexpectedError('Error parsing push message', { extra: { userId, data, diff --git a/packages/cli/src/scaling/job-processor.ts b/packages/cli/src/scaling/job-processor.ts index 91d051b240cc2..2fa9288a79aa3 100644 --- a/packages/cli/src/scaling/job-processor.ts +++ b/packages/cli/src/scaling/job-processor.ts @@ -13,7 +13,7 @@ import type { IRun, IWorkflowExecutionDataProcess, } from 'n8n-workflow'; -import { BINARY_ENCODING, ApplicationError, Workflow } from 'n8n-workflow'; +import { BINARY_ENCODING, Workflow, UnexpectedError } from 'n8n-workflow'; import type PCancelable from 'p-cancelable'; import config from '@/config'; @@ -61,9 +61,8 @@ export class JobProcessor { }); if (!execution) { - throw new ApplicationError( + throw new UnexpectedError( `Worker failed to find data for execution ${executionId} (job ${job.id})`, - { level: 'warning' }, ); } @@ -92,9 +91,8 @@ export class JobProcessor { }); if (workflowData === null) { - throw new ApplicationError( + throw new UnexpectedError( `Worker failed to find workflow ${workflowId} to run execution ${executionId} (job ${job.id})`, - { level: 'warning' }, ); } diff --git a/packages/cli/src/scaling/scaling.service.ts b/packages/cli/src/scaling/scaling.service.ts index 7c48ce57e2632..231d607acdc21 100644 --- a/packages/cli/src/scaling/scaling.service.ts +++ b/packages/cli/src/scaling/scaling.service.ts @@ -2,12 +2,12 @@ import { GlobalConfig } from '@n8n/config'; import { Container, Service } from '@n8n/di'; import { ErrorReporter, InstanceSettings, isObjectLiteral, Logger } from 'n8n-core'; import { - ApplicationError, BINARY_ENCODING, sleep, jsonStringify, ensureError, ExecutionCancelledError, + UnexpectedError, } from 'n8n-workflow'; import type { IExecuteResponsePromiseData } from 'n8n-workflow'; import assert, { strict } from 'node:assert'; @@ -93,7 +93,7 @@ export class ScalingService { void this.queue.process(JOB_TYPE_NAME, concurrency, async (job: Job) => { try { if (!this.hasValidJobData(job)) { - throw new ApplicationError('Worker received invalid job', { + throw new UnexpectedError('Worker received invalid job', { extra: { jobData: jsonStringify(job, { replaceCircularRefs: true }) }, }); } @@ -369,13 +369,13 @@ export class ScalingService { private assertQueue() { if (this.queue) return; - throw new ApplicationError('This method must be called after `setupQueue`'); + throw new UnexpectedError('This method must be called after `setupQueue`'); } private assertWorker() { if (this.instanceSettings.instanceType === 'worker') return; - throw new ApplicationError('This method must be called on a `worker` instance'); + throw new UnexpectedError('This method must be called on a `worker` instance'); } // #region Queue metrics diff --git a/packages/cli/src/services/cache/cache.service.ts b/packages/cli/src/services/cache/cache.service.ts index ab681d3e5bfb0..16721e7e4ff03 100644 --- a/packages/cli/src/services/cache/cache.service.ts +++ b/packages/cli/src/services/cache/cache.service.ts @@ -1,7 +1,7 @@ import { GlobalConfig } from '@n8n/config'; import { Container, Service } from '@n8n/di'; import { caching } from 'cache-manager'; -import { ApplicationError, jsonStringify } from 'n8n-workflow'; +import { jsonStringify, UserError } from 'n8n-workflow'; import config from '@/config'; import { Time } from '@/constants'; @@ -155,9 +155,7 @@ export class CacheService extends TypedEmitter { if (!key?.length) return; if (this.cache.kind === 'memory') { - throw new ApplicationError('Method `expire` not yet implemented for in-memory cache', { - level: 'warning', - }); + throw new UserError('Method `expire` not yet implemented for in-memory cache'); } await this.cache.store.expire(key, ttlMs * Time.milliseconds.toSeconds); diff --git a/packages/cli/src/services/cache/redis.cache-manager.ts b/packages/cli/src/services/cache/redis.cache-manager.ts index 4014f051c0342..6116b497579f0 100644 --- a/packages/cli/src/services/cache/redis.cache-manager.ts +++ b/packages/cli/src/services/cache/redis.cache-manager.ts @@ -5,7 +5,7 @@ import type { Cache, Store, Config } from 'cache-manager'; import Redis from 'ioredis'; import type { Cluster, ClusterNode, ClusterOptions, RedisOptions } from 'ioredis'; -import { ApplicationError, jsonParse } from 'n8n-workflow'; +import { jsonParse, UnexpectedError } from 'n8n-workflow'; export class NoCacheableError implements Error { name = 'NoCacheableError'; @@ -82,7 +82,7 @@ function builder( await redisCache.mset( args.flatMap(([key, value]) => { if (!isCacheable(value)) - throw new ApplicationError(`"${getVal(value)}" is not a cacheable value`); + throw new UnexpectedError(`"${getVal(value)}" is not a cacheable value`); return [key, getVal(value)] as [string, string]; }), ); diff --git a/packages/cli/src/services/community-packages.service.ts b/packages/cli/src/services/community-packages.service.ts index c85872c5e21ee..10fe04f80ce2d 100644 --- a/packages/cli/src/services/community-packages.service.ts +++ b/packages/cli/src/services/community-packages.service.ts @@ -5,7 +5,7 @@ import { exec } from 'child_process'; import { access as fsAccess, mkdir as fsMkdir } from 'fs/promises'; import type { PackageDirectoryLoader } from 'n8n-core'; import { InstanceSettings, Logger } from 'n8n-core'; -import { ApplicationError, type PublicInstalledPackage } from 'n8n-workflow'; +import { UnexpectedError, UserError, type PublicInstalledPackage } from 'n8n-workflow'; import { promisify } from 'util'; import { @@ -102,10 +102,10 @@ export class CommunityPackagesService { } parseNpmPackageName(rawString?: string): CommunityPackages.ParsedPackageName { - if (!rawString) throw new ApplicationError(PACKAGE_NAME_NOT_PROVIDED); + if (!rawString) throw new UnexpectedError(PACKAGE_NAME_NOT_PROVIDED); if (INVALID_OR_SUSPICIOUS_PACKAGE_NAME.test(rawString)) { - throw new ApplicationError('Package name must be a single word'); + throw new UnexpectedError('Package name must be a single word'); } const scope = rawString.includes('/') ? rawString.split('/')[0] : undefined; @@ -113,7 +113,7 @@ export class CommunityPackagesService { const packageNameWithoutScope = scope ? rawString.replace(`${scope}/`, '') : rawString; if (!packageNameWithoutScope.startsWith(NODE_PACKAGE_PREFIX)) { - throw new ApplicationError(`Package name must start with ${NODE_PACKAGE_PREFIX}`); + throw new UnexpectedError(`Package name must start with ${NODE_PACKAGE_PREFIX}`); } const version = packageNameWithoutScope.includes('@') @@ -164,12 +164,12 @@ export class CommunityPackagesService { }; Object.entries(map).forEach(([npmMessage, n8nMessage]) => { - if (errorMessage.includes(npmMessage)) throw new ApplicationError(n8nMessage); + if (errorMessage.includes(npmMessage)) throw new UnexpectedError(n8nMessage); }); this.logger.warn('npm command failed', { errorMessage }); - throw new ApplicationError(PACKAGE_FAILED_TO_INSTALL); + throw new UnexpectedError(PACKAGE_FAILED_TO_INSTALL); } } @@ -346,7 +346,7 @@ export class CommunityPackagesService { await this.executeNpmCommand(command); } catch (error) { if (error instanceof Error && error.message === RESPONSE_ERROR_MESSAGES.PACKAGE_NOT_FOUND) { - throw new ApplicationError('npm package not found', { extra: { packageName } }); + throw new UserError('npm package not found', { extra: { packageName } }); } throw error; } @@ -360,7 +360,7 @@ export class CommunityPackagesService { try { await this.executeNpmCommand(`npm remove ${packageName}`); } catch {} - throw new ApplicationError(RESPONSE_ERROR_MESSAGES.PACKAGE_LOADING_FAILED, { cause: error }); + throw new UnexpectedError(RESPONSE_ERROR_MESSAGES.PACKAGE_LOADING_FAILED, { cause: error }); } if (loader.loadedNodes.length > 0) { @@ -378,7 +378,7 @@ export class CommunityPackagesService { this.logger.info(`Community package installed: ${packageName}`); return installedPackage; } catch (error) { - throw new ApplicationError('Failed to save installed package', { + throw new UnexpectedError('Failed to save installed package', { extra: { packageName }, cause: error, }); @@ -388,7 +388,7 @@ export class CommunityPackagesService { try { await this.executeNpmCommand(`npm remove ${packageName}`); } catch {} - throw new ApplicationError(RESPONSE_ERROR_MESSAGES.PACKAGE_DOES_NOT_CONTAIN_NODES); + throw new UnexpectedError(RESPONSE_ERROR_MESSAGES.PACKAGE_DOES_NOT_CONTAIN_NODES); } } diff --git a/packages/cli/src/services/credentials-tester.service.ts b/packages/cli/src/services/credentials-tester.service.ts index 46d445640345d..c0a3fbb575f9e 100644 --- a/packages/cli/src/services/credentials-tester.service.ts +++ b/packages/cli/src/services/credentials-tester.service.ts @@ -32,7 +32,7 @@ import type { IDataObject, IExecuteData, } from 'n8n-workflow'; -import { VersionedNodeType, NodeHelpers, Workflow, ApplicationError } from 'n8n-workflow'; +import { VersionedNodeType, NodeHelpers, Workflow, UnexpectedError } from 'n8n-workflow'; import { CredentialTypes } from '@/credential-types'; import type { User } from '@/databases/entities/user'; @@ -62,7 +62,7 @@ const mockNodeTypes: INodeTypes = { }, getByNameAndVersion(nodeType: string, version?: number): INodeType { if (!mockNodesData[nodeType]) { - throw new ApplicationError(RESPONSE_ERROR_MESSAGES.NO_NODE, { + throw new UnexpectedError(RESPONSE_ERROR_MESSAGES.NO_NODE, { tags: { nodeType }, }); } diff --git a/packages/cli/src/services/dynamic-node-parameters.service.ts b/packages/cli/src/services/dynamic-node-parameters.service.ts index 1cbaf42363f1f..a060d09ffc594 100644 --- a/packages/cli/src/services/dynamic-node-parameters.service.ts +++ b/packages/cli/src/services/dynamic-node-parameters.service.ts @@ -21,7 +21,7 @@ import type { ILocalLoadOptionsFunctions, IExecuteData, } from 'n8n-workflow'; -import { Workflow, ApplicationError } from 'n8n-workflow'; +import { Workflow, UnexpectedError } from 'n8n-workflow'; import { NodeTypes } from '@/node-types'; @@ -91,7 +91,7 @@ export class DynamicNodeParametersService { // requiring a baseURL to be defined can at least not a random server be called. // In the future this code has to get improved that it does not use the request information from // the request rather resolves it via the parameter-path and nodeType data. - throw new ApplicationError( + throw new UnexpectedError( 'Node type does not exist or does not have "requestDefaults.baseURL" defined!', { tags: { nodeType: nodeType.description.name } }, ); @@ -152,7 +152,7 @@ export class DynamicNodeParametersService { } if (!Array.isArray(optionsData)) { - throw new ApplicationError('The returned data is not an array'); + throw new UnexpectedError('The returned data is not an array'); } return optionsData[0].map((item) => item.json) as unknown as INodePropertyOptions[]; @@ -258,7 +258,7 @@ export class DynamicNodeParametersService { ): NodeMethod { const method = nodeType.methods?.[type]?.[methodName] as NodeMethod; if (typeof method !== 'function') { - throw new ApplicationError('Node type does not have method defined', { + throw new UnexpectedError('Node type does not have method defined', { tags: { nodeType: nodeType.description.name }, extra: { methodName }, }); diff --git a/packages/cli/src/services/role.service.ts b/packages/cli/src/services/role.service.ts index e060c0dd81ced..1b210a62b7d01 100644 --- a/packages/cli/src/services/role.service.ts +++ b/packages/cli/src/services/role.service.ts @@ -1,7 +1,7 @@ import type { ProjectRole } from '@n8n/api-types'; import { Service } from '@n8n/di'; import { combineScopes, type Resource, type Scope } from '@n8n/permissions'; -import { ApplicationError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; import type { CredentialsEntity } from '@/databases/entities/credentials-entity'; import type { ProjectRelation } from '@/databases/entities/project-relation'; @@ -199,7 +199,7 @@ export class RoleService { } if (!('active' in entity) && !('type' in entity)) { - throw new ApplicationError('Cannot detect if entity is a workflow or credential.'); + throw new UnexpectedError('Cannot detect if entity is a workflow or credential.'); } entity.scopes = this.combineResourceScopes( diff --git a/packages/cli/src/services/user.service.ts b/packages/cli/src/services/user.service.ts index ab188fc9721a0..4eae487eb5fcf 100644 --- a/packages/cli/src/services/user.service.ts +++ b/packages/cli/src/services/user.service.ts @@ -1,7 +1,7 @@ import { Service } from '@n8n/di'; import { Logger } from 'n8n-core'; import type { IUserSettings } from 'n8n-workflow'; -import { ApplicationError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; import type { User, AssignableRole } from '@/databases/entities/user'; import { UserRepository } from '@/databases/repositories/user.repository'; @@ -68,7 +68,7 @@ export class UserService { }; if (options?.withInviteUrl && !options?.inviterId) { - throw new ApplicationError('Inviter ID is required to generate invite URL'); + throw new UnexpectedError('Inviter ID is required to generate invite URL'); } if (options?.withInviteUrl && options?.inviterId && publicUser.isPending) { diff --git a/packages/cli/src/services/workflow-loader.service.ts b/packages/cli/src/services/workflow-loader.service.ts index 5d5e08c1fe7ec..fdb91e2cb6da6 100644 --- a/packages/cli/src/services/workflow-loader.service.ts +++ b/packages/cli/src/services/workflow-loader.service.ts @@ -1,5 +1,5 @@ import { Service } from '@n8n/di'; -import { ApplicationError, type IWorkflowBase, type IWorkflowLoader } from 'n8n-workflow'; +import { UnexpectedError, type IWorkflowBase, type IWorkflowLoader } from 'n8n-workflow'; import { WorkflowRepository } from '@/databases/repositories/workflow.repository'; @@ -11,7 +11,7 @@ export class WorkflowLoaderService implements IWorkflowLoader { const workflow = await this.workflowRepository.findById(workflowId); if (!workflow) { - throw new ApplicationError(`Failed to find workflow with ID "${workflowId}"`); + throw new UnexpectedError(`Failed to find workflow with ID "${workflowId}"`); } return workflow; diff --git a/packages/cli/src/shutdown/shutdown.service.ts b/packages/cli/src/shutdown/shutdown.service.ts index d31ca9b154ab8..af8368bd9de36 100644 --- a/packages/cli/src/shutdown/shutdown.service.ts +++ b/packages/cli/src/shutdown/shutdown.service.ts @@ -1,7 +1,7 @@ import { Container, Service } from '@n8n/di'; import { type Class, ErrorReporter } from 'n8n-core'; import { Logger } from 'n8n-core'; -import { ApplicationError, assert } from 'n8n-workflow'; +import { ApplicationError, assert, UnexpectedError, UserError } from 'n8n-workflow'; import { LOWEST_SHUTDOWN_PRIORITY, HIGHEST_SHUTDOWN_PRIORITY } from '@/constants'; @@ -39,7 +39,7 @@ export class ShutdownService { /** Registers given listener to be notified when the application is shutting down */ register(priority: number, handler: ShutdownHandler) { if (priority < LOWEST_SHUTDOWN_PRIORITY || priority > HIGHEST_SHUTDOWN_PRIORITY) { - throw new ApplicationError( + throw new UserError( `Invalid shutdown priority. Please set it between ${LOWEST_SHUTDOWN_PRIORITY} and ${HIGHEST_SHUTDOWN_PRIORITY}.`, { extra: { priority } }, ); @@ -57,14 +57,14 @@ export class ShutdownService { for (const { serviceClass, methodName } of handlers) { if (!Container.has(serviceClass)) { - throw new ApplicationError( + throw new UserError( `Component "${serviceClass.name}" is not registered with the DI container. Any component using @OnShutdown() must be decorated with @Service()`, ); } const service = Container.get(serviceClass); if (!service[methodName]) { - throw new ApplicationError( + throw new UserError( `Component "${serviceClass.name}" does not have a "${methodName}" method`, ); } @@ -74,7 +74,7 @@ export class ShutdownService { /** Signals all registered listeners that the application is shutting down */ shutdown() { if (this.shutdownPromise) { - throw new ApplicationError('App is already shutting down'); + throw new UnexpectedError('App is already shutting down'); } this.shutdownPromise = this.startShutdown(); @@ -83,7 +83,7 @@ export class ShutdownService { /** Returns a promise that resolves when all the registered listeners have shut down */ async waitForShutdown(): Promise { if (!this.shutdownPromise) { - throw new ApplicationError('App is not shutting down'); + throw new UnexpectedError('App is not shutting down'); } await this.shutdownPromise; diff --git a/packages/cli/src/sso.ee/saml/saml.service.ee.ts b/packages/cli/src/sso.ee/saml/saml.service.ee.ts index a0d20e4102009..d01b4f5cc28c0 100644 --- a/packages/cli/src/sso.ee/saml/saml.service.ee.ts +++ b/packages/cli/src/sso.ee/saml/saml.service.ee.ts @@ -4,7 +4,7 @@ import axios from 'axios'; import type express from 'express'; import https from 'https'; import { Logger } from 'n8n-core'; -import { ApplicationError, jsonParse } from 'n8n-workflow'; +import { jsonParse, UnexpectedError } from 'n8n-workflow'; import type { IdentityProviderInstance, ServiceProviderInstance } from 'samlify'; import type { BindingContext, PostBindingContext } from 'samlify/types/src/entity'; @@ -124,7 +124,7 @@ export class SamlService { getIdentityProviderInstance(forceRecreate = false): IdentityProviderInstance { if (this.samlify === undefined) { - throw new ApplicationError('Samlify is not initialized'); + throw new UnexpectedError('Samlify is not initialized'); } if (this.identityProviderInstance === undefined || forceRecreate) { this.identityProviderInstance = this.samlify.IdentityProvider({ @@ -137,7 +137,7 @@ export class SamlService { getServiceProviderInstance(): ServiceProviderInstance { if (this.samlify === undefined) { - throw new ApplicationError('Samlify is not initialized'); + throw new UnexpectedError('Samlify is not initialized'); } return getServiceProviderInstance(this._samlPreferences, this.samlify); } diff --git a/packages/cli/src/task-runners/task-broker/task-broker-ws-server.ts b/packages/cli/src/task-runners/task-broker/task-broker-ws-server.ts index 7b651407175d4..6a5e1ab429b7c 100644 --- a/packages/cli/src/task-runners/task-broker/task-broker-ws-server.ts +++ b/packages/cli/src/task-runners/task-broker/task-broker-ws-server.ts @@ -2,7 +2,7 @@ import { TaskRunnersConfig } from '@n8n/config'; import { Service } from '@n8n/di'; import type { BrokerMessage, RunnerMessage } from '@n8n/task-runner'; import { Logger } from 'n8n-core'; -import { ApplicationError, jsonStringify } from 'n8n-workflow'; +import { jsonStringify, UserError } from 'n8n-workflow'; import type WebSocket from 'ws'; import { Time, WsStatusCodes } from '@/constants'; @@ -49,7 +49,7 @@ export class TaskBrokerWsServer { const { heartbeatInterval } = this.taskTunnersConfig; if (heartbeatInterval <= 0) { - throw new ApplicationError('Heartbeat interval must be greater than 0'); + throw new UserError('Heartbeat interval must be greater than 0'); } this.heartbeatTimer = setInterval(() => { diff --git a/packages/cli/src/task-runners/task-broker/task-broker.service.ts b/packages/cli/src/task-runners/task-broker/task-broker.service.ts index 3afd182914a48..2d702994ca0df 100644 --- a/packages/cli/src/task-runners/task-broker/task-broker.service.ts +++ b/packages/cli/src/task-runners/task-broker/task-broker.service.ts @@ -7,7 +7,7 @@ import type { TaskResultData, } from '@n8n/task-runner'; import { Logger } from 'n8n-core'; -import { ApplicationError } from 'n8n-workflow'; +import { UnexpectedError, UserError } from 'n8n-workflow'; import { nanoid } from 'nanoid'; import config from '@/config'; @@ -92,7 +92,7 @@ export class TaskBroker { private readonly taskRunnerLifecycleEvents: TaskRunnerLifecycleEvents, ) { if (this.taskRunnersConfig.taskTimeout <= 0) { - throw new ApplicationError('Task timeout must be greater than 0'); + throw new UserError('Task timeout must be greater than 0'); } } @@ -420,17 +420,12 @@ export class TaskBroker { private async getRunnerOrFailTask(taskId: Task['id']): Promise { const task = this.tasks.get(taskId); if (!task) { - throw new ApplicationError(`Cannot find runner, failed to find task (${taskId})`, { - level: 'error', - }); + throw new UnexpectedError(`Cannot find runner, failed to find task (${taskId})`); } const runner = this.knownRunners.get(task.runnerId); if (!runner) { - const error = new ApplicationError( + const error = new UnexpectedError( `Cannot find runner, failed to find runner (${task.runnerId})`, - { - level: 'error', - }, ); await this.failTask(taskId, error); throw error; diff --git a/packages/cli/src/wait-tracker.ts b/packages/cli/src/wait-tracker.ts index d3c9d54c3aaea..cca69548bd729 100644 --- a/packages/cli/src/wait-tracker.ts +++ b/packages/cli/src/wait-tracker.ts @@ -1,6 +1,6 @@ import { Service } from '@n8n/di'; import { InstanceSettings, Logger } from 'n8n-core'; -import { ApplicationError, type IWorkflowExecutionDataProcess } from 'n8n-workflow'; +import { UnexpectedError, type IWorkflowExecutionDataProcess } from 'n8n-workflow'; import { ActiveExecutions } from '@/active-executions'; import { ExecutionRepository } from '@/databases/repositories/execution.repository'; @@ -110,14 +110,14 @@ export class WaitTracker { }); if (!fullExecutionData) { - throw new ApplicationError('Execution does not exist.', { extra: { executionId } }); + throw new UnexpectedError('Execution does not exist.', { extra: { executionId } }); } if (fullExecutionData.finished) { - throw new ApplicationError('The execution did succeed and can so not be started again.'); + throw new UnexpectedError('The execution did succeed and can so not be started again.'); } if (!fullExecutionData.workflowData.id) { - throw new ApplicationError('Only saved workflows can be resumed.'); + throw new UnexpectedError('Only saved workflows can be resumed.'); } const workflowId = fullExecutionData.workflowData.id; diff --git a/packages/cli/src/webhooks/webhook.service.ts b/packages/cli/src/webhooks/webhook.service.ts index 1a3dbf37cbb53..feacb6412c3c3 100644 --- a/packages/cli/src/webhooks/webhook.service.ts +++ b/packages/cli/src/webhooks/webhook.service.ts @@ -1,6 +1,6 @@ import { Service } from '@n8n/di'; import { HookContext, WebhookContext, Logger } from 'n8n-core'; -import { ApplicationError, Node, NodeHelpers } from 'n8n-workflow'; +import { Node, NodeHelpers, UnexpectedError } from 'n8n-workflow'; import type { IHttpRequestMethods, INode, @@ -321,7 +321,7 @@ export class WebhookService { ): Promise { const nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion); if (nodeType.webhook === undefined) { - throw new ApplicationError('Node does not have any webhooks defined', { + throw new UnexpectedError('Node does not have any webhooks defined', { extra: { nodeName: node.name }, }); } diff --git a/packages/cli/src/workflow-execute-additional-data.ts b/packages/cli/src/workflow-execute-additional-data.ts index a83ee5b26366d..43b478703eeb8 100644 --- a/packages/cli/src/workflow-execute-additional-data.ts +++ b/packages/cli/src/workflow-execute-additional-data.ts @@ -6,7 +6,7 @@ import type { PushMessage, PushType } from '@n8n/api-types'; import { GlobalConfig } from '@n8n/config'; import { Container } from '@n8n/di'; import { Logger, WorkflowExecute } from 'n8n-core'; -import { ApplicationError, Workflow } from 'n8n-workflow'; +import { UnexpectedError, Workflow } from 'n8n-workflow'; import type { IDataObject, IExecuteData, @@ -105,7 +105,7 @@ export async function getWorkflowData( parentWorkflowSettings?: IWorkflowSettings, ): Promise { if (workflowInfo.id === undefined && workflowInfo.code === undefined) { - throw new ApplicationError( + throw new UnexpectedError( 'No information about the workflow to execute found. Please provide either the "id" or "code"!', ); } @@ -120,7 +120,7 @@ export async function getWorkflowData( ); if (workflowData === undefined || workflowData === null) { - throw new ApplicationError('Workflow does not exist.', { + throw new UnexpectedError('Workflow does not exist.', { extra: { workflowId: workflowInfo.id }, }); } diff --git a/packages/cli/src/workflows/workflow.service.ee.ts b/packages/cli/src/workflows/workflow.service.ee.ts index 4ff8bbc469a66..b2ce3630a643a 100644 --- a/packages/cli/src/workflows/workflow.service.ee.ts +++ b/packages/cli/src/workflows/workflow.service.ee.ts @@ -4,7 +4,7 @@ import { In, type EntityManager } from '@n8n/typeorm'; import omit from 'lodash/omit'; import { Logger } from 'n8n-core'; import type { IWorkflowBase, WorkflowId } from 'n8n-workflow'; -import { ApplicationError, NodeOperationError, WorkflowActivationError } from 'n8n-workflow'; +import { NodeOperationError, UserError, WorkflowActivationError } from 'n8n-workflow'; import { ActiveWorkflowManager } from '@/active-workflow-manager'; import { CredentialsService } from '@/credentials/credentials.service'; @@ -142,9 +142,7 @@ export class EnterpriseWorkflowService { if (credentialId === undefined) return; const matchedCredential = allowedCredentials.find(({ id }) => id === credentialId); if (!matchedCredential) { - throw new ApplicationError( - 'The workflow contains credentials that you do not have access to', - ); + throw new UserError('The workflow contains credentials that you do not have access to'); } }); }); diff --git a/packages/cli/src/workflows/workflows.controller.ts b/packages/cli/src/workflows/workflows.controller.ts index b4b0136e4e592..6b78888aee45d 100644 --- a/packages/cli/src/workflows/workflows.controller.ts +++ b/packages/cli/src/workflows/workflows.controller.ts @@ -9,7 +9,7 @@ import { In, type FindOptionsRelations } from '@n8n/typeorm'; import axios from 'axios'; import express from 'express'; import { Logger } from 'n8n-core'; -import { ApplicationError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; import { v4 as uuid } from 'uuid'; import type { Project } from '@/databases/entities/project'; @@ -152,7 +152,7 @@ export class WorkflowsController { // Safe guard in case the personal project does not exist for whatever reason. if (project === null) { - throw new ApplicationError('No personal project found'); + throw new UnexpectedError('No personal project found'); } const newSharedWorkflow = this.sharedWorkflowRepository.create({ @@ -388,15 +388,11 @@ export class WorkflowsController { @Query query: ManualRunQueryDto, ) { if (!req.body.workflowData.id) { - throw new ApplicationError('You cannot execute a workflow without an ID', { - level: 'warning', - }); + throw new UnexpectedError('You cannot execute a workflow without an ID'); } if (req.params.workflowId !== req.body.workflowData.id) { - throw new ApplicationError('Workflow ID in body does not match workflow ID in URL', { - level: 'warning', - }); + throw new UnexpectedError('Workflow ID in body does not match workflow ID in URL'); } if (this.license.isSharingEnabled()) { From 26f2194fd18b66e62ec6349dfef0cd8f000b025c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Mon, 24 Feb 2025 17:18:48 +0100 Subject: [PATCH 02/11] Go through all extends --- packages/cli/src/commands/ldap/reset.ts | 2 +- .../source-control/source-control-export.service.ee.ts | 7 +------ .../source-control/source-control-helper.ee.ts | 2 +- packages/cli/src/errors/aborted-execution-retry.error.ts | 8 +++----- .../errors/cache-errors/malformed-refresh-value.error.ts | 4 ++-- .../src/errors/cache-errors/uncacheable-value.error.ts | 4 ++-- packages/cli/src/errors/credential-not-found.error.ts | 5 ++--- .../errors/credentials-overwrites-already-set.error.ts | 4 ++-- packages/cli/src/errors/deduplication.error.ts | 4 ++-- packages/cli/src/errors/execution-not-found-error.ts | 4 ++-- .../errors/external-secrets-provider-not-found.error.ts | 4 ++-- packages/cli/src/errors/feature-not-licensed.error.ts | 4 ++-- .../cli/src/errors/invalid-concurrency-limit.error.ts | 6 +++--- packages/cli/src/errors/invalid-role.error.ts | 4 ++-- packages/cli/src/errors/max-stalled-count.error.ts | 4 ++-- packages/cli/src/errors/missing-execution-stop.error.ts | 4 ++-- packages/cli/src/errors/non-json-body.error.ts | 4 ++-- packages/cli/src/errors/not-string-array.error.ts | 4 ++-- .../cli/src/errors/postgres-live-rows-retrieval.error.ts | 4 ++-- packages/cli/src/errors/queued-execution-retry.error.ts | 8 +++----- packages/cli/src/errors/redactable.error.ts | 4 ++-- .../src/errors/response-errors/abstract/response.error.ts | 4 ++-- .../cli/src/errors/shared-workflow-not-found.error.ts | 4 ++-- packages/cli/src/errors/unknown-auth-type.error.ts | 4 ++-- packages/cli/src/errors/unknown-execution-mode.error.ts | 4 ++-- .../cli/src/errors/variable-count-limit-reached.error.ts | 4 ++-- packages/cli/src/errors/variable-validation.error.ts | 4 ++-- .../cli/src/errors/worker-missing-encryption-key.error.ts | 5 ++--- .../errors/workflow-history-version-not-found.error.ts | 4 ++-- packages/cli/src/errors/workflow-missing-id.error.ts | 4 ++-- packages/cli/src/evaluation.ee/test-runner/errors.ee.ts | 6 +++--- packages/cli/src/services/project.service.ee.ts | 6 +++--- packages/cli/src/shutdown/shutdown.service.ts | 5 ++--- .../src/sso.ee/saml/errors/invalid-saml-metadata.error.ts | 6 +++--- .../task-runners/errors/task-runner-disconnected-error.ts | 4 ++-- .../errors/task-runner-failed-heartbeat.error.ts | 4 ++-- .../cli/src/task-runners/errors/task-runner-oom-error.ts | 6 +++--- .../task-runners/errors/task-runner-restart-loop-error.ts | 8 +++----- .../task-broker/errors/task-deferred.error.ts | 6 +++--- .../task-runners/task-broker/errors/task-reject.error.ts | 6 +++--- .../task-broker/errors/task-runner-timeout.error.ts | 4 ++-- 41 files changed, 89 insertions(+), 103 deletions(-) diff --git a/packages/cli/src/commands/ldap/reset.ts b/packages/cli/src/commands/ldap/reset.ts index c38781f6d66a8..3a9d377fd582c 100644 --- a/packages/cli/src/commands/ldap/reset.ts +++ b/packages/cli/src/commands/ldap/reset.ts @@ -2,7 +2,7 @@ import { Container } from '@n8n/di'; // eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import import { In } from '@n8n/typeorm'; import { Flags } from '@oclif/core'; -import { ApplicationError, UserError } from 'n8n-workflow'; +import { UserError } from 'n8n-workflow'; import { UM_FIX_INSTRUCTION } from '@/constants'; import { CredentialsService } from '@/credentials/credentials.service'; diff --git a/packages/cli/src/environments.ee/source-control/source-control-export.service.ee.ts b/packages/cli/src/environments.ee/source-control/source-control-export.service.ee.ts index 3a2505cc757ef..8318f63ff3ed7 100644 --- a/packages/cli/src/environments.ee/source-control/source-control-export.service.ee.ts +++ b/packages/cli/src/environments.ee/source-control/source-control-export.service.ee.ts @@ -2,11 +2,7 @@ import type { SourceControlledFile } from '@n8n/api-types'; import { Service } from '@n8n/di'; import { rmSync } from 'fs'; import { Credentials, InstanceSettings, Logger } from 'n8n-core'; -import { - ApplicationError, - UnexpectedError, - type ICredentialDataDecryptedObject, -} from 'n8n-workflow'; +import { UnexpectedError, type ICredentialDataDecryptedObject } from 'n8n-workflow'; import { writeFile as fsWriteFile, rm as fsRm } from 'node:fs/promises'; import path from 'path'; @@ -168,7 +164,6 @@ export class SourceControlExportService { })), }; } catch (error) { - if (error instanceof ApplicationError) throw error; throw new UnexpectedError('Failed to export workflows to work folder', { cause: error }); } } diff --git a/packages/cli/src/environments.ee/source-control/source-control-helper.ee.ts b/packages/cli/src/environments.ee/source-control/source-control-helper.ee.ts index 43132018a2a74..8e98fbdc8e536 100644 --- a/packages/cli/src/environments.ee/source-control/source-control-helper.ee.ts +++ b/packages/cli/src/environments.ee/source-control/source-control-helper.ee.ts @@ -171,7 +171,7 @@ export function getTrackingInformationFromPostPushResult(result: SourceControlle * Normalizes and validates the given source controlled file path. Ensures * the path is absolute and contained within the git folder. * - * @throws {ApplicationError} If the path is not within the git folder + * @throws {UserError} If the path is not within the git folder */ export function normalizeAndValidateSourceControlledFilePath( gitFolderPath: string, diff --git a/packages/cli/src/errors/aborted-execution-retry.error.ts b/packages/cli/src/errors/aborted-execution-retry.error.ts index 20d8b57e14087..88ba834ee06b5 100644 --- a/packages/cli/src/errors/aborted-execution-retry.error.ts +++ b/packages/cli/src/errors/aborted-execution-retry.error.ts @@ -1,9 +1,7 @@ -import { ApplicationError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; -export class AbortedExecutionRetryError extends ApplicationError { +export class AbortedExecutionRetryError extends UnexpectedError { constructor() { - super('The execution was aborted before starting, so it cannot be retried', { - level: 'warning', - }); + super('The execution was aborted before starting, so it cannot be retried'); } } diff --git a/packages/cli/src/errors/cache-errors/malformed-refresh-value.error.ts b/packages/cli/src/errors/cache-errors/malformed-refresh-value.error.ts index 178cbc85826ab..e008bce831ec9 100644 --- a/packages/cli/src/errors/cache-errors/malformed-refresh-value.error.ts +++ b/packages/cli/src/errors/cache-errors/malformed-refresh-value.error.ts @@ -1,6 +1,6 @@ -import { ApplicationError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; -export class MalformedRefreshValueError extends ApplicationError { +export class MalformedRefreshValueError extends UnexpectedError { constructor() { super('Refresh value must have the same number of values as keys'); } diff --git a/packages/cli/src/errors/cache-errors/uncacheable-value.error.ts b/packages/cli/src/errors/cache-errors/uncacheable-value.error.ts index 7888ee97b2b41..83f859e99ded1 100644 --- a/packages/cli/src/errors/cache-errors/uncacheable-value.error.ts +++ b/packages/cli/src/errors/cache-errors/uncacheable-value.error.ts @@ -1,6 +1,6 @@ -import { ApplicationError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; -export class UncacheableValueError extends ApplicationError { +export class UncacheableValueError extends UnexpectedError { constructor(key: string) { super('Value cannot be cached in Redis', { extra: { key, hint: 'Does the value contain circular references?' }, diff --git a/packages/cli/src/errors/credential-not-found.error.ts b/packages/cli/src/errors/credential-not-found.error.ts index ac47a0eaea261..28829a397d94d 100644 --- a/packages/cli/src/errors/credential-not-found.error.ts +++ b/packages/cli/src/errors/credential-not-found.error.ts @@ -1,8 +1,7 @@ -import { ApplicationError } from 'n8n-workflow'; +import { UserError } from 'n8n-workflow'; -export class CredentialNotFoundError extends ApplicationError { +export class CredentialNotFoundError extends UserError { constructor(credentialId: string, credentialType: string) { super(`Credential with ID "${credentialId}" does not exist for type "${credentialType}".`); - this.level = 'warning'; } } diff --git a/packages/cli/src/errors/credentials-overwrites-already-set.error.ts b/packages/cli/src/errors/credentials-overwrites-already-set.error.ts index 4c7534c4c1723..84480d641a283 100644 --- a/packages/cli/src/errors/credentials-overwrites-already-set.error.ts +++ b/packages/cli/src/errors/credentials-overwrites-already-set.error.ts @@ -1,6 +1,6 @@ -import { ApplicationError } from 'n8n-workflow'; +import { UserError } from 'n8n-workflow'; -export class CredentialsOverwritesAlreadySetError extends ApplicationError { +export class CredentialsOverwritesAlreadySetError extends UserError { constructor() { super('Credentials overwrites may not be set more than once.'); } diff --git a/packages/cli/src/errors/deduplication.error.ts b/packages/cli/src/errors/deduplication.error.ts index 8e9173abb9014..996a41fc36287 100644 --- a/packages/cli/src/errors/deduplication.error.ts +++ b/packages/cli/src/errors/deduplication.error.ts @@ -1,6 +1,6 @@ -import { ApplicationError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; -export class DeduplicationError extends ApplicationError { +export class DeduplicationError extends UnexpectedError { constructor(message: string) { super(`Deduplication Failed: ${message}`); } diff --git a/packages/cli/src/errors/execution-not-found-error.ts b/packages/cli/src/errors/execution-not-found-error.ts index 45e0f02033de3..f6e6f3a7c4a3c 100644 --- a/packages/cli/src/errors/execution-not-found-error.ts +++ b/packages/cli/src/errors/execution-not-found-error.ts @@ -1,6 +1,6 @@ -import { ApplicationError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; -export class ExecutionNotFoundError extends ApplicationError { +export class ExecutionNotFoundError extends UnexpectedError { constructor(executionId: string) { super('No active execution found', { extra: { executionId } }); } diff --git a/packages/cli/src/errors/external-secrets-provider-not-found.error.ts b/packages/cli/src/errors/external-secrets-provider-not-found.error.ts index 422236d14859f..92fd1384a41b8 100644 --- a/packages/cli/src/errors/external-secrets-provider-not-found.error.ts +++ b/packages/cli/src/errors/external-secrets-provider-not-found.error.ts @@ -1,6 +1,6 @@ -import { ApplicationError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; -export class ExternalSecretsProviderNotFoundError extends ApplicationError { +export class ExternalSecretsProviderNotFoundError extends UnexpectedError { constructor(public providerName: string) { super(`External secrets provider not found: ${providerName}`); } diff --git a/packages/cli/src/errors/feature-not-licensed.error.ts b/packages/cli/src/errors/feature-not-licensed.error.ts index aa53655154fa4..0c264777e529c 100644 --- a/packages/cli/src/errors/feature-not-licensed.error.ts +++ b/packages/cli/src/errors/feature-not-licensed.error.ts @@ -1,8 +1,8 @@ -import { ApplicationError } from 'n8n-workflow'; +import { UserError } from 'n8n-workflow'; import type { LICENSE_FEATURES } from '@/constants'; -export class FeatureNotLicensedError extends ApplicationError { +export class FeatureNotLicensedError extends UserError { constructor(feature: (typeof LICENSE_FEATURES)[keyof typeof LICENSE_FEATURES]) { super( `Your license does not allow for ${feature}. To enable ${feature}, please upgrade to a license that supports this feature.`, diff --git a/packages/cli/src/errors/invalid-concurrency-limit.error.ts b/packages/cli/src/errors/invalid-concurrency-limit.error.ts index 3f59373525bd5..48c584b8b0fae 100644 --- a/packages/cli/src/errors/invalid-concurrency-limit.error.ts +++ b/packages/cli/src/errors/invalid-concurrency-limit.error.ts @@ -1,7 +1,7 @@ -import { ApplicationError } from 'n8n-workflow'; +import { UserError } from 'n8n-workflow'; -export class InvalidConcurrencyLimitError extends ApplicationError { +export class InvalidConcurrencyLimitError extends UserError { constructor(value: number) { - super('Concurrency limit set to invalid value', { level: 'warning', extra: { value } }); + super('Concurrency limit set to invalid value', { extra: { value } }); } } diff --git a/packages/cli/src/errors/invalid-role.error.ts b/packages/cli/src/errors/invalid-role.error.ts index fc5ebb8aa90f1..7e14bdc2e12d1 100644 --- a/packages/cli/src/errors/invalid-role.error.ts +++ b/packages/cli/src/errors/invalid-role.error.ts @@ -1,3 +1,3 @@ -import { ApplicationError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; -export class InvalidRoleError extends ApplicationError {} +export class InvalidRoleError extends UnexpectedError {} diff --git a/packages/cli/src/errors/max-stalled-count.error.ts b/packages/cli/src/errors/max-stalled-count.error.ts index 38f73023a7485..cea5250f4cfc9 100644 --- a/packages/cli/src/errors/max-stalled-count.error.ts +++ b/packages/cli/src/errors/max-stalled-count.error.ts @@ -1,9 +1,9 @@ -import { ApplicationError } from 'n8n-workflow'; +import { UserError } from 'n8n-workflow'; /** * @docs https://docs.bullmq.io/guide/workers/stalled-jobs */ -export class MaxStalledCountError extends ApplicationError { +export class MaxStalledCountError extends UserError { constructor(cause: Error) { super( 'This execution failed to be processed too many times and will no longer retry. To allow this execution to complete, please break down your workflow or scale up your workers or adjust your worker settings.', diff --git a/packages/cli/src/errors/missing-execution-stop.error.ts b/packages/cli/src/errors/missing-execution-stop.error.ts index 3662cc4a72f6d..6190b3f783c91 100644 --- a/packages/cli/src/errors/missing-execution-stop.error.ts +++ b/packages/cli/src/errors/missing-execution-stop.error.ts @@ -1,6 +1,6 @@ -import { ApplicationError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; -export class MissingExecutionStopError extends ApplicationError { +export class MissingExecutionStopError extends UnexpectedError { constructor(executionId: string) { super('Failed to find execution to stop', { extra: { executionId } }); } diff --git a/packages/cli/src/errors/non-json-body.error.ts b/packages/cli/src/errors/non-json-body.error.ts index cd2086dbf5e86..b6da36aead9d8 100644 --- a/packages/cli/src/errors/non-json-body.error.ts +++ b/packages/cli/src/errors/non-json-body.error.ts @@ -1,6 +1,6 @@ -import { ApplicationError } from 'n8n-workflow'; +import { UserError } from 'n8n-workflow'; -export class NonJsonBodyError extends ApplicationError { +export class NonJsonBodyError extends UserError { constructor() { super('Body must be valid JSON. Please make sure `content-type` is `application/json`.'); } diff --git a/packages/cli/src/errors/not-string-array.error.ts b/packages/cli/src/errors/not-string-array.error.ts index 22e9fae90282b..7a9c2f1bc69f7 100644 --- a/packages/cli/src/errors/not-string-array.error.ts +++ b/packages/cli/src/errors/not-string-array.error.ts @@ -1,6 +1,6 @@ -import { ApplicationError } from 'n8n-workflow'; +import { UserError } from 'n8n-workflow'; -export class NotStringArrayError extends ApplicationError { +export class NotStringArrayError extends UserError { constructor(env: string) { super(`${env} is not a string array.`); } diff --git a/packages/cli/src/errors/postgres-live-rows-retrieval.error.ts b/packages/cli/src/errors/postgres-live-rows-retrieval.error.ts index ea97f80684d52..0bd59000cbdc2 100644 --- a/packages/cli/src/errors/postgres-live-rows-retrieval.error.ts +++ b/packages/cli/src/errors/postgres-live-rows-retrieval.error.ts @@ -1,6 +1,6 @@ -import { ApplicationError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; -export class PostgresLiveRowsRetrievalError extends ApplicationError { +export class PostgresLiveRowsRetrievalError extends UnexpectedError { constructor(rows: unknown) { super('Failed to retrieve live execution rows in Postgres', { extra: { rows } }); } diff --git a/packages/cli/src/errors/queued-execution-retry.error.ts b/packages/cli/src/errors/queued-execution-retry.error.ts index a3c0bcb588d5d..fae616c718b7e 100644 --- a/packages/cli/src/errors/queued-execution-retry.error.ts +++ b/packages/cli/src/errors/queued-execution-retry.error.ts @@ -1,9 +1,7 @@ -import { ApplicationError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; -export class QueuedExecutionRetryError extends ApplicationError { +export class QueuedExecutionRetryError extends UnexpectedError { constructor() { - super('Execution is queued to run (not yet started) so it cannot be retried', { - level: 'warning', - }); + super('Execution is queued to run (not yet started) so it cannot be retried', {}); } } diff --git a/packages/cli/src/errors/redactable.error.ts b/packages/cli/src/errors/redactable.error.ts index 0d5b07ac504fb..ecc492d22b88c 100644 --- a/packages/cli/src/errors/redactable.error.ts +++ b/packages/cli/src/errors/redactable.error.ts @@ -1,6 +1,6 @@ -import { ApplicationError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; -export class RedactableError extends ApplicationError { +export class RedactableError extends UnexpectedError { constructor(fieldName: string, args: string) { super( `Failed to find "${fieldName}" property in argument "${args.toString()}". Please set the decorator \`@Redactable()\` only on \`LogStreamingEventRelay\` methods where the argument contains a "${fieldName}" property.`, diff --git a/packages/cli/src/errors/response-errors/abstract/response.error.ts b/packages/cli/src/errors/response-errors/abstract/response.error.ts index fa7d4a8b0693e..0e3d34b164e76 100644 --- a/packages/cli/src/errors/response-errors/abstract/response.error.ts +++ b/packages/cli/src/errors/response-errors/abstract/response.error.ts @@ -1,9 +1,9 @@ -import { ApplicationError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; /** * Special Error which allows to return also an error code and http status code */ -export abstract class ResponseError extends ApplicationError { +export abstract class ResponseError extends UnexpectedError { /** * Creates an instance of ResponseError. * Must be used inside a block with `ResponseHelper.send()`. diff --git a/packages/cli/src/errors/shared-workflow-not-found.error.ts b/packages/cli/src/errors/shared-workflow-not-found.error.ts index ae4c99f3f302b..64463dea6b9ff 100644 --- a/packages/cli/src/errors/shared-workflow-not-found.error.ts +++ b/packages/cli/src/errors/shared-workflow-not-found.error.ts @@ -1,3 +1,3 @@ -import { ApplicationError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; -export class SharedWorkflowNotFoundError extends ApplicationError {} +export class SharedWorkflowNotFoundError extends UnexpectedError {} diff --git a/packages/cli/src/errors/unknown-auth-type.error.ts b/packages/cli/src/errors/unknown-auth-type.error.ts index b8ac45bf8e6fd..0c40ff135d5d0 100644 --- a/packages/cli/src/errors/unknown-auth-type.error.ts +++ b/packages/cli/src/errors/unknown-auth-type.error.ts @@ -1,6 +1,6 @@ -import { ApplicationError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; -export class UnknownAuthTypeError extends ApplicationError { +export class UnknownAuthTypeError extends UnexpectedError { constructor(authType: string) { super('Unknown auth type', { extra: { authType } }); } diff --git a/packages/cli/src/errors/unknown-execution-mode.error.ts b/packages/cli/src/errors/unknown-execution-mode.error.ts index 8350bfa4afde1..8b30b4fec8454 100644 --- a/packages/cli/src/errors/unknown-execution-mode.error.ts +++ b/packages/cli/src/errors/unknown-execution-mode.error.ts @@ -1,6 +1,6 @@ -import { ApplicationError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; -export class UnknownExecutionModeError extends ApplicationError { +export class UnknownExecutionModeError extends UnexpectedError { constructor(mode: string) { super('Unknown execution mode', { extra: { mode } }); } diff --git a/packages/cli/src/errors/variable-count-limit-reached.error.ts b/packages/cli/src/errors/variable-count-limit-reached.error.ts index 67d14efebf605..ac1bdc0059fff 100644 --- a/packages/cli/src/errors/variable-count-limit-reached.error.ts +++ b/packages/cli/src/errors/variable-count-limit-reached.error.ts @@ -1,3 +1,3 @@ -import { ApplicationError } from 'n8n-workflow'; +import { UserError } from 'n8n-workflow'; -export class VariableCountLimitReachedError extends ApplicationError {} +export class VariableCountLimitReachedError extends UserError {} diff --git a/packages/cli/src/errors/variable-validation.error.ts b/packages/cli/src/errors/variable-validation.error.ts index bd2026e7ef8b7..4e5a97407e348 100644 --- a/packages/cli/src/errors/variable-validation.error.ts +++ b/packages/cli/src/errors/variable-validation.error.ts @@ -1,3 +1,3 @@ -import { ApplicationError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; -export class VariableValidationError extends ApplicationError {} +export class VariableValidationError extends UnexpectedError {} diff --git a/packages/cli/src/errors/worker-missing-encryption-key.error.ts b/packages/cli/src/errors/worker-missing-encryption-key.error.ts index 29b8dad929a82..788b982baf909 100644 --- a/packages/cli/src/errors/worker-missing-encryption-key.error.ts +++ b/packages/cli/src/errors/worker-missing-encryption-key.error.ts @@ -1,6 +1,6 @@ -import { ApplicationError } from 'n8n-workflow'; +import { UserError } from 'n8n-workflow'; -export class WorkerMissingEncryptionKey extends ApplicationError { +export class WorkerMissingEncryptionKey extends UserError { constructor() { super( [ @@ -8,7 +8,6 @@ export class WorkerMissingEncryptionKey extends ApplicationError { 'Please set the `N8N_ENCRYPTION_KEY` env var when starting the worker.', 'See: https://docs.n8n.io/hosting/configuration/configuration-examples/encryption-key/', ].join(' '), - { level: 'warning' }, ); } } diff --git a/packages/cli/src/errors/workflow-history-version-not-found.error.ts b/packages/cli/src/errors/workflow-history-version-not-found.error.ts index 104385672fcc5..177ee22d9aad7 100644 --- a/packages/cli/src/errors/workflow-history-version-not-found.error.ts +++ b/packages/cli/src/errors/workflow-history-version-not-found.error.ts @@ -1,3 +1,3 @@ -import { ApplicationError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; -export class WorkflowHistoryVersionNotFoundError extends ApplicationError {} +export class WorkflowHistoryVersionNotFoundError extends UnexpectedError {} diff --git a/packages/cli/src/errors/workflow-missing-id.error.ts b/packages/cli/src/errors/workflow-missing-id.error.ts index fe9db748a65ae..c709b75356537 100644 --- a/packages/cli/src/errors/workflow-missing-id.error.ts +++ b/packages/cli/src/errors/workflow-missing-id.error.ts @@ -1,7 +1,7 @@ import type { Workflow, IWorkflowBase } from 'n8n-workflow'; -import { ApplicationError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; -export class WorkflowMissingIdError extends ApplicationError { +export class WorkflowMissingIdError extends UnexpectedError { constructor(workflow: Workflow | IWorkflowBase) { super('Detected ID-less worklfow', { extra: { workflow } }); } diff --git a/packages/cli/src/evaluation.ee/test-runner/errors.ee.ts b/packages/cli/src/evaluation.ee/test-runner/errors.ee.ts index 05b88903fbb64..0b2df294b2cec 100644 --- a/packages/cli/src/evaluation.ee/test-runner/errors.ee.ts +++ b/packages/cli/src/evaluation.ee/test-runner/errors.ee.ts @@ -1,4 +1,4 @@ -import { ApplicationError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; export type TestCaseExecutionErrorCode = | 'MOCKED_NODE_DOES_NOT_EXIST' @@ -12,7 +12,7 @@ export type TestCaseExecutionErrorCode = | 'PAYLOAD_LIMIT_EXCEEDED' | 'UNKNOWN_ERROR'; -export class TestCaseExecutionError extends ApplicationError { +export class TestCaseExecutionError extends UnexpectedError { readonly code: TestCaseExecutionErrorCode; constructor(code: TestCaseExecutionErrorCode, extra: Record = {}) { @@ -28,7 +28,7 @@ export type TestRunErrorCode = | 'INTERRUPTED' | 'UNKNOWN_ERROR'; -export class TestRunError extends ApplicationError { +export class TestRunError extends UnexpectedError { readonly code: TestRunErrorCode; constructor(code: TestRunErrorCode, extra: Record = {}) { diff --git a/packages/cli/src/services/project.service.ee.ts b/packages/cli/src/services/project.service.ee.ts index 9eb3f8efa14d5..95306bddf7b87 100644 --- a/packages/cli/src/services/project.service.ee.ts +++ b/packages/cli/src/services/project.service.ee.ts @@ -5,7 +5,7 @@ import { type Scope } from '@n8n/permissions'; import type { FindOptionsWhere, EntityManager } from '@n8n/typeorm'; // eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import import { In, Not } from '@n8n/typeorm'; -import { ApplicationError } from 'n8n-workflow'; +import { UserError } from 'n8n-workflow'; import { UNLIMITED_LICENSE_QUOTA } from '@/constants'; import { Project } from '@/databases/entities/project'; @@ -23,7 +23,7 @@ import { License } from '@/license'; import { CacheService } from './cache/cache.service'; import { RoleService } from './role.service'; -export class TeamProjectOverQuotaError extends ApplicationError { +export class TeamProjectOverQuotaError extends UserError { constructor(limit: number) { super( `Attempted to create a new project but quota is already exhausted. You may have a maximum of ${limit} team projects.`, @@ -31,7 +31,7 @@ export class TeamProjectOverQuotaError extends ApplicationError { } } -export class UnlicensedProjectRoleError extends ApplicationError { +export class UnlicensedProjectRoleError extends UserError { constructor(role: ProjectRole) { super(`Your instance is not licensed to use role "${role}".`); } diff --git a/packages/cli/src/shutdown/shutdown.service.ts b/packages/cli/src/shutdown/shutdown.service.ts index af8368bd9de36..aff01b087579b 100644 --- a/packages/cli/src/shutdown/shutdown.service.ts +++ b/packages/cli/src/shutdown/shutdown.service.ts @@ -1,7 +1,7 @@ import { Container, Service } from '@n8n/di'; import { type Class, ErrorReporter } from 'n8n-core'; import { Logger } from 'n8n-core'; -import { ApplicationError, assert, UnexpectedError, UserError } from 'n8n-workflow'; +import { assert, UnexpectedError, UserError } from 'n8n-workflow'; import { LOWEST_SHUTDOWN_PRIORITY, HIGHEST_SHUTDOWN_PRIORITY } from '@/constants'; @@ -14,10 +14,9 @@ export interface ShutdownHandler { } /** Error reported when a listener fails to shutdown gracefully */ -export class ComponentShutdownError extends ApplicationError { +export class ComponentShutdownError extends UnexpectedError { constructor(componentName: string, cause: Error) { super('Failed to shutdown gracefully', { - level: 'error', cause, extra: { component: componentName }, }); diff --git a/packages/cli/src/sso.ee/saml/errors/invalid-saml-metadata.error.ts b/packages/cli/src/sso.ee/saml/errors/invalid-saml-metadata.error.ts index 3d2ceaddb6523..7c228a71c69bf 100644 --- a/packages/cli/src/sso.ee/saml/errors/invalid-saml-metadata.error.ts +++ b/packages/cli/src/sso.ee/saml/errors/invalid-saml-metadata.error.ts @@ -1,7 +1,7 @@ -import { ApplicationError } from 'n8n-workflow'; +import { UserError } from 'n8n-workflow'; -export class InvalidSamlMetadataError extends ApplicationError { +export class InvalidSamlMetadataError extends UserError { constructor() { - super('Invalid SAML metadata', { level: 'warning' }); + super('Invalid SAML metadata'); } } diff --git a/packages/cli/src/task-runners/errors/task-runner-disconnected-error.ts b/packages/cli/src/task-runners/errors/task-runner-disconnected-error.ts index e29958adfba79..072148a93411f 100644 --- a/packages/cli/src/task-runners/errors/task-runner-disconnected-error.ts +++ b/packages/cli/src/task-runners/errors/task-runner-disconnected-error.ts @@ -1,7 +1,7 @@ import type { TaskRunner } from '@n8n/task-runner'; -import { ApplicationError } from 'n8n-workflow'; +import { UserError } from 'n8n-workflow'; -export class TaskRunnerDisconnectedError extends ApplicationError { +export class TaskRunnerDisconnectedError extends UserError { description: string; constructor( diff --git a/packages/cli/src/task-runners/errors/task-runner-failed-heartbeat.error.ts b/packages/cli/src/task-runners/errors/task-runner-failed-heartbeat.error.ts index 55b94485740ff..3fded78d41a4a 100644 --- a/packages/cli/src/task-runners/errors/task-runner-failed-heartbeat.error.ts +++ b/packages/cli/src/task-runners/errors/task-runner-failed-heartbeat.error.ts @@ -1,6 +1,6 @@ -import { ApplicationError } from 'n8n-workflow'; +import { UserError } from 'n8n-workflow'; -export class TaskRunnerFailedHeartbeatError extends ApplicationError { +export class TaskRunnerFailedHeartbeatError extends UserError { description: string; constructor(heartbeatInterval: number, isSelfHosted: boolean) { diff --git a/packages/cli/src/task-runners/errors/task-runner-oom-error.ts b/packages/cli/src/task-runners/errors/task-runner-oom-error.ts index 412cf6e6266df..fab8b5e3e9b36 100644 --- a/packages/cli/src/task-runners/errors/task-runner-oom-error.ts +++ b/packages/cli/src/task-runners/errors/task-runner-oom-error.ts @@ -1,15 +1,15 @@ -import { ApplicationError } from 'n8n-workflow'; +import { UserError } from 'n8n-workflow'; import type { TaskRunner } from '@/task-runners/task-broker/task-broker.service'; -export class TaskRunnerOomError extends ApplicationError { +export class TaskRunnerOomError extends UserError { description: string; constructor( readonly runnerId: TaskRunner['id'], isCloudDeployment: boolean, ) { - super('Node ran out of memory.', { level: 'error' }); + super('Node ran out of memory'); const fixSuggestions = { reduceItems: diff --git a/packages/cli/src/task-runners/errors/task-runner-restart-loop-error.ts b/packages/cli/src/task-runners/errors/task-runner-restart-loop-error.ts index fa02430adeb0b..5727ff69a6f37 100644 --- a/packages/cli/src/task-runners/errors/task-runner-restart-loop-error.ts +++ b/packages/cli/src/task-runners/errors/task-runner-restart-loop-error.ts @@ -1,14 +1,12 @@ -import { ApplicationError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; -export class TaskRunnerRestartLoopError extends ApplicationError { +export class TaskRunnerRestartLoopError extends UnexpectedError { constructor( readonly howManyTimes: number, readonly timePeriodMs: number, ) { const message = `Task runner has restarted ${howManyTimes} times within ${timePeriodMs / 1000} seconds. This is an abnormally high restart rate that suggests a bug or other issue is preventing your runner process from starting up. If this issues persists, please file a report at: https://github.com/n8n-io/n8n/issues`; - super(message, { - level: 'fatal', - }); + super(message); } } diff --git a/packages/cli/src/task-runners/task-broker/errors/task-deferred.error.ts b/packages/cli/src/task-runners/task-broker/errors/task-deferred.error.ts index 34f365af5f145..5c6629c701342 100644 --- a/packages/cli/src/task-runners/task-broker/errors/task-deferred.error.ts +++ b/packages/cli/src/task-runners/task-broker/errors/task-deferred.error.ts @@ -1,7 +1,7 @@ -import { ApplicationError } from 'n8n-workflow'; +import { UserError } from 'n8n-workflow'; -export class TaskDeferredError extends ApplicationError { +export class TaskDeferredError extends UserError { constructor() { - super('Task deferred until runner is ready', { level: 'info' }); + super('Task deferred until runner is ready'); } } diff --git a/packages/cli/src/task-runners/task-broker/errors/task-reject.error.ts b/packages/cli/src/task-runners/task-broker/errors/task-reject.error.ts index 4e3f903dd8152..f5615469e2901 100644 --- a/packages/cli/src/task-runners/task-broker/errors/task-reject.error.ts +++ b/packages/cli/src/task-runners/task-broker/errors/task-reject.error.ts @@ -1,7 +1,7 @@ -import { ApplicationError } from 'n8n-workflow'; +import { UserError } from 'n8n-workflow'; -export class TaskRejectError extends ApplicationError { +export class TaskRejectError extends UserError { constructor(public reason: string) { - super(`Task rejected with reason: ${reason}`, { level: 'info' }); + super(`Task rejected with reason: ${reason}`); } } diff --git a/packages/cli/src/task-runners/task-broker/errors/task-runner-timeout.error.ts b/packages/cli/src/task-runners/task-broker/errors/task-runner-timeout.error.ts index 1d9d463e3a5c1..0741d0d95f915 100644 --- a/packages/cli/src/task-runners/task-broker/errors/task-runner-timeout.error.ts +++ b/packages/cli/src/task-runners/task-broker/errors/task-runner-timeout.error.ts @@ -1,7 +1,7 @@ import type { TaskRunnerMode } from '@n8n/config/src/configs/runners.config'; -import { ApplicationError } from 'n8n-workflow'; +import { UserError } from 'n8n-workflow'; -export class TaskRunnerTimeoutError extends ApplicationError { +export class TaskRunnerTimeoutError extends UserError { description: string; constructor({ From 2d6e89b9d729eb45bf67a4a79fb90ea4cbc8d778 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Mon, 24 Feb 2025 17:23:19 +0100 Subject: [PATCH 03/11] Disregard file to refactor --- packages/cli/src/webhooks/webhook-helpers.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/cli/src/webhooks/webhook-helpers.ts b/packages/cli/src/webhooks/webhook-helpers.ts index 90ec615e247f6..f9e4a29b5bc10 100644 --- a/packages/cli/src/webhooks/webhook-helpers.ts +++ b/packages/cli/src/webhooks/webhook-helpers.ts @@ -813,6 +813,7 @@ export async function executeWebhook( } const internalServerError = new InternalServerError(e.message, e); + // @ts-expect-error Disregarding while we refactor this file if (e instanceof ExecutionCancelledError) internalServerError.level = 'warning'; throw internalServerError; }); From 3ebc3a9216f4951904e01748f0b0a103067228cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Mon, 24 Feb 2025 17:35:46 +0100 Subject: [PATCH 04/11] Improve error reporter --- packages/core/src/errors/error-reporter.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/core/src/errors/error-reporter.ts b/packages/core/src/errors/error-reporter.ts index cdcf58ea2fc02..f2c99bffbce8c 100644 --- a/packages/core/src/errors/error-reporter.ts +++ b/packages/core/src/errors/error-reporter.ts @@ -172,8 +172,8 @@ export class ErrorReporter { } if (this.isIgnoredSqliteError(originalException)) return null; - if (originalException instanceof ApplicationError) { - if (this.isIgnoredApplicationError(originalException)) return null; + if (originalException instanceof ApplicationError || originalException instanceof BaseError) { + if (this.isIgnoredN8nError(originalException)) return null; this.extractEventDetailsFromN8nError(event, originalException); } @@ -227,8 +227,8 @@ export class ErrorReporter { ); } - private isIgnoredApplicationError(error: ApplicationError) { - return error.level === 'warning'; + private isIgnoredN8nError(error: ApplicationError | BaseError) { + return error.level === 'warning' || error.level === 'info'; } private extractEventDetailsFromN8nError( From dd35329ea801f0313dbcb9e36430c882aeb40d32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Mon, 24 Feb 2025 17:35:58 +0100 Subject: [PATCH 05/11] Introduce RuntimeError --- packages/workflow/src/errors/base/runtime.error.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 packages/workflow/src/errors/base/runtime.error.ts diff --git a/packages/workflow/src/errors/base/runtime.error.ts b/packages/workflow/src/errors/base/runtime.error.ts new file mode 100644 index 0000000000000..bface319bdf0b --- /dev/null +++ b/packages/workflow/src/errors/base/runtime.error.ts @@ -0,0 +1,13 @@ +import { BaseError } from './base.error'; + +/** + * Error that indicates a runtime issue typically because of a programming mistake or misuse, + * e.g. placing a decorator on a target that does not support it. + * + * Default level: info + */ +export class RuntimeError extends BaseError { + constructor(message: string) { + super(message, { level: 'info' }); + } +} From 7e9d8b94b6d528e73b38dfc7c26b629a21427154 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Mon, 24 Feb 2025 17:45:48 +0100 Subject: [PATCH 06/11] Improve error reporter --- packages/cli/src/databases/utils/migration-helpers.ts | 4 ++-- packages/cli/src/decorators/on-shutdown.ts | 6 ++++-- packages/cli/src/errors/queued-execution-retry.error.ts | 2 +- packages/cli/src/permissions.ee/check-access.ts | 4 ++-- packages/workflow/src/errors/index.ts | 1 + 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/cli/src/databases/utils/migration-helpers.ts b/packages/cli/src/databases/utils/migration-helpers.ts index 81e32b9eedc7a..ec5ff67083223 100644 --- a/packages/cli/src/databases/utils/migration-helpers.ts +++ b/packages/cli/src/databases/utils/migration-helpers.ts @@ -4,7 +4,7 @@ import type { ObjectLiteral } from '@n8n/typeorm'; import type { QueryRunner } from '@n8n/typeorm/query-runner/QueryRunner'; import { readFileSync, rmSync } from 'fs'; import { InstanceSettings, Logger } from 'n8n-core'; -import { jsonParse, UnexpectedError } from 'n8n-workflow'; +import { RuntimeError, jsonParse, UnexpectedError } from 'n8n-workflow'; import { inTest } from '@/constants'; import { createSchemaBuilder } from '@/databases/dsl'; @@ -69,7 +69,7 @@ const runDisablingForeignKeys = async ( ) => { const { dbType, queryRunner } = context; if (dbType !== 'sqlite') - throw new UnexpectedError('Disabling transactions only available in sqlite'); + throw new RuntimeError('Disabling transactions only available in sqlite'); await queryRunner.query('PRAGMA foreign_keys=OFF'); await queryRunner.startTransaction(); try { diff --git a/packages/cli/src/decorators/on-shutdown.ts b/packages/cli/src/decorators/on-shutdown.ts index 5cd5211c67e16..bc341d921a4b6 100644 --- a/packages/cli/src/decorators/on-shutdown.ts +++ b/packages/cli/src/decorators/on-shutdown.ts @@ -1,5 +1,5 @@ import { Container } from '@n8n/di'; -import { UserError } from 'n8n-workflow'; +import { RuntimeError } from 'n8n-workflow'; import { DEFAULT_SHUTDOWN_PRIORITY } from '@/constants'; import { type ServiceClass, ShutdownService } from '@/shutdown/shutdown.service'; @@ -33,6 +33,8 @@ export const OnShutdown = Container.get(ShutdownService).register(priority, { serviceClass, methodName }); } else { const name = `${serviceClass.name}.${methodName}()`; - throw new UserError(`${name} must be a method on ${serviceClass.name} to use "OnShutdown"`); + throw new RuntimeError( + `${name} must be a method on ${serviceClass.name} to use "OnShutdown"`, + ); } }; diff --git a/packages/cli/src/errors/queued-execution-retry.error.ts b/packages/cli/src/errors/queued-execution-retry.error.ts index fae616c718b7e..9ccd17732cbf5 100644 --- a/packages/cli/src/errors/queued-execution-retry.error.ts +++ b/packages/cli/src/errors/queued-execution-retry.error.ts @@ -2,6 +2,6 @@ import { UnexpectedError } from 'n8n-workflow'; export class QueuedExecutionRetryError extends UnexpectedError { constructor() { - super('Execution is queued to run (not yet started) so it cannot be retried', {}); + super('Execution is queued to run (not yet started) so it cannot be retried'); } } diff --git a/packages/cli/src/permissions.ee/check-access.ts b/packages/cli/src/permissions.ee/check-access.ts index 6d45ddfe65b62..4a5a7e3e2f5a2 100644 --- a/packages/cli/src/permissions.ee/check-access.ts +++ b/packages/cli/src/permissions.ee/check-access.ts @@ -2,7 +2,7 @@ import { Container } from '@n8n/di'; import type { Scope } from '@n8n/permissions'; // eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import import { In } from '@n8n/typeorm'; -import { UnexpectedError } from 'n8n-workflow'; +import { RuntimeError } from 'n8n-workflow'; import type { User } from '@/databases/entities/user'; import { ProjectRepository } from '@/databases/repositories/project.repository'; @@ -71,7 +71,7 @@ export async function userHasScopes( if (projectId) return userProjectIds.includes(projectId); - throw new UnexpectedError( + throw new RuntimeError( "`@ProjectScope` decorator was used but does not have a `credentialId`, `workflowId`, or `projectId` in its URL parameters. This is likely an implementation error. If you're a developer, please check your URL is correct or that this should be using `@GlobalScope`.", ); } diff --git a/packages/workflow/src/errors/index.ts b/packages/workflow/src/errors/index.ts index fd501dc56659c..18d668484422a 100644 --- a/packages/workflow/src/errors/index.ts +++ b/packages/workflow/src/errors/index.ts @@ -3,6 +3,7 @@ export { BaseError, type BaseErrorOptions } from './base/base.error'; export { OperationalError, type OperationalErrorOptions } from './base/operational.error'; export { UnexpectedError, type UnexpectedErrorOptions } from './base/unexpected.error'; export { UserError, type UserErrorOptions } from './base/user.error'; +export { RuntimeError } from './base/runtime.error'; export { ApplicationError } from './application.error'; export { ExpressionError } from './expression.error'; export { CredentialAccessError } from './credential-access-error'; From a532a71cd18e816b1483a1c7144bbff3f40300c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Mon, 24 Feb 2025 17:49:50 +0100 Subject: [PATCH 07/11] Fix tests --- packages/cli/src/__tests__/external-hooks.test.ts | 8 +++----- .../source-control/source-control-export.service.ee.ts | 1 + .../cli/src/shutdown/__tests__/shutdown.service.test.ts | 6 +++--- .../task-runners/errors/task-runner-disconnected-error.ts | 4 ++-- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/packages/cli/src/__tests__/external-hooks.test.ts b/packages/cli/src/__tests__/external-hooks.test.ts index aa42b7e9d93cc..576fb0405ed19 100644 --- a/packages/cli/src/__tests__/external-hooks.test.ts +++ b/packages/cli/src/__tests__/external-hooks.test.ts @@ -2,7 +2,7 @@ import type { GlobalConfig } from '@n8n/config'; import { mock } from 'jest-mock-extended'; import type { ErrorReporter, Logger } from 'n8n-core'; import type { IWorkflowBase } from 'n8n-workflow'; -import { ApplicationError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; import type { CredentialsRepository } from '@/databases/repositories/credentials.repository'; import type { SettingsRepository } from '@/databases/repositories/settings.repository'; @@ -57,7 +57,7 @@ describe('ExternalHooks', () => { { virtual: true }, ); - await expect(externalHooks.init()).rejects.toThrow(ApplicationError); + await expect(externalHooks.init()).rejects.toThrow(UnexpectedError); }); it('should successfully load hooks from valid hook file', async () => { @@ -113,9 +113,7 @@ describe('ExternalHooks', () => { await expect(externalHooks.run('workflow.create', [workflowData])).rejects.toThrow(error); - expect(errorReporter.error).toHaveBeenCalledWith(expect.any(ApplicationError), { - level: 'fatal', - }); + expect(errorReporter.error).toHaveBeenCalledWith(UnexpectedError); expect(logger.error).toHaveBeenCalledWith( 'There was a problem running hook "workflow.create"', ); diff --git a/packages/cli/src/environments.ee/source-control/source-control-export.service.ee.ts b/packages/cli/src/environments.ee/source-control/source-control-export.service.ee.ts index 8318f63ff3ed7..a46135d59e2f2 100644 --- a/packages/cli/src/environments.ee/source-control/source-control-export.service.ee.ts +++ b/packages/cli/src/environments.ee/source-control/source-control-export.service.ee.ts @@ -164,6 +164,7 @@ export class SourceControlExportService { })), }; } catch (error) { + if (error instanceof UnexpectedError) throw error; throw new UnexpectedError('Failed to export workflows to work folder', { cause: error }); } } diff --git a/packages/cli/src/shutdown/__tests__/shutdown.service.test.ts b/packages/cli/src/shutdown/__tests__/shutdown.service.test.ts index 41afed9398db3..40f727183a566 100644 --- a/packages/cli/src/shutdown/__tests__/shutdown.service.test.ts +++ b/packages/cli/src/shutdown/__tests__/shutdown.service.test.ts @@ -1,7 +1,7 @@ import { Container } from '@n8n/di'; import { mock } from 'jest-mock-extended'; import type { ErrorReporter } from 'n8n-core'; -import { ApplicationError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; import type { ServiceClass } from '@/shutdown/shutdown.service'; import { ShutdownService } from '@/shutdown/shutdown.service'; @@ -84,8 +84,8 @@ describe('ShutdownService', () => { await shutdownService.waitForShutdown(); expect(errorReporter.error).toHaveBeenCalledTimes(1); - const error = errorReporter.error.mock.calls[0][0] as ApplicationError; - expect(error).toBeInstanceOf(ApplicationError); + const error = errorReporter.error.mock.calls[0][0] as UnexpectedError; + expect(error).toBeInstanceOf(UnexpectedError); expect(error.message).toBe('Failed to shutdown gracefully'); expect(error.extra).toEqual({ component: 'MockComponent.onShutdown()', diff --git a/packages/cli/src/task-runners/errors/task-runner-disconnected-error.ts b/packages/cli/src/task-runners/errors/task-runner-disconnected-error.ts index 072148a93411f..ac1f9bb28b9f6 100644 --- a/packages/cli/src/task-runners/errors/task-runner-disconnected-error.ts +++ b/packages/cli/src/task-runners/errors/task-runner-disconnected-error.ts @@ -1,7 +1,7 @@ import type { TaskRunner } from '@n8n/task-runner'; -import { UserError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; -export class TaskRunnerDisconnectedError extends UserError { +export class TaskRunnerDisconnectedError extends UnexpectedError { description: string; constructor( From d0d75fee4c0d25c42572ff5abd582e99317268d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Tue, 25 Feb 2025 11:29:07 +0100 Subject: [PATCH 08/11] Fix test --- packages/cli/src/__tests__/external-hooks.test.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/__tests__/external-hooks.test.ts b/packages/cli/src/__tests__/external-hooks.test.ts index 576fb0405ed19..a6688fbd1f74b 100644 --- a/packages/cli/src/__tests__/external-hooks.test.ts +++ b/packages/cli/src/__tests__/external-hooks.test.ts @@ -112,8 +112,13 @@ describe('ExternalHooks', () => { externalHooks['registered']['workflow.create'] = [hookFn]; await expect(externalHooks.run('workflow.create', [workflowData])).rejects.toThrow(error); - - expect(errorReporter.error).toHaveBeenCalledWith(UnexpectedError); + expect(errorReporter.error).toHaveBeenCalledWith( + expect.objectContaining({ + message: 'External hook "workflow.create" failed', + cause: error, + }), + { level: 'fatal' }, + ); expect(logger.error).toHaveBeenCalledWith( 'There was a problem running hook "workflow.create"', ); From 86e31ae8a9f31729c95339c5245d4a1fb5cd79ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Tue, 25 Feb 2025 17:08:20 +0100 Subject: [PATCH 09/11] Remove `RuntimeError` --- .../cli/src/databases/utils/migration-helpers.ts | 4 ++-- packages/cli/src/decorators/on-shutdown.ts | 4 ++-- packages/cli/src/permissions.ee/check-access.ts | 4 ++-- packages/workflow/src/errors/base/runtime.error.ts | 13 ------------- packages/workflow/src/errors/index.ts | 1 - 5 files changed, 6 insertions(+), 20 deletions(-) delete mode 100644 packages/workflow/src/errors/base/runtime.error.ts diff --git a/packages/cli/src/databases/utils/migration-helpers.ts b/packages/cli/src/databases/utils/migration-helpers.ts index ec5ff67083223..81e32b9eedc7a 100644 --- a/packages/cli/src/databases/utils/migration-helpers.ts +++ b/packages/cli/src/databases/utils/migration-helpers.ts @@ -4,7 +4,7 @@ import type { ObjectLiteral } from '@n8n/typeorm'; import type { QueryRunner } from '@n8n/typeorm/query-runner/QueryRunner'; import { readFileSync, rmSync } from 'fs'; import { InstanceSettings, Logger } from 'n8n-core'; -import { RuntimeError, jsonParse, UnexpectedError } from 'n8n-workflow'; +import { jsonParse, UnexpectedError } from 'n8n-workflow'; import { inTest } from '@/constants'; import { createSchemaBuilder } from '@/databases/dsl'; @@ -69,7 +69,7 @@ const runDisablingForeignKeys = async ( ) => { const { dbType, queryRunner } = context; if (dbType !== 'sqlite') - throw new RuntimeError('Disabling transactions only available in sqlite'); + throw new UnexpectedError('Disabling transactions only available in sqlite'); await queryRunner.query('PRAGMA foreign_keys=OFF'); await queryRunner.startTransaction(); try { diff --git a/packages/cli/src/decorators/on-shutdown.ts b/packages/cli/src/decorators/on-shutdown.ts index bc341d921a4b6..08601e583e8f2 100644 --- a/packages/cli/src/decorators/on-shutdown.ts +++ b/packages/cli/src/decorators/on-shutdown.ts @@ -1,5 +1,5 @@ import { Container } from '@n8n/di'; -import { RuntimeError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; import { DEFAULT_SHUTDOWN_PRIORITY } from '@/constants'; import { type ServiceClass, ShutdownService } from '@/shutdown/shutdown.service'; @@ -33,7 +33,7 @@ export const OnShutdown = Container.get(ShutdownService).register(priority, { serviceClass, methodName }); } else { const name = `${serviceClass.name}.${methodName}()`; - throw new RuntimeError( + throw new UnexpectedError( `${name} must be a method on ${serviceClass.name} to use "OnShutdown"`, ); } diff --git a/packages/cli/src/permissions.ee/check-access.ts b/packages/cli/src/permissions.ee/check-access.ts index 4a5a7e3e2f5a2..6d45ddfe65b62 100644 --- a/packages/cli/src/permissions.ee/check-access.ts +++ b/packages/cli/src/permissions.ee/check-access.ts @@ -2,7 +2,7 @@ import { Container } from '@n8n/di'; import type { Scope } from '@n8n/permissions'; // eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import import { In } from '@n8n/typeorm'; -import { RuntimeError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; import type { User } from '@/databases/entities/user'; import { ProjectRepository } from '@/databases/repositories/project.repository'; @@ -71,7 +71,7 @@ export async function userHasScopes( if (projectId) return userProjectIds.includes(projectId); - throw new RuntimeError( + throw new UnexpectedError( "`@ProjectScope` decorator was used but does not have a `credentialId`, `workflowId`, or `projectId` in its URL parameters. This is likely an implementation error. If you're a developer, please check your URL is correct or that this should be using `@GlobalScope`.", ); } diff --git a/packages/workflow/src/errors/base/runtime.error.ts b/packages/workflow/src/errors/base/runtime.error.ts deleted file mode 100644 index bface319bdf0b..0000000000000 --- a/packages/workflow/src/errors/base/runtime.error.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { BaseError } from './base.error'; - -/** - * Error that indicates a runtime issue typically because of a programming mistake or misuse, - * e.g. placing a decorator on a target that does not support it. - * - * Default level: info - */ -export class RuntimeError extends BaseError { - constructor(message: string) { - super(message, { level: 'info' }); - } -} diff --git a/packages/workflow/src/errors/index.ts b/packages/workflow/src/errors/index.ts index 18d668484422a..fd501dc56659c 100644 --- a/packages/workflow/src/errors/index.ts +++ b/packages/workflow/src/errors/index.ts @@ -3,7 +3,6 @@ export { BaseError, type BaseErrorOptions } from './base/base.error'; export { OperationalError, type OperationalErrorOptions } from './base/operational.error'; export { UnexpectedError, type UnexpectedErrorOptions } from './base/unexpected.error'; export { UserError, type UserErrorOptions } from './base/user.error'; -export { RuntimeError } from './base/runtime.error'; export { ApplicationError } from './application.error'; export { ExpressionError } from './expression.error'; export { CredentialAccessError } from './credential-access-error'; From 950064c7a603faa24ad67c4e44ea1df9c3ab8c82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Tue, 25 Feb 2025 17:21:54 +0100 Subject: [PATCH 10/11] Recategorize some errors --- packages/cli/src/errors/max-stalled-count.error.ts | 4 ++-- packages/cli/src/errors/missing-execution-stop.error.ts | 4 ++-- packages/cli/src/errors/shared-workflow-not-found.error.ts | 4 ++-- packages/cli/src/executions/execution.service.ts | 3 ++- .../task-broker/errors/task-runner-timeout.error.ts | 4 ++-- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/cli/src/errors/max-stalled-count.error.ts b/packages/cli/src/errors/max-stalled-count.error.ts index cea5250f4cfc9..4e92fad40541f 100644 --- a/packages/cli/src/errors/max-stalled-count.error.ts +++ b/packages/cli/src/errors/max-stalled-count.error.ts @@ -1,9 +1,9 @@ -import { UserError } from 'n8n-workflow'; +import { OperationalError } from 'n8n-workflow'; /** * @docs https://docs.bullmq.io/guide/workers/stalled-jobs */ -export class MaxStalledCountError extends UserError { +export class MaxStalledCountError extends OperationalError { constructor(cause: Error) { super( 'This execution failed to be processed too many times and will no longer retry. To allow this execution to complete, please break down your workflow or scale up your workers or adjust your worker settings.', diff --git a/packages/cli/src/errors/missing-execution-stop.error.ts b/packages/cli/src/errors/missing-execution-stop.error.ts index 6190b3f783c91..be8dea83022ed 100644 --- a/packages/cli/src/errors/missing-execution-stop.error.ts +++ b/packages/cli/src/errors/missing-execution-stop.error.ts @@ -1,6 +1,6 @@ -import { UnexpectedError } from 'n8n-workflow'; +import { UserError } from 'n8n-workflow'; -export class MissingExecutionStopError extends UnexpectedError { +export class MissingExecutionStopError extends UserError { constructor(executionId: string) { super('Failed to find execution to stop', { extra: { executionId } }); } diff --git a/packages/cli/src/errors/shared-workflow-not-found.error.ts b/packages/cli/src/errors/shared-workflow-not-found.error.ts index 64463dea6b9ff..f9b9d6564e9f7 100644 --- a/packages/cli/src/errors/shared-workflow-not-found.error.ts +++ b/packages/cli/src/errors/shared-workflow-not-found.error.ts @@ -1,3 +1,3 @@ -import { UnexpectedError } from 'n8n-workflow'; +import { UserError } from 'n8n-workflow'; -export class SharedWorkflowNotFoundError extends UnexpectedError {} +export class SharedWorkflowNotFoundError extends UserError {} diff --git a/packages/cli/src/executions/execution.service.ts b/packages/cli/src/executions/execution.service.ts index eca486289a5f5..c4a8188f0ece5 100644 --- a/packages/cli/src/executions/execution.service.ts +++ b/packages/cli/src/executions/execution.service.ts @@ -14,6 +14,7 @@ import type { import { ExecutionStatusList, UnexpectedError, + UserError, Workflow, WorkflowOperationError, } from 'n8n-workflow'; @@ -188,7 +189,7 @@ export class ExecutionService { })) as IWorkflowBase; if (workflowData === undefined) { - throw new UnexpectedError( + throw new UserError( 'Workflow could not be found and so the data not be loaded for the retry.', { extra: { workflowId } }, ); diff --git a/packages/cli/src/task-runners/task-broker/errors/task-runner-timeout.error.ts b/packages/cli/src/task-runners/task-broker/errors/task-runner-timeout.error.ts index 0741d0d95f915..ce5b53d8753da 100644 --- a/packages/cli/src/task-runners/task-broker/errors/task-runner-timeout.error.ts +++ b/packages/cli/src/task-runners/task-broker/errors/task-runner-timeout.error.ts @@ -1,7 +1,7 @@ import type { TaskRunnerMode } from '@n8n/config/src/configs/runners.config'; -import { UserError } from 'n8n-workflow'; +import { OperationalError } from 'n8n-workflow'; -export class TaskRunnerTimeoutError extends UserError { +export class TaskRunnerTimeoutError extends OperationalError { description: string; constructor({ From e56358055f26f54c837c77477feb0542352c971e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Tue, 25 Feb 2025 17:28:42 +0100 Subject: [PATCH 11/11] Set level based on error code --- .../response-errors/abstract/response.error.ts | 12 ++++++++++-- packages/workflow/src/errors/base/base.error.ts | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/errors/response-errors/abstract/response.error.ts b/packages/cli/src/errors/response-errors/abstract/response.error.ts index 0e3d34b164e76..3d96ff1f62be7 100644 --- a/packages/cli/src/errors/response-errors/abstract/response.error.ts +++ b/packages/cli/src/errors/response-errors/abstract/response.error.ts @@ -1,9 +1,9 @@ -import { UnexpectedError } from 'n8n-workflow'; +import { BaseError } from 'n8n-workflow'; /** * Special Error which allows to return also an error code and http status code */ -export abstract class ResponseError extends UnexpectedError { +export abstract class ResponseError extends BaseError { /** * Creates an instance of ResponseError. * Must be used inside a block with `ResponseHelper.send()`. @@ -20,5 +20,13 @@ export abstract class ResponseError extends UnexpectedError { ) { super(message, { cause }); this.name = 'ResponseError'; + + if (httpStatusCode >= 400 && httpStatusCode < 500) { + this.level = 'warning'; // client errors (4xx) + } else if (httpStatusCode >= 502 && httpStatusCode <= 504) { + this.level = 'info'; // transient errors (502, 503, 504) + } else { + this.level = 'error'; // other 5xx + } } } diff --git a/packages/workflow/src/errors/base/base.error.ts b/packages/workflow/src/errors/base/base.error.ts index 15d1d4dacba8a..50f61ba1e688d 100644 --- a/packages/workflow/src/errors/base/base.error.ts +++ b/packages/workflow/src/errors/base/base.error.ts @@ -13,7 +13,7 @@ export abstract class BaseError extends Error { * Error level. Defines which level the error should be logged/reported * @default 'error' */ - readonly level: ErrorLevel; + level: ErrorLevel; /** * Whether the error should be reported to Sentry.