n8n/packages/cli/src/scaling/pubsub/publisher.service.ts
Iván Ovejero fe7fb41ad8
Some checks failed
Test Master / install-and-build (push) Has been cancelled
Test Master / Unit tests (18.x) (push) Has been cancelled
Test Master / Unit tests (20.x) (push) Has been cancelled
Test Master / Unit tests (22.4) (push) Has been cancelled
Test Master / Lint (push) Has been cancelled
Test Master / Notify Slack on failure (push) Has been cancelled
refactor(core): Fix push message type inference (#12331)
2024-12-20 19:45:04 +01:00

110 lines
3 KiB
TypeScript

import type { Redis as SingleNodeClient, Cluster as MultiNodeClient } from 'ioredis';
import { InstanceSettings } from 'n8n-core';
import { Service } from 'typedi';
import config from '@/config';
import { Logger } from '@/logging/logger.service';
import type { LogMetadata } from '@/logging/types';
import { RedisClientService } from '@/services/redis-client.service';
import type { PubSub } from './pubsub.types';
import { IMMEDIATE_COMMANDS, SELF_SEND_COMMANDS } from '../constants';
/**
* Responsible for publishing messages into the pubsub channels used by scaling mode.
*/
@Service()
export class Publisher {
private readonly client: SingleNodeClient | MultiNodeClient;
// #region Lifecycle
constructor(
private readonly logger: Logger,
private readonly redisClientService: RedisClientService,
private readonly instanceSettings: InstanceSettings,
) {
// @TODO: Once this class is only ever initialized in scaling mode, assert in the next line.
if (config.getEnv('executions.mode') !== 'queue') return;
this.logger = this.logger.scoped(['scaling', 'pubsub']);
this.client = this.redisClientService.createClient({ type: 'publisher(n8n)' });
}
getClient() {
return this.client;
}
// @TODO: Use `@OnShutdown()` decorator
shutdown() {
this.client.disconnect();
}
// #endregion
// #region Publishing
/** Publish a command into the `n8n.commands` channel. */
async publishCommand(msg: PubSub.Command) {
// @TODO: Once this class is only ever used in scaling mode, remove next line.
if (config.getEnv('executions.mode') !== 'queue') return;
await this.client.publish(
'n8n.commands',
JSON.stringify({
...msg,
senderId: this.instanceSettings.hostId,
selfSend: SELF_SEND_COMMANDS.has(msg.command),
debounce: !IMMEDIATE_COMMANDS.has(msg.command),
}),
);
let msgName = msg.command;
const metadata: LogMetadata = { msg: msg.command, channel: 'n8n.commands' };
if (msg.command === 'relay-execution-lifecycle-event') {
const { data, type } = msg.payload;
msgName += ` (${type})`;
metadata.type = type;
if ('executionId' in data) metadata.executionId = data.executionId;
}
this.logger.debug(`Published pubsub msg: ${msgName}`, metadata);
}
/** Publish a response to a command into the `n8n.worker-response` channel. */
async publishWorkerResponse(msg: PubSub.WorkerResponse) {
await this.client.publish('n8n.worker-response', JSON.stringify(msg));
this.logger.debug(`Published ${msg.response} to worker response channel`);
}
// #endregion
// #region Utils for multi-main setup
// @TODO: The following methods are not pubsub-specific. Consider a dedicated client for multi-main setup.
async setIfNotExists(key: string, value: string) {
const success = await this.client.setnx(key, value);
return !!success;
}
async setExpiration(key: string, ttl: number) {
await this.client.expire(key, ttl);
}
async get(key: string) {
return await this.client.get(key);
}
async clear(key: string) {
await this.client?.del(key);
}
// #endregion
}