refactor(core): Port cache config (no-changelog) (#10286)

This commit is contained in:
Iván Ovejero 2024-08-02 17:10:03 +02:00 committed by GitHub
parent aa0a470dce
commit acbae928f2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 190 additions and 162 deletions

View file

@ -0,0 +1,36 @@
import { Config, Env, Nested } from '../decorators';
@Config
class MemoryConfig {
/** Max size of memory cache in bytes */
@Env('N8N_CACHE_MEMORY_MAX_SIZE')
maxSize = 3 * 1024 * 1024; // 3 MiB
/** Time to live (in milliseconds) for data cached in memory. */
@Env('N8N_CACHE_MEMORY_TTL')
ttl = 3600 * 1000; // 1 hour
}
@Config
class RedisConfig {
/** Prefix for cache keys in Redis. */
@Env('N8N_CACHE_REDIS_KEY_PREFIX')
prefix = 'redis';
/** Time to live (in milliseconds) for data cached in Redis. 0 for no TTL. */
@Env('N8N_CACHE_REDIS_TTL')
ttl = 3600 * 1000; // 1 hour
}
@Config
export class CacheConfig {
/** Backend to use for caching. */
@Env('N8N_CACHE_BACKEND')
backend: 'memory' | 'redis' | 'auto' = 'auto';
@Nested
memory: MemoryConfig;
@Nested
redis: RedisConfig;
}

View file

@ -7,19 +7,19 @@ class CredentialsOverwrite {
* Format: { CREDENTIAL_NAME: { PARAMETER: VALUE }} * Format: { CREDENTIAL_NAME: { PARAMETER: VALUE }}
*/ */
@Env('CREDENTIALS_OVERWRITE_DATA') @Env('CREDENTIALS_OVERWRITE_DATA')
readonly data: string = '{}'; data = '{}';
/** Internal API endpoint to fetch overwritten credential types from. */ /** Internal API endpoint to fetch overwritten credential types from. */
@Env('CREDENTIALS_OVERWRITE_ENDPOINT') @Env('CREDENTIALS_OVERWRITE_ENDPOINT')
readonly endpoint: string = ''; endpoint = '';
} }
@Config @Config
export class CredentialsConfig { export class CredentialsConfig {
/** Default name for credentials */ /** Default name for credentials */
@Env('CREDENTIALS_DEFAULT_NAME') @Env('CREDENTIALS_DEFAULT_NAME')
readonly defaultName: string = 'My credentials'; defaultName = 'My credentials';
@Nested @Nested
readonly overwrite: CredentialsOverwrite; overwrite: CredentialsOverwrite;
} }

View file

