chore: Migrate packages/cli to Typescript 7 (#34159)

This commit is contained in:
Matsu
2026-07-15 14:49:51 +03:00
committed by GitHub
parent 0e8420bbff
commit cec46445ee
132 changed files with 807 additions and 590 deletions
-5
View File
@@ -69,11 +69,6 @@
"import": "./dist/esm/utils/http-proxy-agent.js",
"require": "./dist/cjs/utils/http-proxy-agent.js"
},
"./lazy-import": {
"types": "./dist/esm/utils/lazy-import.d.ts",
"import": "./dist/esm/utils/lazy-import.js",
"require": "./dist/cjs/utils/lazy-import.js"
},
"./web-search": {
"types": "./dist/esm/web-search/index.d.ts",
"import": "./dist/esm/web-search/index.js",
-4
View File
@@ -2,10 +2,6 @@
export { AI_NODE_SDK_VERSION } from './ai-node-sdk-version';
// Utils
// NOTE: `lazyImport` is intentionally NOT re-exported here — import it from the
// lightweight `@n8n/ai-utilities/lazy-import` subpath so callers that use it to
// defer loading provider SDKs don't eagerly pull this barrel (which statically
// imports @langchain/*).
export { logWrapper } from './utils/log-wrapper';
export { logAiEvent } from './utils/log-ai-event';
export { parseSSEStream } from './utils/sse';
@@ -50,9 +50,9 @@
],
"exports": {
".": {
"types": "./dist/index.d.ts",
"require": "./dist/index.js",
"import": "./src/index.ts",
"types": "./dist/index.d.ts"
"import": "./dist/index.js"
}
},
"dependencies": {
@@ -41,3 +41,4 @@ export type { ParseAndValidateResult, ValidationWarning } from './code-builder';
// SSRF guard contract for the web_fetch tool (cli injects its SsrfProtectionService here)
export { type SsrfGuard, createPassthroughSsrfGuard } from './tools/utils/ssrf-guard';
export { type ChatPayload } from './workflow-builder-agent';
@@ -2,8 +2,8 @@
import type * as LangchainAnthropic from '@langchain/anthropic';
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
import type * as LangchainOpenAI from '@langchain/openai';
import { lazyImport } from '@n8n/ai-utilities/lazy-import';
import type { CustomFetch } from '@n8n/backend-network/transport';
import { lazyImport } from '@n8n/utils/lazy-import';
import { MAX_OUTPUT_TOKENS } from '@/constants';
+1
View File
@@ -61,6 +61,7 @@
"express": "catalog:",
"testcontainers": "catalog:",
"typescript": "catalog:",
"typescript6": "npm:@typescript/typescript6@6.0.2",
"vite": "catalog:",
"vitest": "catalog:",
"vitest-mock-extended": "catalog:"
+3 -1
View File
@@ -1,7 +1,9 @@
import { createVitestConfigWithDecorators } from '@n8n/vitest-config/node-decorators';
import fs from 'node:fs';
import path from 'node:path';
import ts from 'typescript';
// Uses the legacy JS compiler via the `typescript6` alias: tsgo (typescript 7)
// ships no programmatic API for this LanguageService-based entity transform.
import ts from 'typescript6';
import { mergeConfig, type Plugin } from 'vite';
import { configDefaults } from 'vitest/config';
@@ -157,7 +157,7 @@ export const baseConfig = tseslint.config(
/**
* https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/consistent-type-imports.md
*/
'@typescript-eslint/consistent-type-imports': 'error',
'@typescript-eslint/consistent-type-imports': ['error', { disallowTypeAnnotations: false }],
'@typescript-eslint/consistent-type-exports': 'error',
+2 -2
View File
@@ -35,9 +35,9 @@
],
"exports": {
".": {
"types": "./dist/index.d.ts",
"require": "./dist/index.js",
"import": "./src/index.ts",
"types": "./dist/index.d.ts"
"import": "./dist/index.js"
},
"./parsers": {
"require": "./dist/parsers/index.js",
+4 -4
View File
@@ -26,14 +26,14 @@
],
"exports": {
"./start": {
"types": "./dist/start.d.ts",
"require": "./dist/start.js",
"import": "./src/start.ts",
"types": "./dist/start.d.ts"
"import": "./src/start.ts"
},
".": {
"types": "./dist/index.d.ts",
"require": "./dist/index.js",
"import": "./src/index.ts",
"types": "./dist/index.d.ts"
"import": "./src/index.ts"
}
},
"dependencies": {
+2 -1
View File
@@ -117,7 +117,8 @@
"ts-essentials": "^7.0.3",
"tsconfig-paths": "^4.2.0",
"@vitest/coverage-v8": "catalog:",
"typescript": "catalog:",
"typescript": "catalog:typescript",
"typescript6": "npm:@typescript/typescript6@6.0.2",
"vite": "catalog:",
"vitest": "catalog:",
"vitest-mock-extended": "catalog:"
@@ -1281,7 +1281,7 @@ describe('CredentialsHelper', () => {
dynamicCredentialProxy.setResolverProvider(mockCredentialResolutionProvider);
const { CredentialResolutionError } = await import(
'@/modules/dynamic-credentials.ee/errors/credential-resolution.error'
'@/modules/dynamic-credentials.ee/errors/credential-resolution.error.js'
);
const resolvableCredentialEntity = {
@@ -1315,7 +1315,7 @@ describe('CredentialsHelper', () => {
dynamicCredentialProxy.setResolverProvider(mockCredentialResolutionProvider);
const { CredentialResolutionError } = await import(
'@/modules/dynamic-credentials.ee/errors/credential-resolution.error'
'@/modules/dynamic-credentials.ee/errors/credential-resolution.error.js'
);
const resolvableCredentialEntity = {
@@ -317,7 +317,7 @@ describe('CredentialsOverwrites', () => {
});
// Mock Publisher service - need to import the class first
const { Publisher } = await import('@/scaling/pubsub/publisher.service');
const { Publisher } = await import('@/scaling/pubsub/publisher.service.js');
publisherMock = { publishCommand: vi.fn() };
mockInstance(Publisher, publisherMock);
@@ -582,7 +582,7 @@ describe('CredentialsOverwrites', () => {
});
// Mock Publisher service
const { Publisher } = await import('@/scaling/pubsub/publisher.service');
const { Publisher } = await import('@/scaling/pubsub/publisher.service.js');
publisherMock = { publishCommand: vi.fn() };
mockInstance(Publisher, publisherMock);
@@ -972,7 +972,7 @@ describe('CredentialsOverwrites', () => {
});
// Mock Publisher service
const { Publisher } = await import('@/scaling/pubsub/publisher.service');
const { Publisher } = await import('@/scaling/pubsub/publisher.service.js');
publisherMock = { publishCommand: vi.fn() };
mockInstance(Publisher, publisherMock);
@@ -584,7 +584,7 @@ describe('LoadNodesAndCredentials', () => {
beforeEach(async () => {
// Import the mocked functions
const toolGeneration = await import('@/tool-generation');
const toolGeneration = await import('@/tool-generation/index.js');
createAiTools = toolGeneration.createAiTools as Mock;
createHitlTools = toolGeneration.createHitlTools as Mock;
@@ -71,7 +71,7 @@ describe('Webhook', () => {
describe('run', () => {
beforeEach(async () => {
const { ScalingService } = await import('@/scaling/scaling.service');
const { ScalingService } = await import('@/scaling/scaling.service.js');
Container.set(ScalingService, { setupQueue: vi.fn() } as unknown as InstanceType<
typeof ScalingService
>);
+1 -1
View File
@@ -71,7 +71,7 @@ export class SecurityAudit extends BaseCommand<z.infer<typeof flagsSchema>> {
throw new UserError([message, hint].join('. '));
}
const { SecurityAuditService } = await import('@/security-audit/security-audit.service');
const { SecurityAuditService } = await import('@/security-audit/security-audit.service.js');
const result = await Container.get(SecurityAuditService).run(
categories,
+12 -10
View File
@@ -141,7 +141,7 @@ export abstract class BaseCommand<F = never> {
this.globalConfig.multiMainSetup.enabled ||
this.globalConfig.cache.backend === 'redis';
if (useRedisForLocking) {
const { RedisLockService } = await import('@/scaling/redis-lock.service');
const { RedisLockService } = await import('@/scaling/redis-lock.service.js');
Container.get(LockService).setProvider(Container.get(RedisLockService));
}
@@ -194,7 +194,7 @@ export abstract class BaseCommand<F = never> {
);
}
const { TaskRunnerModule } = await import('@/task-runners/task-runner-module');
const { TaskRunnerModule } = await import('@/task-runners/task-runner-module.js');
await Container.get(TaskRunnerModule).start();
}
@@ -227,7 +227,7 @@ export abstract class BaseCommand<F = never> {
const communityPackagesConfig = Container.get(CommunityPackagesConfig);
if (communityPackagesConfig.enabled && this.needsCommunityPackages) {
const { CommunityPackagesService } = await import(
'@/modules/community-packages/community-packages.service'
'@/modules/community-packages/community-packages.service.js'
);
await Container.get(CommunityPackagesService).init();
}
@@ -269,7 +269,7 @@ export abstract class BaseCommand<F = never> {
const isS3WriteMode = binaryDataConfig.mode === 's3';
const isAzureWriteMode = binaryDataConfig.mode === 'azure';
const { DatabaseManager } = await import('@/binary-data/database.manager');
const { DatabaseManager } = await import('@/binary-data/database.manager.js');
binaryDataService.setManager('database', Container.get(DatabaseManager));
if (isS3WriteMode) {
@@ -340,7 +340,7 @@ export abstract class BaseCommand<F = never> {
const objectStoreService = await this.initObjectStoreIfConfigured();
if (objectStoreService) {
const { ObjectStoreManager } = await import(
'n8n-core/dist/binary-data/object-store.manager'
'n8n-core/dist/binary-data/object-store.manager.js'
);
binaryDataService.setManager('s3', new ObjectStoreManager(objectStoreService));
}
@@ -354,7 +354,9 @@ export abstract class BaseCommand<F = never> {
try {
const azureBlobService = await this.initAzureStoreIfConfigured();
if (azureBlobService) {
const { AzureBlobManager } = await import('n8n-core/dist/binary-data/azure-blob.manager');
const { AzureBlobManager } = await import(
'n8n-core/dist/binary-data/azure-blob.manager.js'
);
binaryDataService.setManager('azure', new AzureBlobManager(azureBlobService));
}
} catch {
@@ -373,12 +375,12 @@ export abstract class BaseCommand<F = never> {
if (Container.get(ObjectStoreConfig).bucket.name === '') return undefined;
const { ObjectStoreService } = await import(
'n8n-core/dist/binary-data/object-store/object-store.service.ee'
'n8n-core/dist/binary-data/object-store/object-store.service.ee.js'
);
const objectStoreService = Container.get(ObjectStoreService);
await objectStoreService.init();
const { S3ByteStore } = await import('@/blob-storage/s3-byte-store.ee');
const { S3ByteStore } = await import('@/blob-storage/s3-byte-store.ee.js');
Container.get(ExecutionDataJsonStore).registerByteStore(
's3',
new S3ByteStore(objectStoreService),
@@ -391,12 +393,12 @@ export abstract class BaseCommand<F = never> {
if (Container.get(AzureBlobConfig).containerName === '') return;
const { AzureBlobService } = await import(
'n8n-core/dist/binary-data/azure-blob/azure-blob.service.ee'
'n8n-core/dist/binary-data/azure-blob/azure-blob.service.ee.js'
);
const azureBlobService = Container.get(AzureBlobService);
await azureBlobService.init();
const { AzureByteStore } = await import('@/blob-storage/azure-byte-store.ee');
const { AzureByteStore } = await import('@/blob-storage/azure-byte-store.ee.js');
Container.get(ExecutionDataJsonStore).registerByteStore(
'az',
new AzureByteStore(azureBlobService),
+7 -7
View File
@@ -284,7 +284,7 @@ export class Start extends BaseCommand<z.infer<typeof flagsSchema>> {
await this.moduleRegistry.initModules(this.instanceSettings.instanceType);
// Initialize auth handler registry after modules are loaded
const { AuthHandlerRegistry } = await import('@/auth/auth-handler.registry');
const { AuthHandlerRegistry } = await import('@/auth/auth-handler.registry.js');
await Container.get(AuthHandlerRegistry).init();
if (this.instanceSettings.isMultiMain) {
@@ -297,7 +297,7 @@ export class Start extends BaseCommand<z.infer<typeof flagsSchema>> {
private async initInstanceSettingsLoader(): Promise<void> {
const { InstanceSettingsLoaderService } = await import(
'@/instance-settings-loader/instance-settings-loader.service'
'@/instance-settings-loader/instance-settings-loader.service.js'
);
await Container.get(InstanceSettingsLoaderService).init();
}
@@ -411,21 +411,21 @@ export class Start extends BaseCommand<z.infer<typeof flagsSchema>> {
// Start to get active workflows and run their triggers
if (this.globalConfig.workflows.useWorkflowPublicationService) {
const { PublishedWorkflowEnqueuer } = await import(
'@/workflows/publication/published-workflow-enqueuer'
'@/workflows/publication/published-workflow-enqueuer.js'
);
const { WorkflowPublicationOutboxConsumer } = await import(
'@/workflows/publication/workflow-publication-outbox-consumer'
'@/workflows/publication/workflow-publication-outbox-consumer.js'
);
const { WorkflowPublicationOutboxCleanupService } = await import(
'@/workflows/publication/workflow-publication-outbox-cleanup.service'
'@/workflows/publication/workflow-publication-outbox-cleanup.service.js'
);
const { WorkflowPublicationReconciler } = await import(
'@/workflows/publication/workflow-publication-reconciler.service'
'@/workflows/publication/workflow-publication-reconciler.service.js'
);
// Import for its side effect: registering the trigger deactivator's
// @OnLeaderStepdown and @OnShutdown handlers. Nothing else loads this module.
await import('@/workflows/publication/published-workflow-trigger-deactivator');
await import('@/workflows/publication/published-workflow-trigger-deactivator.js');
// Enqueue needs to happen before outbox consumer init, so it can activate
// everything on the first drain
+1 -1
View File
@@ -98,7 +98,7 @@ export class Webhook extends BaseCommand {
}
async run() {
const { ScalingService } = await import('@/scaling/scaling.service');
const { ScalingService } = await import('@/scaling/scaling.service.js');
await Container.get(ScalingService).setupQueue();
await this.server.start();
this.server.markAsReady();
+2 -2
View File
@@ -178,7 +178,7 @@ export class Worker extends BaseCommand<z.infer<typeof flagsSchema>> {
}
async initScalingService() {
const { ScalingService } = await import('@/scaling/scaling.service');
const { ScalingService } = await import('@/scaling/scaling.service.js');
this.scalingService = Container.get(ScalingService);
await this.scalingService.setupQueue();
@@ -193,7 +193,7 @@ export class Worker extends BaseCommand<z.infer<typeof flagsSchema>> {
let workerServer: WorkerServer | undefined;
if (Object.values(endpointsConfig).some((e) => e)) {
const { WorkerServer } = await import('@/scaling/worker-server');
const { WorkerServer } = await import('@/scaling/worker-server.js');
workerServer = Container.get(WorkerServer);
await workerServer.init(endpointsConfig);
}
+2 -2
View File
@@ -77,7 +77,7 @@ export class CredentialsOverwrites {
}
private async broadcastReloadOverwriteCredentialsCommand(): Promise<void> {
const { Publisher } = await import('@/scaling/pubsub/publisher.service');
const { Publisher } = await import('@/scaling/pubsub/publisher.service.js');
await Container.get(Publisher).publishCommand({ command: 'reload-overwrite-credentials' });
}
@@ -133,7 +133,7 @@ export class CredentialsOverwrites {
private async reloadFrontendService() {
// FrontendService has CredentialOverwrites injected via the constructor
// to break the circular dependency we need to use the container to get the instance
const { FrontendService } = await import('./services/frontend.service');
const { FrontendService } = await import('./services/frontend.service.js');
await Container.get(FrontendService)?.generateTypes();
}
@@ -1555,7 +1555,7 @@ export class TelemetryEventRelay extends EventRelay {
}
private async getOtelTelemetryInfo() {
const { OtelConfig } = await import('@/modules/otel/otel.config');
const { OtelConfig } = await import('@/modules/otel/otel.config.js');
const otelConfig = Container.get(OtelConfig);
return {
+1 -1
View File
@@ -155,7 +155,7 @@ export class License implements LicenseProvider {
private async broadcastReloadLicenseCommand() {
if (this.globalConfig.executions.mode === 'queue' && this.instanceSettings.isLeader) {
const { Publisher } = await import('@/scaling/pubsub/publisher.service');
const { Publisher } = await import('@/scaling/pubsub/publisher.service.js');
await Container.get(Publisher).publishCommand({ command: 'reload-license' });
}
}
@@ -656,11 +656,11 @@ export class LoadNodesAndCredentials {
}
async setupHotReload() {
const { default: debounce } = await import('lodash/debounce');
const { default: debounce } = await import('lodash/debounce.js');
const { subscribe } = await import('@parcel/watcher');
const { Push } = await import('@/push');
const { Push } = await import('@/push/index.js');
const push = Container.get(Push);
for (const loader of Object.values(this.loaders)) {
@@ -108,7 +108,7 @@ export class AgentPublishService {
);
}
const { AgentTaskService } = await import('./agent-task.service');
const { AgentTaskService } = await import('./agent-task.service.js');
await Container.get(AgentTaskService)
.requestReconcile(agentId)
.catch((error) =>
@@ -145,7 +145,7 @@ export class AgentPublishService {
});
}
const { AgentTaskService } = await import('./agent-task.service');
const { AgentTaskService } = await import('./agent-task.service.js');
await Container.get(AgentTaskService)
.requestReconcile(agentId)
.catch((error) =>
@@ -113,14 +113,14 @@ export interface ReconstructAgentRuntimeParams {
async function getChatIntegrationToolServices() {
const { IntegrationMessageContextService } = await import(
'./integrations/integration-message-context.service'
'./integrations/integration-message-context.service.js'
);
// eslint-disable-next-line import-x/no-cycle
const { ChatIntegrationActionExecutor } = await import(
'./integrations/integration-action-executor'
'./integrations/integration-action-executor.js'
);
const { ChatIntegrationContextQueryExecutor } = await import(
'./integrations/integration-context-query-executor'
'./integrations/integration-context-query-executor.js'
);
return {
@@ -131,7 +131,7 @@ async function getChatIntegrationToolServices() {
}
async function getWorkflowRunner(): Promise<WorkflowRunner> {
const { WorkflowRunner } = await import('@/workflow-runner');
const { WorkflowRunner } = await import('@/workflow-runner.js');
return Container.get(WorkflowRunner);
}
@@ -467,7 +467,7 @@ export class AgentRuntimeReconstructionService {
private makeToolResolver(projectId: string): ToolResolver {
return async (ref: AgentJsonToolConfig) => {
if (ref.type === 'workflow') {
const { resolveWorkflowTool } = await import('./tools/workflow-tool-factory');
const { resolveWorkflowTool } = await import('./tools/workflow-tool-factory.js');
return await resolveWorkflowTool(ref, {
workflowRepository: this.workflowRepository,
workflowRunner: await getWorkflowRunner(),
@@ -478,7 +478,7 @@ export class AgentRuntimeReconstructionService {
}
if (ref.type === 'node') {
const { resolveNodeTool } = await import('./tools/node-tool-factory');
const { resolveNodeTool } = await import('./tools/node-tool-factory.js');
return await resolveNodeTool(ref, {
executor: this.ephemeralNodeExecutor,
projectId,
@@ -524,7 +524,7 @@ export class AgentRuntimeReconstructionService {
(await this.agentFileRepository.hasFilesForAgent(agentId))
) {
const { createKnowledgeRetrievalTools } = await import(
'./tools/knowledge/search-knowledge.tool'
'./tools/knowledge/search-knowledge.tool.js'
);
agent.tool(
createKnowledgeRetrievalTools({
@@ -197,7 +197,7 @@ export class AgentSkillsService {
}
private async clearRuntimes(agentId: string): Promise<void> {
const { AgentRuntimeCacheService } = await import('./agent-runtime-cache.service');
const { AgentRuntimeCacheService } = await import('./agent-runtime-cache.service.js');
Container.get(AgentRuntimeCacheService).clearRuntimes(agentId);
}
}
@@ -8,50 +8,52 @@ import { InstanceSettings } from 'n8n-core';
@BackendModule({ name: 'agents' })
export class AgentsModule implements ModuleInterface {
async init() {
await import('./agents-catalog.controller');
await import('./agent-threads.controller');
await import('./agents.controller');
await import('./agents-config.controller');
await import('./agents-skills.controller');
await import('./agent-knowledge.controller');
await import('./agent-publish.controller');
await import('./agent-chat.controller');
await import('./agent-integrations.controller');
await import('./agent-vector-stores.controller');
await import('./agent-tasks.controller');
await import('./agent-sandbox.controller');
await import('./agents-list.controller');
await import('./builder/agents-builder-settings.controller');
await import('./agents-catalog.controller.js');
await import('./agent-threads.controller.js');
await import('./agents.controller.js');
await import('./agents-config.controller.js');
await import('./agents-skills.controller.js');
await import('./agent-knowledge.controller.js');
await import('./agent-publish.controller.js');
await import('./agent-chat.controller.js');
await import('./agent-integrations.controller.js');
await import('./agent-vector-stores.controller.js');
await import('./agent-tasks.controller.js');
await import('./agent-sandbox.controller.js');
await import('./agents-list.controller.js');
await import('./builder/agents-builder-settings.controller.js');
const { AgentsService } = await import('./agents.service');
const { AgentsService } = await import('./agents.service.js');
Container.get(AgentsService);
const { AgentsBuilderSettingsService } = await import(
'./builder/agents-builder-settings.service'
'./builder/agents-builder-settings.service.js'
);
Container.get(AgentsBuilderSettingsService);
const { AgentExecutionService } = await import('./agent-execution.service');
const { AgentExecutionService } = await import('./agent-execution.service.js');
Container.get(AgentExecutionService);
const { AgentRuntimeCacheService } = await import('./agent-runtime-cache.service');
const { AgentRuntimeCacheService } = await import('./agent-runtime-cache.service.js');
Container.get(AgentRuntimeCacheService);
const { AgentHistoryRepository } = await import('./repositories/agent-history.repository');
const { AgentHistoryRepository } = await import('./repositories/agent-history.repository.js');
Container.get(AgentHistoryRepository);
// Register the sandboxed runtime service (lazy — the V8 isolate is only
// created on first use, so this import has negligible startup cost).
const { AgentSecureRuntime } = await import('./runtime/agent-secure-runtime');
const { AgentSecureRuntime } = await import('./runtime/agent-secure-runtime.js');
Container.get(AgentSecureRuntime);
// Populate the integration registry with supported chat platforms.
// Adding a new platform is adding one subclass + one register() call.
const { ChatIntegrationRegistry } = await import('./integrations/agent-chat-integration');
const { SlackIntegration } = await import('./integrations/platforms/slack-integration');
const { TelegramIntegration } = await import('./integrations/platforms/telegram-integration');
const { LinearIntegration } = await import('./integrations/platforms/linear-integration');
const { N8nChatIntegration } = await import('./integrations/platforms/n8n-chat-integration');
const { ChatIntegrationRegistry } = await import('./integrations/agent-chat-integration.js');
const { SlackIntegration } = await import('./integrations/platforms/slack-integration.js');
const { TelegramIntegration } = await import(
'./integrations/platforms/telegram-integration.js'
);
const { LinearIntegration } = await import('./integrations/platforms/linear-integration.js');
const { N8nChatIntegration } = await import('./integrations/platforms/n8n-chat-integration.js');
const registry = Container.get(ChatIntegrationRegistry);
registry.register(Container.get(SlackIntegration));
registry.register(Container.get(TelegramIntegration));
@@ -69,8 +71,8 @@ export class AgentsModule implements ModuleInterface {
//
// Tasks remain leader-only by design — a cron firing on multiple
// mains would run the agent twice for the same tick.
const { ChatIntegrationService } = await import('./integrations/chat-integration.service');
const { AgentTaskService } = await import('./agent-task.service');
const { ChatIntegrationService } = await import('./integrations/chat-integration.service.js');
const { AgentTaskService } = await import('./agent-task.service.js');
const chatService = Container.get(ChatIntegrationService);
const taskService = Container.get(AgentTaskService);
const logger = Container.get(Logger);
@@ -93,7 +95,7 @@ export class AgentsModule implements ModuleInterface {
async settings() {
const config = Container.get(AgentsConfig);
const { isAgentKnowledgeBaseEnabled } = await import('./agent-knowledge-gate');
const { isAgentKnowledgeBaseEnabled } = await import('./agent-knowledge-gate.js');
return {
enabled: true,
modules: [...config.modules],
@@ -102,33 +104,35 @@ export class AgentsModule implements ModuleInterface {
}
async entities() {
const { Agent } = await import('./entities/agent.entity');
const { AgentFile } = await import('./entities/agent-file.entity');
const { AgentChatSubscription } = await import('./entities/agent-chat-subscription.entity');
const { AgentCheckpoint } = await import('./entities/agent-checkpoint.entity');
const { AgentResourceEntity } = await import('./entities/agent-resource.entity');
const { AgentThreadEntity } = await import('./entities/agent-thread.entity');
const { AgentMessageEntity } = await import('./entities/agent-message.entity');
const { AgentExecutionThread } = await import('./entities/agent-execution-thread.entity');
const { AgentExecution } = await import('./entities/agent-execution.entity');
const { AgentHistory } = await import('./entities/agent-history.entity');
const { AgentTask } = await import('./entities/agent-task.entity');
const { AgentTaskRunLock } = await import('./entities/agent-task-run-lock.entity');
const { AgentTaskSnapshot } = await import('./entities/agent-task-snapshot.entity');
const { AgentObservationEntity } = await import('./entities/agent-observation.entity');
const { Agent } = await import('./entities/agent.entity.js');
const { AgentFile } = await import('./entities/agent-file.entity.js');
const { AgentChatSubscription } = await import('./entities/agent-chat-subscription.entity.js');
const { AgentCheckpoint } = await import('./entities/agent-checkpoint.entity.js');
const { AgentResourceEntity } = await import('./entities/agent-resource.entity.js');
const { AgentThreadEntity } = await import('./entities/agent-thread.entity.js');
const { AgentMessageEntity } = await import('./entities/agent-message.entity.js');
const { AgentExecutionThread } = await import('./entities/agent-execution-thread.entity.js');
const { AgentExecution } = await import('./entities/agent-execution.entity.js');
const { AgentHistory } = await import('./entities/agent-history.entity.js');
const { AgentTask } = await import('./entities/agent-task.entity.js');
const { AgentTaskRunLock } = await import('./entities/agent-task-run-lock.entity.js');
const { AgentTaskSnapshot } = await import('./entities/agent-task-snapshot.entity.js');
const { AgentObservationEntity } = await import('./entities/agent-observation.entity.js');
const { AgentObservationCursorEntity } = await import(
'./entities/agent-observation-cursor.entity'
'./entities/agent-observation-cursor.entity.js'
);
const { AgentObservationLockEntity } = await import('./entities/agent-observation-lock.entity');
const { AgentMemoryEntryEntity } = await import('./entities/agent-memory-entry.entity');
const { AgentObservationLockEntity } = await import(
'./entities/agent-observation-lock.entity.js'
);
const { AgentMemoryEntryEntity } = await import('./entities/agent-memory-entry.entity.js');
const { AgentMemoryEntryLockEntity } = await import(
'./entities/agent-memory-entry-lock.entity'
'./entities/agent-memory-entry-lock.entity.js'
);
const { AgentMemoryEntrySourceEntity } = await import(
'./entities/agent-memory-entry-source.entity'
'./entities/agent-memory-entry-source.entity.js'
);
const { AgentMemoryEntryCursorEntity } = await import(
'./entities/agent-memory-entry-cursor.entity'
'./entities/agent-memory-entry-cursor.entity.js'
);
return [
@@ -156,7 +160,7 @@ export class AgentsModule implements ModuleInterface {
}
async context() {
const { AgentsService } = await import('./agents.service');
const { AgentsService } = await import('./agents.service.js');
return { agentsService: Container.get(AgentsService) };
}
@@ -219,7 +219,7 @@ export class AgentsService {
this.eventService.emit('agent-deleted', { agentId, projectId });
try {
const { AgentTaskService } = await import('./agent-task.service');
const { AgentTaskService } = await import('./agent-task.service.js');
await Container.get(AgentTaskService).requestReconcile(agentId);
} catch (error) {
this.logger.warn('Failed to stop tasks on agent delete', {
@@ -89,7 +89,7 @@ interface DisconnectChannelOptions {
async function getAgentExecutionOrchestratorService() {
// eslint-disable-next-line import-x/no-cycle
const { AgentExecutionOrchestratorService } = await import(
'../agent-execution-orchestrator.service'
'../agent-execution-orchestrator.service.js'
);
return Container.get(AgentExecutionOrchestratorService);
}
@@ -23,7 +23,7 @@ export async function syncAgentIntegrations(
): Promise<void> {
try {
// eslint-disable-next-line import-x/no-cycle
const { ChatIntegrationService } = await import('./chat-integration.service');
const { ChatIntegrationService } = await import('./chat-integration.service.js');
await Container.get(ChatIntegrationService).syncToConfig(agent, previous, next);
} catch (error) {
logger.warn('Failed to sync chat integrations', {
@@ -240,7 +240,7 @@ export class SubAgentForegroundRunner {
async function getReconstructionService() {
// eslint-disable-next-line import-x/no-cycle
const { AgentRuntimeReconstructionService } = await import(
'../agent-runtime-reconstruction.service'
'../agent-runtime-reconstruction.service.js'
);
return Container.get(AgentRuntimeReconstructionService);
}
@@ -9,12 +9,12 @@ export class BreakingChangesModule implements ModuleInterface {
if (!MIGRATION_REPORT_TARGET_VERSION) return;
// Import rules so that they are added to the BreakingChangeRuleMetadata registry
await import('./rules');
await import('./rules/index.js');
// Register rules in the service
const { BreakingChangeService } = await import('./breaking-changes.service');
const { BreakingChangeService } = await import('./breaking-changes.service.js');
Container.get(BreakingChangeService).registerRules();
await import('./breaking-changes.controller');
await import('./breaking-changes.controller.js');
}
}
@@ -7,9 +7,9 @@ import { InstanceSettings } from 'n8n-core';
@BackendModule({ name: 'chat-hub' })
export class ChatHubModule implements ModuleInterface {
async init() {
await import('./chat-hub.controller');
await import('./chat-hub.settings.controller');
const { ChatHubEventRelay } = await import('./chat-hub-event-relay.service');
await import('./chat-hub.controller.js');
await import('./chat-hub.settings.controller.js');
const { ChatHubEventRelay } = await import('./chat-hub-event-relay.service.js');
Container.get(ChatHubEventRelay);
@@ -18,12 +18,12 @@ export class ChatHubModule implements ModuleInterface {
const isQueueMode = Container.get(ExecutionsConfig).mode === 'queue';
const isWorker = Container.get(InstanceSettings).isWorker;
if (!isQueueMode || isWorker) {
await import('./chat-hub-execution-watcher.service');
await import('./chat-hub-execution-watcher.service.js');
}
}
async settings() {
const { ChatHubSettingsService } = await import('./chat-hub.settings.service');
const { ChatHubSettingsService } = await import('./chat-hub.settings.service.js');
const service = Container.get(ChatHubSettingsService);
const [enabled, providers, semanticSearch] = await Promise.all([
service.getEnabled(),
@@ -40,10 +40,10 @@ export class ChatHubModule implements ModuleInterface {
}
async entities() {
const { ChatHubSession } = await import('./chat-hub-session.entity');
const { ChatHubMessage } = await import('./chat-hub-message.entity');
const { ChatHubAgent } = await import('./chat-hub-agent.entity');
const { ChatHubTool } = await import('./chat-hub-tool.entity');
const { ChatHubSession } = await import('./chat-hub-session.entity.js');
const { ChatHubMessage } = await import('./chat-hub-message.entity.js');
const { ChatHubAgent } = await import('./chat-hub-agent.entity.js');
const { ChatHubTool } = await import('./chat-hub-tool.entity.js');
return [ChatHubSession, ChatHubMessage, ChatHubAgent, ChatHubTool];
}
@@ -453,7 +453,7 @@ describe('executeNpmCommand', () => {
const importFreshModule = async () => {
vi.resetModules();
return await import('../npm-utils');
return await import('../npm-utils.js');
};
beforeEach(() => {
@@ -8,23 +8,23 @@ import path from 'node:path';
@BackendModule({ name: 'community-packages' })
export class CommunityPackagesModule implements ModuleInterface {
async init() {
await import('./community-packages.controller');
await import('./community-node-types.controller');
await import('./community-packages.controller.js');
await import('./community-node-types.controller.js');
}
async commands() {
await import('./community-node.command');
await import('./community-node.command.js');
}
async entities() {
const { InstalledNodes } = await import('./installed-nodes.entity');
const { InstalledPackages } = await import('./installed-packages.entity');
const { InstalledNodes } = await import('./installed-nodes.entity.js');
const { InstalledPackages } = await import('./installed-packages.entity.js');
return [InstalledNodes, InstalledPackages] as EntityClass[];
}
async settings() {
const { CommunityPackagesConfig } = await import('./community-packages.config');
const { CommunityPackagesConfig } = await import('./community-packages.config.js');
return {
communityNodesEnabled: Container.get(CommunityPackagesConfig).enabled,
@@ -33,7 +33,7 @@ export class CommunityPackagesModule implements ModuleInterface {
}
async nodeLoaders() {
const { CommunityPackagesConfig } = await import('./community-packages.config');
const { CommunityPackagesConfig } = await import('./community-packages.config.js');
if (Container.get(CommunityPackagesConfig).preventLoading) return [];
const dir = path.join(Container.get(InstanceSettings).nodesDownloadDir, 'node_modules');
@@ -5,15 +5,15 @@ import { Container } from '@n8n/di';
@BackendModule({ name: 'data-table' })
export class DataTableModule implements ModuleInterface {
async init() {
await import('./data-table.controller');
await import('./data-table-aggregate.controller');
await import('./data-table-uploads.controller');
await import('./data-table.controller.js');
await import('./data-table-aggregate.controller.js');
await import('./data-table-uploads.controller.js');
const { DataTableService } = await import('./data-table.service');
const { DataTableService } = await import('./data-table.service.js');
await Container.get(DataTableService).start();
const { OwnershipTransferHandlerRegistry } = await import(
'@/services/ownership-transfer/ownership-transfer-handler.registry'
'@/services/ownership-transfer/ownership-transfer-handler.registry.js'
);
Container.get(OwnershipTransferHandlerRegistry).register({
resource: 'data-table',
@@ -29,34 +29,34 @@ export class DataTableModule implements ModuleInterface {
},
});
const { DataTableAggregateService } = await import('./data-table-aggregate.service');
const { DataTableAggregateService } = await import('./data-table-aggregate.service.js');
await Container.get(DataTableAggregateService).start();
const { DataTableFileCleanupService } = await import('./data-table-file-cleanup.service');
const { DataTableFileCleanupService } = await import('./data-table-file-cleanup.service.js');
await Container.get(DataTableFileCleanupService).start();
}
@OnShutdown()
async shutdown() {
const { DataTableService } = await import('./data-table.service');
const { DataTableService } = await import('./data-table.service.js');
await Container.get(DataTableService).shutdown();
const { DataTableAggregateService } = await import('./data-table-aggregate.service');
const { DataTableAggregateService } = await import('./data-table-aggregate.service.js');
await Container.get(DataTableAggregateService).shutdown();
const { DataTableFileCleanupService } = await import('./data-table-file-cleanup.service');
const { DataTableFileCleanupService } = await import('./data-table-file-cleanup.service.js');
await Container.get(DataTableFileCleanupService).shutdown();
}
async entities() {
const { DataTable } = await import('./data-table.entity');
const { DataTableColumn } = await import('./data-table-column.entity');
const { DataTable } = await import('./data-table.entity.js');
const { DataTableColumn } = await import('./data-table-column.entity.js');
return [DataTable, DataTableColumn];
}
async context() {
const { DataTableProxyService } = await import('./data-table-proxy.service');
const { DataTableProxyService } = await import('./data-table-proxy.service.js');
return { dataTableProxyProvider: Container.get(DataTableProxyService) };
}
@@ -332,7 +332,7 @@ export class DataTableService {
return result;
}
async upsertRow<T extends boolean | undefined>(
async upsertRow(
dataTableId: string,
projectId: string,
dto: Omit<UpsertDataTableRowDto, 'returnData' | 'dryRun'>,
@@ -433,7 +433,7 @@ export class DataTableService {
return { data: transformedData, filter: transformedFilter };
}
async updateRows<T extends boolean | undefined>(
async updateRows(
dataTableId: string,
projectId: string,
dto: Omit<UpdateDataTableRowDto, 'returnData' | 'dryRun'>,
@@ -304,7 +304,7 @@ describe('GET /workflows/:workflowId/execution-status - CORS Integration', () =>
test('should set CORS headers on GET request', async () => {
// Mock the workflow status service to return a valid response
const { CredentialResolverWorkflowService } = await import(
'../services/credential-resolver-workflow.service'
'../services/credential-resolver-workflow.service.js'
);
const workflowService = Container.get(CredentialResolverWorkflowService);
vi.spyOn(workflowService, 'getWorkflowStatus').mockResolvedValue([
@@ -77,7 +77,7 @@ beforeAll(async () => {
mockInstance(EnterpriseCredentialsService);
const { CredentialResolverWorkflowService } = await import(
'../services/credential-resolver-workflow.service'
'../services/credential-resolver-workflow.service.js'
);
workflowService = Container.get(CredentialResolverWorkflowService);
});
@@ -17,10 +17,12 @@ function isExternalResolversEnabled(): boolean {
@BackendModule({ name: 'dynamic-credentials', licenseFlag: LICENSE_FEATURES.DYNAMIC_CREDENTIALS })
export class DynamicCredentialsModule implements ModuleInterface {
async init() {
await import('./dynamic-credentials.controller');
await import('./dynamic-credentials.controller.js');
// Import the n8n oauth extractor and seeder
const { N8nOAuthIdentitySeeder } = await import('./context-establishment-hooks/n8n-oauth');
const { N8nOAuthIdentitySeeder } = await import(
'./context-establishment-hooks/n8n-oauth/index.js'
);
Container.get(TriggerAuthIdentitySeederProxy).registerSeeder(
Container.get(N8nOAuthIdentitySeeder),
@@ -28,12 +30,12 @@ export class DynamicCredentialsModule implements ModuleInterface {
// System resolver powers private credentials; OAuth/Slack resolvers and
// their management/identity-extractor surfaces are external-only.
await import('./credential-resolvers/n8n-credential-resolver');
await import('./credential-resolvers/n8n-credential-resolver.js');
if (isExternalResolversEnabled()) {
await import('./credential-resolvers.controller');
await import('./context-establishment-hooks');
await import('./credential-resolvers/oauth-credential-resolver');
await import('./credential-resolvers/slack-credential-resolver');
await import('./credential-resolvers.controller.js');
await import('./context-establishment-hooks/index.js');
await import('./credential-resolvers/oauth-credential-resolver.js');
await import('./credential-resolvers/slack-credential-resolver.js');
}
const {
DynamicCredentialResolverRegistry,
@@ -41,14 +43,16 @@ export class DynamicCredentialsModule implements ModuleInterface {
DynamicCredentialService,
N8nResolverSeeder,
CredentialConnectionStatusService,
} = await import('./services');
await import('./workflow-status.controller');
} = await import('./services/index.js');
await import('./workflow-status.controller.js');
await Container.get(DynamicCredentialResolverRegistry).init();
await Container.get(N8nResolverSeeder).seed();
// Register the credential resolution provider with CredentialsHelper
const { DynamicCredentialsProxy } = await import('../../credentials/dynamic-credentials-proxy');
const { DynamicCredentialsProxy } = await import(
'../../credentials/dynamic-credentials-proxy.js'
);
const credentialsProxy = Container.get(DynamicCredentialsProxy);
const dynamicCredentialService = Container.get(DynamicCredentialService);
const dynamicCredentialStorageService = Container.get(DynamicCredentialStorageService);
@@ -58,7 +62,7 @@ export class DynamicCredentialsModule implements ModuleInterface {
// Register the per-user connection status provider so the credentials
// service can populate `connectedByMe` on responses.
const { CredentialConnectionStatusProxy } = await import(
'../../credentials/credential-connection-status-proxy'
'../../credentials/credential-connection-status-proxy.js'
);
Container.get(CredentialConnectionStatusProxy).setProvider(
Container.get(CredentialConnectionStatusService),
@@ -66,10 +70,14 @@ export class DynamicCredentialsModule implements ModuleInterface {
}
async entities() {
const { DynamicCredentialResolver } = await import('./database/entities/credential-resolver');
const { DynamicCredentialEntry } = await import('./database/entities/dynamic-credential-entry');
const { DynamicCredentialResolver } = await import(
'./database/entities/credential-resolver.js'
);
const { DynamicCredentialEntry } = await import(
'./database/entities/dynamic-credential-entry.js'
);
const { DynamicCredentialUserEntry } = await import(
'./database/entities/dynamic-credential-user-entry'
'./database/entities/dynamic-credential-user-entry.js'
);
return [DynamicCredentialResolver, DynamicCredentialEntry, DynamicCredentialUserEntry];
@@ -77,7 +85,7 @@ export class DynamicCredentialsModule implements ModuleInterface {
async context() {
const { CredentialCheckProxyService } = await import(
'./services/credential-check-proxy.service'
'./services/credential-check-proxy.service.js'
);
return { credentialCheckProxy: Container.get(CredentialCheckProxyService) };
}
@@ -11,11 +11,11 @@ function isKeyRotationApiEnabled(): boolean {
export class EncryptionKeyManagerModule implements ModuleInterface {
async init() {
if (isKeyRotationApiEnabled()) {
await import('./key-manager.service');
await import('./key-manager.service.js');
if (Container.get(InstanceSettings).instanceType === 'main') {
await import('./encryption-key.controller');
await import('./encryption-key.controller.js');
}
const { EncryptionBootstrapService } = await import('./encryption-bootstrap.service');
const { EncryptionBootstrapService } = await import('./encryption-bootstrap.service.js');
await Container.get(EncryptionBootstrapService).run();
}
}
@@ -5,15 +5,15 @@ import { Container } from '@n8n/di';
@BackendModule({ name: 'external-secrets', licenseFlag: 'feat:externalSecrets' })
export class ExternalSecretsModule implements ModuleInterface {
async init() {
await import('./external-secrets.controller.ee');
await import('./external-secrets-settings.controller.ee');
await import('./external-secrets.controller.ee.js');
await import('./external-secrets-settings.controller.ee.js');
await import('./secrets-providers-types.controller.ee');
await import('./secrets-providers-connections.controller.ee');
await import('./secrets-providers-completions.controller.ee');
await import('./secrets-providers-project.controller.ee');
await import('./secrets-providers-types.controller.ee.js');
await import('./secrets-providers-connections.controller.ee.js');
await import('./secrets-providers-completions.controller.ee.js');
await import('./secrets-providers-project.controller.ee.js');
const { ExternalSecretsManager } = await import('./external-secrets-manager.ee');
const { ExternalSecretsManager } = await import('./external-secrets-manager.ee.js');
const { ExternalSecretsProxy } = await import('n8n-core');
const externalSecretsManager = Container.get(ExternalSecretsManager);
@@ -24,11 +24,11 @@ export class ExternalSecretsModule implements ModuleInterface {
}
async settings() {
const { ExternalSecretsConfig } = await import('./external-secrets.config');
const { ExternalSecretsConfig } = await import('./external-secrets.config.js');
const config = Container.get(ExternalSecretsConfig);
const { ExternalSecretsSettingsService } = await import(
'./external-secrets-settings.service.ee'
'./external-secrets-settings.service.ee.js'
);
const settingsService = Container.get(ExternalSecretsSettingsService);
@@ -44,7 +44,7 @@ export class ExternalSecretsModule implements ModuleInterface {
@OnShutdown()
async shutdown() {
const { ExternalSecretsManager } = await import('./external-secrets-manager.ee');
const { ExternalSecretsManager } = await import('./external-secrets-manager.ee.js');
Container.get(ExternalSecretsManager).shutdown();
}
@@ -5,14 +5,14 @@ import { Container } from '@n8n/di';
@BackendModule({ name: 'favorites', instanceTypes: ['main'] })
export class FavoritesModule implements ModuleInterface {
async init() {
await import('./favorites.controller');
await import('./favorites.controller.js');
const { FavoritesEventRelay } = await import('./favorites.event-relay');
const { FavoritesEventRelay } = await import('./favorites.event-relay.js');
Container.get(FavoritesEventRelay).init();
}
async entities() {
const { UserFavorite } = await import('./database/entities/user-favorite.entity');
const { UserFavorite } = await import('./database/entities/user-favorite.entity.js');
return [UserFavorite] as never;
}
}
@@ -9,29 +9,29 @@ import { Container } from '@n8n/di';
@BackendModule({ name: 'insights', instanceTypes: ['main', 'webhook'] })
export class InsightsModule implements ModuleInterface {
async init() {
await import('./insights.controller');
await import('./insights.controller.js');
const { InsightsService } = await import('./insights.service');
const { InsightsService } = await import('./insights.service.js');
await Container.get(InsightsService).init();
}
async entities() {
const { InsightsByPeriod } = await import('./database/entities/insights-by-period');
const { InsightsMetadata } = await import('./database/entities/insights-metadata');
const { InsightsRaw } = await import('./database/entities/insights-raw');
const { InsightsByPeriod } = await import('./database/entities/insights-by-period.js');
const { InsightsMetadata } = await import('./database/entities/insights-metadata.js');
const { InsightsRaw } = await import('./database/entities/insights-raw.js');
return [InsightsByPeriod, InsightsMetadata, InsightsRaw];
}
async settings() {
const { InsightsSettings } = await import('./insights.settings');
const { InsightsSettings } = await import('./insights.settings.js');
return await Container.get(InsightsSettings).settings();
}
@OnShutdown()
async shutdown() {
const { InsightsService } = await import('./insights.service');
const { InsightsService } = await import('./insights.service.js');
await Container.get(InsightsService).shutdown();
}
@@ -34,7 +34,7 @@ export class InsightsService {
return;
}
const { InsightsCollectionService } = await import('./insights-collection.service');
const { InsightsCollectionService } = await import('./insights-collection.service.js');
const collectionService = Container.get(InsightsCollectionService);
if (enable) {
collectionService.init();
@@ -10,7 +10,7 @@ let findAgentNodeInTree: (
beforeAll(async () => {
({ buildAgentTreeFromEvents, findAgentNodeInTree } = (await import(
'../../../../../@n8n/instance-ai/src/utils/agent-tree'
'../../../../../@n8n/instance-ai/src/utils/agent-tree.js'
)) as {
buildAgentTreeFromEvents: (events: InstanceAiEvent[]) => InstanceAiAgentNode;
findAgentNodeInTree: (
@@ -1,7 +1,7 @@
// Mock the barrel import so these adapter tests only exercise local formatting helpers.
vi.mock('@n8n/instance-ai', async () => {
const { WorkflowSaveConflictError } = await import(
'../../../../../@n8n/instance-ai/src/errors/workflow-save-conflict.error'
'../../../../../@n8n/instance-ai/src/errors/workflow-save-conflict.error.js'
);
return {
WorkflowSaveConflictError,
@@ -59,7 +59,7 @@ afterEach(() => {
describe('resolveLibraryId (via fetchApiDocs)', () => {
it('should return docs when library search and docs fetch both succeed', async () => {
const { fetchApiDocs } = await import('../api-docs');
const { fetchApiDocs } = await import('../api-docs.js');
// Library search
mockFetchResponse(200, [{ id: '/lib/slack-api', trust_score: 90 }]);
@@ -73,7 +73,7 @@ describe('resolveLibraryId (via fetchApiDocs)', () => {
});
it('should cache library ID — second call does not re-fetch library search', async () => {
const { fetchApiDocs } = await import('../api-docs');
const { fetchApiDocs } = await import('../api-docs.js');
// First call: library search + docs
mockFetchResponse(200, [{ id: '/lib/slack-api', trust_score: 90 }]);
@@ -92,7 +92,7 @@ describe('resolveLibraryId (via fetchApiDocs)', () => {
});
it('should return fallback when library search returns non-200', async () => {
const { fetchApiDocs } = await import('../api-docs');
const { fetchApiDocs } = await import('../api-docs.js');
mockFetchResponse(500, 'Internal Server Error');
@@ -103,7 +103,7 @@ describe('resolveLibraryId (via fetchApiDocs)', () => {
});
it('should return fallback when library search returns empty results', async () => {
const { fetchApiDocs } = await import('../api-docs');
const { fetchApiDocs } = await import('../api-docs.js');
mockFetchResponse(200, []);
@@ -114,7 +114,7 @@ describe('resolveLibraryId (via fetchApiDocs)', () => {
});
it('should log warning on 429 status (quota exceeded)', async () => {
const { fetchApiDocs } = await import('../api-docs');
const { fetchApiDocs } = await import('../api-docs.js');
mockFetchResponse(429, 'Rate limit exceeded');
@@ -126,7 +126,7 @@ describe('resolveLibraryId (via fetchApiDocs)', () => {
});
it('should log warning when response body contains "Quota"', async () => {
const { fetchApiDocs } = await import('../api-docs');
const { fetchApiDocs } = await import('../api-docs.js');
mockFetchResponse(403, 'Quota limit reached for your plan');
@@ -138,7 +138,7 @@ describe('resolveLibraryId (via fetchApiDocs)', () => {
});
it('should log warning only once per session (context7WarningLogged flag)', async () => {
const { fetchApiDocs } = await import('../api-docs');
const { fetchApiDocs } = await import('../api-docs.js');
// First call — 429, should warn
mockFetchResponse(429, 'Rate limit');
@@ -152,7 +152,7 @@ describe('resolveLibraryId (via fetchApiDocs)', () => {
});
it('should return fallback on fetch timeout / network error', async () => {
const { fetchApiDocs } = await import('../api-docs');
const { fetchApiDocs } = await import('../api-docs.js');
mockFetch.mockRejectedValueOnce(new Error('network timeout'));
@@ -171,7 +171,7 @@ describe('resolveLibraryId (via fetchApiDocs)', () => {
describe('fetchApiDocs', () => {
it('should return docs text from Context7 on success', async () => {
const { fetchApiDocs } = await import('../api-docs');
const { fetchApiDocs } = await import('../api-docs.js');
mockFetchResponse(200, [{ id: '/lib/github-api', trust_score: 80 }]);
mockFetchResponse(200, 'GET /repos/{owner}/{repo} — returns a repository');
@@ -182,7 +182,7 @@ describe('fetchApiDocs', () => {
});
it('should cache docs per serviceName + endpointQuery', async () => {
const { fetchApiDocs } = await import('../api-docs');
const { fetchApiDocs } = await import('../api-docs.js');
// First call: library search + docs
mockFetchResponse(200, [{ id: '/lib/slack-api', trust_score: 90 }]);
@@ -200,7 +200,7 @@ describe('fetchApiDocs', () => {
});
it('should return fallback when docs endpoint returns non-200', async () => {
const { fetchApiDocs } = await import('../api-docs');
const { fetchApiDocs } = await import('../api-docs.js');
mockFetchResponse(200, [{ id: '/lib/slack-api', trust_score: 90 }]);
mockFetchResponse(500, 'Server Error');
@@ -211,7 +211,7 @@ describe('fetchApiDocs', () => {
});
it('should return fallback when docs response is empty text', async () => {
const { fetchApiDocs } = await import('../api-docs');
const { fetchApiDocs } = await import('../api-docs.js');
mockFetchResponse(200, [{ id: '/lib/slack-api', trust_score: 90 }]);
mockFetchResponse(200, ' ');
@@ -223,7 +223,7 @@ describe('fetchApiDocs', () => {
it('should pass CONTEXT7_API_KEY as Bearer Authorization header when env var is set', async () => {
process.env.CONTEXT7_API_KEY = 'test-api-key-123';
const { fetchApiDocs } = await import('../api-docs');
const { fetchApiDocs } = await import('../api-docs.js');
mockFetchResponse(200, [{ id: '/lib/slack-api', trust_score: 90 }]);
mockFetchResponse(200, 'docs');
@@ -239,7 +239,7 @@ describe('fetchApiDocs', () => {
it('should not send Authorization header when CONTEXT7_API_KEY is not set', async () => {
delete process.env.CONTEXT7_API_KEY;
const { fetchApiDocs } = await import('../api-docs');
const { fetchApiDocs } = await import('../api-docs.js');
mockFetchResponse(200, [{ id: '/lib/slack-api', trust_score: 90 }]);
mockFetchResponse(200, 'docs');
@@ -485,7 +485,7 @@ describe('createLlmMockHandler', () => {
});
it('should cache node config across calls for the same node name', async () => {
const { extractNodeConfig } = (await import('../node-config')) as unknown as {
const { extractNodeConfig } = (await import('../node-config.js')) as unknown as {
extractNodeConfig: Mock;
};
extractNodeConfig.mockReturnValue('{"resource":"message"}');
@@ -534,7 +534,7 @@ describe('createLlmMockHandler', () => {
});
it('should extract config separately for different node names', async () => {
const { extractNodeConfig } = (await import('../node-config')) as unknown as {
const { extractNodeConfig } = (await import('../node-config.js')) as unknown as {
extractNodeConfig: Mock;
};
extractNodeConfig.mockReturnValue('{}');
@@ -1699,7 +1699,7 @@ export class InstanceAiAdapterService {
const raw = await credentialsService.decrypt(credential, true);
const tokenData = raw.oauthTokenData;
if (tokenData && typeof tokenData === 'object') {
const { OauthService } = await import('@/oauth/oauth.service');
const { OauthService } = await import('@/oauth/oauth.service.js');
const identifier = OauthService.extractAccountIdentifier(
tokenData as Record<string, unknown>,
);
@@ -16,25 +16,25 @@ export class InstanceAiModule implements ModuleInterface {
const logger = Container.get(Logger).scoped('instance-ai');
logger.warn(`${YELLOW}${WARNING_MESSAGE}${CLEAR}`);
const { InstanceAiSettingsService } = await import('./instance-ai-settings.service');
const { InstanceAiSettingsService } = await import('./instance-ai-settings.service.js');
await Container.get(InstanceAiSettingsService).loadFromDb();
await import('./instance-ai.controller');
await import('./mcp/instance-ai-mcp-connection.controller');
await import('./instance-ai.controller.js');
await import('./mcp/instance-ai-mcp-connection.controller.js');
// Instantiating the relay registers its `user-deleted` listener, which
// cleans up Instance AI data owned by the deleted user.
const { InstanceAiEventRelay } = await import('./instance-ai-event-relay.service');
const { InstanceAiEventRelay } = await import('./instance-ai-event-relay.service.js');
Container.get(InstanceAiEventRelay);
if (process.env.E2E_TESTS === 'true' && process.env.NODE_ENV !== 'production') {
await import('./instance-ai-test.controller');
await import('./instance-ai-test.controller.js');
}
}
async settings() {
const { GlobalConfig } = await import('@n8n/config');
const { InstanceAiService } = await import('./instance-ai.service');
const { InstanceAiSettingsService } = await import('./instance-ai-settings.service');
const { InstanceAiService } = await import('./instance-ai.service.js');
const { InstanceAiSettingsService } = await import('./instance-ai-settings.service.js');
const globalConfig = Container.get(GlobalConfig);
const service = Container.get(InstanceAiService);
const settingsService = Container.get(InstanceAiSettingsService);
@@ -56,28 +56,30 @@ export class InstanceAiModule implements ModuleInterface {
}
async entities() {
const { InstanceAiThread } = await import('./entities/instance-ai-thread.entity');
const { InstanceAiMessage } = await import('./entities/instance-ai-message.entity');
const { InstanceAiResource } = await import('./entities/instance-ai-resource.entity');
const { InstanceAiRunSnapshot } = await import('./entities/instance-ai-run-snapshot.entity');
const { InstanceAiIterationLog } = await import('./entities/instance-ai-iteration-log.entity');
const { InstanceAiCheckpoint } = await import('./entities/instance-ai-checkpoint.entity');
const { InstanceAiPendingConfirmation } = await import(
'./entities/instance-ai-pending-confirmation.entity'
const { InstanceAiThread } = await import('./entities/instance-ai-thread.entity.js');
const { InstanceAiMessage } = await import('./entities/instance-ai-message.entity.js');
const { InstanceAiResource } = await import('./entities/instance-ai-resource.entity.js');
const { InstanceAiRunSnapshot } = await import('./entities/instance-ai-run-snapshot.entity.js');
const { InstanceAiIterationLog } = await import(
'./entities/instance-ai-iteration-log.entity.js'
);
const { InstanceAiObservation } = await import('./entities/instance-ai-observation.entity');
const { InstanceAiCheckpoint } = await import('./entities/instance-ai-checkpoint.entity.js');
const { InstanceAiPendingConfirmation } = await import(
'./entities/instance-ai-pending-confirmation.entity.js'
);
const { InstanceAiObservation } = await import('./entities/instance-ai-observation.entity.js');
const { InstanceAiObservationCursor } = await import(
'./entities/instance-ai-observation-cursor.entity'
'./entities/instance-ai-observation-cursor.entity.js'
);
const { InstanceAiObservationLock } = await import(
'./entities/instance-ai-observation-lock.entity'
'./entities/instance-ai-observation-lock.entity.js'
);
const { InstanceAiMcpRegistryConnection } = await import(
'./entities/instance-ai-mcp-registry-connection.entity'
'./entities/instance-ai-mcp-registry-connection.entity.js'
);
const { InstanceAiThreadGrant } = await import('./entities/instance-ai-thread-grant.entity');
const { InstanceAiThreadGrant } = await import('./entities/instance-ai-thread-grant.entity.js');
const { InstanceAiEventLogEntry } = await import(
'./entities/instance-ai-event-log-entry.entity'
'./entities/instance-ai-event-log-entry.entity.js'
);
return [
@@ -99,7 +101,7 @@ export class InstanceAiModule implements ModuleInterface {
@OnShutdown()
async shutdown() {
const { InstanceAiService } = await import('./instance-ai.service');
const { InstanceAiService } = await import('./instance-ai.service.js');
await Container.get(InstanceAiService).shutdown();
}
}
@@ -94,6 +94,7 @@ import {
WorkflowLoopStorage,
ThreadTaskStorage,
} from '@n8n/instance-ai';
import { lazyImport } from '@n8n/utils/lazy-import';
import { setSchemaBaseDirs } from '@n8n/workflow-sdk';
import { ErrorReporter, InstanceSettings } from 'n8n-core';
import { OperationalError, UnexpectedError, UserError } from 'n8n-workflow';
@@ -102,8 +103,8 @@ import { nanoid } from 'nanoid';
import { N8N_VERSION, WORKFLOW_SDK_VERSION } from '@/constants';
import { EventService } from '@/events/event.service';
import { SourceControlPreferencesService } from '@/modules/source-control.ee/source-control-preferences.service.ee';
import type { PubSubCommandMap } from '@/scaling/pubsub/pubsub.event-map';
import { Publisher } from '@/scaling/pubsub/publisher.service';
import type { PubSubCommandMap } from '@/scaling/pubsub/pubsub.event-map';
import { AiService } from '@/services/ai.service';
import { ProxyTokenManager } from '@/services/proxy-token-manager';
import { UrlService } from '@/services/url.service';
@@ -115,6 +116,7 @@ import { EvalThreadCredentialAllowlistService } from './eval/thread-credential-a
import { DurableEventLog } from './event-bus/durable-event-log';
import { InProcessEventBus } from './event-bus/in-process-event-bus';
import { InstanceAiCreditService } from './instance-ai-credit.service';
import { InstanceAiErrorReporterService } from './instance-ai-error-reporter.service';
import { BROWSER_TOOL_CATEGORY, InstanceAiGatewayService } from './instance-ai-gateway.service';
import { InstanceAiMemoryService } from './instance-ai-memory.service';
import { InstanceAiModelService } from './instance-ai-model.service';
@@ -134,7 +136,6 @@ import {
} from './internal-messages';
import { INSTANCE_AI_RUN_TIMEOUT_REASON, InstanceAiLivenessService } from './liveness';
import { InstanceAiMcpRegistryService } from './mcp';
import { InstanceAiErrorReporterService } from './instance-ai-error-reporter.service';
import {
buildInstanceAiObservabilityContext,
type InstanceAiObservabilityContext,
@@ -2924,6 +2925,7 @@ export class InstanceAiService {
* `startExecuteRun` so the promise is registered with `inFlightExecutions`
* and shutdown can drain it before the DB closes.
*/
// eslint-disable-next-line complexity
private async executeRun(
user: User,
threadId: string,
@@ -3133,7 +3135,9 @@ export class InstanceAiService {
// When trace replay is enabled but LangSmith isn't configured,
// create a minimal context that only supports replay/record wrapping.
if (!tracing && process.env.E2E_TESTS === 'true') {
const { createTraceReplayOnlyContext } = await import('@n8n/instance-ai');
const { createTraceReplayOnlyContext } = await lazyImport<
typeof import('@n8n/instance-ai')
>(async () => await import('@n8n/instance-ai'));
tracing = createTraceReplayOnlyContext();
}
@@ -5,6 +5,7 @@ import type {
TraceWriter as TraceWriterType,
TraceEvent,
} from '@n8n/instance-ai';
import { lazyImport } from '@n8n/utils/lazy-import';
const TOOL_TRACE_EVENT_KINDS = new Set(['tool-call', 'tool-suspend', 'tool-resume']);
@@ -155,7 +156,14 @@ export class TraceReplayState {
return;
}
const { TraceIndex: TI, IdRemapper: IR, TraceWriter: TW } = await import('@n8n/instance-ai');
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
type InstanceAiImport = typeof import('@n8n/instance-ai');
const {
TraceIndex: TI,
IdRemapper: IR,
TraceWriter: TW,
} = await lazyImport<InstanceAiImport>(async () => await import('@n8n/instance-ai'));
const slug = this.activeSlug;
const events = slug ? this.eventsBySlug.get(slug) : undefined;
@@ -13,28 +13,28 @@ import { Container } from '@n8n/di';
@BackendModule({ name: 'instance-registry' })
export class InstanceRegistryModule implements ModuleInterface {
async init() {
await import('./instance-registry.controller');
await import('./instance-registry.controller.js');
const { InstanceRegistryService } = await import('./instance-registry.service');
const { InstanceRegistryService } = await import('./instance-registry.service.js');
const instanceRegistryService = Container.get(InstanceRegistryService);
await instanceRegistryService.init();
const { InstanceRegistryProxyService } = await import(
'@/services/instance-registry-proxy.service'
'@/services/instance-registry-proxy.service.js'
);
Container.get(InstanceRegistryProxyService).registerProvider(instanceRegistryService);
const { StaleMemberCleanupService } = await import('./stale-member-cleanup.service');
const { StaleMemberCleanupService } = await import('./stale-member-cleanup.service.js');
Container.get(StaleMemberCleanupService).init();
await import('./checks');
const { CheckService } = await import('./checks/check.service');
await import('./checks/index.js');
const { CheckService } = await import('./checks/check.service.js');
Container.get(CheckService).init();
}
@OnShutdown()
async shutdown() {
const { InstanceRegistryService } = await import('./instance-registry.service');
const { InstanceRegistryService } = await import('./instance-registry.service.js');
await Container.get(InstanceRegistryService).shutdown();
}
}
@@ -110,12 +110,12 @@ export class InstanceRegistryService {
const useRedis = this.instanceSettings.isMultiMain || this.executionsConfig.mode === 'queue';
if (useRedis) {
const { RedisInstanceStorage } = await import('./storage/redis-instance-storage');
const { RedisInstanceStorage } = await import('./storage/redis-instance-storage.js');
const { Container } = await import('@n8n/di');
return Container.get(RedisInstanceStorage);
}
const { MemoryInstanceStorage } = await import('./storage/memory-storage');
const { MemoryInstanceStorage } = await import('./storage/memory-storage.js');
return new MemoryInstanceStorage();
}
@@ -5,15 +5,15 @@ import { Container } from '@n8n/di';
@BackendModule({ name: 'instance-version-history', instanceTypes: ['main'] })
export class InstanceVersionHistoryModule implements ModuleInterface {
async init() {
await import('./instance-version-history.controller');
await import('./instance-version-history.controller.js');
const { InstanceVersionHistoryService } = await import('./instance-version-history.service');
const { InstanceVersionHistoryService } = await import('./instance-version-history.service.js');
await Container.get(InstanceVersionHistoryService).init();
}
async entities() {
const { InstanceVersionHistory } = await import(
'./database/entities/instance-version-history.entity'
'./database/entities/instance-version-history.entity.js'
);
return [InstanceVersionHistory];
}
@@ -4,9 +4,9 @@ import { BackendModule } from '@n8n/decorators';
@BackendModule({ name: 'ldap', licenseFlag: 'feat:ldap', instanceTypes: ['main'] })
export class LdapModule implements ModuleInterface {
async init() {
await import('./ldap.controller.ee');
await import('./ldap.controller.ee.js');
// Import LdapService to trigger @PasswordAuthHandler() decorator registration
await import('./ldap.service.ee');
await import('./ldap.service.ee.js');
}
}
@@ -7,6 +7,7 @@ import type { RunningMode, SyncStatus } from '@n8n/db';
import type { IPasswordAuthHandler } from '@n8n/decorators';
import { AuthHandler } from '@n8n/decorators';
import { Constructable, Container } from '@n8n/di';
import { lazyImport } from '@n8n/utils/lazy-import';
import type { Entry as LdapUser, ClientOptions, Client } from 'ldapts';
import { Cipher } from 'n8n-core';
import { jsonParse, UnexpectedError } from 'n8n-workflow';
@@ -51,7 +52,6 @@ export class LdapService implements IPasswordAuthHandler<User> {
readonly metadata = { name: 'ldap', type: 'password' as const };
private client: Client | undefined;
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
private ldapts: typeof import('ldapts');
private syncTimer: NodeJS.Timeout | undefined = undefined;
@@ -184,7 +184,7 @@ export class LdapService implements IPasswordAuthHandler<User> {
}
if (this.client === undefined) {
if (!this.ldapts) {
this.ldapts = await import('ldapts');
this.ldapts = await lazyImport(async () => await import('ldapts'));
}
const url = formatUrl(
@@ -17,16 +17,18 @@ import { Container } from '@n8n/di';
})
export class LogStreamingModule implements ModuleInterface {
async init() {
await import('./log-streaming.controller');
await import('./log-streaming.controller.js');
const { LogStreamingDestinationService } = await import('./log-streaming-destination.service');
const { LogStreamingDestinationService } = await import(
'./log-streaming-destination.service.js'
);
const destinationService = Container.get(LogStreamingDestinationService);
await destinationService.loadDestinationsFromDb();
await destinationService.initialize();
}
async entities() {
const { EventDestinations } = await import('./database/entities/event-destination.entity');
const { EventDestinations } = await import('./database/entities/event-destination.entity.js');
return [EventDestinations];
}
}
@@ -8,23 +8,23 @@ import { LoadNodesAndCredentials } from '@/load-nodes-and-credentials';
@BackendModule({ name: 'mcp-registry' })
export class McpRegistryModule implements ModuleInterface {
async init() {
const { McpRegistryService } = await import('./registry/mcp-registry.service');
const { McpRegistryService } = await import('./registry/mcp-registry.service.js');
await Container.get(McpRegistryService).init();
await import('./mcp-registry.controller');
await import('./mcp-registry.controller.js');
if (process.env.E2E_TESTS === 'true' && process.env.NODE_ENV !== 'production') {
await import('./mcp-registry-test.controller');
await import('./mcp-registry-test.controller.js');
}
}
async entities() {
const { McpRegistryServerEntity } = await import('./registry/mcp-registry-server.entity');
const { McpRegistryServerEntity } = await import('./registry/mcp-registry-server.entity.js');
return [McpRegistryServerEntity];
}
async nodeLoaders() {
const { McpRegistryNodeLoader } = await import('./mcp-registry-node-loader');
const { McpRegistryNodeLoader } = await import('./mcp-registry-node-loader.js');
return [
new McpRegistryNodeLoader(Container.get(LoadNodesAndCredentials), Container.get(Logger)),
@@ -84,7 +84,7 @@ describe('McpController', () => {
Container.set(ApiKeyRepository, mock<ApiKeyRepository>());
// Imported here (not statically) so the Container.set above runs first.
({ McpController } = await import('../mcp.controller'));
({ McpController } = await import('../mcp.controller.js'));
controller = Container.get(McpController);
});
+6 -6
View File
@@ -12,14 +12,14 @@ import { Container } from '@n8n/di';
@BackendModule({ name: 'mcp', instanceTypes: ['main'] })
export class McpModule implements ModuleInterface {
async init() {
await import('./mcp.controller');
await import('./mcp.settings.controller');
await import('./mcp.controller.js');
await import('./mcp.settings.controller.js');
// Register the instance MCP server as a protected resource of the shared
// OAuth server, so its tokens are minted and verified with the right
// audiences and its discovery metadata is served.
const { ProtectedResourceRegistry } = await import('@/services/protected-resource.registry');
const { McpProtectedResource } = await import('./mcp-protected-resource');
const { ProtectedResourceRegistry } = await import('@/services/protected-resource.registry.js');
const { McpProtectedResource } = await import('./mcp-protected-resource.js');
Container.get(ProtectedResourceRegistry).register(Container.get(McpProtectedResource));
}
@@ -30,8 +30,8 @@ export class McpModule implements ModuleInterface {
* `{ mcp: { mcpAccessEnabled: boolean, mcpManagedByEnv: boolean, serverUrl: string } }`.
*/
async settings() {
const { McpSettingsService } = await import('./mcp.settings.service');
const { McpProtectedResource } = await import('./mcp-protected-resource');
const { McpSettingsService } = await import('./mcp.settings.service.js');
const { McpProtectedResource } = await import('./mcp-protected-resource.js');
const mcpAccessEnabled = await Container.get(McpSettingsService).getEnabled();
const { mcpManagedByEnv } = Container.get(InstanceSettingsLoaderConfig);
// Canonical resource URL, so the UI shows the URL clients must actually
+24 -18
View File
@@ -16,19 +16,20 @@ import {
WORKFLOW_PREVIEW_APP_URI,
type McpAppTelemetryConfig,
} from '@n8n/mcp-apps/server';
import { lazyImport } from '@n8n/utils/lazy-import';
import { createDeferredPromise, type IDeferredPromise } from '@n8n/utils/promise/deferred-promise';
import { InstanceSettings } from 'n8n-core';
import { ManualExecutionCancelledError, type IRun } from 'n8n-workflow';
import {
createAddDataTableColumnTool,
createAddDataTableRowsTool,
createCreateDataTableTool,
createDeleteDataTableColumnTool,
createRenameDataTableColumnTool,
createRenameDataTableTool,
createSearchDataTablesTool,
} from './tools/data-table';
import { ActiveExecutions } from '@/active-executions';
import { CollaborationService } from '@/collaboration/collaboration.service';
import { N8N_VERSION } from '@/constants';
import { CredentialsService } from '@/credentials/credentials.service';
import { ExecutionService } from '@/executions/execution.service';
import { SubworkflowPolicyChecker } from '@/executions/pre-execution-checks/subworkflow-policy-checker';
import { DataTableProxyService } from '@/modules/data-table/data-table-proxy.service';
import { NodeCatalogService } from '@/node-catalog';
import { createExecuteWorkflowTool } from './tools/execute-workflow.tool';
import { createGetExecutionTool } from './tools/get-execution.tool';
import { createSearchExecutionsTool } from './tools/search-executions.tool';
@@ -56,13 +57,7 @@ import { createSearchWorkflowNodesTool } from './tools/workflow-builder/search-w
import { getSdkReferenceContent } from './tools/workflow-builder/sdk-reference-content';
import { createValidateNodeTool } from './tools/workflow-builder/validate-node.tool';
import { createValidateWorkflowCodeTool } from './tools/workflow-builder/validate-workflow-code.tool';
import { NodeCatalogService } from '@/node-catalog';
import { ActiveExecutions } from '@/active-executions';
import { CollaborationService } from '@/collaboration/collaboration.service';
import { N8N_VERSION } from '@/constants';
import { CredentialsService } from '@/credentials/credentials.service';
import { DataTableProxyService } from '@/modules/data-table/data-table-proxy.service';
import { NodeTypes } from '@/node-types';
import { PostHogClient } from '@/posthog';
import { NodeResourceExplorerService } from '@/services/node-resource-explorer.service';
@@ -77,13 +72,21 @@ import { WorkflowFinderService } from '@/workflows/workflow-finder.service';
import { WorkflowHistoryService } from '@/workflows/workflow-history/workflow-history.service';
import { WorkflowPublishedDataService } from '@/workflows/workflow-published-data.service';
import { WorkflowService } from '@/workflows/workflow.service';
import { SubworkflowPolicyChecker } from '@/executions/pre-execution-checks/subworkflow-policy-checker';
import { MCP_PREVIEW_RENDER_REQUESTED_EVENT } from './mcp.constants';
import { getAllowedToolNames } from './mcp-scopes';
import type { McpAppsTelemetryVariant, McpClientInfo, RegisterToolFn } from './mcp.types';
import {
createAddDataTableColumnTool,
createAddDataTableRowsTool,
createCreateDataTableTool,
createDeleteDataTableColumnTool,
createRenameDataTableColumnTool,
createRenameDataTableTool,
createSearchDataTablesTool,
} from './tools/data-table';
import { createPrepareTestPinDataTool } from './tools/prepare-workflow-pin-data.tool';
import { createTestWorkflowTool } from './tools/test-workflow.tool';
import { ExecutionService } from '@/executions/execution.service';
/**
* Pending MCP execution response, used for queue mode support.
@@ -217,7 +220,10 @@ export class McpService {
clientInfo?: McpClientInfo,
grantedScopes?: string[],
) {
const { McpServer } = await import('@modelcontextprotocol/sdk/server/mcp.js');
const { McpServer } = await lazyImport<
typeof import('@modelcontextprotocol/sdk/server/mcp.js')
>(async () => await import('@modelcontextprotocol/sdk/server/mcp.js'));
const builderEnabled = this.globalConfig.endpoints.mcpBuilderEnabled;
const allowedToolNames = getAllowedToolNames(grantedScopes);
// The builder walkthrough is only useful when the grant can actually
+2 -3
View File
@@ -1,9 +1,8 @@
import type { AuthenticatedRequest } from '@n8n/db';
import { isRecord } from '@n8n/utils/is-record';
import type { Request } from 'express';
import type { INode } from 'n8n-workflow';
import { isRecord } from '@n8n/utils/is-record';
import { SUPPORTED_MCP_TRIGGERS, SUPPORTED_PRODUCTION_MCP_TRIGGERS } from './mcp.constants';
import { isJSONRPCRequest } from './mcp.typeguards';
import type { McpClientInfo } from './mcp.types';
@@ -44,7 +43,7 @@ export const getToolArguments = (body: unknown): Record<string, unknown> => {
if (!isJSONRPCRequest(body)) return {};
if (!body.params) return {};
const { arguments: args } = body.params;
const args = body.params.arguments;
if (isRecord(args)) {
return args;
}
@@ -6,6 +6,6 @@ import { BackendModule } from '@n8n/decorators';
})
export class N8nPackagesModule implements ModuleInterface {
async init() {
await import('./n8n-packages.service');
await import('./n8n-packages.service.js');
}
}
@@ -14,7 +14,7 @@ export class OAuthJweModule implements ModuleInterface {
async init() {
if (!isFeatureFlagEnabled()) return;
const { OAuthJweDecryptService } = await import('./oauth-jwe-decrypt.service');
const { OAuthJweDecryptService } = await import('./oauth-jwe-decrypt.service.js');
Container.get(OAuthJweServiceProxy).setHandler(Container.get(OAuthJweDecryptService));
// Eager key bootstrap and the JWKS controller belong on main only.
@@ -22,9 +22,9 @@ export class OAuthJweModule implements ModuleInterface {
// the cache is cold and main hasn't generated yet, the partial unique
// index on `(type, algorithm)` serializes any concurrent generation.
if (Container.get(InstanceSettings).instanceType === 'main') {
const { OAuthJweKeyService } = await import('./oauth-jwe-key.service');
const { OAuthJweKeyService } = await import('./oauth-jwe-key.service.js');
await Container.get(OAuthJweKeyService).initialize();
await import('./oauth-jwe.controller');
await import('./oauth-jwe.controller.js');
}
}
@@ -405,7 +405,7 @@ describe('POST /rest/consent/approve', () => {
expect(response.statusCode).toBe(200);
const { UserConsentRepository } = await import(
'../database/repositories/oauth-user-consent.repository'
'../database/repositories/oauth-user-consent.repository.js'
);
const userConsentRepository = Container.get(UserConsentRepository);
const consent = await userConsentRepository.findOne({
@@ -428,7 +428,7 @@ describe('POST /rest/consent/approve', () => {
expect(response.statusCode).toBe(200);
const { UserConsentRepository } = await import(
'../database/repositories/oauth-user-consent.repository'
'../database/repositories/oauth-user-consent.repository.js'
);
const userConsentRepository = Container.get(UserConsentRepository);
const consent = await userConsentRepository.findOne({
@@ -298,7 +298,7 @@ describe('POST /mcp-oauth/register', () => {
});
test('should reject with descriptive server_error on the post-insert rollback (race path)', async () => {
const { OAuthServerService } = await import('../oauth-server.service');
const { OAuthServerService } = await import('../oauth-server.service.js');
const globalConfig = Container.get(GlobalConfig);
const originalLimit = globalConfig.endpoints.mcpMaxRegisteredClients;
globalConfig.endpoints.mcpMaxRegisteredClients = 1;
@@ -805,7 +805,7 @@ describe('IP rate limit configuration', () => {
let OAuthController: typeof OAuthControllerClass;
beforeAll(async () => {
({ OAuthController } = await import('../oauth.controller'));
({ OAuthController } = await import('../oauth.controller.js'));
});
test('applies the configured limits to the shared OAuth endpoints', () => {
@@ -1,4 +1,4 @@
import type { OAuthRegisteredClientsStore } from '@modelcontextprotocol/sdk/server/auth/clients';
import type { OAuthRegisteredClientsStore } from '@modelcontextprotocol/sdk/server/auth/clients.js';
import { mockInstance } from '@n8n/backend-test-utils';
import type { Request, Response } from 'express';
import { mock } from 'vitest-mock-extended';
@@ -22,7 +22,7 @@ beforeAll(async () => {
// The SDK's `clientRegistrationHandler` validates `clientsStore.registerClient`
// when the module builds its routers, so the mock needs a real store.
mockInstance(OAuthServerService, { clientsStore: mock<OAuthRegisteredClientsStore>() });
({ OAuthController } = await import('../oauth.controller'));
({ OAuthController } = await import('../oauth.controller.js'));
});
const urlService = mock<UrlService>();
@@ -21,29 +21,29 @@ export class OAuthServerModule implements ModuleInterface {
async init() {
// Only import controllers in the main process, since the webhook/worker processes don't run an HTTP server and don't need them.
if (Container.get(InstanceSettings).instanceType === 'main') {
await import('./oauth.controller');
await import('./oauth-consent.controller');
await import('./oauth-clients.controller');
await import('./oauth.controller.js');
await import('./oauth-consent.controller.js');
await import('./oauth-clients.controller.js');
}
// Register the token service as the OAuth token verifier provider, so
// protected-resource modules verify bearer tokens through the core
// `OAuthTokenVerifierProxy` instead of importing this module.
const { OAuthTokenVerifierProxy } = await import(
'@/services/oauth-token-verifier-proxy.service'
'@/services/oauth-token-verifier-proxy.service.js'
);
const { OAuthTokenService } = await import('./oauth-token.service');
const { OAuthTokenService } = await import('./oauth-token.service.js');
Container.get(OAuthTokenVerifierProxy).registerProvider(Container.get(OAuthTokenService));
const { WorkflowMcpTriggerResourceResolver } = await import(
'./protected-resource-resolvers/workflow-mcp-trigger-resource.resolver'
'./protected-resource-resolvers/workflow-mcp-trigger-resource.resolver.js'
);
Container.get(ProtectedResourceRegistry).registerResolver(
Container.get(WorkflowMcpTriggerResourceResolver),
);
const { WorkflowMcpTestTriggerResourceResolver } = await import(
'./protected-resource-resolvers/workflow-mcp-test-trigger-resource.resolver'
'./protected-resource-resolvers/workflow-mcp-test-trigger-resource.resolver.js'
);
Container.get(ProtectedResourceRegistry).registerResolver(
Container.get(WorkflowMcpTestTriggerResourceResolver),
@@ -51,13 +51,13 @@ export class OAuthServerModule implements ModuleInterface {
}
async entities() {
const { OAuthClient } = await import('./database/entities/oauth-client.entity');
const { OAuthClient } = await import('./database/entities/oauth-client.entity.js');
const { AuthorizationCode } = await import(
'./database/entities/oauth-authorization-code.entity'
'./database/entities/oauth-authorization-code.entity.js'
);
const { AccessToken } = await import('./database/entities/oauth-access-token.entity');
const { RefreshToken } = await import('./database/entities/oauth-refresh-token.entity');
const { UserConsent } = await import('./database/entities/oauth-user-consent.entity');
const { AccessToken } = await import('./database/entities/oauth-access-token.entity.js');
const { RefreshToken } = await import('./database/entities/oauth-refresh-token.entity.js');
const { UserConsent } = await import('./database/entities/oauth-user-consent.entity.js');
return [OAuthClient, AuthorizationCode, AccessToken, RefreshToken, UserConsent] as never;
}
@@ -1,4 +1,4 @@
import type { OAuthRegisteredClientsStore } from '@modelcontextprotocol/sdk/server/auth/clients';
import type { OAuthRegisteredClientsStore } from '@modelcontextprotocol/sdk/server/auth/clients.js';
import {
InvalidGrantError,
InvalidTargetError,
@@ -6,18 +6,20 @@ import {
import type {
AuthorizationParams,
OAuthServerProvider,
} from '@modelcontextprotocol/sdk/server/auth/provider';
import type { AuthInfo } from '@modelcontextprotocol/sdk/server/auth/types';
} from '@modelcontextprotocol/sdk/server/auth/provider.js';
import type { AuthInfo } from '@modelcontextprotocol/sdk/server/auth/types.js';
import type {
OAuthClientInformationFull,
OAuthTokens,
OAuthTokenRevocationRequest,
} from '@modelcontextprotocol/sdk/shared/auth';
} from '@modelcontextprotocol/sdk/shared/auth.js';
import { Logger } from '@n8n/backend-common';
import { GlobalConfig } from '@n8n/config';
import { Service } from '@n8n/di';
import type { Response } from 'express';
import { ProtectedResourceRegistry } from '@/services/protected-resource.registry';
import { OAuthClient } from './database/entities/oauth-client.entity';
import { OAuthClientRepository } from './database/repositories/oauth-client.repository';
import { UserConsentRepository } from './database/repositories/oauth-user-consent.repository';
@@ -25,7 +27,6 @@ import { OAuthAuthorizationCodeService } from './oauth-authorization-code.servic
import { OAuthSessionService } from './oauth-session.service';
import { OAuthTokenService } from './oauth-token.service';
import { OAuthClientLimitReachedError } from './oauth.errors';
import { ProtectedResourceRegistry } from '@/services/protected-resource.registry';
/** Maximum number of redirect URIs per client */
const MAX_REDIRECT_URIS = 10;
@@ -1,6 +1,6 @@
import { InvalidGrantError } from '@modelcontextprotocol/sdk/server/auth/errors.js';
import { AuthInfo } from '@modelcontextprotocol/sdk/server/auth/types';
import { OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth';
import { AuthInfo } from '@modelcontextprotocol/sdk/server/auth/types.js';
import { OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js';
import { Logger } from '@n8n/backend-common';
import { Time } from '@n8n/constants';
import { UserRepository, withTransaction } from '@n8n/db';
@@ -10,19 +10,19 @@ import { ensureError } from '@n8n/utils/errors/ensure-error';
import { UnexpectedError } from 'n8n-workflow';
import { randomBytes, randomUUID } from 'node:crypto';
import { AccessToken } from './database/entities/oauth-access-token.entity';
import { RefreshToken } from './database/entities/oauth-refresh-token.entity';
import { AccessTokenRepository } from './database/repositories/oauth-access-token.repository';
import { RefreshTokenRepository } from './database/repositories/oauth-refresh-token.repository';
import type { ProtectedResource } from '@/services/protected-resource.registry';
import { ProtectedResourceRegistry } from '@/services/protected-resource.registry';
import { AccessTokenNotFoundError, JWTVerificationError } from './oauth.errors';
import { JwtService } from '@/services/jwt.service';
import type {
OAuthTokenVerifier,
UserWithContext,
} from '@/services/oauth-token-verifier-proxy.service';
import type { ProtectedResource } from '@/services/protected-resource.registry';
import { ProtectedResourceRegistry } from '@/services/protected-resource.registry';
import { AccessToken } from './database/entities/oauth-access-token.entity';
import { RefreshToken } from './database/entities/oauth-refresh-token.entity';
import { AccessTokenRepository } from './database/repositories/oauth-access-token.repository';
import { RefreshTokenRepository } from './database/repositories/oauth-refresh-token.repository';
import { AccessTokenNotFoundError, JWTVerificationError } from './oauth.errors';
/**
* Manages the OAuth 2.1 token lifecycle for the shared OAuth server.
+6 -6
View File
@@ -9,24 +9,24 @@ import { InstanceSettings } from 'n8n-core';
})
export class OtelModule implements ModuleInterface {
async init() {
await import('./otel-lifecycle-handler');
await import('./otel-lifecycle-handler.js');
const { OtelService } = await import('./otel.service');
const { OtelService } = await import('./otel.service.js');
await Container.get(OtelService).init();
if (Container.get(InstanceSettings).instanceType === 'main') {
await import('./otel-settings.controller');
await import('./otel-settings.controller.js');
}
}
async settings() {
const { OtelSettingsService } = await import('./otel-settings.service');
const { OtelSettingsService } = await import('./otel-settings.service.js');
const { enabled } = Container.get(OtelSettingsService).getSettings();
return { enabled };
}
async context(): Promise<ModuleContext> {
const { ExecutionLevelTracer } = await import('./execution-level-tracer');
const { ExecutionLevelTracer } = await import('./execution-level-tracer.js');
const tracer = Container.get(ExecutionLevelTracer);
return {
@@ -36,7 +36,7 @@ export class OtelModule implements ModuleInterface {
@OnShutdown()
async shutdown() {
const { OtelService } = await import('./otel.service');
const { OtelService } = await import('./otel.service.js');
await Container.get(OtelService).shutdown();
}
}
@@ -9,14 +9,16 @@ import { Container } from '@n8n/di';
})
export class ProvisioningModule implements ModuleInterface {
async init() {
await import('./provisioning.controller.ee');
await import('./role-mapping-rule.controller.ee');
await import('./provisioning.controller.ee.js');
await import('./role-mapping-rule.controller.ee.js');
// Register the role-deletion checker so the core `RoleService` blocks
// deleting a role still targeted by a mapping rule, without importing
// this module.
const { RoleDeletionCheckProxy } = await import('@/services/role-deletion-check-proxy.service');
const { ProvisioningRoleDeletionChecker } = await import('./role-deletion-checker.ee');
const { RoleDeletionCheckProxy } = await import(
'@/services/role-deletion-check-proxy.service.js'
);
const { ProvisioningRoleDeletionChecker } = await import('./role-deletion-checker.ee.js');
Container.get(RoleDeletionCheckProxy).registerProvider(
Container.get(ProvisioningRoleDeletionChecker),
);
@@ -6,7 +6,7 @@ import { Container } from '@n8n/di';
export class QuickConnectModule implements ModuleInterface {
async init() {
await this.registerHandlers();
await import('./quick-connect.controller');
await import('./quick-connect.controller.js');
}
/**
@@ -16,7 +16,7 @@ export class QuickConnectModule implements ModuleInterface {
* Note: backendFlowConfig is intentionally excluded from the response to keep it server-side only.
*/
async settings() {
const { QuickConnectConfig } = await import('./quick-connect.config');
const { QuickConnectConfig } = await import('./quick-connect.config.js');
const { options } = Container.get(QuickConnectConfig);
// Strip backendFlowConfig before sending options to frontend
return {
@@ -27,7 +27,7 @@ export class QuickConnectModule implements ModuleInterface {
}
private async registerHandlers() {
const { QuickConnectService } = await import('./quick-connect.service');
const { QuickConnectService } = await import('./quick-connect.service.js');
const quickConnectService = Container.get(QuickConnectService);
await quickConnectService.registerHandlers();
}
@@ -12,7 +12,7 @@ import { QuickConnectConfig } from './quick-connect.config';
import { QuickConnectError } from './quick-connect.errors';
const backendHandlers = {
firecrawl: async () => (await import('./handlers/firecrawl.handler')).FirecrawlHandler,
firecrawl: async () => (await import('./handlers/firecrawl.handler.js')).FirecrawlHandler,
};
@Service()
@@ -7,16 +7,18 @@ import { ExecutionRedactionServiceProxy } from '@/executions/execution-redaction
@BackendModule({ name: 'redaction', instanceTypes: ['main'] })
export class RedactionModule implements ModuleInterface {
async init() {
await import('./redaction-context-hook');
await import('./redaction-context-hook.js');
// Importing the service here registers its @OnPubSubEvent handler with the
// pubsub metadata before PubSubRegistry.init() wires up the listeners.
// The decorator runs at class-evaluation (import) time, so the import
// side-effect alone is sufficient — the registry instantiates the handler
// lazily on event receipt, so we must not eagerly resolve it here.
await import('./instance-redaction-enforcement.service');
await import('./instance-redaction-enforcement.service.js');
const { ExecutionRedactionService } = await import('./executions/execution-redaction.service');
const { ExecutionRedactionService } = await import(
'./executions/execution-redaction.service.js'
);
const executionRedactionService = Container.get(ExecutionRedactionService);
await executionRedactionService.init();
@@ -13,13 +13,13 @@ export class RuntimeCredentialsModule implements ModuleInterface {
async init() {
if (!isFeatureFlagEnabled()) return;
const { RuntimeCredentialsService } = await import('./runtime-credentials.service');
const { RuntimeCredentialsService } = await import('./runtime-credentials.service.js');
Container.get(RuntimeCredentialsService).init();
await import('./runtime-credentials-context-hook');
await import('./runtime-credentials.config');
await import('./runtime-credentials-context-hook.js');
await import('./runtime-credentials.config.js');
const { RuntimeCredentialsAccessService } = await import(
'./runtime-credentials-access.service'
'./runtime-credentials-access.service.js'
);
Container.get(RuntimeCredentialProxyService).registerProvider(
Container.get(RuntimeCredentialsAccessService),
@@ -525,7 +525,7 @@ describe('Source Control Helper', () => {
const filePath = 'invalid/path/tags-and-mappings.json';
// Import the function after resetting modules
const { readTagAndMappingsFromSourceControlFile } = await import(
'@/modules/source-control.ee/source-control-helper.ee'
'@/modules/source-control.ee/source-control-helper.ee.js'
);
const result = await readTagAndMappingsFromSourceControlFile(filePath);
expect(result).toEqual({
@@ -546,7 +546,7 @@ describe('Source Control Helper', () => {
const filePath = 'invalid/path/folders.json';
// Import the function after resetting modules
const { readFoldersFromSourceControlFile } = await import(
'@/modules/source-control.ee/source-control-helper.ee'
'@/modules/source-control.ee/source-control-helper.ee.js'
);
const result = await readFoldersFromSourceControlFile(filePath);
expect(result).toEqual({
@@ -566,7 +566,7 @@ describe('Source Control Helper', () => {
const filePath = 'invalid/path/data_tables.json';
// Import the function after resetting modules
const { readDataTablesFromSourceControlFile } = await import(
'@/modules/source-control.ee/source-control-helper.ee'
'@/modules/source-control.ee/source-control-helper.ee.js'
);
const result = await readDataTablesFromSourceControlFile(filePath);
expect(result).toEqual([]);
@@ -594,7 +594,7 @@ describe('Source Control Helper', () => {
// Import the function after mocking
const { readDataTablesFromSourceControlFile } = await import(
'@/modules/source-control.ee/source-control-helper.ee'
'@/modules/source-control.ee/source-control-helper.ee.js'
);
const result = await readDataTablesFromSourceControlFile('valid/path/data_tables.json');
@@ -283,7 +283,10 @@ export function isSourceControlLicensed() {
}
export async function generateSshKeyPair(keyType: KeyPairType) {
const sshpk = await import('sshpk');
// sshpk is CommonJS (`export =`): under nodenext, a native dynamic import only
// hoists some named exports onto the namespace (parsePrivateKey is missed), so
// read the real module.exports off `.default`.
const { default: sshpk } = await import('sshpk');
const keyPair: KeyPair = {
publicKey: '',
privateKey: '',
@@ -323,7 +323,7 @@ export class SourceControlPreferencesService {
*/
private async broadcastReloadSourceControlConfiguration(): Promise<void> {
if (this.instanceSettings.isMultiMain) {
const { Publisher } = await import('@/scaling/pubsub/publisher.service');
const { Publisher } = await import('@/scaling/pubsub/publisher.service.js');
await Container.get(Publisher).publishCommand({ command: 'reload-source-control-config' });
this.logger.debug('Broadcasting source control configuration reload to other main instances');
}
@@ -9,9 +9,9 @@ import { Container } from '@n8n/di';
})
export class SourceControlModule implements ModuleInterface {
async init() {
await import('./source-control.controller.ee');
await import('./source-control.controller.ee.js');
const { SourceControlService } = await import('./source-control.service.ee');
const { SourceControlService } = await import('./source-control.service.ee.js');
await Container.get(SourceControlService).start();
}
}
@@ -526,7 +526,7 @@ export class OidcService {
private async broadcastReloadOIDCConfigurationCommand(): Promise<void> {
if (this.instanceSettings.isMultiMain) {
const { Publisher } = await import('@/scaling/pubsub/publisher.service');
const { Publisher } = await import('@/scaling/pubsub/publisher.service.js');
await Container.get(Publisher).publishCommand({ command: 'reload-oidc-config' });
}
}
@@ -5,9 +5,9 @@ import { Container } from '@n8n/di';
@BackendModule({ name: 'sso-oidc', licenseFlag: 'feat:oidc', instanceTypes: ['main'] })
export class OidcModule implements ModuleInterface {
async init() {
await import('./oidc.controller.ee');
await import('./oidc.controller.ee.js');
const { OidcService } = await import('./oidc.service.ee');
const { OidcService } = await import('./oidc.service.ee.js');
await Container.get(OidcService).init();
}
}
@@ -57,24 +57,24 @@ export class SamlValidator {
// dynamically load schema files
private async loadSchemas(): Promise<void> {
this.xmlProtocol = (await import('./schema/saml-schema-protocol-2.0.xsd')).xmlFileInfo;
this.xmlMetadata = (await import('./schema/saml-schema-metadata-2.0.xsd')).xmlFileInfo;
this.xmlProtocol = (await import('./schema/saml-schema-protocol-2.0.xsd.js')).xmlFileInfo;
this.xmlMetadata = (await import('./schema/saml-schema-metadata-2.0.xsd.js')).xmlFileInfo;
this.preload = (
await Promise.all([
// SAML
import('./schema/saml-schema-assertion-2.0.xsd'),
import('./schema/xmldsig-core-schema.xsd'),
import('./schema/xenc-schema.xsd'),
import('./schema/xml.xsd'),
import('./schema/saml-schema-assertion-2.0.xsd.js'),
import('./schema/xmldsig-core-schema.xsd.js'),
import('./schema/xenc-schema.xsd.js'),
import('./schema/xml.xsd.js'),
// WS-Federation
import('./schema/ws-federation.xsd'),
import('./schema/oasis-200401-wss-wssecurity-secext-1.0.xsd'),
import('./schema/oasis-200401-wss-wssecurity-utility-1.0.xsd'),
import('./schema/ws-addr.xsd'),
import('./schema/metadata-exchange.xsd'),
import('./schema/ws-securitypolicy-1.2.xsd'),
import('./schema/ws-authorization.xsd'),
import('./schema/ws-federation.xsd.js'),
import('./schema/oasis-200401-wss-wssecurity-secext-1.0.xsd.js'),
import('./schema/oasis-200401-wss-wssecurity-utility-1.0.xsd.js'),
import('./schema/ws-addr.xsd.js'),
import('./schema/metadata-exchange.xsd.js'),
import('./schema/ws-securitypolicy-1.2.xsd.js'),
import('./schema/ws-authorization.xsd.js'),
])
).map((m) => m.xmlFileInfo);
}
@@ -459,7 +459,7 @@ export class SamlService {
private async broadcastReloadSAMLConfigurationCommand(): Promise<void> {
if (this.instanceSettings.isMultiMain) {
const { Publisher } = await import('@/scaling/pubsub/publisher.service');
const { Publisher } = await import('@/scaling/pubsub/publisher.service.js');
await Container.get(Publisher).publishCommand({ command: 'reload-saml-config' });
}
}
@@ -5,9 +5,9 @@ import { Container } from '@n8n/di';
@BackendModule({ name: 'sso-saml', licenseFlag: 'feat:saml', instanceTypes: ['main'] })
export class SamlModule implements ModuleInterface {
async init() {
await import('./saml.controller.ee');
await import('./saml.controller.ee.js');
const { SamlService } = await import('./saml.service.ee');
const { SamlService } = await import('./saml.service.ee.js');
await Container.get(SamlService).init();
}
}
@@ -188,8 +188,8 @@ describe('EmbedAuthController', () => {
const req = mock<AuthlessRequest>({ browserId: 'browser-id-789', ip: '10.0.0.1' });
const res = mock<Response>();
const query = new EmbedLoginQueryDto({ token: 'bad-token' });
const { TokenExchangeAuthError } = await import('../../token-exchange.errors');
const { TokenExchangeFailureReason } = await import('../../token-exchange.types');
const { TokenExchangeAuthError } = await import('../../token-exchange.errors.js');
const { TokenExchangeFailureReason } = await import('../../token-exchange.types.js');
tokenExchangeService.embedLogin.mockRejectedValue(
new TokenExchangeAuthError(
TokenExchangeFailureReason.InvalidSignature,
@@ -213,7 +213,7 @@ describe('EmbedAuthController', () => {
const req = mock<AuthlessRequest>({ browserId: 'browser-id-789', ip: '10.0.0.1' });
const res = mock<Response>();
const query = new EmbedLoginQueryDto({ token: 'bad-token' });
const { TokenExchangeFailureReason } = await import('../../token-exchange.types');
const { TokenExchangeFailureReason } = await import('../../token-exchange.types.js');
tokenExchangeService.embedLogin.mockRejectedValue(new Error('Some unexpected error'));
await expect(controller.getLogin(req, res, query)).rejects.toThrow('Some unexpected error');
@@ -14,11 +14,11 @@ function isFeatureFlagEnabled(): boolean {
})
export class TokenExchangeModule implements ModuleInterface {
async entities() {
const { TokenExchangeJti } = await import('./database/entities/token-exchange-jti.entity');
const { TokenExchangeJti } = await import('./database/entities/token-exchange-jti.entity.js');
const { TrustedKeySourceEntity } = await import(
'./database/entities/trusted-key-source.entity'
'./database/entities/trusted-key-source.entity.js'
);
const { TrustedKeyEntity } = await import('./database/entities/trusted-key.entity');
const { TrustedKeyEntity } = await import('./database/entities/trusted-key.entity.js');
return [TokenExchangeJti, TrustedKeySourceEntity, TrustedKeyEntity] as never;
}
@@ -27,19 +27,19 @@ export class TokenExchangeModule implements ModuleInterface {
return;
}
const { TrustedKeyService } = await import('./services/trusted-key.service');
const { TrustedKeyService } = await import('./services/trusted-key.service.js');
await Container.get(TrustedKeyService).initialize();
await import('./controllers/token-exchange.controller');
await import('./controllers/embed-auth.controller');
await import('./controllers/token-exchange.controller.js');
await import('./controllers/embed-auth.controller.js');
const { JtiCleanupService } = await import('./services/jti-cleanup.service');
const { JtiCleanupService } = await import('./services/jti-cleanup.service.js');
Container.get(JtiCleanupService).init();
// Register the scoped JWT auth strategy into the public API auth chain.
// ScopedJwtStrategy runs after ApiKeyAuthStrategy (which abstains for token-exchange JWTs).
const { ScopedJwtStrategy } = await import('./services/scoped-jwt.strategy');
const { AuthStrategyRegistry } = await import('@/services/auth-strategy.registry');
const { ScopedJwtStrategy } = await import('./services/scoped-jwt.strategy.js');
const { AuthStrategyRegistry } = await import('@/services/auth-strategy.registry.js');
Container.get(AuthStrategyRegistry).register(Container.get(ScopedJwtStrategy));
}
}
@@ -4,7 +4,7 @@ import { BackendModule } from '@n8n/decorators';
@BackendModule({ name: 'workflow-builder', instanceTypes: ['main'] })
export class WorkflowBuilderModule implements ModuleInterface {
async entities() {
const { WorkflowBuilderSession } = await import('./workflow-builder-session.entity');
const { WorkflowBuilderSession } = await import('./workflow-builder-session.entity.js');
return [WorkflowBuilderSession];
}
}
@@ -127,7 +127,7 @@ export async function userHasScopes(
throw new NotFoundError(`Data table with ID "${dataTableId}" not found.`);
}
const { DataTableRepository } = await import('@/modules/data-table/data-table.repository');
const { DataTableRepository } = await import('@/modules/data-table/data-table.repository.js');
const dataTable = await Container.get(DataTableRepository).findOne({
where: { id: dataTableId },
relations: ['project'],
@@ -13,7 +13,7 @@ const auditHandlers: AuditHandlers = {
generateAudit: [
apiKeyHasScopeWithGlobalScopeFallback({ scope: 'securityAudit:generate' }),
async (req, res) => {
const { SecurityAuditService } = await import('@/security-audit/security-audit.service');
const { SecurityAuditService } = await import('@/security-audit/security-audit.service.js');
const result = await Container.get(SecurityAuditService).run(
req.body?.additionalOptions?.categories,
req.body?.additionalOptions?.daysAbandonedWorkflow,
@@ -23,7 +23,7 @@ vi.spyOn(middlewares, 'publicApiScope').mockReturnValue(mockMiddleware);
let handler: Record<string, Array<(...args: unknown[]) => unknown>>;
beforeAll(async () => {
handler = (await import('../community-packages.handler')) as unknown as typeof handler;
handler = (await import('../community-packages.handler.js')) as unknown as typeof handler;
});
describe('CommunityPackages Handler', () => {
@@ -26,8 +26,8 @@ let mainHandler: Record<string, Array<(...args: unknown[]) => unknown>>;
let handler: Record<string, Array<(...args: unknown[]) => unknown>>;
beforeAll(async () => {
mainHandler = (await import('../data-tables.handler')) as unknown as typeof mainHandler;
handler = (await import('../data-tables.rows.handler')) as unknown as typeof handler;
mainHandler = (await import('../data-tables.handler.js')) as unknown as typeof mainHandler;
handler = (await import('../data-tables.rows.handler.js')) as unknown as typeof handler;
});
describe('DataTable Handler', () => {
@@ -14,7 +14,7 @@ import * as discoverService from '../discover.service';
let handler: Record<string, Array<(...args: unknown[]) => unknown>>;
beforeAll(async () => {
handler = (await import('../discover.handler')) as unknown as typeof handler;
handler = (await import('../discover.handler.js')) as unknown as typeof handler;
});
describe('Discover Handler', () => {
@@ -32,11 +32,11 @@ vi.spyOn(middlewares, 'isLicensed').mockReturnValue(createMockMiddleware as any)
// `discover.service` builds its endpoint registry at module-evaluation time by
// reading middleware metadata, so it must be imported *after* the spies above
// are installed. A static import is hoisted above them, so load it dynamically.
let buildDiscoverResponse: typeof import('../discover.service').buildDiscoverResponse;
let _resetCache: typeof import('../discover.service')._resetCache;
let buildDiscoverResponse: typeof import('../discover.service.js').buildDiscoverResponse;
let _resetCache: typeof import('../discover.service.js')._resetCache;
beforeAll(async () => {
({ buildDiscoverResponse, _resetCache } = await import('../discover.service'));
({ buildDiscoverResponse, _resetCache } = await import('../discover.service.js'));
// Warm the registry once. The first call cold-loads and transforms all
// handler modules (and their transitive graph) through Vite, which can
// approach the default 5s test timeout. Paying it here in the hook keeps
@@ -118,7 +118,10 @@ async function _parseEndpointsFromSpec(): Promise<EndpointInfo[]> {
let handlerModule = handlerCache.get(handlerPath);
if (!handlerModule) {
try {
const fullHandlerPath = path.join(publicApiRoot, handlerPath);
// The `.js` extension is required: under NodeNext, `await import()` is emitted
// as a native dynamic import, which (unlike `require`) does no extension guessing.
// The spec's handler paths are extensionless, so append it here.
const fullHandlerPath = path.join(publicApiRoot, `${handlerPath}.js`);
const imported: unknown = await import(fullHandlerPath);
if (!isRecord(imported)) continue;
// Handlers use `export = xHandlers`, which surfaces as `.default`
@@ -34,7 +34,7 @@ let exportPackage: (...args: unknown[]) => unknown;
let importPackage: (...args: unknown[]) => unknown;
beforeAll(async () => {
handler = (await import('../n8n-packages.handler')) as unknown as typeof handler;
handler = (await import('../n8n-packages.handler.js')) as unknown as typeof handler;
exportPackage = handler.exportPackage[1];
importPackage = handler.importPackage[1];
});

Some files were not shown because too many files have changed in this diff Show More