diff --git a/codecov.yml b/codecov.yml index 444f6db052..f42112df09 100644 --- a/codecov.yml +++ b/codecov.yml @@ -37,6 +37,7 @@ component_management: - packages/@n8n/api-types/** - packages/@n8n/config/** - packages/@n8n/client-oauth2/** + - packages/@n8n/decorators/** - packages/@n8n/constants/** - packages/@n8n/di/** - packages/@n8n/imap/** diff --git a/packages/@n8n/decorators/.eslintrc.js b/packages/@n8n/decorators/.eslintrc.js new file mode 100644 index 0000000000..8c5b78c5da --- /dev/null +++ b/packages/@n8n/decorators/.eslintrc.js @@ -0,0 +1,7 @@ +const sharedOptions = require('@n8n/eslint-config/shared'); + +/** @type {import('@types/eslint').ESLint.ConfigData} */ +module.exports = { + extends: ['@n8n/eslint-config/base'], + ...sharedOptions(__dirname), +}; diff --git a/packages/@n8n/decorators/jest.config.js b/packages/@n8n/decorators/jest.config.js new file mode 100644 index 0000000000..d14f2d60c6 --- /dev/null +++ b/packages/@n8n/decorators/jest.config.js @@ -0,0 +1,7 @@ +/** @type {import('jest').Config} */ +module.exports = { + ...require('../../../jest.config'), + transform: { + '^.+\\.ts$': ['ts-jest', { isolatedModules: false }], + }, +}; diff --git a/packages/@n8n/decorators/package.json b/packages/@n8n/decorators/package.json new file mode 100644 index 0000000000..cb3128e117 --- /dev/null +++ b/packages/@n8n/decorators/package.json @@ -0,0 +1,35 @@ +{ + "name": "@n8n/decorators", + "version": "0.1.0", + "scripts": { + "clean": "rimraf dist .turbo", + "dev": "pnpm watch", + "typecheck": "tsc --noEmit", + "build": "tsc -p tsconfig.build.json", + "format": "biome format --write .", + "format:check": "biome ci .", + "lint": "eslint .", + "lintfix": "eslint . --fix", + "watch": "tsc -p tsconfig.build.json --watch", + "test": "jest", + "test:dev": "jest --watch" + }, + "main": "dist/index.js", + "module": "src/index.ts", + "types": "dist/index.d.ts", + "files": [ + "dist/**/*" + ], + "devDependencies": { + "@n8n/typescript-config": "workspace:*", + "@types/express": "catalog:", + "@types/lodash": "catalog:" + }, + "dependencies": { + "@n8n/constants": "workspace:^", + "@n8n/di": "workspace:^", + "@n8n/permissions": "workspace:^", + "lodash": "catalog:", + "n8n-workflow": "workspace:^" + } +} diff --git a/packages/cli/src/decorators/__tests__/module.test.ts b/packages/@n8n/decorators/src/__tests__/module.test.ts similarity index 88% rename from packages/cli/src/decorators/__tests__/module.test.ts rename to packages/@n8n/decorators/src/__tests__/module.test.ts index 0cadfbe555..540690f44a 100644 --- a/packages/cli/src/decorators/__tests__/module.test.ts +++ b/packages/@n8n/decorators/src/__tests__/module.test.ts @@ -1,8 +1,7 @@ import { Container } from '@n8n/di'; import { mock } from 'jest-mock-extended'; -import type { ExecutionLifecycleHooks } from 'n8n-core'; -import type { BaseN8nModule } from '../module'; +import type { BaseN8nModule, ExecutionLifecycleHooks } from '../module'; import { ModuleRegistry, N8nModule } from '../module'; let moduleRegistry: ModuleRegistry; diff --git a/packages/cli/src/decorators/__tests__/on-shutdown.test.ts b/packages/@n8n/decorators/src/__tests__/on-shutdown.test.ts similarity index 66% rename from packages/cli/src/decorators/__tests__/on-shutdown.test.ts rename to packages/@n8n/decorators/src/__tests__/on-shutdown.test.ts index a5c720c993..a9753dcf50 100644 --- a/packages/cli/src/decorators/__tests__/on-shutdown.test.ts +++ b/packages/@n8n/decorators/src/__tests__/on-shutdown.test.ts @@ -1,16 +1,15 @@ import { Container, Service } from '@n8n/di'; -import { mock } from 'jest-mock-extended'; -import { OnShutdown } from '@/decorators/on-shutdown'; -import { ShutdownService } from '@/shutdown/shutdown.service'; +import { OnShutdown } from '../on-shutdown'; +import { ShutdownRegistryMetadata } from '../shutdown-registry-metadata'; describe('OnShutdown', () => { - let shutdownService: ShutdownService; + let shutdownRegistryMetadata: ShutdownRegistryMetadata; beforeEach(() => { - shutdownService = new ShutdownService(mock(), mock()); - Container.set(ShutdownService, shutdownService); - jest.spyOn(shutdownService, 'register'); + shutdownRegistryMetadata = new ShutdownRegistryMetadata(); + Container.set(ShutdownRegistryMetadata, shutdownRegistryMetadata); + jest.spyOn(shutdownRegistryMetadata, 'register'); }); it('should register a methods that is decorated with OnShutdown', () => { @@ -20,8 +19,8 @@ describe('OnShutdown', () => { async onShutdown() {} } - expect(shutdownService.register).toHaveBeenCalledTimes(1); - expect(shutdownService.register).toHaveBeenCalledWith(100, { + expect(shutdownRegistryMetadata.register).toHaveBeenCalledTimes(1); + expect(shutdownRegistryMetadata.register).toHaveBeenCalledWith(100, { methodName: 'onShutdown', serviceClass: TestClass, }); @@ -37,12 +36,12 @@ describe('OnShutdown', () => { async two() {} } - expect(shutdownService.register).toHaveBeenCalledTimes(2); - expect(shutdownService.register).toHaveBeenCalledWith(100, { + expect(shutdownRegistryMetadata.register).toHaveBeenCalledTimes(2); + expect(shutdownRegistryMetadata.register).toHaveBeenCalledWith(100, { methodName: 'one', serviceClass: TestClass, }); - expect(shutdownService.register).toHaveBeenCalledWith(100, { + expect(shutdownRegistryMetadata.register).toHaveBeenCalledWith(100, { methodName: 'two', serviceClass: TestClass, }); @@ -57,9 +56,9 @@ describe('OnShutdown', () => { } } - expect(shutdownService.register).toHaveBeenCalledTimes(1); + expect(shutdownRegistryMetadata.register).toHaveBeenCalledTimes(1); // @ts-expect-error We are checking internal parts of the shutdown service - expect(shutdownService.handlersByPriority[10].length).toEqual(1); + expect(shutdownRegistryMetadata.handlersByPriority[10].length).toEqual(1); }); it('should throw an error if the decorated member is not a function', () => { diff --git a/packages/cli/src/decorators/args.ts b/packages/@n8n/decorators/src/args.ts similarity index 66% rename from packages/cli/src/decorators/args.ts rename to packages/@n8n/decorators/src/args.ts index 21c9e94755..cb9c0f0344 100644 --- a/packages/cli/src/decorators/args.ts +++ b/packages/@n8n/decorators/src/args.ts @@ -1,10 +1,15 @@ -import { getRouteMetadata } from './controller.registry'; +import { Container } from '@n8n/di'; + +import { ControllerRegistryMetadata } from './controller-registry-metadata'; import type { Arg, Controller } from './types'; const ArgDecorator = (arg: Arg): ParameterDecorator => (target, handlerName, parameterIndex) => { - const routeMetadata = getRouteMetadata(target.constructor as Controller, String(handlerName)); + const routeMetadata = Container.get(ControllerRegistryMetadata).getRouteMetadata( + target.constructor as Controller, + String(handlerName), + ); routeMetadata.args[parameterIndex] = arg; }; diff --git a/packages/@n8n/decorators/src/controller-registry-metadata.ts b/packages/@n8n/decorators/src/controller-registry-metadata.ts new file mode 100644 index 0000000000..5803df41ce --- /dev/null +++ b/packages/@n8n/decorators/src/controller-registry-metadata.ts @@ -0,0 +1,36 @@ +import { Service } from '@n8n/di'; + +import type { Controller, ControllerMetadata, HandlerName, RouteMetadata } from './types'; + +@Service() +export class ControllerRegistryMetadata { + private registry = new Map(); + + getControllerMetadata(controllerClass: Controller) { + let metadata = this.registry.get(controllerClass); + if (!metadata) { + metadata = { + basePath: '/', + middlewares: [], + routes: new Map(), + }; + this.registry.set(controllerClass, metadata); + } + return metadata; + } + + getRouteMetadata(controllerClass: Controller, handlerName: HandlerName) { + const metadata = this.getControllerMetadata(controllerClass); + let route = metadata.routes.get(handlerName); + if (!route) { + route = {} as RouteMetadata; + route.args = []; + metadata.routes.set(handlerName, route); + } + return route; + } + + get controllerClasses() { + return this.registry.keys(); + } +} diff --git a/packages/cli/src/decorators/debounce.ts b/packages/@n8n/decorators/src/debounce.ts similarity index 95% rename from packages/cli/src/decorators/debounce.ts rename to packages/@n8n/decorators/src/debounce.ts index 6096ce522a..bcaf8e400a 100644 --- a/packages/cli/src/decorators/debounce.ts +++ b/packages/@n8n/decorators/src/debounce.ts @@ -19,7 +19,7 @@ export const Debounce = (waitMs: number): MethodDecorator => ( _: object, - methodName: string, + methodName: string | symbol, originalDescriptor: PropertyDescriptor, ): TypedPropertyDescriptor => ({ configurable: true, diff --git a/packages/@n8n/decorators/src/index.ts b/packages/@n8n/decorators/src/index.ts new file mode 100644 index 0000000000..32461f07dd --- /dev/null +++ b/packages/@n8n/decorators/src/index.ts @@ -0,0 +1,20 @@ +export { Body, Query, Param } from './args'; +export { RestController } from './rest-controller'; +export { Get, Post, Put, Patch, Delete } from './route'; +export { Middleware } from './middleware'; +export { ControllerRegistryMetadata } from './controller-registry-metadata'; +export { Licensed } from './licensed'; +export { GlobalScope, ProjectScope } from './scoped'; +export { + HIGHEST_SHUTDOWN_PRIORITY, + DEFAULT_SHUTDOWN_PRIORITY, + LOWEST_SHUTDOWN_PRIORITY, +} from './shutdown/constants'; +export { ShutdownRegistryMetadata } from './shutdown-registry-metadata'; +export { ModuleRegistry } from './module'; +export { OnShutdown } from './on-shutdown'; +export { Redactable } from './redactable'; +export { BaseN8nModule, N8nModule } from './module'; +export { Debounce } from './debounce'; +export type { AccessScope, Controller, RateLimit } from './types'; +export type { ShutdownHandler } from './types'; diff --git a/packages/cli/src/decorators/licensed.ts b/packages/@n8n/decorators/src/licensed.ts similarity index 50% rename from packages/cli/src/decorators/licensed.ts rename to packages/@n8n/decorators/src/licensed.ts index 028ff4a28e..410c6946d5 100644 --- a/packages/cli/src/decorators/licensed.ts +++ b/packages/@n8n/decorators/src/licensed.ts @@ -1,11 +1,15 @@ import type { BooleanLicenseFeature } from '@n8n/constants'; +import { Container } from '@n8n/di'; -import { getRouteMetadata } from './controller.registry'; +import { ControllerRegistryMetadata } from './controller-registry-metadata'; import type { Controller } from './types'; export const Licensed = (licenseFeature: BooleanLicenseFeature): MethodDecorator => (target, handlerName) => { - const routeMetadata = getRouteMetadata(target.constructor as Controller, String(handlerName)); + const routeMetadata = Container.get(ControllerRegistryMetadata).getRouteMetadata( + target.constructor as Controller, + String(handlerName), + ); routeMetadata.licenseFeature = licenseFeature; }; diff --git a/packages/@n8n/decorators/src/middleware.ts b/packages/@n8n/decorators/src/middleware.ts new file mode 100644 index 0000000000..0228ca5b19 --- /dev/null +++ b/packages/@n8n/decorators/src/middleware.ts @@ -0,0 +1,11 @@ +import { Container } from '@n8n/di'; + +import { ControllerRegistryMetadata } from './controller-registry-metadata'; +import type { Controller } from './types'; + +export const Middleware = (): MethodDecorator => (target, handlerName) => { + const metadata = Container.get(ControllerRegistryMetadata).getControllerMetadata( + target.constructor as Controller, + ); + metadata.middlewares.push(String(handlerName)); +}; diff --git a/packages/cli/src/decorators/module.ts b/packages/@n8n/decorators/src/module.ts similarity index 78% rename from packages/cli/src/decorators/module.ts rename to packages/@n8n/decorators/src/module.ts index 4b4358c548..c93bff7bfa 100644 --- a/packages/cli/src/decorators/module.ts +++ b/packages/@n8n/decorators/src/module.ts @@ -1,7 +1,15 @@ import { Container, Service, type Constructable } from '@n8n/di'; -import type { ExecutionLifecycleHooks } from 'n8n-core'; +import type EventEmitter from 'node:events'; -import type { MultiMainSetup } from '@/scaling/multi-main-setup.ee'; +/** + * @TODO Temporary dummy type until `MultiMainSetup` registers listeners via decorators. + */ +type MultiMainSetup = EventEmitter; + +/** + * @TODO Temporary dummy type until `ExecutionLifecycleHooks` registers hooks via decorators. + */ +export type ExecutionLifecycleHooks = object; export interface BaseN8nModule { initialize?(): void; diff --git a/packages/cli/src/decorators/on-shutdown.ts b/packages/@n8n/decorators/src/on-shutdown.ts similarity index 79% rename from packages/cli/src/decorators/on-shutdown.ts rename to packages/@n8n/decorators/src/on-shutdown.ts index 08601e583e..9fbc39a83d 100644 --- a/packages/cli/src/decorators/on-shutdown.ts +++ b/packages/@n8n/decorators/src/on-shutdown.ts @@ -1,8 +1,9 @@ import { Container } from '@n8n/di'; import { UnexpectedError } from 'n8n-workflow'; -import { DEFAULT_SHUTDOWN_PRIORITY } from '@/constants'; -import { type ServiceClass, ShutdownService } from '@/shutdown/shutdown.service'; +import { DEFAULT_SHUTDOWN_PRIORITY } from './shutdown/constants'; +import { ShutdownRegistryMetadata } from './shutdown-registry-metadata'; +import type { ServiceClass } from './types'; /** * Decorator that registers a method as a shutdown hook. The method will @@ -30,7 +31,7 @@ export const OnShutdown = const methodName = String(propertyKey); // TODO: assert that serviceClass is decorated with @Service if (typeof descriptor?.value === 'function') { - Container.get(ShutdownService).register(priority, { serviceClass, methodName }); + Container.get(ShutdownRegistryMetadata).register(priority, { serviceClass, methodName }); } else { const name = `${serviceClass.name}.${methodName}()`; throw new UnexpectedError( diff --git a/packages/cli/src/decorators/redactable.ts b/packages/@n8n/decorators/src/redactable.ts similarity index 73% rename from packages/cli/src/decorators/redactable.ts rename to packages/@n8n/decorators/src/redactable.ts index e2df19daa6..5821024e4e 100644 --- a/packages/cli/src/decorators/redactable.ts +++ b/packages/@n8n/decorators/src/redactable.ts @@ -1,5 +1,20 @@ -import { RedactableError } from '@/errors/redactable.error'; -import type { UserLike } from '@/events/maps/relay.event-map'; +import { UnexpectedError } from 'n8n-workflow'; + +type UserLike = { + id: string; + email?: string; + firstName?: string; + lastName?: string; + role: string; +}; + +export class RedactableError extends UnexpectedError { + constructor(fieldName: string, args: string) { + super( + `Failed to find "${fieldName}" property in argument "${args.toString()}". Please set the decorator \`@Redactable()\` only on \`LogStreamingEventRelay\` methods where the argument contains a "${fieldName}" property.`, + ); + } +} function toRedactable(userLike: UserLike) { return { @@ -28,6 +43,7 @@ type FieldName = 'user' | 'inviter' | 'invitee'; export const Redactable = (fieldName: FieldName = 'user'): MethodDecorator => (_target, _propertyName, propertyDescriptor: PropertyDescriptor) => { + // eslint-disable-next-line @typescript-eslint/ban-types const originalMethod = propertyDescriptor.value as Function; type MethodArgs = Array<{ [fieldName: string]: UserLike }>; diff --git a/packages/cli/src/decorators/rest-controller.ts b/packages/@n8n/decorators/src/rest-controller.ts similarity index 52% rename from packages/cli/src/decorators/rest-controller.ts rename to packages/@n8n/decorators/src/rest-controller.ts index cdd32b6e21..be3c3c0c13 100644 --- a/packages/cli/src/decorators/rest-controller.ts +++ b/packages/@n8n/decorators/src/rest-controller.ts @@ -1,12 +1,14 @@ -import { Service } from '@n8n/di'; +import { Container, Service } from '@n8n/di'; -import { getControllerMetadata } from './controller.registry'; +import { ControllerRegistryMetadata } from './controller-registry-metadata'; import type { Controller } from './types'; export const RestController = (basePath: `/${string}` = '/'): ClassDecorator => (target) => { - const metadata = getControllerMetadata(target as unknown as Controller); + const metadata = Container.get(ControllerRegistryMetadata).getControllerMetadata( + target as unknown as Controller, + ); metadata.basePath = basePath; // eslint-disable-next-line @typescript-eslint/no-unsafe-return return Service()(target); diff --git a/packages/cli/src/decorators/route.ts b/packages/@n8n/decorators/src/route.ts similarity index 80% rename from packages/cli/src/decorators/route.ts rename to packages/@n8n/decorators/src/route.ts index f221cbb841..0d3852f806 100644 --- a/packages/cli/src/decorators/route.ts +++ b/packages/@n8n/decorators/src/route.ts @@ -1,6 +1,7 @@ +import { Container } from '@n8n/di'; import type { RequestHandler } from 'express'; -import { getRouteMetadata } from './controller.registry'; +import { ControllerRegistryMetadata } from './controller-registry-metadata'; import type { Controller, Method, RateLimit } from './types'; interface RouteOptions { @@ -16,7 +17,10 @@ const RouteFactory = (method: Method) => (path: `/${string}`, options: RouteOptions = {}): MethodDecorator => (target, handlerName) => { - const routeMetadata = getRouteMetadata(target.constructor as Controller, String(handlerName)); + const routeMetadata = Container.get(ControllerRegistryMetadata).getRouteMetadata( + target.constructor as Controller, + String(handlerName), + ); routeMetadata.method = method; routeMetadata.path = path; routeMetadata.middlewares = options.middlewares ?? []; diff --git a/packages/cli/src/decorators/scoped.ts b/packages/@n8n/decorators/src/scoped.ts similarity index 81% rename from packages/cli/src/decorators/scoped.ts rename to packages/@n8n/decorators/src/scoped.ts index 1a30fe3a69..9c9b761de9 100644 --- a/packages/cli/src/decorators/scoped.ts +++ b/packages/@n8n/decorators/src/scoped.ts @@ -1,12 +1,16 @@ +import { Container } from '@n8n/di'; import type { Scope } from '@n8n/permissions'; -import { getRouteMetadata } from './controller.registry'; +import { ControllerRegistryMetadata } from './controller-registry-metadata'; import type { Controller } from './types'; const Scoped = (scope: Scope, { globalOnly } = { globalOnly: false }): MethodDecorator => (target, handlerName) => { - const routeMetadata = getRouteMetadata(target.constructor as Controller, String(handlerName)); + const routeMetadata = Container.get(ControllerRegistryMetadata).getRouteMetadata( + target.constructor as Controller, + String(handlerName), + ); routeMetadata.accessScope = { scope, globalOnly }; }; diff --git a/packages/@n8n/decorators/src/shutdown-registry-metadata.ts b/packages/@n8n/decorators/src/shutdown-registry-metadata.ts new file mode 100644 index 0000000000..6107eb07a3 --- /dev/null +++ b/packages/@n8n/decorators/src/shutdown-registry-metadata.ts @@ -0,0 +1,31 @@ +import { Service } from '@n8n/di'; +import { UserError } from 'n8n-workflow'; + +import { HIGHEST_SHUTDOWN_PRIORITY, LOWEST_SHUTDOWN_PRIORITY } from './shutdown/constants'; +import type { ShutdownHandler } from './types'; + +@Service() +export class ShutdownRegistryMetadata { + private handlersByPriority: ShutdownHandler[][] = []; + + register(priority: number, handler: ShutdownHandler) { + if (priority < LOWEST_SHUTDOWN_PRIORITY || priority > HIGHEST_SHUTDOWN_PRIORITY) { + throw new UserError( + `Invalid shutdown priority. Please set it between ${LOWEST_SHUTDOWN_PRIORITY} and ${HIGHEST_SHUTDOWN_PRIORITY}.`, + { extra: { priority } }, + ); + } + + if (!this.handlersByPriority[priority]) this.handlersByPriority[priority] = []; + + this.handlersByPriority[priority].push(handler); + } + + getHandlersByPriority(): ShutdownHandler[][] { + return this.handlersByPriority; + } + + clear() { + this.handlersByPriority = []; + } +} diff --git a/packages/@n8n/decorators/src/shutdown/constants.ts b/packages/@n8n/decorators/src/shutdown/constants.ts new file mode 100644 index 0000000000..7c157f0950 --- /dev/null +++ b/packages/@n8n/decorators/src/shutdown/constants.ts @@ -0,0 +1,3 @@ +export const LOWEST_SHUTDOWN_PRIORITY = 0; +export const DEFAULT_SHUTDOWN_PRIORITY = 100; +export const HIGHEST_SHUTDOWN_PRIORITY = 200; diff --git a/packages/cli/src/decorators/types.ts b/packages/@n8n/decorators/src/types.ts similarity index 81% rename from packages/cli/src/decorators/types.ts rename to packages/@n8n/decorators/src/types.ts index fa752cd061..11263ab1c3 100644 --- a/packages/cli/src/decorators/types.ts +++ b/packages/@n8n/decorators/src/types.ts @@ -47,3 +47,14 @@ export interface ControllerMetadata { export type Controller = Constructable & Record Promise>; + +type RouteHandlerFn = () => Promise | void; + +type Class = new (...args: A) => T; + +export type ServiceClass = Class>; + +export interface ShutdownHandler { + serviceClass: ServiceClass; + methodName: string; +} diff --git a/packages/@n8n/decorators/tsconfig.build.json b/packages/@n8n/decorators/tsconfig.build.json new file mode 100644 index 0000000000..ee0e3e20fd --- /dev/null +++ b/packages/@n8n/decorators/tsconfig.build.json @@ -0,0 +1,11 @@ +{ + "extends": ["./tsconfig.json", "@n8n/typescript-config/tsconfig.build.json"], + "compilerOptions": { + "composite": true, + "rootDir": "src", + "outDir": "dist", + "tsBuildInfoFile": "dist/build.tsbuildinfo" + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/__tests__/**"] +} diff --git a/packages/@n8n/decorators/tsconfig.json b/packages/@n8n/decorators/tsconfig.json new file mode 100644 index 0000000000..eca44d32aa --- /dev/null +++ b/packages/@n8n/decorators/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "@n8n/typescript-config/tsconfig.common.json", + "compilerOptions": { + "rootDir": ".", + "types": ["node", "jest"], + "baseUrl": "src", + "tsBuildInfoFile": "dist/typecheck.tsbuildinfo", + "experimentalDecorators": true, + "emitDecoratorMetadata": true + }, + "include": ["src/**/*.ts"] +} diff --git a/packages/cli/package.json b/packages/cli/package.json index 0d39d88c9a..63c7eddc1b 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -93,6 +93,7 @@ "@n8n/client-oauth2": "workspace:*", "@n8n/config": "workspace:*", "@n8n/constants": "workspace:^", + "@n8n/decorators": "workspace:*", "@n8n/di": "workspace:*", "@n8n/localtunnel": "3.0.0", "@n8n/n8n-nodes-langchain": "workspace:*", diff --git a/packages/cli/src/decorators/__tests__/controller.registry.test.ts b/packages/cli/src/__tests__/controller.registry.test.ts similarity index 90% rename from packages/cli/src/decorators/__tests__/controller.registry.test.ts rename to packages/cli/src/__tests__/controller.registry.test.ts index 3b058243f6..e11ec88df4 100644 --- a/packages/cli/src/decorators/__tests__/controller.registry.test.ts +++ b/packages/cli/src/__tests__/controller.registry.test.ts @@ -3,27 +3,30 @@ jest.mock('@/constants', () => ({ })); import type { GlobalConfig } from '@n8n/config'; +import { ControllerRegistryMetadata } from '@n8n/decorators'; +import { Param } from '@n8n/decorators'; +import { Get, Licensed, RestController } from '@n8n/decorators'; +import { Container } from '@n8n/di'; import express from 'express'; import { mock } from 'jest-mock-extended'; import { agent as testAgent } from 'supertest'; import type { AuthService } from '@/auth/auth.service'; -import { ControllerRegistry, Get, Licensed, RestController } from '@/decorators'; +import { ControllerRegistry } from '@/controller.registry'; import type { License } from '@/license'; import type { SuperAgentTest } from '@test-integration/types'; -import { Param } from '../args'; - describe('ControllerRegistry', () => { const license = mock(); const authService = mock(); const globalConfig = mock({ endpoints: { rest: 'rest' } }); + const metadata = Container.get(ControllerRegistryMetadata); let agent: SuperAgentTest; beforeEach(() => { jest.resetAllMocks(); const app = express(); - new ControllerRegistry(license, authService, globalConfig).activate(app); + new ControllerRegistry(license, authService, globalConfig, metadata).activate(app); agent = testAgent(app); }); diff --git a/packages/cli/src/abstract-server.ts b/packages/cli/src/abstract-server.ts index 0dd6b15607..f17bb6aae1 100644 --- a/packages/cli/src/abstract-server.ts +++ b/packages/cli/src/abstract-server.ts @@ -1,4 +1,5 @@ import { GlobalConfig } from '@n8n/config'; +import { OnShutdown } from '@n8n/decorators'; import { Container, Service } from '@n8n/di'; import compression from 'compression'; import express from 'express'; @@ -11,7 +12,6 @@ import { Logger } from 'n8n-core'; import config from '@/config'; import { N8N_VERSION, TEMPLATES_DIR, inDevelopment, inTest } from '@/constants'; import * as Db from '@/db'; -import { OnShutdown } from '@/decorators/on-shutdown'; import { ExternalHooks } from '@/external-hooks'; import { rawBodyReader, bodyParser, corsMiddleware } from '@/middlewares'; import { send, sendErrorResponse } from '@/response-helper'; diff --git a/packages/cli/src/active-workflow-manager.ts b/packages/cli/src/active-workflow-manager.ts index 83854a85e1..2cd6cc1937 100644 --- a/packages/cli/src/active-workflow-manager.ts +++ b/packages/cli/src/active-workflow-manager.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ import { WorkflowsConfig } from '@n8n/config'; +import { OnShutdown } from '@n8n/decorators'; import { Service } from '@n8n/di'; import { chunk } from 'lodash'; import { @@ -42,7 +43,6 @@ import { } from '@/constants'; import type { WorkflowEntity } from '@/databases/entities/workflow-entity'; import { WorkflowRepository } from '@/databases/repositories/workflow.repository'; -import { OnShutdown } from '@/decorators/on-shutdown'; import { executeErrorWorkflow } from '@/execution-lifecycle/execute-error-workflow'; import { ExecutionService } from '@/executions/execution.service'; import { ExternalHooks } from '@/external-hooks'; diff --git a/packages/cli/src/commands/base-command.ts b/packages/cli/src/commands/base-command.ts index 81242e024e..98a500a69c 100644 --- a/packages/cli/src/commands/base-command.ts +++ b/packages/cli/src/commands/base-command.ts @@ -1,6 +1,7 @@ import 'reflect-metadata'; import { GlobalConfig } from '@n8n/config'; import { LICENSE_FEATURES } from '@n8n/constants'; +import { ModuleRegistry } from '@n8n/decorators'; import { Container } from '@n8n/di'; import { Command, Errors } from '@oclif/core'; import { @@ -19,7 +20,6 @@ import config from '@/config'; import { N8N_VERSION, N8N_RELEASE_DATE, inDevelopment, inTest } from '@/constants'; import * as CrashJournal from '@/crash-journal'; import * as Db from '@/db'; -import { ModuleRegistry } from '@/decorators/module'; import { getDataDeduplicationService } from '@/deduplication'; import { DeprecationService } from '@/deprecation/deprecation.service'; import { TestRunnerService } from '@/evaluation.ee/test-runner/test-runner.service.ee'; diff --git a/packages/cli/src/decorators/controller.registry.ts b/packages/cli/src/controller.registry.ts similarity index 83% rename from packages/cli/src/decorators/controller.registry.ts rename to packages/cli/src/controller.registry.ts index ce31eb40bb..4078172b8f 100644 --- a/packages/cli/src/decorators/controller.registry.ts +++ b/packages/cli/src/controller.registry.ts @@ -1,5 +1,7 @@ import { GlobalConfig } from '@n8n/config'; import type { BooleanLicenseFeature } from '@n8n/constants'; +import { ControllerRegistryMetadata } from '@n8n/decorators'; +import type { AccessScope, Controller, RateLimit } from '@n8n/decorators'; import { Container, Service } from '@n8n/di'; import { Router } from 'express'; import type { Application, Request, Response, RequestHandler } from 'express'; @@ -15,57 +17,23 @@ import { userHasScopes } from '@/permissions.ee/check-access'; import type { AuthenticatedRequest } from '@/requests'; import { send } from '@/response-helper'; // TODO: move `ResponseHelper.send` to this file -import type { - AccessScope, - Controller, - ControllerMetadata, - HandlerName, - RateLimit, - RouteMetadata, -} from './types'; - -const registry = new Map(); - -export const getControllerMetadata = (controllerClass: Controller) => { - let metadata = registry.get(controllerClass); - if (!metadata) { - metadata = { - basePath: '/', - middlewares: [], - routes: new Map(), - }; - registry.set(controllerClass, metadata); - } - return metadata; -}; - -export const getRouteMetadata = (controllerClass: Controller, handlerName: HandlerName) => { - const metadata = getControllerMetadata(controllerClass); - let route = metadata.routes.get(handlerName); - if (!route) { - route = {} as RouteMetadata; - route.args = []; - metadata.routes.set(handlerName, route); - } - return route; -}; - @Service() export class ControllerRegistry { constructor( private readonly license: License, private readonly authService: AuthService, private readonly globalConfig: GlobalConfig, + private readonly metadata: ControllerRegistryMetadata, ) {} activate(app: Application) { - for (const controllerClass of registry.keys()) { + for (const controllerClass of this.metadata.controllerClasses) { this.activateController(app, controllerClass); } } private activateController(app: Application, controllerClass: Controller) { - const metadata = registry.get(controllerClass)!; + const metadata = this.metadata.getControllerMetadata(controllerClass); const router = Router({ mergeParams: true }); const prefix = `/${this.globalConfig.endpoints.rest}/${metadata.basePath}` diff --git a/packages/cli/src/controllers/active-workflows.controller.ts b/packages/cli/src/controllers/active-workflows.controller.ts index 1a21c07942..a4da47119e 100644 --- a/packages/cli/src/controllers/active-workflows.controller.ts +++ b/packages/cli/src/controllers/active-workflows.controller.ts @@ -1,4 +1,5 @@ -import { Get, RestController } from '@/decorators'; +import { Get, RestController } from '@n8n/decorators'; + import { ActiveWorkflowRequest } from '@/requests'; import { ActiveWorkflowsService } from '@/services/active-workflows.service'; diff --git a/packages/cli/src/controllers/ai.controller.ts b/packages/cli/src/controllers/ai.controller.ts index e2f8c4e5ae..f26f9a36a8 100644 --- a/packages/cli/src/controllers/ai.controller.ts +++ b/packages/cli/src/controllers/ai.controller.ts @@ -5,6 +5,7 @@ import { AiAskRequestDto, AiFreeCreditsRequestDto, } from '@n8n/api-types'; +import { Body, Post, RestController } from '@n8n/decorators'; import type { AiAssistantSDK } from '@n8n_io/ai-assistant-sdk'; import { Response } from 'express'; import { OPEN_AI_API_CREDENTIAL_TYPE } from 'n8n-workflow'; @@ -13,7 +14,6 @@ import { WritableStream } from 'node:stream/web'; import { FREE_AI_CREDITS_CREDENTIAL_NAME } from '@/constants'; import { CredentialsService } from '@/credentials/credentials.service'; -import { Body, Post, RestController } from '@/decorators'; import { InternalServerError } from '@/errors/response-errors/internal-server.error'; import { AuthenticatedRequest } from '@/requests'; import { AiService } from '@/services/ai.service'; diff --git a/packages/cli/src/controllers/annotation-tags.controller.ee.ts b/packages/cli/src/controllers/annotation-tags.controller.ee.ts index fb57ed6ff1..eb2116bbcc 100644 --- a/packages/cli/src/controllers/annotation-tags.controller.ee.ts +++ b/packages/cli/src/controllers/annotation-tags.controller.ee.ts @@ -1,4 +1,5 @@ -import { Delete, Get, Patch, Post, RestController, GlobalScope } from '@/decorators'; +import { Delete, Get, Patch, Post, RestController, GlobalScope } from '@n8n/decorators'; + import { AnnotationTagsRequest } from '@/requests'; import { AnnotationTagService } from '@/services/annotation-tag.service.ee'; diff --git a/packages/cli/src/controllers/api-keys.controller.ts b/packages/cli/src/controllers/api-keys.controller.ts index 6e847b567b..152bd852d2 100644 --- a/packages/cli/src/controllers/api-keys.controller.ts +++ b/packages/cli/src/controllers/api-keys.controller.ts @@ -1,7 +1,7 @@ import { CreateApiKeyRequestDto, UpdateApiKeyRequestDto } from '@n8n/api-types'; +import { Body, Delete, Get, Param, Patch, Post, RestController } from '@n8n/decorators'; import type { RequestHandler } from 'express'; -import { Body, Delete, Get, Param, Patch, Post, RestController } from '@/decorators'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { EventService } from '@/events/event.service'; import { isApiEnabled } from '@/public-api'; diff --git a/packages/cli/src/controllers/auth.controller.ts b/packages/cli/src/controllers/auth.controller.ts index dac65903fe..5eb2b7a805 100644 --- a/packages/cli/src/controllers/auth.controller.ts +++ b/packages/cli/src/controllers/auth.controller.ts @@ -1,4 +1,5 @@ import { LoginRequestDto, ResolveSignupTokenQueryDto } from '@n8n/api-types'; +import { Body, Get, Post, Query, RestController } from '@n8n/decorators'; import { isEmail } from 'class-validator'; import { Response } from 'express'; import { Logger } from 'n8n-core'; @@ -8,7 +9,6 @@ import { AuthService } from '@/auth/auth.service'; import { RESPONSE_ERROR_MESSAGES } from '@/constants'; import type { User } from '@/databases/entities/user'; import { UserRepository } from '@/databases/repositories/user.repository'; -import { Body, Get, Post, Query, RestController } from '@/decorators'; import { AuthError } from '@/errors/response-errors/auth.error'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { ForbiddenError } from '@/errors/response-errors/forbidden.error'; diff --git a/packages/cli/src/controllers/binary-data.controller.ts b/packages/cli/src/controllers/binary-data.controller.ts index 6ba49f8793..f6344da6df 100644 --- a/packages/cli/src/controllers/binary-data.controller.ts +++ b/packages/cli/src/controllers/binary-data.controller.ts @@ -1,9 +1,9 @@ import { BinaryDataQueryDto, BinaryDataSignedQueryDto, ViewableMimeTypes } from '@n8n/api-types'; +import { Get, Query, RestController } from '@n8n/decorators'; import { Request, Response } from 'express'; import { JsonWebTokenError } from 'jsonwebtoken'; import { BinaryDataService, FileNotFoundError, isValidNonDefaultMode } from 'n8n-core'; -import { Get, Query, RestController } from '@/decorators'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; @RestController('/binary-data') diff --git a/packages/cli/src/controllers/community-packages.controller.ts b/packages/cli/src/controllers/community-packages.controller.ts index ab2134b7e0..3753214aed 100644 --- a/packages/cli/src/controllers/community-packages.controller.ts +++ b/packages/cli/src/controllers/community-packages.controller.ts @@ -1,10 +1,11 @@ +import { Delete, Get, Patch, Post, RestController, GlobalScope } from '@n8n/decorators'; + import { RESPONSE_ERROR_MESSAGES, STARTER_TEMPLATE_NAME, UNKNOWN_FAILURE_REASON, } from '@/constants'; import type { InstalledPackages } from '@/databases/entities/installed-packages'; -import { Delete, Get, Patch, Post, RestController, GlobalScope } from '@/decorators'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { InternalServerError } from '@/errors/response-errors/internal-server.error'; import { EventService } from '@/events/event.service'; diff --git a/packages/cli/src/controllers/cta.controller.ts b/packages/cli/src/controllers/cta.controller.ts index bea2476163..517b45d1ff 100644 --- a/packages/cli/src/controllers/cta.controller.ts +++ b/packages/cli/src/controllers/cta.controller.ts @@ -1,6 +1,6 @@ +import { Get, RestController } from '@n8n/decorators'; import express from 'express'; -import { Get, RestController } from '@/decorators'; import { AuthenticatedRequest } from '@/requests'; import { CtaService } from '@/services/cta.service'; diff --git a/packages/cli/src/controllers/debug.controller.ts b/packages/cli/src/controllers/debug.controller.ts index 1a2b08d550..58ce67bc38 100644 --- a/packages/cli/src/controllers/debug.controller.ts +++ b/packages/cli/src/controllers/debug.controller.ts @@ -1,8 +1,8 @@ +import { Get, RestController } from '@n8n/decorators'; import { InstanceSettings } from 'n8n-core'; import { ActiveWorkflowManager } from '@/active-workflow-manager'; import { WorkflowRepository } from '@/databases/repositories/workflow.repository'; -import { Get, RestController } from '@/decorators'; import { OrchestrationService } from '@/services/orchestration.service'; @RestController('/debug') diff --git a/packages/cli/src/controllers/dynamic-node-parameters.controller.ts b/packages/cli/src/controllers/dynamic-node-parameters.controller.ts index 987040d010..5e02e6f39d 100644 --- a/packages/cli/src/controllers/dynamic-node-parameters.controller.ts +++ b/packages/cli/src/controllers/dynamic-node-parameters.controller.ts @@ -4,9 +4,9 @@ import { ResourceMapperFieldsRequestDto, ActionResultRequestDto, } from '@n8n/api-types'; +import { Post, RestController, Body } from '@n8n/decorators'; import type { INodePropertyOptions, NodeParameterValueType } from 'n8n-workflow'; -import { Post, RestController, Body } from '@/decorators'; import { AuthenticatedRequest } from '@/requests'; import { DynamicNodeParametersService } from '@/services/dynamic-node-parameters.service'; import { getBase } from '@/workflow-execute-additional-data'; diff --git a/packages/cli/src/controllers/e2e.controller.ts b/packages/cli/src/controllers/e2e.controller.ts index fe7000c144..97b6f2cfc8 100644 --- a/packages/cli/src/controllers/e2e.controller.ts +++ b/packages/cli/src/controllers/e2e.controller.ts @@ -1,6 +1,7 @@ import type { PushMessage } from '@n8n/api-types'; import type { BooleanLicenseFeature, NumericLicenseFeature } from '@n8n/constants'; import { LICENSE_FEATURES, LICENSE_QUOTAS, UNLIMITED_LICENSE_QUOTA } from '@n8n/constants'; +import { Patch, Post, RestController } from '@n8n/decorators'; import { Container } from '@n8n/di'; import { Request } from 'express'; import { Logger } from 'n8n-core'; @@ -12,7 +13,6 @@ import { inE2ETests } from '@/constants'; import { AuthUserRepository } from '@/databases/repositories/auth-user.repository'; import { SettingsRepository } from '@/databases/repositories/settings.repository'; import { UserRepository } from '@/databases/repositories/user.repository'; -import { Patch, Post, RestController } from '@/decorators'; import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus'; import type { FeatureReturnType } from '@/license'; import { License } from '@/license'; diff --git a/packages/cli/src/controllers/folder.controller.ts b/packages/cli/src/controllers/folder.controller.ts index 565b54a6e4..7e00196fd2 100644 --- a/packages/cli/src/controllers/folder.controller.ts +++ b/packages/cli/src/controllers/folder.controller.ts @@ -4,10 +4,19 @@ import { ListFolderQueryDto, UpdateFolderDto, } from '@n8n/api-types'; +import { + Post, + RestController, + ProjectScope, + Body, + Get, + Patch, + Delete, + Query, +} from '@n8n/decorators'; import { Response } from 'express'; import { UserError } from 'n8n-workflow'; -import { Post, RestController, ProjectScope, Body, Get, Patch, Delete, Query } from '@/decorators'; import { FolderNotFoundError } from '@/errors/folder-not-found.error'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { InternalServerError } from '@/errors/response-errors/internal-server.error'; diff --git a/packages/cli/src/controllers/invitation.controller.ts b/packages/cli/src/controllers/invitation.controller.ts index d8a0e503f6..89013547e0 100644 --- a/packages/cli/src/controllers/invitation.controller.ts +++ b/packages/cli/src/controllers/invitation.controller.ts @@ -1,4 +1,5 @@ import { AcceptInvitationRequestDto, InviteUsersRequestDto } from '@n8n/api-types'; +import { Post, GlobalScope, RestController, Body, Param } from '@n8n/decorators'; import { Response } from 'express'; import { Logger } from 'n8n-core'; @@ -7,7 +8,6 @@ import config from '@/config'; import { RESPONSE_ERROR_MESSAGES } from '@/constants'; import type { User } from '@/databases/entities/user'; import { UserRepository } from '@/databases/repositories/user.repository'; -import { Post, GlobalScope, RestController, Body, Param } from '@/decorators'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { ForbiddenError } from '@/errors/response-errors/forbidden.error'; import { EventService } from '@/events/event.service'; diff --git a/packages/cli/src/controllers/me.controller.ts b/packages/cli/src/controllers/me.controller.ts index bb42d13878..14cb8b1aef 100644 --- a/packages/cli/src/controllers/me.controller.ts +++ b/packages/cli/src/controllers/me.controller.ts @@ -4,6 +4,7 @@ import { SettingsUpdateRequestDto, UserUpdateRequestDto, } from '@n8n/api-types'; +import { Body, Patch, Post, RestController } from '@n8n/decorators'; import { plainToInstance } from 'class-transformer'; import { Response } from 'express'; import { Logger } from 'n8n-core'; @@ -11,7 +12,6 @@ import { Logger } from 'n8n-core'; import { AuthService } from '@/auth/auth.service'; import type { User } from '@/databases/entities/user'; import { UserRepository } from '@/databases/repositories/user.repository'; -import { Body, Patch, Post, RestController } from '@/decorators'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { InvalidMfaCodeError } from '@/errors/response-errors/invalid-mfa-code.error'; import { EventService } from '@/events/event.service'; diff --git a/packages/cli/src/controllers/mfa.controller.ts b/packages/cli/src/controllers/mfa.controller.ts index 26aeca5432..39e2ce50e2 100644 --- a/packages/cli/src/controllers/mfa.controller.ts +++ b/packages/cli/src/controllers/mfa.controller.ts @@ -1,4 +1,5 @@ -import { Get, Post, RestController } from '@/decorators'; +import { Get, Post, RestController } from '@n8n/decorators'; + import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { ExternalHooks } from '@/external-hooks'; import { MfaService } from '@/mfa/mfa.service'; diff --git a/packages/cli/src/controllers/node-types.controller.ts b/packages/cli/src/controllers/node-types.controller.ts index b8c50eb61d..5d61a590d4 100644 --- a/packages/cli/src/controllers/node-types.controller.ts +++ b/packages/cli/src/controllers/node-types.controller.ts @@ -1,10 +1,10 @@ +import { Post, RestController } from '@n8n/decorators'; import { Request } from 'express'; import { readFile } from 'fs/promises'; import get from 'lodash/get'; import type { INodeTypeDescription, INodeTypeNameVersion } from 'n8n-workflow'; import config from '@/config'; -import { Post, RestController } from '@/decorators'; import { NodeTypes } from '@/node-types'; @RestController('/node-types') diff --git a/packages/cli/src/controllers/oauth/oauth1-credential.controller.ts b/packages/cli/src/controllers/oauth/oauth1-credential.controller.ts index 3f8f2cf0e1..9809e86d93 100644 --- a/packages/cli/src/controllers/oauth/oauth1-credential.controller.ts +++ b/packages/cli/src/controllers/oauth/oauth1-credential.controller.ts @@ -1,3 +1,4 @@ +import { Get, RestController } from '@n8n/decorators'; import type { AxiosRequestConfig } from 'axios'; import axios from 'axios'; import { createHmac } from 'crypto'; @@ -6,7 +7,6 @@ import { ensureError, jsonStringify } from 'n8n-workflow'; import type { RequestOptions } from 'oauth-1.0a'; import clientOAuth1 from 'oauth-1.0a'; -import { Get, RestController } from '@/decorators'; import { OAuthRequest } from '@/requests'; import { AbstractOAuthController, skipAuthOnOAuthCallback } from './abstract-oauth.controller'; diff --git a/packages/cli/src/controllers/oauth/oauth2-credential.controller.ts b/packages/cli/src/controllers/oauth/oauth2-credential.controller.ts index 3a855858b0..bc4aecc75e 100644 --- a/packages/cli/src/controllers/oauth/oauth2-credential.controller.ts +++ b/packages/cli/src/controllers/oauth/oauth2-credential.controller.ts @@ -1,5 +1,6 @@ import type { ClientOAuth2Options, OAuth2CredentialData } from '@n8n/client-oauth2'; import { ClientOAuth2 } from '@n8n/client-oauth2'; +import { Get, RestController } from '@n8n/decorators'; import { Response } from 'express'; import omit from 'lodash/omit'; import set from 'lodash/set'; @@ -9,7 +10,6 @@ import pkceChallenge from 'pkce-challenge'; import * as qs from 'querystring'; import { GENERIC_OAUTH2_CREDENTIALS_WITH_EDITABLE_SCOPE as GENERIC_OAUTH2_CREDENTIALS_WITH_EDITABLE_SCOPE } from '@/constants'; -import { Get, RestController } from '@/decorators'; import { OAuthRequest } from '@/requests'; import { AbstractOAuthController, skipAuthOnOAuthCallback } from './abstract-oauth.controller'; diff --git a/packages/cli/src/controllers/orchestration.controller.ts b/packages/cli/src/controllers/orchestration.controller.ts index 14d38cfa43..807c25d00d 100644 --- a/packages/cli/src/controllers/orchestration.controller.ts +++ b/packages/cli/src/controllers/orchestration.controller.ts @@ -1,4 +1,5 @@ -import { Post, RestController, GlobalScope } from '@/decorators'; +import { Post, RestController, GlobalScope } from '@n8n/decorators'; + import { License } from '@/license'; import { Publisher } from '@/scaling/pubsub/publisher.service'; diff --git a/packages/cli/src/controllers/owner.controller.ts b/packages/cli/src/controllers/owner.controller.ts index 5c7e8d1e2a..7497a099a2 100644 --- a/packages/cli/src/controllers/owner.controller.ts +++ b/packages/cli/src/controllers/owner.controller.ts @@ -1,4 +1,5 @@ import { DismissBannerRequestDto, OwnerSetupRequestDto } from '@n8n/api-types'; +import { Body, GlobalScope, Post, RestController } from '@n8n/decorators'; import { Response } from 'express'; import { Logger } from 'n8n-core'; @@ -6,7 +7,6 @@ import { AuthService } from '@/auth/auth.service'; import config from '@/config'; import { SettingsRepository } from '@/databases/repositories/settings.repository'; import { UserRepository } from '@/databases/repositories/user.repository'; -import { Body, GlobalScope, Post, RestController } from '@/decorators'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { EventService } from '@/events/event.service'; import { validateEntity } from '@/generic-helpers'; diff --git a/packages/cli/src/controllers/password-reset.controller.ts b/packages/cli/src/controllers/password-reset.controller.ts index c2652aa785..3a874464ac 100644 --- a/packages/cli/src/controllers/password-reset.controller.ts +++ b/packages/cli/src/controllers/password-reset.controller.ts @@ -3,13 +3,13 @@ import { ForgotPasswordRequestDto, ResolvePasswordTokenQueryDto, } from '@n8n/api-types'; +import { Body, Get, Post, Query, RestController } from '@n8n/decorators'; import { Response } from 'express'; import { Logger } from 'n8n-core'; import { AuthService } from '@/auth/auth.service'; import { RESPONSE_ERROR_MESSAGES } from '@/constants'; import { UserRepository } from '@/databases/repositories/user.repository'; -import { Body, Get, Post, Query, RestController } from '@/decorators'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { ForbiddenError } from '@/errors/response-errors/forbidden.error'; import { InternalServerError } from '@/errors/response-errors/internal-server.error'; diff --git a/packages/cli/src/controllers/project.controller.ts b/packages/cli/src/controllers/project.controller.ts index 665eba9636..2563054d97 100644 --- a/packages/cli/src/controllers/project.controller.ts +++ b/packages/cli/src/controllers/project.controller.ts @@ -1,12 +1,4 @@ import { CreateProjectDto, DeleteProjectDto, UpdateProjectDto } from '@n8n/api-types'; -import { combineScopes } from '@n8n/permissions'; -import type { Scope } from '@n8n/permissions'; -// eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import -import { In, Not } from '@n8n/typeorm'; -import { Response } from 'express'; - -import type { Project } from '@/databases/entities/project'; -import { ProjectRepository } from '@/databases/repositories/project.repository'; import { Get, Post, @@ -19,7 +11,15 @@ import { Body, Param, Query, -} from '@/decorators'; +} from '@n8n/decorators'; +import { combineScopes } from '@n8n/permissions'; +import type { Scope } from '@n8n/permissions'; +// eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import +import { In, Not } from '@n8n/typeorm'; +import { Response } from 'express'; + +import type { Project } from '@/databases/entities/project'; +import { ProjectRepository } from '@/databases/repositories/project.repository'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { NotFoundError } from '@/errors/response-errors/not-found.error'; import { EventService } from '@/events/event.service'; diff --git a/packages/cli/src/controllers/role.controller.ts b/packages/cli/src/controllers/role.controller.ts index 3a9cd3c376..3343e8e3ce 100644 --- a/packages/cli/src/controllers/role.controller.ts +++ b/packages/cli/src/controllers/role.controller.ts @@ -1,4 +1,5 @@ -import { Get, RestController } from '@/decorators'; +import { Get, RestController } from '@n8n/decorators'; + import { type AllRoleTypes, RoleService } from '@/services/role.service'; @RestController('/roles') diff --git a/packages/cli/src/controllers/tags.controller.ts b/packages/cli/src/controllers/tags.controller.ts index 5f0d87812a..efa51668a6 100644 --- a/packages/cli/src/controllers/tags.controller.ts +++ b/packages/cli/src/controllers/tags.controller.ts @@ -1,6 +1,4 @@ import { CreateOrUpdateTagRequestDto, RetrieveTagQueryDto } from '@n8n/api-types'; -import { Response } from 'express'; - import { Delete, Get, @@ -11,7 +9,9 @@ import { Body, Param, Query, -} from '@/decorators'; +} from '@n8n/decorators'; +import { Response } from 'express'; + import { AuthenticatedRequest } from '@/requests'; import { TagService } from '@/services/tag.service'; diff --git a/packages/cli/src/controllers/translation.controller.ts b/packages/cli/src/controllers/translation.controller.ts index d6ae656afe..8c78946b21 100644 --- a/packages/cli/src/controllers/translation.controller.ts +++ b/packages/cli/src/controllers/translation.controller.ts @@ -1,3 +1,4 @@ +import { Get, RestController } from '@n8n/decorators'; import type { Request } from 'express'; import { access } from 'fs/promises'; import { join } from 'path'; @@ -5,7 +6,6 @@ import { join } from 'path'; import config from '@/config'; import { NODES_BASE_DIR } from '@/constants'; import { CredentialTypes } from '@/credential-types'; -import { Get, RestController } from '@/decorators'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { InternalServerError } from '@/errors/response-errors/internal-server.error'; diff --git a/packages/cli/src/controllers/user-settings.controller.ts b/packages/cli/src/controllers/user-settings.controller.ts index 7b0fede08c..7b1505fe0b 100644 --- a/packages/cli/src/controllers/user-settings.controller.ts +++ b/packages/cli/src/controllers/user-settings.controller.ts @@ -1,6 +1,6 @@ +import { Patch, RestController } from '@n8n/decorators'; import type { NpsSurveyState } from 'n8n-workflow'; -import { Patch, RestController } from '@/decorators'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { NpsSurveyRequest } from '@/requests'; import { UserService } from '@/services/user.service'; diff --git a/packages/cli/src/controllers/users.controller.ts b/packages/cli/src/controllers/users.controller.ts index 9673540efe..5bb7cf73b3 100644 --- a/packages/cli/src/controllers/users.controller.ts +++ b/packages/cli/src/controllers/users.controller.ts @@ -1,4 +1,14 @@ import { RoleChangeRequestDto, SettingsUpdateRequestDto } from '@n8n/api-types'; +import { + GlobalScope, + Delete, + Get, + RestController, + Patch, + Licensed, + Body, + Param, +} from '@n8n/decorators'; import { Response } from 'express'; import { Logger } from 'n8n-core'; @@ -11,16 +21,6 @@ import { ProjectRepository } from '@/databases/repositories/project.repository'; import { SharedCredentialsRepository } from '@/databases/repositories/shared-credentials.repository'; import { SharedWorkflowRepository } from '@/databases/repositories/shared-workflow.repository'; import { UserRepository } from '@/databases/repositories/user.repository'; -import { - GlobalScope, - Delete, - Get, - RestController, - Patch, - Licensed, - Body, - Param, -} from '@/decorators'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { ForbiddenError } from '@/errors/response-errors/forbidden.error'; import { NotFoundError } from '@/errors/response-errors/not-found.error'; diff --git a/packages/cli/src/controllers/workflow-statistics.controller.ts b/packages/cli/src/controllers/workflow-statistics.controller.ts index b14afc9179..c455c7249c 100644 --- a/packages/cli/src/controllers/workflow-statistics.controller.ts +++ b/packages/cli/src/controllers/workflow-statistics.controller.ts @@ -1,3 +1,4 @@ +import { Get, Middleware, RestController } from '@n8n/decorators'; import { Response, NextFunction } from 'express'; import { Logger } from 'n8n-core'; @@ -5,7 +6,6 @@ import type { WorkflowStatistics } from '@/databases/entities/workflow-statistic import { StatisticsNames } from '@/databases/entities/workflow-statistics'; import { SharedWorkflowRepository } from '@/databases/repositories/shared-workflow.repository'; import { WorkflowStatisticsRepository } from '@/databases/repositories/workflow-statistics.repository'; -import { Get, Middleware, RestController } from '@/decorators'; import { NotFoundError } from '@/errors/response-errors/not-found.error'; import type { IWorkflowStatisticsDataLoaded } from '@/interfaces'; diff --git a/packages/cli/src/credentials/credentials.controller.ts b/packages/cli/src/credentials/credentials.controller.ts index cddabf6ad2..d25598223b 100644 --- a/packages/cli/src/credentials/credentials.controller.ts +++ b/packages/cli/src/credentials/credentials.controller.ts @@ -5,17 +5,6 @@ import { GenerateCredentialNameRequestQuery, } from '@n8n/api-types'; import { GlobalConfig } from '@n8n/config'; -// eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import -import { In } from '@n8n/typeorm'; -import { Logger } from 'n8n-core'; -import type { ICredentialDataDecryptedObject } from 'n8n-workflow'; -import { deepCopy } from 'n8n-workflow'; -import { z } from 'zod'; - -import { SharedCredentials } from '@/databases/entities/shared-credentials'; -import { ProjectRelationRepository } from '@/databases/repositories/project-relation.repository'; -import { SharedCredentialsRepository } from '@/databases/repositories/shared-credentials.repository'; -import * as Db from '@/db'; import { Delete, Get, @@ -25,8 +14,21 @@ import { Put, RestController, ProjectScope, -} from '@/decorators'; -import { Body, Param, Query } from '@/decorators/args'; + Body, + Param, + Query, +} from '@n8n/decorators'; +// eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import +import { In } from '@n8n/typeorm'; +import { Logger } from 'n8n-core'; +import { deepCopy } from 'n8n-workflow'; +import type { ICredentialDataDecryptedObject } from 'n8n-workflow'; +import { z } from 'zod'; + +import { SharedCredentials } from '@/databases/entities/shared-credentials'; +import { ProjectRelationRepository } from '@/databases/repositories/project-relation.repository'; +import { SharedCredentialsRepository } from '@/databases/repositories/shared-credentials.repository'; +import * as Db from '@/db'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { ForbiddenError } from '@/errors/response-errors/forbidden.error'; import { NotFoundError } from '@/errors/response-errors/not-found.error'; diff --git a/packages/cli/src/decorators/index.ts b/packages/cli/src/decorators/index.ts deleted file mode 100644 index 8002bbe094..0000000000 --- a/packages/cli/src/decorators/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export { Body, Query, Param } from './args'; -export { RestController } from './rest-controller'; -export { Get, Post, Put, Patch, Delete } from './route'; -export { Middleware } from './middleware'; -export { ControllerRegistry } from './controller.registry'; -export { Licensed } from './licensed'; -export { GlobalScope, ProjectScope } from './scoped'; diff --git a/packages/cli/src/decorators/middleware.ts b/packages/cli/src/decorators/middleware.ts deleted file mode 100644 index 57e9b6782e..0000000000 --- a/packages/cli/src/decorators/middleware.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { getControllerMetadata } from './controller.registry'; -import type { Controller } from './types'; - -export const Middleware = (): MethodDecorator => (target, handlerName) => { - const metadata = getControllerMetadata(target.constructor as Controller); - metadata.middlewares.push(String(handlerName)); -}; diff --git a/packages/cli/src/environments.ee/source-control/source-control.controller.ee.ts b/packages/cli/src/environments.ee/source-control/source-control.controller.ee.ts index 88269a199c..781b98643f 100644 --- a/packages/cli/src/environments.ee/source-control/source-control.controller.ee.ts +++ b/packages/cli/src/environments.ee/source-control/source-control.controller.ee.ts @@ -1,9 +1,9 @@ import { PullWorkFolderRequestDto, PushWorkFolderRequestDto } from '@n8n/api-types'; import type { SourceControlledFile } from '@n8n/api-types'; +import { Get, Post, Patch, RestController, GlobalScope, Body } from '@n8n/decorators'; import express from 'express'; import type { PullResult } from 'simple-git'; -import { Get, Post, Patch, RestController, GlobalScope, Body } from '@/decorators'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { EventService } from '@/events/event.service'; import { AuthenticatedRequest } from '@/requests'; diff --git a/packages/cli/src/environments.ee/variables/variables.controller.ee.ts b/packages/cli/src/environments.ee/variables/variables.controller.ee.ts index 0ae5940d72..f5daf0b327 100644 --- a/packages/cli/src/environments.ee/variables/variables.controller.ee.ts +++ b/packages/cli/src/environments.ee/variables/variables.controller.ee.ts @@ -1,5 +1,4 @@ import { VariableListRequestDto } from '@n8n/api-types'; - import { Delete, Get, @@ -9,7 +8,8 @@ import { Post, Query, RestController, -} from '@/decorators'; +} from '@n8n/decorators'; + import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { NotFoundError } from '@/errors/response-errors/not-found.error'; import { VariableCountLimitReachedError } from '@/errors/variable-count-limit-reached.error'; diff --git a/packages/cli/src/evaluation.ee/test-definitions.controller.ee.ts b/packages/cli/src/evaluation.ee/test-definitions.controller.ee.ts index 0cec91638b..50077b4d08 100644 --- a/packages/cli/src/evaluation.ee/test-definitions.controller.ee.ts +++ b/packages/cli/src/evaluation.ee/test-definitions.controller.ee.ts @@ -1,7 +1,7 @@ +import { Get, Post, Patch, RestController, Delete } from '@n8n/decorators'; import express from 'express'; import assert from 'node:assert'; -import { Get, Post, Patch, RestController, Delete } from '@/decorators'; import { ForbiddenError } from '@/errors/response-errors/forbidden.error'; import { NotFoundError } from '@/errors/response-errors/not-found.error'; import { diff --git a/packages/cli/src/evaluation.ee/test-runs.controller.ee.ts b/packages/cli/src/evaluation.ee/test-runs.controller.ee.ts index 333cbb9fae..5acc45c2bd 100644 --- a/packages/cli/src/evaluation.ee/test-runs.controller.ee.ts +++ b/packages/cli/src/evaluation.ee/test-runs.controller.ee.ts @@ -1,9 +1,9 @@ +import { Delete, Get, Post, RestController } from '@n8n/decorators'; import express from 'express'; import { InstanceSettings } from 'n8n-core'; import { TestCaseExecutionRepository } from '@/databases/repositories/test-case-execution.repository.ee'; import { TestRunRepository } from '@/databases/repositories/test-run.repository.ee'; -import { Delete, Get, Post, RestController } from '@/decorators'; import { ConflictError } from '@/errors/response-errors/conflict.error'; import { NotFoundError } from '@/errors/response-errors/not-found.error'; import { NotImplementedError } from '@/errors/response-errors/not-implemented.error'; diff --git a/packages/cli/src/eventbus/event-bus.controller.ts b/packages/cli/src/eventbus/event-bus.controller.ts index 43603e935d..ea8311fd93 100644 --- a/packages/cli/src/eventbus/event-bus.controller.ts +++ b/packages/cli/src/eventbus/event-bus.controller.ts @@ -1,3 +1,4 @@ +import { RestController, Get, Post, Delete, GlobalScope, Licensed } from '@n8n/decorators'; import express from 'express'; import type { MessageEventBusDestinationWebhookOptions, @@ -5,7 +6,6 @@ import type { } from 'n8n-workflow'; import { MessageEventBusDestinationTypeNames } from 'n8n-workflow'; -import { RestController, Get, Post, Delete, GlobalScope, Licensed } from '@/decorators'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { AuthenticatedRequest } from '@/requests'; diff --git a/packages/cli/src/events/events.controller.ts b/packages/cli/src/events/events.controller.ts index 994f803242..8256bcd086 100644 --- a/packages/cli/src/events/events.controller.ts +++ b/packages/cli/src/events/events.controller.ts @@ -1,4 +1,5 @@ -import { Get, RestController } from '@/decorators'; +import { Get, RestController } from '@n8n/decorators'; + import { AuthenticatedRequest } from '@/requests'; import { EventService } from './event.service'; diff --git a/packages/cli/src/events/relays/log-streaming.event-relay.ts b/packages/cli/src/events/relays/log-streaming.event-relay.ts index 76b578451d..4110185ddd 100644 --- a/packages/cli/src/events/relays/log-streaming.event-relay.ts +++ b/packages/cli/src/events/relays/log-streaming.event-relay.ts @@ -1,7 +1,7 @@ +import { Redactable } from '@n8n/decorators'; import { Service } from '@n8n/di'; import type { IWorkflowBase } from 'n8n-workflow'; -import { Redactable } from '@/decorators/redactable'; import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus'; import { EventService } from '@/events/event.service'; import type { RelayEventMap } from '@/events/maps/relay.event-map'; diff --git a/packages/cli/src/execution-lifecycle/execution-lifecycle-hooks.ts b/packages/cli/src/execution-lifecycle/execution-lifecycle-hooks.ts index 3ef265f7e9..f588da4d7c 100644 --- a/packages/cli/src/execution-lifecycle/execution-lifecycle-hooks.ts +++ b/packages/cli/src/execution-lifecycle/execution-lifecycle-hooks.ts @@ -1,3 +1,4 @@ +import { ModuleRegistry } from '@n8n/decorators'; import { Container } from '@n8n/di'; import { stringify } from 'flatted'; import { ErrorReporter, Logger, InstanceSettings, ExecutionLifecycleHooks } from 'n8n-core'; @@ -8,7 +9,6 @@ import type { } from 'n8n-workflow'; import { ExecutionRepository } from '@/databases/repositories/execution.repository'; -import { ModuleRegistry } from '@/decorators/module'; import { EventService } from '@/events/event.service'; import { ExternalHooks } from '@/external-hooks'; import { Push } from '@/push'; diff --git a/packages/cli/src/executions/executions.controller.ts b/packages/cli/src/executions/executions.controller.ts index 158242eb29..057ad847bc 100644 --- a/packages/cli/src/executions/executions.controller.ts +++ b/packages/cli/src/executions/executions.controller.ts @@ -1,7 +1,7 @@ +import { Get, Patch, Post, RestController } from '@n8n/decorators'; import type { Scope } from '@n8n/permissions'; import type { User } from '@/databases/entities/user'; -import { Get, Patch, Post, RestController } from '@/decorators'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { NotFoundError } from '@/errors/response-errors/not-found.error'; import { License } from '@/license'; diff --git a/packages/cli/src/external-secrets.ee/external-secrets-manager.ee.ts b/packages/cli/src/external-secrets.ee/external-secrets-manager.ee.ts index 6d4589d939..84e6943cb6 100644 --- a/packages/cli/src/external-secrets.ee/external-secrets-manager.ee.ts +++ b/packages/cli/src/external-secrets.ee/external-secrets-manager.ee.ts @@ -1,9 +1,9 @@ +import { OnShutdown } from '@n8n/decorators'; import { Service } from '@n8n/di'; import { Cipher, Logger } from 'n8n-core'; import { jsonParse, type IDataObject, ensureError, UnexpectedError } from 'n8n-workflow'; import { SettingsRepository } from '@/databases/repositories/settings.repository'; -import { OnShutdown } from '@/decorators/on-shutdown'; import { EventService } from '@/events/event.service'; import { License } from '@/license'; import { Publisher } from '@/scaling/pubsub/publisher.service'; diff --git a/packages/cli/src/external-secrets.ee/external-secrets.controller.ee.ts b/packages/cli/src/external-secrets.ee/external-secrets.controller.ee.ts index 5b17e8f336..99d0e3fa90 100644 --- a/packages/cli/src/external-secrets.ee/external-secrets.controller.ee.ts +++ b/packages/cli/src/external-secrets.ee/external-secrets.controller.ee.ts @@ -1,6 +1,6 @@ +import { Get, Post, RestController, GlobalScope, Middleware } from '@n8n/decorators'; import { Request, Response, NextFunction } from 'express'; -import { Get, Post, RestController, GlobalScope, Middleware } from '@/decorators'; import { NotFoundError } from '@/errors/response-errors/not-found.error'; import { ExternalSecretsProviders } from './external-secrets-providers.ee'; diff --git a/packages/cli/src/ldap.ee/ldap.controller.ee.ts b/packages/cli/src/ldap.ee/ldap.controller.ee.ts index c951b6d41d..473f9c9d6b 100644 --- a/packages/cli/src/ldap.ee/ldap.controller.ee.ts +++ b/packages/cli/src/ldap.ee/ldap.controller.ee.ts @@ -1,6 +1,6 @@ +import { Get, Post, Put, RestController, GlobalScope } from '@n8n/decorators'; import pick from 'lodash/pick'; -import { Get, Post, Put, RestController, GlobalScope } from '@/decorators'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { EventService } from '@/events/event.service'; diff --git a/packages/cli/src/license.ts b/packages/cli/src/license.ts index 750caad5f4..c5583f091c 100644 --- a/packages/cli/src/license.ts +++ b/packages/cli/src/license.ts @@ -6,6 +6,7 @@ import { type BooleanLicenseFeature, type NumericLicenseFeature, } from '@n8n/constants'; +import { OnShutdown } from '@n8n/decorators'; import { Container, Service } from '@n8n/di'; import type { TEntitlement, TFeatures, TLicenseBlock } from '@n8n_io/license-sdk'; import { LicenseManager } from '@n8n_io/license-sdk'; @@ -13,7 +14,6 @@ import { InstanceSettings, Logger } from 'n8n-core'; import config from '@/config'; import { SettingsRepository } from '@/databases/repositories/settings.repository'; -import { OnShutdown } from '@/decorators/on-shutdown'; import { LicenseMetricsService } from '@/metrics/license-metrics.service'; import { N8N_VERSION, SETTINGS_LICENSE_CERT_KEY, Time } from './constants'; diff --git a/packages/cli/src/license/license.controller.ts b/packages/cli/src/license/license.controller.ts index 3c284a25cb..204d0c2ff0 100644 --- a/packages/cli/src/license/license.controller.ts +++ b/packages/cli/src/license/license.controller.ts @@ -1,8 +1,8 @@ import { CommunityRegisteredRequestDto } from '@n8n/api-types'; +import { Get, Post, RestController, GlobalScope, Body } from '@n8n/decorators'; import type { AxiosError } from 'axios'; import { InstanceSettings } from 'n8n-core'; -import { Get, Post, RestController, GlobalScope, Body } from '@/decorators'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { AuthenticatedRequest, LicenseRequest } from '@/requests'; import { UrlService } from '@/services/url.service'; diff --git a/packages/cli/src/modules/insights/insights.controller.ts b/packages/cli/src/modules/insights/insights.controller.ts index a808fad3d4..7404e2ec82 100644 --- a/packages/cli/src/modules/insights/insights.controller.ts +++ b/packages/cli/src/modules/insights/insights.controller.ts @@ -1,7 +1,7 @@ import { ListInsightsWorkflowQueryDto } from '@n8n/api-types'; import type { InsightsSummary, InsightsByTime, InsightsByWorkflow } from '@n8n/api-types'; +import { Get, GlobalScope, Licensed, Query, RestController } from '@n8n/decorators'; -import { Get, GlobalScope, Licensed, Query, RestController } from '@/decorators'; import { AuthenticatedRequest } from '@/requests'; import { InsightsService } from './insights.service'; diff --git a/packages/cli/src/modules/insights/insights.module.ts b/packages/cli/src/modules/insights/insights.module.ts index e751e15383..e2fb716596 100644 --- a/packages/cli/src/modules/insights/insights.module.ts +++ b/packages/cli/src/modules/insights/insights.module.ts @@ -1,8 +1,8 @@ +import type { BaseN8nModule } from '@n8n/decorators'; +import { N8nModule } from '@n8n/decorators'; import type { ExecutionLifecycleHooks } from 'n8n-core'; import { InstanceSettings, Logger } from 'n8n-core'; -import type { BaseN8nModule } from '@/decorators/module'; -import { N8nModule } from '@/decorators/module'; import type { MultiMainSetup } from '@/scaling/multi-main-setup.ee'; import { InsightsService } from './insights.service'; diff --git a/packages/cli/src/modules/insights/insights.service.ts b/packages/cli/src/modules/insights/insights.service.ts index 4da3d9c3e0..6b017143bd 100644 --- a/packages/cli/src/modules/insights/insights.service.ts +++ b/packages/cli/src/modules/insights/insights.service.ts @@ -1,11 +1,11 @@ import type { InsightsSummary } from '@n8n/api-types'; import type { InsightsDateRange } from '@n8n/api-types/src/schemas/insights.schema'; +import { OnShutdown } from '@n8n/decorators'; import { Service } from '@n8n/di'; import { Logger } from 'n8n-core'; import type { ExecutionLifecycleHooks } from 'n8n-core'; import type { IRun } from 'n8n-workflow'; -import { OnShutdown } from '@/decorators/on-shutdown'; import { License } from '@/license'; import type { PeriodUnit, TypeUnit } from './database/entities/insights-shared'; diff --git a/packages/cli/src/push/index.ts b/packages/cli/src/push/index.ts index af34de80ad..8056c68402 100644 --- a/packages/cli/src/push/index.ts +++ b/packages/cli/src/push/index.ts @@ -1,4 +1,5 @@ import type { PushMessage } from '@n8n/api-types'; +import { OnShutdown } from '@n8n/decorators'; import { Container, Service } from '@n8n/di'; import type { Application } from 'express'; import { ServerResponse } from 'http'; @@ -11,7 +12,6 @@ import { Server as WSServer } from 'ws'; import { AuthService } from '@/auth/auth.service'; import { inProduction, TRIMMED_TASK_DATA_CONNECTIONS } from '@/constants'; import type { User } from '@/databases/entities/user'; -import { OnShutdown } from '@/decorators/on-shutdown'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { Publisher } from '@/scaling/pubsub/publisher.service'; import { TypedEmitter } from '@/typed-emitter'; diff --git a/packages/cli/src/scaling/scaling.service.ts b/packages/cli/src/scaling/scaling.service.ts index ea9d2fe447..ac14b11655 100644 --- a/packages/cli/src/scaling/scaling.service.ts +++ b/packages/cli/src/scaling/scaling.service.ts @@ -1,4 +1,5 @@ import { GlobalConfig } from '@n8n/config'; +import { OnShutdown } from '@n8n/decorators'; import { Container, Service } from '@n8n/di'; import { ErrorReporter, InstanceSettings, isObjectLiteral, Logger } from 'n8n-core'; import { @@ -16,7 +17,6 @@ import { ActiveExecutions } from '@/active-executions'; import config from '@/config'; import { HIGHEST_SHUTDOWN_PRIORITY, Time } from '@/constants'; import { ExecutionRepository } from '@/databases/repositories/execution.repository'; -import { OnShutdown } from '@/decorators/on-shutdown'; import { EventService } from '@/events/event.service'; import { OrchestrationService } from '@/services/orchestration.service'; import { assertNever } from '@/utils'; diff --git a/packages/cli/src/server.ts b/packages/cli/src/server.ts index c9f688a160..dd923fada2 100644 --- a/packages/cli/src/server.ts +++ b/packages/cli/src/server.ts @@ -20,8 +20,8 @@ import { N8N_VERSION, Time, } from '@/constants'; +import { ControllerRegistry } from '@/controller.registry'; import { CredentialsOverwrites } from '@/credentials-overwrites'; -import { ControllerRegistry } from '@/decorators'; import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus'; import { EventService } from '@/events/event.service'; import { LogStreamingEventRelay } from '@/events/relays/log-streaming.event-relay'; diff --git a/packages/cli/src/services/pruning/pruning.service.ts b/packages/cli/src/services/pruning/pruning.service.ts index 749c2d9d45..ebac8ec462 100644 --- a/packages/cli/src/services/pruning/pruning.service.ts +++ b/packages/cli/src/services/pruning/pruning.service.ts @@ -1,4 +1,5 @@ import { ExecutionsConfig } from '@n8n/config'; +import { OnShutdown } from '@n8n/decorators'; import { Service } from '@n8n/di'; import { BinaryDataService, InstanceSettings, Logger } from 'n8n-core'; import { ensureError } from 'n8n-workflow'; @@ -7,7 +8,6 @@ import { strict } from 'node:assert'; import { Time } from '@/constants'; import { ExecutionRepository } from '@/databases/repositories/execution.repository'; import { connectionState as dbConnectionState } from '@/db'; -import { OnShutdown } from '@/decorators/on-shutdown'; import { OrchestrationService } from '../orchestration.service'; diff --git a/packages/cli/src/services/redis-client.service.ts b/packages/cli/src/services/redis-client.service.ts index e31d55fb6c..f97ab70317 100644 --- a/packages/cli/src/services/redis-client.service.ts +++ b/packages/cli/src/services/redis-client.service.ts @@ -1,10 +1,10 @@ import { GlobalConfig } from '@n8n/config'; +import { Debounce } from '@n8n/decorators'; import { Service } from '@n8n/di'; import ioRedis from 'ioredis'; import type { Cluster, RedisOptions } from 'ioredis'; import { Logger } from 'n8n-core'; -import { Debounce } from '@/decorators/debounce'; import { TypedEmitter } from '@/typed-emitter'; import type { RedisClientType } from '../scaling/redis/redis.types'; diff --git a/packages/cli/src/shutdown/__tests__/shutdown.service.test.ts b/packages/cli/src/shutdown/__tests__/shutdown.service.test.ts index 40f727183a..adbf9841b3 100644 --- a/packages/cli/src/shutdown/__tests__/shutdown.service.test.ts +++ b/packages/cli/src/shutdown/__tests__/shutdown.service.test.ts @@ -1,10 +1,11 @@ +import { ShutdownRegistryMetadata } from '@n8n/decorators'; +import type { ServiceClass } from '@n8n/decorators/src/types'; import { Container } from '@n8n/di'; import { mock } from 'jest-mock-extended'; import type { ErrorReporter } from 'n8n-core'; import { UnexpectedError } from 'n8n-workflow'; -import type { ServiceClass } from '@/shutdown/shutdown.service'; -import { ShutdownService } from '@/shutdown/shutdown.service'; +import { ShutdownService } from '../shutdown.service'; class MockComponent { onShutdown() {} @@ -15,9 +16,11 @@ describe('ShutdownService', () => { let mockComponent: MockComponent; let onShutdownSpy: jest.SpyInstance; const errorReporter = mock(); + const shutdownRegistryMetadata = Container.get(ShutdownRegistryMetadata); beforeEach(() => { - shutdownService = new ShutdownService(mock(), errorReporter); + shutdownRegistryMetadata.clear(); + shutdownService = new ShutdownService(mock(), errorReporter, shutdownRegistryMetadata); mockComponent = new MockComponent(); Container.set(MockComponent, mockComponent); onShutdownSpy = jest.spyOn(mockComponent, 'onShutdown'); diff --git a/packages/cli/src/shutdown/shutdown.service.ts b/packages/cli/src/shutdown/shutdown.service.ts index aff01b0875..35c738dcfb 100644 --- a/packages/cli/src/shutdown/shutdown.service.ts +++ b/packages/cli/src/shutdown/shutdown.service.ts @@ -1,18 +1,9 @@ +import type { ShutdownHandler } from '@n8n/decorators'; +import { ShutdownRegistryMetadata } from '@n8n/decorators'; import { Container, Service } from '@n8n/di'; -import { type Class, ErrorReporter } from 'n8n-core'; -import { Logger } from 'n8n-core'; +import { ErrorReporter, Logger } from 'n8n-core'; import { assert, UnexpectedError, UserError } from 'n8n-workflow'; -import { LOWEST_SHUTDOWN_PRIORITY, HIGHEST_SHUTDOWN_PRIORITY } from '@/constants'; - -type HandlerFn = () => Promise | void; -export type ServiceClass = Class>; - -export interface ShutdownHandler { - serviceClass: ServiceClass; - methodName: string; -} - /** Error reported when a listener fails to shutdown gracefully */ export class ComponentShutdownError extends UnexpectedError { constructor(componentName: string, cause: Error) { @@ -26,33 +17,22 @@ export class ComponentShutdownError extends UnexpectedError { /** Service responsible for orchestrating a graceful shutdown of the application */ @Service() export class ShutdownService { - private readonly handlersByPriority: ShutdownHandler[][] = []; - private shutdownPromise: Promise | undefined; constructor( private readonly logger: Logger, private readonly errorReporter: ErrorReporter, + private readonly shutdownRegistry: ShutdownRegistryMetadata, ) {} /** Registers given listener to be notified when the application is shutting down */ register(priority: number, handler: ShutdownHandler) { - if (priority < LOWEST_SHUTDOWN_PRIORITY || priority > HIGHEST_SHUTDOWN_PRIORITY) { - throw new UserError( - `Invalid shutdown priority. Please set it between ${LOWEST_SHUTDOWN_PRIORITY} and ${HIGHEST_SHUTDOWN_PRIORITY}.`, - { extra: { priority } }, - ); - } - - if (!this.handlersByPriority[priority]) { - this.handlersByPriority[priority] = []; - } - this.handlersByPriority[priority].push(handler); + this.shutdownRegistry.register(priority, handler); } /** Validates that all the registered shutdown handlers are properly configured */ validate() { - const handlers = this.handlersByPriority.flat(); + const handlers = this.shutdownRegistry.getHandlersByPriority().flat(); for (const { serviceClass, methodName } of handlers) { if (!Container.has(serviceClass)) { @@ -93,7 +73,8 @@ export class ShutdownService { } private async startShutdown() { - const handlers = Object.values(this.handlersByPriority).reverse(); + const handlers = Object.values(this.shutdownRegistry.getHandlersByPriority()).reverse(); + for (const handlerGroup of handlers) { await Promise.allSettled( handlerGroup.map(async (handler) => await this.shutdownComponent(handler)), diff --git a/packages/cli/src/sso.ee/saml/routes/saml.controller.ee.ts b/packages/cli/src/sso.ee/saml/routes/saml.controller.ee.ts index c8f636eec4..5de4c07122 100644 --- a/packages/cli/src/sso.ee/saml/routes/saml.controller.ee.ts +++ b/packages/cli/src/sso.ee/saml/routes/saml.controller.ee.ts @@ -1,11 +1,11 @@ import { SamlAcsDto, SamlPreferences, SamlToggleDto } from '@n8n/api-types'; +import { Get, Post, RestController, GlobalScope, Body } from '@n8n/decorators'; import { Response } from 'express'; import querystring from 'querystring'; import type { PostBindingContext } from 'samlify/types/src/entity'; import url from 'url'; import { AuthService } from '@/auth/auth.service'; -import { Get, Post, RestController, GlobalScope, Body } from '@/decorators'; import { AuthError } from '@/errors/response-errors/auth.error'; import { EventService } from '@/events/event.service'; import { AuthenticatedRequest, AuthlessRequest } from '@/requests'; diff --git a/packages/cli/src/task-runners/task-runner-module.ts b/packages/cli/src/task-runners/task-runner-module.ts index 0ba60123f9..535eceb649 100644 --- a/packages/cli/src/task-runners/task-runner-module.ts +++ b/packages/cli/src/task-runners/task-runner-module.ts @@ -1,10 +1,10 @@ import { TaskRunnersConfig } from '@n8n/config'; +import { OnShutdown } from '@n8n/decorators'; import { Container, Service } from '@n8n/di'; import { ErrorReporter, Logger } from 'n8n-core'; import { sleep } from 'n8n-workflow'; import * as a from 'node:assert/strict'; -import { OnShutdown } from '@/decorators/on-shutdown'; import type { TaskRunnerRestartLoopError } from '@/task-runners/errors/task-runner-restart-loop-error'; import { TaskBrokerWsServer } from '@/task-runners/task-broker/task-broker-ws-server'; import type { TaskRunnerProcess } from '@/task-runners/task-runner-process'; diff --git a/packages/cli/src/task-runners/task-runner-process.ts b/packages/cli/src/task-runners/task-runner-process.ts index b625056e41..b51fbdf1e2 100644 --- a/packages/cli/src/task-runners/task-runner-process.ts +++ b/packages/cli/src/task-runners/task-runner-process.ts @@ -1,12 +1,11 @@ import { TaskRunnersConfig } from '@n8n/config'; +import { OnShutdown } from '@n8n/decorators'; import { Service } from '@n8n/di'; import { Logger } from 'n8n-core'; import * as a from 'node:assert/strict'; import { spawn } from 'node:child_process'; import * as process from 'node:process'; -import { OnShutdown } from '@/decorators/on-shutdown'; - import { forwardToLogger } from './forward-to-logger'; import { NodeProcessOomDetector } from './node-process-oom-detector'; import { TaskBrokerAuthService } from './task-broker/auth/task-broker-auth.service'; diff --git a/packages/cli/src/telemetry/index.ts b/packages/cli/src/telemetry/index.ts index 5b16656713..b74e30a3bc 100644 --- a/packages/cli/src/telemetry/index.ts +++ b/packages/cli/src/telemetry/index.ts @@ -1,4 +1,5 @@ import { GlobalConfig } from '@n8n/config'; +import { OnShutdown } from '@n8n/decorators'; import { Container, Service } from '@n8n/di'; import type RudderStack from '@rudderstack/rudder-sdk-node'; import axios from 'axios'; @@ -10,7 +11,6 @@ import { ProjectRelationRepository } from '@/databases/repositories/project-rela import { ProjectRepository } from '@/databases/repositories/project.repository'; import { UserRepository } from '@/databases/repositories/user.repository'; import { WorkflowRepository } from '@/databases/repositories/workflow.repository'; -import { OnShutdown } from '@/decorators/on-shutdown'; import type { IExecutionTrackProperties } from '@/interfaces'; import { License } from '@/license'; import { PostHogClient } from '@/posthog'; diff --git a/packages/cli/src/workflows/workflow-history.ee/workflow-history.controller.ee.ts b/packages/cli/src/workflows/workflow-history.ee/workflow-history.controller.ee.ts index cbd392caaa..a52c0adb77 100644 --- a/packages/cli/src/workflows/workflow-history.ee/workflow-history.controller.ee.ts +++ b/packages/cli/src/workflows/workflow-history.ee/workflow-history.controller.ee.ts @@ -1,7 +1,7 @@ import { PaginationDto } from '@n8n/api-types'; +import { RestController, Get, Middleware, Query } from '@n8n/decorators'; import { Request, Response, NextFunction } from 'express'; -import { RestController, Get, Middleware, Query } from '@/decorators'; import { NotFoundError } from '@/errors/response-errors/not-found.error'; import { SharedWorkflowNotFoundError } from '@/errors/shared-workflow-not-found.error'; import { WorkflowHistoryVersionNotFoundError } from '@/errors/workflow-history-version-not-found.error'; diff --git a/packages/cli/src/workflows/workflows.controller.ts b/packages/cli/src/workflows/workflows.controller.ts index 5a6eb9cc9f..d1d8df10fc 100644 --- a/packages/cli/src/workflows/workflows.controller.ts +++ b/packages/cli/src/workflows/workflows.controller.ts @@ -4,6 +4,19 @@ import { TransferWorkflowBodyDto, } from '@n8n/api-types'; import { GlobalConfig } from '@n8n/config'; +import { + Body, + Delete, + Get, + Licensed, + Param, + Patch, + Post, + ProjectScope, + Put, + Query, + RestController, +} from '@n8n/decorators'; // eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import import { In, type FindOptionsRelations } from '@n8n/typeorm'; import axios from 'axios'; @@ -21,19 +34,6 @@ import { SharedWorkflowRepository } from '@/databases/repositories/shared-workfl import { TagRepository } from '@/databases/repositories/tag.repository'; import { WorkflowRepository } from '@/databases/repositories/workflow.repository'; import * as Db from '@/db'; -import { - Body, - Delete, - Get, - Licensed, - Param, - Patch, - Post, - ProjectScope, - Put, - Query, - RestController, -} from '@/decorators'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { ForbiddenError } from '@/errors/response-errors/forbidden.error'; import { InternalServerError } from '@/errors/response-errors/internal-server.error'; diff --git a/packages/cli/test/integration/shared/utils/test-server.ts b/packages/cli/test/integration/shared/utils/test-server.ts index 3b8bc7decd..7a2d203b44 100644 --- a/packages/cli/test/integration/shared/utils/test-server.ts +++ b/packages/cli/test/integration/shared/utils/test-server.ts @@ -8,8 +8,8 @@ import { URL } from 'url'; import { AuthService } from '@/auth/auth.service'; import config from '@/config'; import { AUTH_COOKIE_NAME } from '@/constants'; +import { ControllerRegistry } from '@/controller.registry'; import type { User } from '@/databases/entities/user'; -import { ControllerRegistry } from '@/decorators'; import { License } from '@/license'; import { rawBodyReader, bodyParser } from '@/middlewares'; import { PostHogClient } from '@/posthog'; diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index ae721d276f..1d976ae14b 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -27,6 +27,7 @@ { "path": "../@n8n/client-oauth2/tsconfig.build.json" }, { "path": "../@n8n/config/tsconfig.build.json" }, { "path": "../@n8n/constants/tsconfig.build.json" }, + { "path": "../@n8n/decorators/tsconfig.build.json" }, { "path": "../@n8n/di/tsconfig.build.json" }, { "path": "../@n8n/nodes-langchain/tsconfig.build.json" }, { "path": "../@n8n/permissions/tsconfig.build.json" } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 0a620468bf..08bf331a82 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -4,7 +4,6 @@ export * from './binary-data'; export * from './constants'; export * from './credentials'; export * from './data-deduplication-service'; -export * from './decorators'; export * from './encryption'; export * from './errors'; export * from './execution-engine'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 26a0c7de1b..773fdbe539 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -419,6 +419,34 @@ importers: specifier: workspace:* version: link:../typescript-config + packages/@n8n/decorators: + dependencies: + '@n8n/constants': + specifier: workspace:^ + version: link:../constants + '@n8n/di': + specifier: workspace:^ + version: link:../di + '@n8n/permissions': + specifier: workspace:^ + version: link:../permissions + lodash: + specifier: 'catalog:' + version: 4.17.21 + n8n-workflow: + specifier: workspace:^ + version: link:../../workflow + devDependencies: + '@n8n/typescript-config': + specifier: workspace:* + version: link:../typescript-config + '@types/express': + specifier: 'catalog:' + version: 5.0.1 + '@types/lodash': + specifier: 'catalog:' + version: 4.14.195 + packages/@n8n/di: dependencies: reflect-metadata: @@ -932,6 +960,9 @@ importers: '@n8n/constants': specifier: workspace:^ version: link:../@n8n/constants + '@n8n/decorators': + specifier: workspace:* + version: link:../@n8n/decorators '@n8n/di': specifier: workspace:* version: link:../@n8n/di diff --git a/turbo.json b/turbo.json index 7c1ab52538..43543633e9 100644 --- a/turbo.json +++ b/turbo.json @@ -28,6 +28,7 @@ "dependsOn": [ "@n8n/api-types#lint", "@n8n/config#lint", + "@n8n/decorators#lint", "@n8n/constants#lint", "@n8n/di#lint", "@n8n/client-oauth2#lint", @@ -64,6 +65,7 @@ "dependsOn": [ "@n8n/api-types#test", "@n8n/config#test", + "@n8n/decorators#test", "@n8n/di#test", "@n8n/client-oauth2#test", "@n8n/imap#test",