@ -4,19 +4,19 @@ import { Config, Env, Nested } from '../decorators';
class LoggingConfig { class LoggingConfig {
/** Whether database logging is enabled. */ /** Whether database logging is enabled. */
@Env('DB_LOGGING_ENABLED') @Env('DB_LOGGING_ENABLED')
readonly enabled: boolean = false; enabled = false;
/** /**
* Database logging level. Requires `DB_LOGGING_MAX_EXECUTION_TIME` to be higher than `0`. * Database logging level. Requires `DB_LOGGING_MAX_EXECUTION_TIME` to be higher than `0`.
*/ */
@Env('DB_LOGGING_OPTIONS') @Env('DB_LOGGING_OPTIONS')
readonly options: 'query' | 'error' | 'schema' | 'warn' | 'info' | 'log' | 'all' = 'error'; options: 'query' | 'error' | 'schema' | 'warn' | 'info' | 'log' | 'all' = 'error';
/** /**
* Only queries that exceed this time (ms) will be logged. Set `0` to disable. * Only queries that exceed this time (ms) will be logged. Set `0` to disable.
*/ */
@Env('DB_LOGGING_MAX_EXECUTION_TIME') @Env('DB_LOGGING_MAX_EXECUTION_TIME')
readonly maxQueryExecutionTime: number = 0; maxQueryExecutionTime = 0;
} }
@Config @Config
@ -26,97 +26,97 @@ class PostgresSSLConfig {
* If `DB_POSTGRESDB_SSL_CA`, `DB_POSTGRESDB_SSL_CERT`, or `DB_POSTGRESDB_SSL_KEY` are defined, `DB_POSTGRESDB_SSL_ENABLED` defaults to `true`. * If `DB_POSTGRESDB_SSL_CA`, `DB_POSTGRESDB_SSL_CERT`, or `DB_POSTGRESDB_SSL_KEY` are defined, `DB_POSTGRESDB_SSL_ENABLED` defaults to `true`.
*/ */
@Env('DB_POSTGRESDB_SSL_ENABLED') @Env('DB_POSTGRESDB_SSL_ENABLED')
readonly enabled: boolean = false; enabled = false;
/** SSL certificate authority */ /** SSL certificate authority */
@Env('DB_POSTGRESDB_SSL_CA') @Env('DB_POSTGRESDB_SSL_CA')
readonly ca: string = ''; ca = '';
/** SSL certificate */ /** SSL certificate */
@Env('DB_POSTGRESDB_SSL_CERT') @Env('DB_POSTGRESDB_SSL_CERT')
readonly cert: string = ''; cert = '';
/** SSL key */ /** SSL key */
@Env('DB_POSTGRESDB_SSL_KEY') @Env('DB_POSTGRESDB_SSL_KEY')
readonly key: string = ''; key = '';
/** If unauthorized SSL connections should be rejected */ /** If unauthorized SSL connections should be rejected */
@Env('DB_POSTGRESDB_SSL_REJECT_UNAUTHORIZED') @Env('DB_POSTGRESDB_SSL_REJECT_UNAUTHORIZED')
readonly rejectUnauthorized: boolean = true; rejectUnauthorized = true;
} }
@Config @Config
class PostgresConfig { class PostgresConfig {
/** Postgres database name */ /** Postgres database name */
@Env('DB_POSTGRESDB_DATABASE') @Env('DB_POSTGRESDB_DATABASE')
database: string = 'n8n'; database = 'n8n';
/** Postgres database host */ /** Postgres database host */
@Env('DB_POSTGRESDB_HOST') @Env('DB_POSTGRESDB_HOST')
readonly host: string = 'localhost'; host = 'localhost';
/** Postgres database password */ /** Postgres database password */
@Env('DB_POSTGRESDB_PASSWORD') @Env('DB_POSTGRESDB_PASSWORD')
readonly password: string = ''; password = '';
/** Postgres database port */ /** Postgres database port */
@Env('DB_POSTGRESDB_PORT') @Env('DB_POSTGRESDB_PORT')
readonly port: number = 5432; port: number = 5432;
/** Postgres database user */ /** Postgres database user */
@Env('DB_POSTGRESDB_USER') @Env('DB_POSTGRESDB_USER')
readonly user: string = 'postgres'; user = 'postgres';
/** Postgres database schema */ /** Postgres database schema */
@Env('DB_POSTGRESDB_SCHEMA') @Env('DB_POSTGRESDB_SCHEMA')
readonly schema: string = 'public'; schema = 'public';
/** Postgres database pool size */ /** Postgres database pool size */
@Env('DB_POSTGRESDB_POOL_SIZE') @Env('DB_POSTGRESDB_POOL_SIZE')
readonly poolSize = 2; poolSize = 2;
@Nested @Nested
readonly ssl: PostgresSSLConfig; ssl: PostgresSSLConfig;
} }
@Config @Config
class MysqlConfig { class MysqlConfig {
/** @deprecated MySQL database name */ /** @deprecated MySQL database name */
@Env('DB_MYSQLDB_DATABASE') @Env('DB_MYSQLDB_DATABASE')
database: string = 'n8n'; database = 'n8n';
/** MySQL database host */ /** MySQL database host */
@Env('DB_MYSQLDB_HOST') @Env('DB_MYSQLDB_HOST')
readonly host: string = 'localhost'; host = 'localhost';
/** MySQL database password */ /** MySQL database password */
@Env('DB_MYSQLDB_PASSWORD') @Env('DB_MYSQLDB_PASSWORD')
readonly password: string = ''; password = '';
/** MySQL database port */ /** MySQL database port */
@Env('DB_MYSQLDB_PORT') @Env('DB_MYSQLDB_PORT')
readonly port: number = 3306; port: number = 3306;
/** MySQL database user */ /** MySQL database user */
@Env('DB_MYSQLDB_USER') @Env('DB_MYSQLDB_USER')
readonly user: string = 'root'; user = 'root';
} }
@Config @Config
class SqliteConfig { class SqliteConfig {
/** SQLite database file name */ /** SQLite database file name */
@Env('DB_SQLITE_DATABASE') @Env('DB_SQLITE_DATABASE')
readonly database: string = 'database.sqlite'; database = 'database.sqlite';
/** SQLite database pool size. Set to `0` to disable pooling. */ /** SQLite database pool size. Set to `0` to disable pooling. */
@Env('DB_SQLITE_POOL_SIZE') @Env('DB_SQLITE_POOL_SIZE')
readonly poolSize: number = 0; poolSize: number = 0;
/** /**
* Enable SQLite WAL mode. * Enable SQLite WAL mode.
*/ */
@Env('DB_SQLITE_ENABLE_WAL') @Env('DB_SQLITE_ENABLE_WAL')
readonly enableWAL: boolean = this.poolSize > 1; enableWAL = this.poolSize > 1;
/** /**
* Run `VACUUM` on startup to rebuild the database, reducing file size and optimizing indexes. * Run `VACUUM` on startup to rebuild the database, reducing file size and optimizing indexes.
@ -124,7 +124,7 @@ class SqliteConfig {
* @warning Long-running blocking operation that will increase startup time. * @warning Long-running blocking operation that will increase startup time.
*/ */
@Env('DB_SQLITE_VACUUM_ON_STARTUP') @Env('DB_SQLITE_VACUUM_ON_STARTUP')
readonly executeVacuumOnStartup: boolean = false; executeVacuumOnStartup = false;
} }
@Config @Config
@ -135,17 +135,17 @@ export class DatabaseConfig {
/** Prefix for table names */ /** Prefix for table names */
@Env('DB_TABLE_PREFIX') @Env('DB_TABLE_PREFIX')
readonly tablePrefix: string = ''; tablePrefix = '';
@Nested @Nested
readonly logging: LoggingConfig; logging: LoggingConfig;
@Nested @Nested
readonly postgresdb: PostgresConfig; postgresdb: PostgresConfig;
@Nested @Nested
readonly mysqldb: MysqlConfig; mysqldb: MysqlConfig;
@Nested @Nested
readonly sqlite: SqliteConfig; sqlite: SqliteConfig;
} }

View file

