refactor(core): Continue porting legacy schema (#16318)

This commit is contained in:
Iván Ovejero
2025-06-13 17:43:07 +02:00
committed by GitHub
parent f92e1ea8e4
commit b1305fe5f1
22 changed files with 163 additions and 102 deletions

View File

@@ -0,0 +1,7 @@
import { Config, Env } from '../decorators';
@Config
export class DeploymentConfig {
@Env('N8N_DEPLOYMENT_TYPE')
type: string = 'default';
}

View File

@@ -0,0 +1,8 @@
import { Config, Env } from '../decorators';
@Config
export class HiringBannerConfig {
/** Whether hiring banner in browser console is enabled. */
@Env('N8N_HIRING_BANNER_ENABLED')
enabled: boolean = true;
}

View File

@@ -0,0 +1,8 @@
import { Config, Env } from '../decorators';
@Config
export class MfaConfig {
/** Whether to enable multi-factor authentication. */
@Env('N8N_MFA_ENABLED')
enabled: boolean = true;
}

View File

@@ -0,0 +1,7 @@
import { Config, Env } from '../decorators';
@Config
export class PersonalizationConfig {
@Env('N8N_PERSONALIZATION_ENABLED')
enabled: boolean = true;
}

View File

@@ -5,17 +5,21 @@ import { AuthConfig } from './configs/auth.config';
import { CacheConfig } from './configs/cache.config';
import { CredentialsConfig } from './configs/credentials.config';
import { DatabaseConfig } from './configs/database.config';
import { DeploymentConfig } from './configs/deployment.config';
import { DiagnosticsConfig } from './configs/diagnostics.config';
import { EndpointsConfig } from './configs/endpoints.config';
import { EventBusConfig } from './configs/event-bus.config';
import { ExecutionsConfig } from './configs/executions.config';
import { ExternalHooksConfig } from './configs/external-hooks.config';
import { GenericConfig } from './configs/generic.config';
import { HiringBannerConfig } from './configs/hiring-banner.config';
import { LicenseConfig } from './configs/license.config';
import { LoggingConfig } from './configs/logging.config';
import { MfaConfig } from './configs/mfa.config';
import { MultiMainSetupConfig } from './configs/multi-main-setup.config';
import { NodesConfig } from './configs/nodes.config';
import { PartialExecutionsConfig } from './configs/partial-executions.config';
import { PersonalizationConfig } from './configs/personalization.config';
import { PublicApiConfig } from './configs/public-api.config';
import { TaskRunnersConfig } from './configs/runners.config';
import { ScalingModeConfig } from './configs/scaling-mode.config';
@@ -39,6 +43,10 @@ export { LOG_SCOPES } from './configs/logging.config';
export type { LogScope } from './configs/logging.config';
export { WorkflowsConfig } from './configs/workflows.config';
export * from './custom-types';
export { DeploymentConfig } from './configs/deployment.config';
export { MfaConfig } from './configs/mfa.config';
export { HiringBannerConfig } from './configs/hiring-banner.config';
export { PersonalizationConfig } from './configs/personalization.config';
const protocolSchema = z.enum(['http', 'https']);
@@ -146,4 +154,28 @@ export class GlobalConfig {
@Nested
workflowHistory: WorkflowHistoryConfig;
@Nested
deployment: DeploymentConfig;
@Nested
mfa: MfaConfig;
@Nested
hiringBanner: HiringBannerConfig;
@Nested
personalization: PersonalizationConfig;
/** Default locale for the UI. */
@Env('N8N_DEFAULT_LOCALE')
defaultLocale: string = 'en';
/** Whether to hide the page that shows active workflows and executions count. */
@Env('N8N_HIDE_USAGE_PAGE')
hideUsagePage: boolean = false;
/** Number of reverse proxies n8n is running behind. */
@Env('N8N_PROXY_HOPS')
proxy_hops: number = 0;
}

View File

@@ -33,6 +33,21 @@ describe('GlobalConfig', () => {
secure: true,
},
},
defaultLocale: 'en',
hideUsagePage: false,
deployment: {
type: 'default',
},
mfa: {
enabled: true,
},
hiringBanner: {
enabled: true,
},
personalization: {
enabled: true,
},
proxy_hops: 0,
database: {
logging: {
enabled: false,

View File

@@ -72,7 +72,7 @@ export abstract class AbstractServer {
this.app.set('view engine', 'handlebars');
this.app.set('views', TEMPLATES_DIR);
const proxyHops = config.getEnv('proxy_hops');
const proxyHops = this.globalConfig.proxy_hops;
if (proxyHops > 0) this.app.set('trust proxy', proxyHops);
this.sslKey = config.getEnv('ssl_key');
@@ -260,7 +260,7 @@ export abstract class AbstractServer {
if (!inTest) {
this.logger.info(`Version: ${N8N_VERSION}`);
const defaultLocale = config.getEnv('defaultLocale');
const { defaultLocale } = this.globalConfig;
if (defaultLocale !== 'en') {
this.logger.info(`Locale: ${defaultLocale}`);
}

View File

@@ -1,3 +1,4 @@
import type { GlobalConfig } from '@n8n/config';
import type { ExecutionRepository } from '@n8n/db';
import { mock } from 'jest-mock-extended';
import type { WorkflowExecuteMode as ExecutionMode } from 'n8n-workflow';
@@ -21,6 +22,7 @@ describe('ConcurrencyControlService', () => {
const executionRepository = mock<ExecutionRepository>();
const telemetry = mock<Telemetry>();
const eventService = mock<EventService>();
const globalConfig = mock<GlobalConfig>();
afterEach(() => {
config.set('executions.concurrency.productionLimit', -1);
@@ -47,6 +49,7 @@ describe('ConcurrencyControlService', () => {
executionRepository,
telemetry,
eventService,
globalConfig,
);
/**
@@ -73,7 +76,13 @@ describe('ConcurrencyControlService', () => {
/**
* Act
*/
new ConcurrencyControlService(logger, executionRepository, telemetry, eventService);
new ConcurrencyControlService(
logger,
executionRepository,
telemetry,
eventService,
globalConfig,
);
} catch (error) {
/**
* Assert
@@ -98,6 +107,7 @@ describe('ConcurrencyControlService', () => {
executionRepository,
telemetry,
eventService,
globalConfig,
);
/**
@@ -123,6 +133,7 @@ describe('ConcurrencyControlService', () => {
executionRepository,
telemetry,
eventService,
globalConfig,
);
/**
@@ -148,6 +159,7 @@ describe('ConcurrencyControlService', () => {
executionRepository,
telemetry,
eventService,
globalConfig,
);
/**
@@ -177,6 +189,7 @@ describe('ConcurrencyControlService', () => {
executionRepository,
telemetry,
eventService,
globalConfig,
);
const enqueueSpy = jest.spyOn(ConcurrencyQueue.prototype, 'enqueue');
@@ -203,6 +216,7 @@ describe('ConcurrencyControlService', () => {
executionRepository,
telemetry,
eventService,
globalConfig,
);
const enqueueSpy = jest.spyOn(ConcurrencyQueue.prototype, 'enqueue');
@@ -228,6 +242,7 @@ describe('ConcurrencyControlService', () => {
executionRepository,
telemetry,
eventService,
globalConfig,
);
const enqueueSpy = jest.spyOn(ConcurrencyQueue.prototype, 'enqueue');
@@ -257,6 +272,7 @@ describe('ConcurrencyControlService', () => {
executionRepository,
telemetry,
eventService,
globalConfig,
);
const dequeueSpy = jest.spyOn(ConcurrencyQueue.prototype, 'dequeue');
@@ -283,6 +299,7 @@ describe('ConcurrencyControlService', () => {
executionRepository,
telemetry,
eventService,
globalConfig,
);
const dequeueSpy = jest.spyOn(ConcurrencyQueue.prototype, 'dequeue');
@@ -308,6 +325,7 @@ describe('ConcurrencyControlService', () => {
executionRepository,
telemetry,
eventService,
globalConfig,
);
const dequeueSpy = jest.spyOn(ConcurrencyQueue.prototype, 'dequeue');
@@ -337,6 +355,7 @@ describe('ConcurrencyControlService', () => {
executionRepository,
telemetry,
eventService,
globalConfig,
);
const removeSpy = jest.spyOn(ConcurrencyQueue.prototype, 'remove');
@@ -365,6 +384,7 @@ describe('ConcurrencyControlService', () => {
executionRepository,
telemetry,
eventService,
globalConfig,
);
const removeSpy = jest.spyOn(ConcurrencyQueue.prototype, 'remove');
@@ -391,6 +411,7 @@ describe('ConcurrencyControlService', () => {
executionRepository,
telemetry,
eventService,
globalConfig,
);
const removeSpy = jest.spyOn(ConcurrencyQueue.prototype, 'remove');
@@ -420,6 +441,7 @@ describe('ConcurrencyControlService', () => {
executionRepository,
telemetry,
eventService,
globalConfig,
);
jest
@@ -459,6 +481,7 @@ describe('ConcurrencyControlService', () => {
executionRepository,
telemetry,
eventService,
globalConfig,
);
// @ts-expect-error Private property
const queue = service.getQueue('webhook');
@@ -485,6 +508,7 @@ describe('ConcurrencyControlService', () => {
executionRepository,
telemetry,
eventService,
globalConfig,
);
// @ts-expect-error Private property
const queue = service.getQueue('evaluation');
@@ -515,6 +539,7 @@ describe('ConcurrencyControlService', () => {
executionRepository,
telemetry,
eventService,
globalConfig,
);
const enqueueSpy = jest.spyOn(ConcurrencyQueue.prototype, 'enqueue');
@@ -541,6 +566,7 @@ describe('ConcurrencyControlService', () => {
executionRepository,
telemetry,
eventService,
globalConfig,
);
const enqueueSpy = jest.spyOn(ConcurrencyQueue.prototype, 'enqueue');
@@ -569,6 +595,7 @@ describe('ConcurrencyControlService', () => {
executionRepository,
telemetry,
eventService,
globalConfig,
);
const dequeueSpy = jest.spyOn(ConcurrencyQueue.prototype, 'dequeue');
@@ -594,6 +621,7 @@ describe('ConcurrencyControlService', () => {
executionRepository,
telemetry,
eventService,
globalConfig,
);
const dequeueSpy = jest.spyOn(ConcurrencyQueue.prototype, 'dequeue');
@@ -621,6 +649,7 @@ describe('ConcurrencyControlService', () => {
executionRepository,
telemetry,
eventService,
globalConfig,
);
const removeSpy = jest.spyOn(ConcurrencyQueue.prototype, 'remove');
@@ -646,6 +675,7 @@ describe('ConcurrencyControlService', () => {
executionRepository,
telemetry,
eventService,
globalConfig,
);
const removeSpy = jest.spyOn(ConcurrencyQueue.prototype, 'remove');
@@ -675,12 +705,13 @@ describe('ConcurrencyControlService', () => {
* Arrange
*/
config.set('executions.concurrency.productionLimit', CLOUD_TEMP_PRODUCTION_LIMIT);
config.set('deployment.type', 'cloud');
globalConfig.deployment.type = 'cloud';
const service = new ConcurrencyControlService(
logger,
executionRepository,
telemetry,
eventService,
globalConfig,
);
/**
@@ -708,12 +739,13 @@ describe('ConcurrencyControlService', () => {
* Arrange
*/
config.set('executions.concurrency.productionLimit', CLOUD_TEMP_PRODUCTION_LIMIT);
config.set('deployment.type', 'cloud');
globalConfig.deployment.type = 'cloud';
const service = new ConcurrencyControlService(
logger,
executionRepository,
telemetry,
eventService,
globalConfig,
);
/**
@@ -740,12 +772,13 @@ describe('ConcurrencyControlService', () => {
* Arrange
*/
config.set('executions.concurrency.productionLimit', CLOUD_TEMP_PRODUCTION_LIMIT);
config.set('deployment.type', 'cloud');
globalConfig.deployment.type = 'cloud';
const service = new ConcurrencyControlService(
logger,
executionRepository,
telemetry,
eventService,
globalConfig,
);
/**

View File

@@ -1,4 +1,5 @@
import { Logger } from '@n8n/backend-common';
import { GlobalConfig } from '@n8n/config';
import { ExecutionRepository } from '@n8n/db';
import { Service } from '@n8n/di';
import capitalize from 'lodash/capitalize';
@@ -34,6 +35,7 @@ export class ConcurrencyControlService {
private readonly executionRepository: ExecutionRepository,
private readonly telemetry: Telemetry,
private readonly eventService: EventService,
private readonly globalConfig: GlobalConfig,
) {
this.logger = this.logger.scoped('concurrency');
@@ -185,7 +187,7 @@ export class ConcurrencyControlService {
}
private shouldReport(capacity: number) {
return config.getEnv('deployment.type') === 'cloud' && this.limitsToReport.includes(capacity);
return this.globalConfig.deployment.type === 'cloud' && this.limitsToReport.includes(capacity);
}
/**

View File

@@ -165,23 +165,6 @@ export const schema = {
env: 'EXTERNAL_FRONTEND_HOOKS_URLS',
},
deployment: {
type: {
format: String,
default: 'default',
env: 'N8N_DEPLOYMENT_TYPE',
},
},
mfa: {
enabled: {
format: Boolean,
default: true,
doc: 'Whether to enable MFA feature in instance.',
env: 'N8N_MFA_ENABLED',
},
},
sso: {
justInTimeProvisioning: {
format: Boolean,
@@ -223,38 +206,6 @@ export const schema = {
},
},
hiringBanner: {
enabled: {
doc: 'Whether hiring banner in browser console is enabled.',
format: Boolean,
default: true,
env: 'N8N_HIRING_BANNER_ENABLED',
},
},
personalization: {
enabled: {
doc: 'Whether personalization is enabled.',
format: Boolean,
default: true,
env: 'N8N_PERSONALIZATION_ENABLED',
},
},
defaultLocale: {
doc: 'Default locale for the UI',
format: String,
default: 'en',
env: 'N8N_DEFAULT_LOCALE',
},
hideUsagePage: {
format: Boolean,
default: false,
env: 'N8N_HIDE_USAGE_PAGE',
doc: 'Hide or show the usage page',
},
redis: {
prefix: {
doc: 'Prefix for all n8n related keys',
@@ -285,11 +236,4 @@ export const schema = {
env: 'N8N_AI_ENABLED',
},
},
proxy_hops: {
format: Number,
default: 0,
env: 'N8N_PROXY_HOPS',
doc: 'Number of reverse-proxies n8n is running behind',
},
};

View File

@@ -1,6 +1,6 @@
import type { GlobalConfig } from '@n8n/config';
import { mock } from 'jest-mock-extended';
import config from '@/config';
import type { TranslationRequest } from '@/controllers/translation.controller';
import {
TranslationController,
@@ -10,9 +10,11 @@ import type { CredentialTypes } from '@/credential-types';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
describe('TranslationController', () => {
const configGetSpy = jest.spyOn(config, 'getEnv');
const credentialTypes = mock<CredentialTypes>();
const controller = new TranslationController(credentialTypes);
const controller = new TranslationController(
credentialTypes,
mock<GlobalConfig>({ defaultLocale: 'de' }),
);
describe('getCredentialTranslation', () => {
it('should throw 400 on invalid credential types', async () => {
@@ -28,7 +30,6 @@ describe('TranslationController', () => {
it('should return translation json on valid credential types', async () => {
const credentialType = 'credential-type';
const req = mock<TranslationRequest.Credential>({ query: { credentialType } });
configGetSpy.mockReturnValue('de');
credentialTypes.recognizes.calledWith(credentialType).mockReturnValue(true);
const response = { translation: 'string' };
jest.mock(`${CREDENTIAL_TRANSLATIONS_DIR}/de/credential-type.json`, () => response, {

View File

@@ -1,21 +1,24 @@
import { GlobalConfig } from '@n8n/config';
import { Post, RestController } from '@n8n/decorators';
import { Request } from 'express';
import { readFile } from 'fs/promises';
import get from 'lodash/get';
import type { INodeTypeDescription, INodeTypeNameVersion } from 'n8n-workflow';
import config from '@/config';
import { NodeTypes } from '@/node-types';
@RestController('/node-types')
export class NodeTypesController {
constructor(private readonly nodeTypes: NodeTypes) {}
constructor(
private readonly nodeTypes: NodeTypes,
private readonly globalConfig: GlobalConfig,
) {}
@Post('/')
async getNodeInfo(req: Request) {
const nodeInfos = get(req, 'body.nodeInfos', []) as INodeTypeNameVersion[];
const defaultLocale = config.getEnv('defaultLocale');
const defaultLocale = this.globalConfig.defaultLocale;
if (defaultLocale === 'en') {
return nodeInfos.reduce<INodeTypeDescription[]>((acc, { name, version }) => {

View File

@@ -1,9 +1,9 @@
import { GlobalConfig } from '@n8n/config';
import { Get, RestController } from '@n8n/decorators';
import type { Request } from 'express';
import { access } from 'fs/promises';
import { join } from 'path';
import config from '@/config';
import { NODES_BASE_DIR } from '@/constants';
import { CredentialTypes } from '@/credential-types';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
@@ -18,7 +18,10 @@ export declare namespace TranslationRequest {
@RestController('/')
export class TranslationController {
constructor(private readonly credentialTypes: CredentialTypes) {}
constructor(
private readonly credentialTypes: CredentialTypes,
private readonly globalConfig: GlobalConfig,
) {}
@Get('/credential-translation')
async getCredentialTranslation(req: TranslationRequest.Credential) {
@@ -27,7 +30,7 @@ export class TranslationController {
if (!this.credentialTypes.recognizes(credentialType))
throw new BadRequestError(`Invalid Credential type: "${credentialType}"`);
const defaultLocale = config.getEnv('defaultLocale');
const { defaultLocale } = this.globalConfig;
const translationPath = join(
CREDENTIAL_TRANSLATIONS_DIR,
defaultLocale,

View File

@@ -26,6 +26,9 @@ describe('TelemetryEventRelay', () => {
const telemetry = mock<Telemetry>();
const license = mock<License>();
const globalConfig = mock<GlobalConfig>({
deployment: {
type: 'default',
},
userManagement: {
emails: {
mode: 'smtp',

View File

@@ -578,7 +578,7 @@ export class TelemetryEventRelay extends EventRelay {
}
private async workflowSaved({ user, workflow, publicApi }: RelayEventMap['workflow-saved']) {
const isCloudDeployment = config.getEnv('deployment.type') === 'cloud';
const isCloudDeployment = this.globalConfig.deployment.type === 'cloud';
const { nodeGraph } = TelemetryHelpers.generateNodesGraph(workflow, this.nodeTypes, {
isCloudDeployment,
@@ -835,7 +835,7 @@ export class TelemetryEventRelay extends EventRelay {
executions_data_prune: this.globalConfig.executions.pruneData,
executions_data_max_age: this.globalConfig.executions.pruneDataMaxAge,
},
n8n_deployment_type: config.getEnv('deployment.type'),
n8n_deployment_type: this.globalConfig.deployment.type,
n8n_binary_data_mode: this.binaryDataConfig.mode,
smtp_set_up: this.globalConfig.userManagement.emails.mode === 'smtp',
ldap_allowed: authenticationMethod === 'ldap',

View File

@@ -1 +0,0 @@
export const MFA_FEATURE_ENABLED = 'mfa.enabled';

View File

@@ -1,11 +1,8 @@
import { GlobalConfig } from '@n8n/config';
import { UserRepository } from '@n8n/db';
import { Container } from '@n8n/di';
import config from '@/config';
import { MFA_FEATURE_ENABLED } from './constants';
export const isMfaFeatureEnabled = () => config.get(MFA_FEATURE_ENABLED);
export const isMfaFeatureEnabled = () => Container.get(GlobalConfig).mfa.enabled;
const isMfaFeatureDisabled = () => !isMfaFeatureEnabled();
@@ -18,7 +15,7 @@ export const handleMfaDisable = async () => {
// users, then keep the feature enabled
const users = await getUsersWithMfaEnabled();
if (users) {
config.set(MFA_FEATURE_ENABLED, true);
Container.get(GlobalConfig).mfa.enabled = true;
}
}
};

View File

@@ -6,7 +6,6 @@ import axios from 'axios';
import { InstanceSettings } from 'n8n-core';
import type { IWorkflowBase } from 'n8n-workflow';
import config from '@/config';
import { N8N_VERSION } from '@/constants';
import { isApiEnabled } from '@/public-api';
import {
@@ -84,7 +83,7 @@ export class InstanceRiskReporter implements RiskReporter {
}
private getSecuritySettings() {
if (config.getEnv('deployment.type') === 'cloud') return null;
if (this.globalConfig.deployment.type === 'cloud') return null;
const settings: Record<string, unknown> = {};

View File

@@ -133,12 +133,12 @@ export class FrontendService {
apiHost: this.globalConfig.diagnostics.posthogConfig.apiHost,
apiKey: this.globalConfig.diagnostics.posthogConfig.apiKey,
autocapture: false,
disableSessionRecording: config.getEnv('deployment.type') !== 'cloud',
disableSessionRecording: this.globalConfig.deployment.type !== 'cloud',
debug: this.globalConfig.logging.level === 'debug',
},
personalizationSurveyEnabled:
config.getEnv('personalization.enabled') && this.globalConfig.diagnostics.enabled,
defaultLocale: config.getEnv('defaultLocale'),
this.globalConfig.personalization.enabled && this.globalConfig.diagnostics.enabled,
defaultLocale: this.globalConfig.defaultLocale,
userManagement: {
quota: this.license.getUsersLimit(),
showSetupOnFirstLoad: !config.getEnv('userManagement.isInstanceOwnerSetUp'),
@@ -170,7 +170,7 @@ export class FrontendService {
},
workflowTagsDisabled: this.globalConfig.tags.disabled,
logLevel: this.globalConfig.logging.level,
hiringBannerEnabled: config.getEnv('hiringBanner.enabled'),
hiringBannerEnabled: this.globalConfig.hiringBanner.enabled,
aiAssistant: {
enabled: false,
},
@@ -184,7 +184,7 @@ export class FrontendService {
communityNodesEnabled: this.globalConfig.nodes.communityPackages.enabled,
unverifiedCommunityNodesEnabled: this.globalConfig.nodes.communityPackages.unverifiedEnabled,
deployment: {
type: config.getEnv('deployment.type'),
type: this.globalConfig.deployment.type,
},
allowedModules: {
builtIn: process.env.NODE_FUNCTION_ALLOW_BUILTIN?.split(',') ?? undefined,
@@ -217,7 +217,7 @@ export class FrontendService {
mfa: {
enabled: false,
},
hideUsagePage: config.getEnv('hideUsagePage'),
hideUsagePage: this.globalConfig.hideUsagePage,
license: {
consumerId: 'unknown',
environment: this.globalConfig.license.tenantId === 1 ? 'production' : 'staging',
@@ -395,7 +395,7 @@ export class FrontendService {
dateRanges: getInsightsAvailableDateRanges(this.licenseState),
});
this.settings.mfa.enabled = config.get('mfa.enabled');
this.settings.mfa.enabled = this.globalConfig.mfa.enabled;
this.settings.executionMode = config.getEnv('executions.mode');

View File

@@ -1,6 +1,6 @@
import { Service } from '@n8n/di';
import { GlobalConfig } from '@n8n/config';
import { Container, Service } from '@n8n/di';
import config from '@/config';
import type {
DisconnectAnalyzer,
DisconnectErrorOptions,
@@ -16,7 +16,7 @@ import { TaskRunnerFailedHeartbeatError } from './errors/task-runner-failed-hear
@Service()
export class DefaultTaskRunnerDisconnectAnalyzer implements DisconnectAnalyzer {
get isCloudDeployment() {
return config.get('deployment.type') === 'cloud';
return Container.get(GlobalConfig).deployment.type === 'cloud';
}
async toDisconnectError(opts: DisconnectErrorOptions): Promise<Error> {
@@ -25,7 +25,7 @@ export class DefaultTaskRunnerDisconnectAnalyzer implements DisconnectAnalyzer {
if (reason === 'failed-heartbeat-check' && heartbeatInterval) {
return new TaskRunnerFailedHeartbeatError(
heartbeatInterval,
config.get('deployment.type') !== 'cloud',
Container.get(GlobalConfig).deployment.type !== 'cloud',
);
}

View File

@@ -18,7 +18,7 @@ describe('TaskBroker', () => {
let taskBroker: TaskBroker;
beforeEach(() => {
taskBroker = new TaskBroker(mock(), mock(), mock());
taskBroker = new TaskBroker(mock(), mock(), mock(), mock());
jest.restoreAllMocks();
});
@@ -724,7 +724,7 @@ describe('TaskBroker', () => {
beforeAll(() => {
jest.useFakeTimers();
config = mock<TaskRunnersConfig>({ taskTimeout: 30, mode: 'internal' });
taskBroker = new TaskBroker(mock(), config, runnerLifecycleEvents);
taskBroker = new TaskBroker(mock(), config, runnerLifecycleEvents, mock());
});
afterAll(() => {
@@ -844,7 +844,7 @@ describe('TaskBroker', () => {
it('[external mode] on timeout, we should instruct the runner to cancel and send error to requester', async () => {
const config = mock<TaskRunnersConfig>({ taskTimeout: 30, mode: 'external' });
taskBroker = new TaskBroker(mock(), config, runnerLifecycleEvents);
taskBroker = new TaskBroker(mock(), config, runnerLifecycleEvents, mock());
jest.spyOn(global, 'clearTimeout');
@@ -895,7 +895,7 @@ describe('TaskBroker', () => {
const messageCallback = jest.fn();
const loggerMock = mock<Logger>();
taskBroker = new TaskBroker(loggerMock, mock(), mock());
taskBroker = new TaskBroker(loggerMock, mock(), mock(), mock());
taskBroker.registerRunner(runner, messageCallback);
const offer: TaskOffer = {

View File

@@ -1,5 +1,5 @@
import { Logger } from '@n8n/backend-common';
import { TaskRunnersConfig } from '@n8n/config';
import { GlobalConfig, TaskRunnersConfig } from '@n8n/config';
import { Service } from '@n8n/di';
import type {
BrokerMessage,
@@ -10,7 +10,6 @@ import type {
import { UnexpectedError, UserError } from 'n8n-workflow';
import { nanoid } from 'nanoid';
import config from '@/config';
import { Time } from '@/constants';
import { TaskDeferredError } from '@/task-runners/task-broker/errors/task-deferred.error';
import { TaskRejectError } from '@/task-runners/task-broker/errors/task-reject.error';
@@ -91,6 +90,7 @@ export class TaskBroker {
private readonly logger: Logger,
private readonly taskRunnersConfig: TaskRunnersConfig,
private readonly taskRunnerLifecycleEvents: TaskRunnerLifecycleEvents,
private readonly globalConfig: GlobalConfig,
) {
if (this.taskRunnersConfig.taskTimeout <= 0) {
throw new UserError('Task timeout must be greater than 0');
@@ -471,7 +471,7 @@ export class TaskBroker {
taskId,
new TaskRunnerExecutionTimeoutError({
taskTimeout,
isSelfHosted: config.getEnv('deployment.type') !== 'cloud',
isSelfHosted: this.globalConfig.deployment.type !== 'cloud',
mode,
}),
);