mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
refactor(core): Decouple module settings from frontend service (#16324)
Co-authored-by: Danny Martini <danny@n8n.io>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { Container } from '@n8n/di';
|
||||
|
||||
import type { ModuleInterface } from '../module';
|
||||
import { BackendModule } from '../module';
|
||||
import { ModuleMetadata } from '../module-metadata';
|
||||
|
||||
@@ -14,34 +15,26 @@ describe('@BackendModule decorator', () => {
|
||||
});
|
||||
|
||||
it('should register module in ModuleMetadata', () => {
|
||||
@BackendModule()
|
||||
class TestModule {
|
||||
initialize() {}
|
||||
}
|
||||
@BackendModule({ name: 'test' })
|
||||
class TestModule implements ModuleInterface {}
|
||||
|
||||
const registeredModules = moduleMetadata.getEntries().map((entry) => entry.class);
|
||||
const registeredModules = moduleMetadata.getClasses();
|
||||
|
||||
expect(registeredModules).toContain(TestModule);
|
||||
expect(registeredModules).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should register multiple modules', () => {
|
||||
@BackendModule()
|
||||
class FirstModule {
|
||||
initialize() {}
|
||||
}
|
||||
@BackendModule({ name: 'test-1' })
|
||||
class FirstModule implements ModuleInterface {}
|
||||
|
||||
@BackendModule()
|
||||
class SecondModule {
|
||||
initialize() {}
|
||||
}
|
||||
@BackendModule({ name: 'test-2' })
|
||||
class SecondModule implements ModuleInterface {}
|
||||
|
||||
@BackendModule()
|
||||
class ThirdModule {
|
||||
initialize() {}
|
||||
}
|
||||
@BackendModule({ name: 'test-3' })
|
||||
class ThirdModule implements ModuleInterface {}
|
||||
|
||||
const registeredModules = moduleMetadata.getEntries().map((entry) => entry.class);
|
||||
const registeredModules = moduleMetadata.getClasses();
|
||||
|
||||
expect(registeredModules).toContain(FirstModule);
|
||||
expect(registeredModules).toContain(SecondModule);
|
||||
@@ -49,55 +42,26 @@ describe('@BackendModule decorator', () => {
|
||||
expect(registeredModules).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('should work with modules without initialize method', () => {
|
||||
@BackendModule()
|
||||
class TestModule {}
|
||||
|
||||
const registeredModules = moduleMetadata.getEntries().map((entry) => entry.class);
|
||||
|
||||
expect(registeredModules).toContain(TestModule);
|
||||
expect(registeredModules).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should support async initialize method', async () => {
|
||||
const mockInitialize = jest.fn();
|
||||
|
||||
@BackendModule()
|
||||
class TestModule {
|
||||
async initialize() {
|
||||
mockInitialize();
|
||||
}
|
||||
}
|
||||
|
||||
const registeredModules = moduleMetadata.getEntries().map((entry) => entry.class);
|
||||
|
||||
expect(registeredModules).toContain(TestModule);
|
||||
|
||||
const moduleInstance = new TestModule();
|
||||
await moduleInstance.initialize();
|
||||
|
||||
expect(mockInitialize).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('ModuleMetadata', () => {
|
||||
it('should allow retrieving and checking registered modules', () => {
|
||||
@BackendModule()
|
||||
class FirstModule {}
|
||||
|
||||
@BackendModule()
|
||||
class SecondModule {}
|
||||
|
||||
const registeredModules = moduleMetadata.getEntries().map((entry) => entry.class);
|
||||
|
||||
expect(registeredModules).toContain(FirstModule);
|
||||
expect(registeredModules).toContain(SecondModule);
|
||||
});
|
||||
});
|
||||
|
||||
it('should apply Service decorator', () => {
|
||||
@BackendModule()
|
||||
class TestModule {}
|
||||
@BackendModule({ name: 'test' })
|
||||
class TestModule implements ModuleInterface {}
|
||||
|
||||
expect(Container.has(TestModule)).toBe(true);
|
||||
});
|
||||
|
||||
it('stores the test name and licenseFlag flag in the metadata', () => {
|
||||
const name = 'test';
|
||||
const licenseFlag = 'feat:ldap';
|
||||
|
||||
@BackendModule({ name, licenseFlag })
|
||||
class TestModule implements ModuleInterface {}
|
||||
|
||||
const registeredModules = moduleMetadata.getEntries();
|
||||
|
||||
expect(registeredModules).toHaveLength(1);
|
||||
const [moduleName, options] = registeredModules[0];
|
||||
expect(moduleName).toBe(name);
|
||||
expect(options.licenseFlag).toBe(licenseFlag);
|
||||
expect(options.class).toBe(TestModule);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export { ModuleInterface, BackendModule, EntityClass } from './module';
|
||||
export { ModuleInterface, BackendModule, EntityClass, ModuleSettings } from './module';
|
||||
export { ModuleMetadata } from './module-metadata';
|
||||
|
||||
@@ -16,6 +16,10 @@ export class ModuleMetadata {
|
||||
}
|
||||
|
||||
getEntries() {
|
||||
return [...this.modules.values()];
|
||||
return [...this.modules.entries()];
|
||||
}
|
||||
|
||||
getClasses() {
|
||||
return [...this.modules.values()].map((entry) => entry.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,9 +18,12 @@ export interface BaseEntity {
|
||||
|
||||
export type EntityClass = new () => BaseEntity;
|
||||
|
||||
export type ModuleSettings = Record<string, unknown>;
|
||||
|
||||
export interface ModuleInterface {
|
||||
init?(): void | Promise<void>;
|
||||
init?(): Promise<void>;
|
||||
entities?(): EntityClass[];
|
||||
settings?(): Promise<ModuleSettings>;
|
||||
}
|
||||
|
||||
export type ModuleClass = Constructable<ModuleInterface>;
|
||||
@@ -28,9 +31,9 @@ export type ModuleClass = Constructable<ModuleInterface>;
|
||||
export type LicenseFlag = (typeof LICENSE_FEATURES)[keyof typeof LICENSE_FEATURES];
|
||||
|
||||
export const BackendModule =
|
||||
(opts?: { licenseFlag: LicenseFlag }): ClassDecorator =>
|
||||
(opts: { name: string; licenseFlag?: LicenseFlag }): ClassDecorator =>
|
||||
(target) => {
|
||||
Container.get(ModuleMetadata).register(target.name, {
|
||||
Container.get(ModuleMetadata).register(opts.name, {
|
||||
class: target as unknown as ModuleClass,
|
||||
licenseFlag: opts?.licenseFlag,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user