@ -4,75 +4,75 @@ import { Config, Env, Nested } from '../decorators';
export class SmtpAuth { export class SmtpAuth {
/** SMTP login username */ /** SMTP login username */
@Env('N8N_SMTP_USER') @Env('N8N_SMTP_USER')
readonly user: string = ''; user = '';
/** SMTP login password */ /** SMTP login password */
@Env('N8N_SMTP_PASS') @Env('N8N_SMTP_PASS')
readonly pass: string = ''; pass = '';
/** SMTP OAuth Service Client */ /** SMTP OAuth Service Client */
@Env('N8N_SMTP_OAUTH_SERVICE_CLIENT') @Env('N8N_SMTP_OAUTH_SERVICE_CLIENT')
readonly serviceClient: string = ''; serviceClient = '';
/** SMTP OAuth Private Key */ /** SMTP OAuth Private Key */
@Env('N8N_SMTP_OAUTH_PRIVATE_KEY') @Env('N8N_SMTP_OAUTH_PRIVATE_KEY')
readonly privateKey: string = ''; privateKey = '';
} }
@Config @Config
export class SmtpConfig { export class SmtpConfig {
/** SMTP server host */ /** SMTP server host */
@Env('N8N_SMTP_HOST') @Env('N8N_SMTP_HOST')
readonly host: string = ''; host = '';
/** SMTP server port */ /** SMTP server port */
@Env('N8N_SMTP_PORT') @Env('N8N_SMTP_PORT')
readonly port: number = 465; port: number = 465;
/** Whether to use SSL for SMTP */ /** Whether to use SSL for SMTP */
@Env('N8N_SMTP_SSL') @Env('N8N_SMTP_SSL')
readonly secure: boolean = true; secure: boolean = true;
/** Whether to use STARTTLS for SMTP when SSL is disabled */ /** Whether to use STARTTLS for SMTP when SSL is disabled */
@Env('N8N_SMTP_STARTTLS') @Env('N8N_SMTP_STARTTLS')
readonly startTLS: boolean = true; startTLS: boolean = true;
/** How to display sender name */ /** How to display sender name */
@Env('N8N_SMTP_SENDER') @Env('N8N_SMTP_SENDER')
readonly sender: string = ''; sender = '';
@Nested @Nested
readonly auth: SmtpAuth; auth: SmtpAuth;
} }
@Config @Config
export class TemplateConfig { export class TemplateConfig {
/** Overrides default HTML template for inviting new people (use full path) */ /** Overrides default HTML template for inviting new people (use full path) */
@Env('N8N_UM_EMAIL_TEMPLATES_INVITE') @Env('N8N_UM_EMAIL_TEMPLATES_INVITE')
readonly invite: string = ''; invite = '';
/** Overrides default HTML template for resetting password (use full path) */ /** Overrides default HTML template for resetting password (use full path) */
@Env('N8N_UM_EMAIL_TEMPLATES_PWRESET') @Env('N8N_UM_EMAIL_TEMPLATES_PWRESET')
readonly passwordReset: string = ''; passwordReset = '';
/** Overrides default HTML template for notifying that a workflow was shared (use full path) */ /** Overrides default HTML template for notifying that a workflow was shared (use full path) */
@Env('N8N_UM_EMAIL_TEMPLATES_WORKFLOW_SHARED') @Env('N8N_UM_EMAIL_TEMPLATES_WORKFLOW_SHARED')
readonly workflowShared: string = ''; workflowShared = '';
/** Overrides default HTML template for notifying that credentials were shared (use full path) */ /** Overrides default HTML template for notifying that credentials were shared (use full path) */
@Env('N8N_UM_EMAIL_TEMPLATES_CREDENTIALS_SHARED') @Env('N8N_UM_EMAIL_TEMPLATES_CREDENTIALS_SHARED')
readonly credentialsShared: string = ''; credentialsShared = '';
} }
@Config @Config
export class EmailConfig { export class EmailConfig {
/** How to send emails */ /** How to send emails */
@Env('N8N_EMAIL_MODE') @Env('N8N_EMAIL_MODE')
readonly mode: '' | 'smtp' = 'smtp'; mode: '' | 'smtp' = 'smtp';
@Nested @Nested
readonly smtp: SmtpConfig; smtp: SmtpConfig;
@Nested @Nested
readonly template: TemplateConfig; template: TemplateConfig;
} }

View file

