mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2026-08-06 20:08:37 +02:00
119 lines
4.0 KiB
TypeScript
119 lines
4.0 KiB
TypeScript
import 'reflect-metadata';
|
|
import { Container, Service } from '@n8n/di';
|
|
import { readFileSync } from 'fs';
|
|
import { z } from 'zod';
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-restricted-types
|
|
type Class = Function;
|
|
type Constructable<T = unknown> = new (rawValue: string) => T;
|
|
type PropertyKey = string | symbol;
|
|
type PropertyType = number | boolean | string | Class;
|
|
interface PropertyMetadata {
|
|
type: PropertyType;
|
|
envName?: string;
|
|
schema?: z.ZodType<unknown>;
|
|
}
|
|
|
|
const globalMetadata = new Map<Class, Map<PropertyKey, PropertyMetadata>>();
|
|
|
|
const readEnv = (envName: string) => {
|
|
if (envName in process.env) return process.env[envName];
|
|
|
|
// Read the value from a file, if "_FILE" environment variable is defined
|
|
const filePath = process.env[`${envName}_FILE`];
|
|
if (filePath) return readFileSync(filePath, 'utf8');
|
|
|
|
return undefined;
|
|
};
|
|
|
|
export const Config: ClassDecorator = (ConfigClass: Class) => {
|
|
const factory = function (...args: unknown[]) {
|
|
const config = new (ConfigClass as new (...a: unknown[]) => Record<PropertyKey, unknown>)(
|
|
...args,
|
|
);
|
|
const classMetadata = globalMetadata.get(ConfigClass);
|
|
if (!classMetadata) {
|
|
throw new Error('Invalid config class: ' + ConfigClass.name);
|
|
}
|
|
|
|
for (const [key, { type, envName, schema }] of classMetadata) {
|
|
if (typeof type === 'function' && globalMetadata.has(type)) {
|
|
config[key] = Container.get(type as Constructable);
|
|
} else if (envName) {
|
|
const value = readEnv(envName);
|
|
if (value === undefined) continue;
|
|
|
|
if (schema) {
|
|
const result = schema.safeParse(value);
|
|
if (result.error) {
|
|
console.warn(
|
|
`Invalid value for ${envName} - ${result.error.issues[0].message}. Falling back to default value.`,
|
|
);
|
|
continue;
|
|
}
|
|
config[key] = result.data;
|
|
} else if (type === Number) {
|
|
const parsed = Number(value);
|
|
if (isNaN(parsed)) {
|
|
console.warn(`Invalid number value for ${envName}: ${value}`);
|
|
} else {
|
|
config[key] = parsed;
|
|
}
|
|
} else if (type === Boolean) {
|
|
if (['true', '1'].includes(value.toLowerCase())) {
|
|
config[key] = true;
|
|
} else if (['false', '0'].includes(value.toLowerCase())) {
|
|
config[key] = false;
|
|
} else {
|
|
console.warn(`Invalid boolean value for ${envName}: ${value}`);
|
|
}
|
|
} else if (type === Date) {
|
|
const timestamp = Date.parse(value);
|
|
if (isNaN(timestamp)) {
|
|
console.warn(`Invalid timestamp value for ${envName}: ${value}`);
|
|
} else {
|
|
config[key] = new Date(timestamp);
|
|
}
|
|
} else if (type === String) {
|
|
config[key] = value.trim().replace(/^(['"])(.*)\1$/, '$2');
|
|
} else {
|
|
config[key] = new (type as Constructable)(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (typeof config.sanitize === 'function') config.sanitize();
|
|
|
|
return config;
|
|
};
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
return Service({ factory })(ConfigClass);
|
|
};
|
|
|
|
export const Nested: PropertyDecorator = (target: object, key: PropertyKey) => {
|
|
const ConfigClass = target.constructor;
|
|
const classMetadata = globalMetadata.get(ConfigClass) ?? new Map<PropertyKey, PropertyMetadata>();
|
|
const type = Reflect.getMetadata('design:type', target, key) as PropertyType;
|
|
classMetadata.set(key, { type });
|
|
globalMetadata.set(ConfigClass, classMetadata);
|
|
};
|
|
|
|
export const Env =
|
|
(envName: string, schema?: PropertyMetadata['schema']): PropertyDecorator =>
|
|
(target: object, key: PropertyKey) => {
|
|
const ConfigClass = target.constructor;
|
|
const classMetadata =
|
|
globalMetadata.get(ConfigClass) ?? new Map<PropertyKey, PropertyMetadata>();
|
|
|
|
const type = Reflect.getMetadata('design:type', target, key) as PropertyType;
|
|
const isZodSchema = schema instanceof z.ZodType;
|
|
if (type === Object && !isZodSchema) {
|
|
throw new Error(
|
|
`Invalid decorator metadata on key "${key as string}" on ${ConfigClass.name}\n Please use explicit typing on all config fields`,
|
|
);
|
|
}
|
|
|
|
classMetadata.set(key, { type, envName, schema });
|
|
globalMetadata.set(ConfigClass, classMetadata);
|
|
};
|