mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
perf(core): Skip init for unlicensed modules (#16311)
This commit is contained in:
@@ -19,7 +19,7 @@ describe('@BackendModule decorator', () => {
|
||||
initialize() {}
|
||||
}
|
||||
|
||||
const registeredModules = Array.from(moduleMetadata.getModules());
|
||||
const registeredModules = moduleMetadata.getEntries().map((entry) => entry.class);
|
||||
|
||||
expect(registeredModules).toContain(TestModule);
|
||||
expect(registeredModules).toHaveLength(1);
|
||||
@@ -41,7 +41,7 @@ describe('@BackendModule decorator', () => {
|
||||
initialize() {}
|
||||
}
|
||||
|
||||
const registeredModules = Array.from(moduleMetadata.getModules());
|
||||
const registeredModules = moduleMetadata.getEntries().map((entry) => entry.class);
|
||||
|
||||
expect(registeredModules).toContain(FirstModule);
|
||||
expect(registeredModules).toContain(SecondModule);
|
||||
@@ -53,7 +53,7 @@ describe('@BackendModule decorator', () => {
|
||||
@BackendModule()
|
||||
class TestModule {}
|
||||
|
||||
const registeredModules = Array.from(moduleMetadata.getModules());
|
||||
const registeredModules = moduleMetadata.getEntries().map((entry) => entry.class);
|
||||
|
||||
expect(registeredModules).toContain(TestModule);
|
||||
expect(registeredModules).toHaveLength(1);
|
||||
@@ -69,7 +69,7 @@ describe('@BackendModule decorator', () => {
|
||||
}
|
||||
}
|
||||
|
||||
const registeredModules = Array.from(moduleMetadata.getModules());
|
||||
const registeredModules = moduleMetadata.getEntries().map((entry) => entry.class);
|
||||
|
||||
expect(registeredModules).toContain(TestModule);
|
||||
|
||||
@@ -87,7 +87,7 @@ describe('@BackendModule decorator', () => {
|
||||
@BackendModule()
|
||||
class SecondModule {}
|
||||
|
||||
const registeredModules = Array.from(moduleMetadata.getModules());
|
||||
const registeredModules = moduleMetadata.getEntries().map((entry) => entry.class);
|
||||
|
||||
expect(registeredModules).toContain(FirstModule);
|
||||
expect(registeredModules).toContain(SecondModule);
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
import { Service } from '@n8n/di';
|
||||
|
||||
import type { ModuleClass } from './module';
|
||||
import type { LicenseFlag, ModuleClass } from './module';
|
||||
|
||||
type ModuleEntry = {
|
||||
class: ModuleClass;
|
||||
licenseFlag?: LicenseFlag;
|
||||
};
|
||||
|
||||
@Service()
|
||||
export class ModuleMetadata {
|
||||
private readonly modules: Set<ModuleClass> = new Set();
|
||||
private readonly modules: Map<string, ModuleEntry> = new Map();
|
||||
|
||||
register(module: ModuleClass) {
|
||||
this.modules.add(module);
|
||||
register(moduleName: string, moduleEntry: ModuleEntry) {
|
||||
this.modules.set(moduleName, moduleEntry);
|
||||
}
|
||||
|
||||
getModules() {
|
||||
return this.modules.keys();
|
||||
getEntries() {
|
||||
return [...this.modules.values()];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { LICENSE_FEATURES } from '@n8n/constants';
|
||||
import { Container, Service, type Constructable } from '@n8n/di';
|
||||
|
||||
import { ModuleMetadata } from './module-metadata';
|
||||
@@ -24,9 +25,16 @@ export interface ModuleInterface {
|
||||
|
||||
export type ModuleClass = Constructable<ModuleInterface>;
|
||||
|
||||
export const BackendModule = (): ClassDecorator => (target) => {
|
||||
Container.get(ModuleMetadata).register(target as unknown as ModuleClass);
|
||||
export type LicenseFlag = (typeof LICENSE_FEATURES)[keyof typeof LICENSE_FEATURES];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
return Service()(target);
|
||||
};
|
||||
export const BackendModule =
|
||||
(opts?: { licenseFlag: LicenseFlag }): ClassDecorator =>
|
||||
(target) => {
|
||||
Container.get(ModuleMetadata).register(target.name, {
|
||||
class: target as unknown as ModuleClass,
|
||||
licenseFlag: opts?.licenseFlag,
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
return Service()(target);
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { ModuleInterface } from '@n8n/decorators';
|
||||
import { BackendModule } from '@n8n/decorators';
|
||||
import { Container } from '@n8n/di';
|
||||
|
||||
@BackendModule()
|
||||
@BackendModule({ licenseFlag: 'feat:externalSecrets' })
|
||||
export class ExternalSecretsModule implements ModuleInterface {
|
||||
async init() {
|
||||
await import('./external-secrets.controller.ee');
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { LicenseState, Logger } from '@n8n/backend-common';
|
||||
import { LifecycleMetadata, ModuleMetadata } from '@n8n/decorators';
|
||||
import type { LifecycleContext, EntityClass } from '@n8n/decorators';
|
||||
import { Container, Service } from '@n8n/di';
|
||||
@@ -19,16 +20,22 @@ export class ModuleRegistry {
|
||||
constructor(
|
||||
private readonly moduleMetadata: ModuleMetadata,
|
||||
private readonly lifecycleMetadata: LifecycleMetadata,
|
||||
private readonly licenseState: LicenseState,
|
||||
private readonly logger: Logger,
|
||||
) {}
|
||||
|
||||
async initModules() {
|
||||
for (const ModuleClass of this.moduleMetadata.getModules()) {
|
||||
for (const { class: ModuleClass, licenseFlag } of this.moduleMetadata.getEntries()) {
|
||||
if (licenseFlag && !this.licenseState.isLicensed(licenseFlag)) {
|
||||
this.logger.debug(`Skipped init for unlicensed module "${ModuleClass.name}"`);
|
||||
continue;
|
||||
}
|
||||
await Container.get(ModuleClass).init?.();
|
||||
}
|
||||
}
|
||||
|
||||
addEntities() {
|
||||
for (const ModuleClass of this.moduleMetadata.getModules()) {
|
||||
for (const { class: ModuleClass } of this.moduleMetadata.getEntries()) {
|
||||
const entities = Container.get(ModuleClass).entities?.();
|
||||
|
||||
if (!entities || entities.length === 0) continue;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { LicenseState } from '@n8n/backend-common';
|
||||
import { SettingsRepository } from '@n8n/db';
|
||||
import { Container } from '@n8n/di';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
@@ -31,6 +32,9 @@ let authMemberAgent: SuperAgentTest;
|
||||
|
||||
const mockProvidersInstance = new MockProviders();
|
||||
mockInstance(ExternalSecretsProviders, mockProvidersInstance);
|
||||
const licenseMock = mock<LicenseState>();
|
||||
licenseMock.isLicensed.mockReturnValue(true);
|
||||
Container.set(LicenseState, licenseMock);
|
||||
|
||||
const testServer = setupTestServer({
|
||||
endpointGroups: ['externalSecrets'],
|
||||
|
||||
Reference in New Issue
Block a user