mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-17 05:29:44 +02:00
refactor(core): Extract Quick Connect into a backend package (#41076)
This commit is contained in:
parent
898701b05b
commit
76c43094cd
2
OWNERS
2
OWNERS
|
|
@ -120,7 +120,7 @@ packages/cli/src/chat/ @n8n-io/ai
|
|||
packages/cli/src/tool-generation/ @n8n-io/ai
|
||||
packages/cli/src/modules/workflow-builder/ @n8n-io/ai-assistant
|
||||
packages/cli/src/modules/mcp/ @n8n-io/context
|
||||
packages/cli/src/modules/quick-connect/ @n8n-io/ai
|
||||
packages/modules/quick-connect/ @n8n-io/ai
|
||||
packages/cli/src/modules/chat-hub/ @n8n-io/ai-assistant
|
||||
packages/cli/src/modules/instance-ai/ @n8n-io/ai-assistant
|
||||
|
||||
|
|
|
|||
|
|
@ -160,6 +160,7 @@
|
|||
"@n8n/api-types": "workspace:*",
|
||||
"@n8n/backend-common": "workspace:*",
|
||||
"@n8n/backend-module-insights": "workspace:*",
|
||||
"@n8n/backend-module-quick-connect": "workspace:*",
|
||||
"@n8n/backend-network": "workspace:*",
|
||||
"@n8n/backend-services": "workspace:*",
|
||||
"@n8n/blob-storage": "workspace:*",
|
||||
|
|
|
|||
23
packages/cli/src/modules/__tests__/modules.manifest.test.ts
Normal file
23
packages/cli/src/modules/__tests__/modules.manifest.test.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { ControllerRegistryMetadata, ModuleMetadata } from '@n8n/decorators';
|
||||
import { Container } from '@n8n/di';
|
||||
|
||||
describe('packaged module manifest', () => {
|
||||
it('loads the Quick Connect package only when requested', async () => {
|
||||
const metadata = Container.get(ModuleMetadata);
|
||||
const controllers = Container.get(ControllerRegistryMetadata);
|
||||
expect(metadata.get('quick-connect')).toBeUndefined();
|
||||
|
||||
const { packagedModules } = await import('../modules.manifest.js');
|
||||
|
||||
expect(metadata.get('quick-connect')).toBeUndefined();
|
||||
expect([...controllers.controllerClasses]).toEqual([]);
|
||||
const loadQuickConnect = packagedModules['quick-connect'];
|
||||
expect(loadQuickConnect).toBeTypeOf('function');
|
||||
const entrypoint = await loadQuickConnect?.();
|
||||
const packagedEntrypoint = await import('@n8n/backend-module-quick-connect/module');
|
||||
|
||||
expect(entrypoint).toBe(packagedEntrypoint);
|
||||
expect(metadata.get('quick-connect')).toEqual({ class: packagedEntrypoint.QuickConnectModule });
|
||||
expect([...controllers.controllerClasses]).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
|
@ -6,4 +6,5 @@ import type { PackagedModules } from '@n8n/backend-common';
|
|||
*/
|
||||
export const packagedModules: PackagedModules = {
|
||||
insights: async () => await import('@n8n/backend-module-insights/module'),
|
||||
'quick-connect': async () => await import('@n8n/backend-module-quick-connect/module'),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@
|
|||
{ "path": "../@n8n/backend-common/tsconfig.build.json" },
|
||||
{ "path": "../@n8n/backend-services/tsconfig.build.json" },
|
||||
{ "path": "../modules/insights/backend/tsconfig.build.json" },
|
||||
{ "path": "../modules/quick-connect/backend/tsconfig.build.json" },
|
||||
{ "path": "../@n8n/backend-test-utils/tsconfig.build.json" },
|
||||
{ "path": "../@n8n/blob-storage/tsconfig.build.json" },
|
||||
{ "path": "../@n8n/di/tsconfig.build.json" },
|
||||
|
|
|
|||
25
packages/modules/quick-connect/backend/README.md
Normal file
25
packages/modules/quick-connect/backend/README.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# @n8n/backend-module-quick-connect
|
||||
|
||||
Backend feature module. The n8n runtime loads the module through the lazy import in
|
||||
`packages/cli/src/modules/modules.manifest.ts`.
|
||||
|
||||
```bash
|
||||
pnpm turbo typecheck --filter=@n8n/backend-module-quick-connect
|
||||
pnpm turbo lint --filter=@n8n/backend-module-quick-connect
|
||||
pnpm turbo test --filter=@n8n/backend-module-quick-connect
|
||||
```
|
||||
|
||||
Go through Turbo. It builds this package's workspace dependencies first.
|
||||
|
||||
Read `scripts/backend-module/backend-module-guide.md` for the module lifecycle, REST controller,
|
||||
service, repository, entity, configuration, and testing patterns.
|
||||
|
||||
## Import rules
|
||||
|
||||
- Do not import from `packages/cli` or from the `n8n` package.
|
||||
- Declare each workspace and third-party import in this package.
|
||||
- Export APIs for other packages from `src/index.ts`.
|
||||
- Keep the module entrypoint in the `./module` export. Do not export it from `src/index.ts`.
|
||||
Importing the entrypoint runs the `@BackendModule` decorator.
|
||||
- Use dynamic imports inside the module entrypoint. This keeps disabled modules unloaded.
|
||||
- Keep TypeORM imports in entity and repository files.
|
||||
8
packages/modules/quick-connect/backend/oxlint.config.mts
Normal file
8
packages/modules/quick-connect/backend/oxlint.config.mts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { backendConfig } from '@n8n/oxlint-config/backend';
|
||||
import { defineConfig } from 'oxlint';
|
||||
|
||||
export default defineConfig({
|
||||
extends: [backendConfig],
|
||||
options: { typeAware: true },
|
||||
ignorePatterns: ['coverage/**', 'dist/**'],
|
||||
});
|
||||
74
packages/modules/quick-connect/backend/package.json
Normal file
74
packages/modules/quick-connect/backend/package.json
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
{
|
||||
"name": "@n8n/backend-module-quick-connect",
|
||||
"version": "0.1.0",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.js"
|
||||
},
|
||||
"./module": {
|
||||
"types": "./dist/quick-connect.module.d.ts",
|
||||
"import": "./dist/quick-connect.module.js",
|
||||
"require": "./dist/quick-connect.module.js"
|
||||
}
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"module": [
|
||||
"dist/quick-connect.module.d.ts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*"
|
||||
],
|
||||
"scripts": {
|
||||
"clean": "rimraf dist .turbo",
|
||||
"dev": "pnpm watch",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"build": "tsc -p tsconfig.build.json",
|
||||
"build:unchecked": "tsc -p tsconfig.build.json --noCheck",
|
||||
"format": "biome format --write .",
|
||||
"format:check": "biome ci .",
|
||||
"lint": "oxlint --type-aware . --quiet",
|
||||
"lint:fix": "oxlint --type-aware . --fix",
|
||||
"watch": "tsc -p tsconfig.build.json --watch",
|
||||
"test": "vitest run",
|
||||
"test:unit": "vitest run",
|
||||
"test:dev": "vitest --silent=false"
|
||||
},
|
||||
"dependencies": {
|
||||
"@n8n/api-types": "workspace:*",
|
||||
"@n8n/backend-common": "workspace:*",
|
||||
"@n8n/backend-network": "workspace:*",
|
||||
"@n8n/config": "workspace:*",
|
||||
"@n8n/constants": "workspace:*",
|
||||
"@n8n/db": "workspace:*",
|
||||
"@n8n/decorators": "workspace:*",
|
||||
"@n8n/di": "workspace:*",
|
||||
"@n8n/errors": "workspace:*",
|
||||
"n8n-workflow": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@n8n/oxlint-config": "workspace:*",
|
||||
"@n8n/typescript-config": "workspace:*",
|
||||
"@n8n/vitest-config": "workspace:*",
|
||||
"@vitest/coverage-v8": "catalog:",
|
||||
"nock": "catalog:",
|
||||
"oxlint": "catalog:",
|
||||
"oxlint-tsgolint": "catalog:",
|
||||
"typescript": "catalog:typescript",
|
||||
"vite": "catalog:",
|
||||
"vitest": "catalog:",
|
||||
"vitest-mock-extended": "catalog:",
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"license": "LicenseRef-n8n-sustainable-use"
|
||||
}
|
||||
41
packages/modules/quick-connect/backend/src/index.test.ts
Normal file
41
packages/modules/quick-connect/backend/src/index.test.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import { ControllerRegistryMetadata, ModuleMetadata } from '@n8n/decorators';
|
||||
import { Container } from '@n8n/di';
|
||||
|
||||
describe('package entrypoints', () => {
|
||||
it('loads registration only through the module entrypoint', async () => {
|
||||
const moduleMetadata = Container.get(ModuleMetadata);
|
||||
const controllerMetadata = Container.get(ControllerRegistryMetadata);
|
||||
|
||||
expect(moduleMetadata.get('quick-connect')).toBeUndefined();
|
||||
expect([...controllerMetadata.controllerClasses]).toEqual([]);
|
||||
|
||||
const publicApi = await import('./index.js');
|
||||
|
||||
expect(Object.keys(publicApi)).toEqual([]);
|
||||
expect(moduleMetadata.get('quick-connect')).toBeUndefined();
|
||||
expect([...controllerMetadata.controllerClasses]).toEqual([]);
|
||||
|
||||
const { QuickConnectModule } = await import('./quick-connect.module.js');
|
||||
|
||||
expect(moduleMetadata.get('quick-connect')).toEqual({ class: QuickConnectModule });
|
||||
expect([...controllerMetadata.controllerClasses]).toEqual([]);
|
||||
|
||||
const { QuickConnectService } = await import('./quick-connect.service.js');
|
||||
const registerHandlers = vi
|
||||
.spyOn(QuickConnectService.prototype, 'registerHandlers')
|
||||
.mockImplementation(async () => {
|
||||
expect([...controllerMetadata.controllerClasses]).toEqual([]);
|
||||
});
|
||||
|
||||
await new QuickConnectModule().init();
|
||||
|
||||
expect(registerHandlers).toHaveBeenCalledOnce();
|
||||
const { QuickConnectController } = await import('./quick-connect.controller.js');
|
||||
expect([...controllerMetadata.controllerClasses]).toEqual([QuickConnectController]);
|
||||
for (const controller of controllerMetadata.controllerClasses) {
|
||||
const metadata = controllerMetadata.getControllerMetadata(controller);
|
||||
expect(metadata.basePath).toBe('/quick-connect');
|
||||
expect(metadata.routes.get('getCredentialData')).toMatchObject({ method: 'post', path: '/' });
|
||||
}
|
||||
});
|
||||
});
|
||||
1
packages/modules/quick-connect/backend/src/index.ts
Normal file
1
packages/modules/quick-connect/backend/src/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export {};
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
|
||||
process.env.N8N_ENCRYPTION_KEY = 'test_key';
|
||||
|
||||
const baseDir = join(tmpdir(), 'n8n-quick-connect-tests');
|
||||
mkdirSync(baseDir, { recursive: true });
|
||||
|
||||
const testDir = mkdtempSync(join(baseDir, 'run-'));
|
||||
const settingsDir = join(testDir, '.n8n');
|
||||
mkdirSync(settingsDir);
|
||||
process.env.N8N_USER_FOLDER = testDir;
|
||||
process.env.N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS = 'true';
|
||||
|
||||
writeFileSync(join(settingsDir, 'config'), JSON.stringify({ encryptionKey: 'test_key' }), {
|
||||
encoding: 'utf-8',
|
||||
mode: 0o600,
|
||||
});
|
||||
10
packages/modules/quick-connect/backend/tsconfig.build.json
Normal file
10
packages/modules/quick-connect/backend/tsconfig.build.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"extends": ["./tsconfig.json", "@n8n/typescript-config/tsconfig.build.go.json"],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"rootDir": "src",
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["src/**/__tests__/**", "src/**/*.test.ts"]
|
||||
}
|
||||
17
packages/modules/quick-connect/backend/tsconfig.json
Normal file
17
packages/modules/quick-connect/backend/tsconfig.json
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"extends": [
|
||||
"@n8n/typescript-config/tsconfig.common.go.json",
|
||||
"@n8n/typescript-config/tsconfig.backend.go.json"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"lib": ["es2023", "dom"],
|
||||
"types": ["node", "vitest/globals"],
|
||||
"strict": true,
|
||||
"strictFunctionTypes": false,
|
||||
"strictPropertyInitialization": false,
|
||||
"useUnknownInCatchVariables": false
|
||||
},
|
||||
"include": ["src/**/*.ts", "test/**/*.ts", "vitest.config.ts"]
|
||||
}
|
||||
18
packages/modules/quick-connect/backend/vitest.config.ts
Normal file
18
packages/modules/quick-connect/backend/vitest.config.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import { createVitestConfigWithDecorators } from '@n8n/vitest-config/node-decorators';
|
||||
import { tscDecoratorTransform } from '@n8n/vitest-config/tsc-decorator-transform';
|
||||
import { mergeConfig } from 'vite';
|
||||
import { configDefaults } from 'vitest/config';
|
||||
|
||||
export default mergeConfig(
|
||||
createVitestConfigWithDecorators({}, { pinCjs: ['@n8n/backend-common', '@n8n/decorators'] }),
|
||||
{
|
||||
plugins: [
|
||||
tscDecoratorTransform({ filePredicate: (fileName) => /\.config\.ts$/.test(fileName) }),
|
||||
],
|
||||
test: {
|
||||
pool: 'forks',
|
||||
setupFiles: ['./test/setup-test-folder.ts'],
|
||||
exclude: [...configDefaults.exclude, '**/dist/**'],
|
||||
},
|
||||
},
|
||||
);
|
||||
|
|
@ -4823,6 +4823,9 @@ importers:
|
|||
'@n8n/backend-module-insights':
|
||||
specifier: workspace:*
|
||||
version: link:../modules/insights/backend
|
||||
'@n8n/backend-module-quick-connect':
|
||||
specifier: workspace:*
|
||||
version: link:../modules/quick-connect/backend
|
||||
'@n8n/backend-network':
|
||||
specifier: workspace:*
|
||||
version: link:../@n8n/backend-network
|
||||
|
|
@ -7549,6 +7552,76 @@ importers:
|
|||
specifier: ^2.2.8
|
||||
version: 2.2.8(patch_hash=e2aee939ccac8a57fe449bfd92bedd8117841579526217bc39aca26c6b8c317f)(typescript@6.0.2)
|
||||
|
||||
packages/modules/quick-connect/backend:
|
||||
dependencies:
|
||||
'@n8n/api-types':
|
||||
specifier: workspace:*
|
||||
version: link:../../../@n8n/api-types
|
||||
'@n8n/backend-common':
|
||||
specifier: workspace:*
|
||||
version: link:../../../@n8n/backend-common
|
||||
'@n8n/backend-network':
|
||||
specifier: workspace:*
|
||||
version: link:../../../@n8n/backend-network
|
||||
'@n8n/config':
|
||||
specifier: workspace:*
|
||||
version: link:../../../@n8n/config
|
||||
'@n8n/constants':
|
||||
specifier: workspace:*
|
||||
version: link:../../../@n8n/constants
|
||||
'@n8n/db':
|
||||
specifier: workspace:*
|
||||
version: link:../../../@n8n/db
|
||||
'@n8n/decorators':
|
||||
specifier: workspace:*
|
||||
version: link:../../../@n8n/decorators
|
||||
'@n8n/di':
|
||||
specifier: workspace:*
|
||||
version: link:../../../@n8n/di
|
||||
'@n8n/errors':
|
||||
specifier: workspace:*
|
||||
version: link:../../../@n8n/errors
|
||||
n8n-workflow:
|
||||
specifier: workspace:*
|
||||
version: link:../../../workflow
|
||||
devDependencies:
|
||||
'@n8n/oxlint-config':
|
||||
specifier: workspace:*
|
||||
version: link:../../../@n8n/oxlint-config
|
||||
'@n8n/typescript-config':
|
||||
specifier: workspace:*
|
||||
version: link:../../../@n8n/typescript-config
|
||||
'@n8n/vitest-config':
|
||||
specifier: workspace:*
|
||||
version: link:../../../@n8n/vitest-config
|
||||
'@vitest/coverage-v8':
|
||||
specifier: 'catalog:'
|
||||
version: 5.0.1(@vitest/browser@5.0.1)(vitest@5.0.1)
|
||||
nock:
|
||||
specifier: 'catalog:'
|
||||
version: 14.0.14
|
||||
oxlint:
|
||||
specifier: 'catalog:'
|
||||
version: 1.78.0(oxlint-tsgolint@0.21.1)
|
||||
oxlint-tsgolint:
|
||||
specifier: 'catalog:'
|
||||
version: 0.21.1
|
||||
typescript:
|
||||
specifier: catalog:typescript
|
||||
version: 7.0.2
|
||||
vite:
|
||||
specifier: 'catalog:'
|
||||
version: 8.0.2(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)
|
||||
vitest:
|
||||
specifier: 'catalog:'
|
||||
version: 5.0.1(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@5.0.1)(@vitest/coverage-istanbul@5.0.1)(@vitest/coverage-v8@5.0.1)(@vitest/ui@5.0.1)(jsdom@23.0.1(bufferutil@4.0.9)(supports-color@8.1.1)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3))
|
||||
vitest-mock-extended:
|
||||
specifier: 'catalog:'
|
||||
version: 3.1.0(typescript@7.0.2)(vitest@5.0.1)
|
||||
zod:
|
||||
specifier: 3.25.76
|
||||
version: 3.25.76
|
||||
|
||||
packages/modules/type-availability-policies/frontend:
|
||||
dependencies:
|
||||
'@n8n/api-types':
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user