@ -4,99 +4,99 @@ import { Config, Env, Nested } from '../decorators';
class PrometheusMetricsConfig { class PrometheusMetricsConfig {
/** Whether to enable the `/metrics` endpoint to expose Prometheus metrics. */ /** Whether to enable the `/metrics` endpoint to expose Prometheus metrics. */
@Env('N8N_METRICS') @Env('N8N_METRICS')
readonly enable: boolean = false; enable = false;
/** Prefix for Prometheus metric names. */ /** Prefix for Prometheus metric names. */
@Env('N8N_METRICS_PREFIX') @Env('N8N_METRICS_PREFIX')
readonly prefix: string = 'n8n_'; prefix = 'n8n_';
/** Whether to expose system and Node.js metrics. See: https://www.npmjs.com/package/prom-client */ /** Whether to expose system and Node.js metrics. See: https://www.npmjs.com/package/prom-client */
@Env('N8N_METRICS_INCLUDE_DEFAULT_METRICS') @Env('N8N_METRICS_INCLUDE_DEFAULT_METRICS')
readonly includeDefaultMetrics = true; includeDefaultMetrics = true;
/** Whether to include a label for workflow ID on workflow metrics. */ /** Whether to include a label for workflow ID on workflow metrics. */
@Env('N8N_METRICS_INCLUDE_WORKFLOW_ID_LABEL') @Env('N8N_METRICS_INCLUDE_WORKFLOW_ID_LABEL')
readonly includeWorkflowIdLabel: boolean = false; includeWorkflowIdLabel = false;
/** Whether to include a label for node type on node metrics. */ /** Whether to include a label for node type on node metrics. */
@Env('N8N_METRICS_INCLUDE_NODE_TYPE_LABEL') @Env('N8N_METRICS_INCLUDE_NODE_TYPE_LABEL')
readonly includeNodeTypeLabel: boolean = false; includeNodeTypeLabel = false;
/** Whether to include a label for credential type on credential metrics. */ /** Whether to include a label for credential type on credential metrics. */
@Env('N8N_METRICS_INCLUDE_CREDENTIAL_TYPE_LABEL') @Env('N8N_METRICS_INCLUDE_CREDENTIAL_TYPE_LABEL')
readonly includeCredentialTypeLabel: boolean = false; includeCredentialTypeLabel = false;
/** Whether to expose metrics for API endpoints. See: https://www.npmjs.com/package/express-prom-bundle */ /** Whether to expose metrics for API endpoints. See: https://www.npmjs.com/package/express-prom-bundle */
@Env('N8N_METRICS_INCLUDE_API_ENDPOINTS') @Env('N8N_METRICS_INCLUDE_API_ENDPOINTS')
readonly includeApiEndpoints: boolean = false; includeApiEndpoints = false;
/** Whether to include a label for the path of API endpoint calls. */ /** Whether to include a label for the path of API endpoint calls. */
@Env('N8N_METRICS_INCLUDE_API_PATH_LABEL') @Env('N8N_METRICS_INCLUDE_API_PATH_LABEL')
readonly includeApiPathLabel: boolean = false; includeApiPathLabel = false;
/** Whether to include a label for the HTTP method of API endpoint calls. */ /** Whether to include a label for the HTTP method of API endpoint calls. */
@Env('N8N_METRICS_INCLUDE_API_METHOD_LABEL') @Env('N8N_METRICS_INCLUDE_API_METHOD_LABEL')
readonly includeApiMethodLabel: boolean = false; includeApiMethodLabel = false;
/** Whether to include a label for the status code of API endpoint calls. */ /** Whether to include a label for the status code of API endpoint calls. */
@Env('N8N_METRICS_INCLUDE_API_STATUS_CODE_LABEL') @Env('N8N_METRICS_INCLUDE_API_STATUS_CODE_LABEL')
readonly includeApiStatusCodeLabel: boolean = false; includeApiStatusCodeLabel = false;
/** Whether to include metrics for cache hits and misses. */ /** Whether to include metrics for cache hits and misses. */
@Env('N8N_METRICS_INCLUDE_CACHE_METRICS') @Env('N8N_METRICS_INCLUDE_CACHE_METRICS')
readonly includeCacheMetrics: boolean = false; includeCacheMetrics = false;
/** Whether to include metrics derived from n8n's internal events */ /** Whether to include metrics derived from n8n's internal events */
@Env('N8N_METRICS_INCLUDE_MESSAGE_EVENT_BUS_METRICS') @Env('N8N_METRICS_INCLUDE_MESSAGE_EVENT_BUS_METRICS')
readonly includeMessageEventBusMetrics: boolean = false; includeMessageEventBusMetrics = false;
} }
@Config @Config
export class EndpointsConfig { export class EndpointsConfig {
/** Max payload size in MiB */ /** Max payload size in MiB */
@Env('N8N_PAYLOAD_SIZE_MAX') @Env('N8N_PAYLOAD_SIZE_MAX')
readonly payloadSizeMax: number = 16; payloadSizeMax: number = 16;
@Nested @Nested
readonly metrics: PrometheusMetricsConfig; metrics: PrometheusMetricsConfig;
/** Path segment for REST API endpoints. */ /** Path segment for REST API endpoints. */
@Env('N8N_ENDPOINT_REST') @Env('N8N_ENDPOINT_REST')
readonly rest: string = 'rest'; rest = 'rest';
/** Path segment for form endpoints. */ /** Path segment for form endpoints. */
@Env('N8N_ENDPOINT_FORM') @Env('N8N_ENDPOINT_FORM')
readonly form: string = 'form'; form = 'form';
/** Path segment for test form endpoints. */ /** Path segment for test form endpoints. */
@Env('N8N_ENDPOINT_FORM_TEST') @Env('N8N_ENDPOINT_FORM_TEST')
readonly formTest: string = 'form-test'; formTest = 'form-test';
/** Path segment for waiting form endpoints. */ /** Path segment for waiting form endpoints. */
@Env('N8N_ENDPOINT_FORM_WAIT') @Env('N8N_ENDPOINT_FORM_WAIT')
readonly formWaiting: string = 'form-waiting'; formWaiting = 'form-waiting';
/** Path segment for webhook endpoints. */ /** Path segment for webhook endpoints. */
@Env('N8N_ENDPOINT_WEBHOOK') @Env('N8N_ENDPOINT_WEBHOOK')
readonly webhook: string = 'webhook'; webhook = 'webhook';
/** Path segment for test webhook endpoints. */ /** Path segment for test webhook endpoints. */
@Env('N8N_ENDPOINT_WEBHOOK_TEST') @Env('N8N_ENDPOINT_WEBHOOK_TEST')
readonly webhookTest: string = 'webhook-test'; webhookTest = 'webhook-test';
/** Path segment for waiting webhook endpoints. */ /** Path segment for waiting webhook endpoints. */
@Env('N8N_ENDPOINT_WEBHOOK_WAIT') @Env('N8N_ENDPOINT_WEBHOOK_WAIT')
readonly webhookWaiting: string = 'webhook-waiting'; webhookWaiting = 'webhook-waiting';
/** Whether to disable n8n's UI (frontend). */ /** Whether to disable n8n's UI (frontend). */
@Env('N8N_DISABLE_UI') @Env('N8N_DISABLE_UI')
readonly disableUi: boolean = false; disableUi = false;
/** Whether to disable production webhooks on the main process, when using webhook-specific processes. */ /** Whether to disable production webhooks on the main process, when using webhook-specific processes. */
@Env('N8N_DISABLE_PRODUCTION_MAIN_PROCESS') @Env('N8N_DISABLE_PRODUCTION_MAIN_PROCESS')
readonly disableProductionWebhooksOnMainProcess: boolean = false; disableProductionWebhooksOnMainProcess = false;
/** Colon-delimited list of additional endpoints to not open the UI on. */ /** Colon-delimited list of additional endpoints to not open the UI on. */
@Env('N8N_ADDITIONAL_NON_UI_ROUTES') @Env('N8N_ADDITIONAL_NON_UI_ROUTES')
readonly additionalNonUIRoutes: string = ''; additionalNonUIRoutes = '';
} }

View file

@ -2,30 +2,30 @@ import { Config, Env, Nested } from '../decorators';
@Config @Config
class LogWriterConfig { class LogWriterConfig {
/** Number of event log files to keep */ /* of event log files to keep */
@Env('N8N_EVENTBUS_LOGWRITER_KEEPLOGCOUNT') @Env('N8N_EVENTBUS_LOGWRITER_KEEPLOGCOUNT')
readonly keepLogCount: number = 3; keepLogCount = 3;
/** Max size (in KB) of an event log file before a new one is started */ /** Max size (in KB) of an event log file before a new one is started */
@Env('N8N_EVENTBUS_LOGWRITER_MAXFILESIZEINKB') @Env('N8N_EVENTBUS_LOGWRITER_MAXFILESIZEINKB')
readonly maxFileSizeInKB: number = 10240; // 10 MB maxFileSizeInKB = 10240; // 10 MB
/** Basename of event log file */ /** Basename of event log file */
@Env('N8N_EVENTBUS_LOGWRITER_LOGBASENAME') @Env('N8N_EVENTBUS_LOGWRITER_LOGBASENAME')
readonly logBaseName: string = 'n8nEventLog'; logBaseName = 'n8nEventLog';
} }
@Config @Config
export class EventBusConfig { export class EventBusConfig {
/** How often (in ms) to check for unsent event messages. Can in rare cases cause a message to be sent twice. `0` to disable */ /** How often (in ms) to check for unsent event messages. Can in rare cases cause a message to be sent twice. `0` to disable */
@Env('N8N_EVENTBUS_CHECKUNSENTINTERVAL') @Env('N8N_EVENTBUS_CHECKUNSENTINTERVAL')
readonly checkUnsentInterval: number = 0; checkUnsentInterval = 0;
/** Endpoint to retrieve n8n version information from */ /** Endpoint to retrieve n8n version information from */
@Nested @Nested
readonly logWriter: LogWriterConfig; logWriter: LogWriterConfig;
/** Whether to recover execution details after a crash or only mark status executions as crashed. */ /** Whether to recover execution details after a crash or only mark status executions as crashed. */
@Env('N8N_EVENTBUS_RECOVERY_MODE') @Env('N8N_EVENTBUS_RECOVERY_MODE')
readonly crashRecoveryMode: 'simple' | 'extensive' = 'extensive'; crashRecoveryMode: 'simple' | 'extensive' = 'extensive';
} }

View file

@ -4,9 +4,9 @@ import { Config, Env } from '../decorators';
export class ExternalSecretsConfig { export class ExternalSecretsConfig {
/** How often (in seconds) to check for secret updates */ /** How often (in seconds) to check for secret updates */
@Env('N8N_EXTERNAL_SECRETS_UPDATE_INTERVAL') @Env('N8N_EXTERNAL_SECRETS_UPDATE_INTERVAL')
readonly updateInterval: number = 300; updateInterval = 300;
/** Whether to prefer GET over LIST when fetching secrets from Hashicorp Vault */ /** Whether to prefer GET over LIST when fetching secrets from Hashicorp Vault */
@Env('N8N_EXTERNAL_SECRETS_PREFER_GET') @Env('N8N_EXTERNAL_SECRETS_PREFER_GET')
readonly preferGet: boolean = false; preferGet = false;
} }

View file

@ -4,39 +4,39 @@ import { Config, Env, Nested } from '../decorators';
class S3BucketConfig { class S3BucketConfig {
/** Name of the n8n bucket in S3-compatible external storage */ /** Name of the n8n bucket in S3-compatible external storage */
@Env('N8N_EXTERNAL_STORAGE_S3_BUCKET_NAME') @Env('N8N_EXTERNAL_STORAGE_S3_BUCKET_NAME')
readonly name: string = ''; name = '';
/** Region of the n8n bucket in S3-compatible external storage @example "us-east-1" */ /** Region of the n8n bucket in S3-compatible external storage @example "us-east-1" */
@Env('N8N_EXTERNAL_STORAGE_S3_BUCKET_REGION') @Env('N8N_EXTERNAL_STORAGE_S3_BUCKET_REGION')
readonly region: string = ''; region = '';
} }
@Config @Config
class S3CredentialsConfig { class S3CredentialsConfig {
/** Access key in S3-compatible external storage */ /** Access key in S3-compatible external storage */
@Env('N8N_EXTERNAL_STORAGE_S3_ACCESS_KEY') @Env('N8N_EXTERNAL_STORAGE_S3_ACCESS_KEY')
readonly accessKey: string = ''; accessKey = '';
/** Access secret in S3-compatible external storage */ /** Access secret in S3-compatible external storage */
@Env('N8N_EXTERNAL_STORAGE_S3_ACCESS_SECRET') @Env('N8N_EXTERNAL_STORAGE_S3_ACCESS_SECRET')
readonly accessSecret: string = ''; accessSecret = '';
} }
@Config @Config
class S3Config { class S3Config {
/** Host of the n8n bucket in S3-compatible external storage @example "s3.us-east-1.amazonaws.com" */ /** Host of the n8n bucket in S3-compatible external storage @example "s3.us-east-1.amazonaws.com" */
@Env('N8N_EXTERNAL_STORAGE_S3_HOST') @Env('N8N_EXTERNAL_STORAGE_S3_HOST')
readonly host: string = ''; host = '';
@Nested @Nested
readonly bucket: S3BucketConfig; bucket: S3BucketConfig;
@Nested @Nested
readonly credentials: S3CredentialsConfig; credentials: S3CredentialsConfig;
} }
@Config @Config
export class ExternalStorageConfig { export class ExternalStorageConfig {
@Nested @Nested
readonly s3: S3Config; s3: S3Config;
} }

View file

@ -31,16 +31,16 @@ class CommunityPackagesConfig {
export class NodesConfig { export class NodesConfig {
/** Node types to load. Includes all if unspecified. @example '["n8n-nodes-base.hackerNews"]' */ /** Node types to load. Includes all if unspecified. @example '["n8n-nodes-base.hackerNews"]' */
@Env('NODES_INCLUDE') @Env('NODES_INCLUDE')
readonly include: JsonStringArray = []; include: JsonStringArray = [];
/** Node types not to load. Excludes none if unspecified. @example '["n8n-nodes-base.hackerNews"]' */ /** Node types not to load. Excludes none if unspecified. @example '["n8n-nodes-base.hackerNews"]' */
@Env('NODES_EXCLUDE') @Env('NODES_EXCLUDE')
readonly exclude: JsonStringArray = []; exclude: JsonStringArray = [];
/** Node type to use as error trigger */ /** Node type to use as error trigger */
@Env('NODES_ERROR_TRIGGER_TYPE') @Env('NODES_ERROR_TRIGGER_TYPE')
readonly errorTriggerType: string = 'n8n-nodes-base.errorTrigger'; errorTriggerType = 'n8n-nodes-base.errorTrigger';
@Nested @Nested
readonly communityPackages: CommunityPackagesConfig; communityPackages: CommunityPackagesConfig;
} }

View file

@ -4,13 +4,13 @@ import { Config, Env } from '../decorators';
export class PublicApiConfig { export class PublicApiConfig {
/** Whether to disable the Public API */ /** Whether to disable the Public API */
@Env('N8N_PUBLIC_API_DISABLED') @Env('N8N_PUBLIC_API_DISABLED')
readonly disabled: boolean = false; disabled = false;
/** Path segment for the Public API */ /** Path segment for the Public API */
@Env('N8N_PUBLIC_API_ENDPOINT') @Env('N8N_PUBLIC_API_ENDPOINT')
readonly path: string = 'api'; path = 'api';
/** Whether to disable the Swagger UI for the Public API */ /** Whether to disable the Swagger UI for the Public API */
@Env('N8N_PUBLIC_API_SWAGGERUI_DISABLED') @Env('N8N_PUBLIC_API_SWAGGERUI_DISABLED')
readonly swaggerUiDisabled: boolean = false; swaggerUiDisabled = false;
} }

View file

@ -4,9 +4,9 @@ import { Config, Env } from '../decorators';
export class TemplatesConfig { export class TemplatesConfig {
/** Whether to load workflow templates. */ /** Whether to load workflow templates. */
@Env('N8N_TEMPLATES_ENABLED') @Env('N8N_TEMPLATES_ENABLED')
readonly enabled: boolean = true; enabled = true;
/** Host to retrieve workflow templates from endpoints. */ /** Host to retrieve workflow templates from endpoints. */
@Env('N8N_TEMPLATES_HOST') @Env('N8N_TEMPLATES_HOST')
readonly host: string = 'https://api.n8n.io/api/'; host = 'https://api.n8n.io/api/';
} }

View file

@ -4,13 +4,13 @@ import { Config, Env } from '../decorators';
export class VersionNotificationsConfig { export class VersionNotificationsConfig {
/** Whether to request notifications about new n8n versions */ /** Whether to request notifications about new n8n versions */
@Env('N8N_VERSION_NOTIFICATIONS_ENABLED') @Env('N8N_VERSION_NOTIFICATIONS_ENABLED')
readonly enabled: boolean = true; enabled = true;
/** Endpoint to retrieve n8n version information from */ /** Endpoint to retrieve n8n version information from */
@Env('N8N_VERSION_NOTIFICATIONS_ENDPOINT') @Env('N8N_VERSION_NOTIFICATIONS_ENDPOINT')
readonly endpoint: string = 'https://api.n8n.io/api/versions/'; endpoint = 'https://api.n8n.io/api/versions/';
/** URL for versions panel to page instructing user on how to update n8n instance */ /** URL for versions panel to page instructing user on how to update n8n instance */
@Env('N8N_VERSION_NOTIFICATIONS_INFO_URL') @Env('N8N_VERSION_NOTIFICATIONS_INFO_URL')
readonly infoUrl: string = 'https://docs.n8n.io/hosting/installation/updating/'; infoUrl = 'https://docs.n8n.io/hosting/installation/updating/';
} }

View file

@ -4,17 +4,14 @@ import { Config, Env } from '../decorators';
export class WorkflowsConfig { export class WorkflowsConfig {
/** Default name for workflow */ /** Default name for workflow */
@Env('WORKFLOWS_DEFAULT_NAME') @Env('WORKFLOWS_DEFAULT_NAME')
readonly defaultName: string = 'My workflow'; defaultName = 'My workflow';
/** Show onboarding flow in new workflow */ /** Show onboarding flow in new workflow */
@Env('N8N_ONBOARDING_FLOW_DISABLED') @Env('N8N_ONBOARDING_FLOW_DISABLED')
readonly onboardingFlowDisabled: boolean = false; onboardingFlowDisabled = false;
/** Default option for which workflows may call the current workflow */ /** Default option for which workflows may call the current workflow */
@Env('N8N_WORKFLOW_CALLER_POLICY_DEFAULT_OPTION') @Env('N8N_WORKFLOW_CALLER_POLICY_DEFAULT_OPTION')
readonly callerPolicyDefaultOption: callerPolicyDefaultOption: 'any' | 'none' | 'workflowsFromAList' | 'workflowsFromSameOwner' =
| 'any' 'workflowsFromSameOwner';
| 'none'
| 'workflowsFromAList'
| 'workflowsFromSameOwner' = 'workflowsFromSameOwner';
} }

View file

@ -11,6 +11,7 @@ import { NodesConfig } from './configs/nodes';
import { ExternalStorageConfig } from './configs/external-storage'; import { ExternalStorageConfig } from './configs/external-storage';
import { WorkflowsConfig } from './configs/workflows'; import { WorkflowsConfig } from './configs/workflows';
import { EndpointsConfig } from './configs/endpoints'; import { EndpointsConfig } from './configs/endpoints';
import { CacheConfig } from './configs/cache';
@Config @Config
class UserManagementConfig { class UserManagementConfig {
@ -75,4 +76,7 @@ export class GlobalConfig {
@Nested @Nested
readonly endpoints: EndpointsConfig; readonly endpoints: EndpointsConfig;
@Nested
readonly cache: CacheConfig;
} }

View file

@ -172,6 +172,17 @@ describe('GlobalConfig', () => {
webhookTest: 'webhook-test', webhookTest: 'webhook-test',
webhookWaiting: 'webhook-waiting', webhookWaiting: 'webhook-waiting',
}, },
cache: {
backend: 'auto',
memory: {
maxSize: 3145728,
ttl: 3600000,
},
redis: {
prefix: 'redis',
ttl: 3600000,
},
},
}; };
it('should use all default values when no env variables are defined', () => { it('should use all default values when no env variables are defined', () => {

View file

@ -660,43 +660,6 @@ export const schema = {
}, },
}, },
cache: {
backend: {
doc: 'Backend to use for caching',
format: ['memory', 'redis', 'auto'] as const,
default: 'auto',
env: 'N8N_CACHE_BACKEND',
},
memory: {
maxSize: {
doc: 'Maximum size of memory cache in bytes',
format: Number,
default: 3 * 1024 * 1024, // 3 MB
env: 'N8N_CACHE_MEMORY_MAX_SIZE',
},
ttl: {
doc: 'Time to live for cached items in memory (in ms)',
format: Number,
default: 3600 * 1000, // 1 hour
env: 'N8N_CACHE_MEMORY_TTL',
},
},
redis: {
prefix: {
doc: 'Prefix for all cache keys',
format: String,
default: 'cache',
env: 'N8N_CACHE_REDIS_KEY_PREFIX',
},
ttl: {
doc: 'Time to live for cached items in redis (in ms), 0 for no TTL',
format: Number,
default: 3600 * 1000, // 1 hour
env: 'N8N_CACHE_REDIS_TTL',
},
},
},
/** /**
* @important Do not remove until after cloud hooks are updated to stop using convict config. * @important Do not remove until after cloud hooks are updated to stop using convict config.
*/ */

View file

@ -13,6 +13,7 @@ import type {
} from '@/services/cache/cache.types'; } from '@/services/cache/cache.types';
import { TIME } from '@/constants'; import { TIME } from '@/constants';
import { TypedEmitter } from '@/TypedEmitter'; import { TypedEmitter } from '@/TypedEmitter';
import { GlobalConfig } from '@n8n/config';
type CacheEvents = { type CacheEvents = {
'metrics.cache.hit': never; 'metrics.cache.hit': never;
@ -22,12 +23,15 @@ type CacheEvents = {
@Service() @Service()
export class CacheService extends TypedEmitter<CacheEvents> { export class CacheService extends TypedEmitter<CacheEvents> {
constructor(private readonly globalConfig: GlobalConfig) {
super();
}
private cache: TaggedRedisCache | TaggedMemoryCache; private cache: TaggedRedisCache | TaggedMemoryCache;
async init() { async init() {
const backend = config.getEnv('cache.backend'); const { backend } = this.globalConfig.cache;
const mode = config.getEnv('executions.mode'); const mode = config.getEnv('executions.mode');
const ttl = config.getEnv('cache.redis.ttl');
const useRedis = backend === 'redis' || (backend === 'auto' && mode === 'queue'); const useRedis = backend === 'redis' || (backend === 'auto' && mode === 'queue');
@ -36,8 +40,9 @@ export class CacheService extends TypedEmitter<CacheEvents> {
const redisClientService = Container.get(RedisClientService); const redisClientService = Container.get(RedisClientService);
const prefixBase = config.getEnv('redis.prefix'); const prefixBase = config.getEnv('redis.prefix');
const cachePrefix = config.getEnv('cache.redis.prefix'); const prefix = redisClientService.toValidPrefix(
const prefix = redisClientService.toValidPrefix(`${prefixBase}:${cachePrefix}:`); `${prefixBase}:${this.globalConfig.cache.redis.prefix}:`,
);
const redisClient = redisClientService.createClient({ const redisClient = redisClientService.createClient({
type: 'client(cache)', type: 'client(cache)',
@ -45,7 +50,9 @@ export class CacheService extends TypedEmitter<CacheEvents> {
}); });
const { redisStoreUsingClient } = await import('@/services/cache/redis.cache-manager'); const { redisStoreUsingClient } = await import('@/services/cache/redis.cache-manager');
const redisStore = redisStoreUsingClient(redisClient, { ttl }); const redisStore = redisStoreUsingClient(redisClient, {
ttl: this.globalConfig.cache.redis.ttl,
});
const redisCache = await caching(redisStore); const redisCache = await caching(redisStore);
@ -54,7 +61,7 @@ export class CacheService extends TypedEmitter<CacheEvents> {
return; return;
} }
const maxSize = config.getEnv('cache.memory.maxSize'); const { maxSize, ttl } = this.globalConfig.cache.memory;
const sizeCalculation = (item: unknown) => { const sizeCalculation = (item: unknown) => {
const str = jsonStringify(item, { replaceCircularRefs: true }); const str = jsonStringify(item, { replaceCircularRefs: true });

View file

@ -1,8 +1,13 @@
import { ActivationErrorsService } from '@/ActivationErrors.service'; import { ActivationErrorsService } from '@/ActivationErrors.service';
import { CacheService } from '@/services/cache/cache.service'; import { CacheService } from '@/services/cache/cache.service';
import { GlobalConfig } from '@n8n/config';
import { mockInstance } from '@test/mocking';
describe('ActivationErrorsService', () => { describe('ActivationErrorsService', () => {
const cacheService = new CacheService(); const globalConfig = mockInstance(GlobalConfig, {
cache: { backend: 'memory', memory: { maxSize: 3 * 1024 * 1024, ttl: 3600 * 1000 } },
});
const cacheService = new CacheService(globalConfig);
const activationErrorsService = new ActivationErrorsService(cacheService); const activationErrorsService = new ActivationErrorsService(cacheService);
const firstWorkflowId = 'GSG0etbfTA2CNPDX'; const firstWorkflowId = 'GSG0etbfTA2CNPDX';

View file

@ -12,8 +12,8 @@ jest.unmock('@/eventbus/MessageEventBus/MessageEventBus');
const toLines = (response: Response) => response.text.trim().split('\n'); const toLines = (response: Response) => response.text.trim().split('\n');
const globalConfig = Container.get(GlobalConfig); const globalConfig = Container.get(GlobalConfig);
// @ts-expect-error `metrics` is a readonly property
globalConfig.endpoints.metrics = { globalConfig.endpoints.metrics = {
enable: true,
prefix: 'n8n_test_', prefix: 'n8n_test_',
includeDefaultMetrics: true, includeDefaultMetrics: true,
includeApiEndpoints: true, includeApiEndpoints: true,

View file

@ -1,6 +1,8 @@
import { CacheService } from '@/services/cache/cache.service'; import { CacheService } from '@/services/cache/cache.service';
import config from '@/config'; import config from '@/config';
import { sleep } from 'n8n-workflow'; import { sleep } from 'n8n-workflow';
import { GlobalConfig } from '@n8n/config';
import Container from 'typedi';
jest.mock('ioredis', () => { jest.mock('ioredis', () => {
const Redis = require('ioredis-mock'); const Redis = require('ioredis-mock');
@ -13,10 +15,12 @@ jest.mock('ioredis', () => {
for (const backend of ['memory', 'redis'] as const) { for (const backend of ['memory', 'redis'] as const) {
describe(backend, () => { describe(backend, () => {
let cacheService: CacheService; let cacheService: CacheService;
let globalConfig: GlobalConfig;
beforeAll(async () => { beforeAll(async () => {
config.set('cache.backend', backend); globalConfig = Container.get(GlobalConfig);
cacheService = new CacheService(); globalConfig.cache.backend = backend;
cacheService = new CacheService(globalConfig);
await cacheService.init(); await cacheService.init();
}); });
@ -43,7 +47,7 @@ for (const backend of ['memory', 'redis'] as const) {
if (backend === 'memory') { if (backend === 'memory') {
test('should honor max size when enough', async () => { test('should honor max size when enough', async () => {
config.set('cache.memory.maxSize', 16); // enough bytes for "withoutUnicode" globalConfig.cache.memory.maxSize = 16; // enough bytes for "withoutUnicode"
await cacheService.init(); await cacheService.init();
await cacheService.set('key', 'withoutUnicode'); await cacheService.set('key', 'withoutUnicode');
@ -51,12 +55,12 @@ for (const backend of ['memory', 'redis'] as const) {
await expect(cacheService.get('key')).resolves.toBe('withoutUnicode'); await expect(cacheService.get('key')).resolves.toBe('withoutUnicode');
// restore // restore
config.set('cache.memory.maxSize', 3 * 1024 * 1024); globalConfig.cache.memory.maxSize = 3 * 1024 * 1024;
await cacheService.init(); await cacheService.init();
}); });
test('should honor max size when not enough', async () => { test('should honor max size when not enough', async () => {
config.set('cache.memory.maxSize', 16); // not enough bytes for "withUnicodeԱԲԳ" globalConfig.cache.memory.maxSize = 16; // not enough bytes for "withUnicodeԱԲԳ"
await cacheService.init(); await cacheService.init();
await cacheService.set('key', 'withUnicodeԱԲԳ'); await cacheService.set('key', 'withUnicodeԱԲԳ');
@ -64,7 +68,8 @@ for (const backend of ['memory', 'redis'] as const) {
await expect(cacheService.get('key')).resolves.toBeUndefined(); await expect(cacheService.get('key')).resolves.toBeUndefined();
// restore // restore
config.set('cache.memory.maxSize', 3 * 1024 * 1024); globalConfig.cache.memory.maxSize = 3 * 1024 * 1024;
// restore
await cacheService.init(); await cacheService.init();
}); });
} }