n8n
Version:
n8n Workflow Automation Tool
273 lines • 12.7 kB
JavaScript
;
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.WorkflowIndexService = void 0;
const backend_common_1 = require("@n8n/backend-common");
const config_1 = require("@n8n/config");
const db_1 = require("@n8n/db");
const di_1 = require("@n8n/di");
const n8n_core_1 = require("n8n-core");
const n8n_workflow_1 = require("n8n-workflow");
const event_service_1 = require("../../events/event.service");
const LOOP_LIMIT = 1_000_000_000;
const WORKFLOW_INDEXED_PLACEHOLDER_KEY = '__INDEXED__';
let WorkflowIndexService = class WorkflowIndexService {
constructor(dependencyRepository, workflowRepository, eventService, logger, errorReporter, tracing, workflowsConfig) {
this.dependencyRepository = dependencyRepository;
this.workflowRepository = workflowRepository;
this.eventService = eventService;
this.logger = logger;
this.errorReporter = errorReporter;
this.tracing = tracing;
this.batchSize = workflowsConfig.indexingBatchSize;
}
init() {
this.eventService.on('server-started', async () => {
this.logger.info('Building workflow dependency index...');
await this.buildIndex().catch((e) => this.errorReporter.error(e));
});
this.eventService.on('workflow-created', async ({ workflow }) => {
await this.updateIndexForDraft(workflow);
});
this.eventService.on('workflow-saved', async ({ workflow }) => {
await this.updateIndexForDraft(workflow);
});
this.eventService.on('workflow-deleted', async ({ workflowId }) => {
await this.removeDependenciesForWorkflow(workflowId);
});
this.eventService.on('workflow-activated', async ({ workflow }) => {
if (workflow.activeVersionId === null) {
this.logger.warn(`Workflow ${workflow.id} activated with null activeVersionId. Skipping index update.`);
return;
}
await this.updateIndexForPublished(workflow, workflow.activeVersionId, workflow.nodes);
});
}
async buildIndex() {
return await this.tracing.startSpan({ name: 'WorkflowIndex build', op: 'workflow-index.build' }, async (span) => {
const draftCount = await this.buildIndexInternal(async (batchSize) => await this.workflowRepository.findWorkflowsNeedingIndexing(batchSize), 'draft');
const publishedCount = await this.buildIndexInternal(async (batchSize) => await this.workflowRepository.findWorkflowsNeedingPublishedVersionIndexing(batchSize), 'published');
this.logger.info(`Finished building workflow dependency index. Processed ${draftCount} draft workflows, ${publishedCount} published workflows.`);
span.setStatus({ code: 1 });
});
}
async buildIndexInternal(unindexedWorkflowFinder, dependencyType) {
const batchSize = this.batchSize;
let processedCount = 0;
while (processedCount < LOOP_LIMIT) {
const workflows = await unindexedWorkflowFinder(batchSize);
if (workflows.length === 0) {
break;
}
for (const workflow of workflows) {
if (dependencyType === 'draft') {
await this.updateIndexForDraft(workflow);
}
else {
const publishedNodes = workflow.activeVersion?.nodes;
if (!publishedNodes) {
this.logger.warn(`Workflow ${workflow.id} has activeVersionId but no activeVersion nodes. Skipping published index.`);
continue;
}
await this.updateIndexForPublished(workflow, workflow.activeVersionId, publishedNodes);
}
}
processedCount += workflows.length;
this.logger.debug(`Indexed ${processedCount} ${dependencyType} workflows so far`);
if (workflows.length < batchSize) {
break;
}
}
if (processedCount >= LOOP_LIMIT) {
const message = `Stopping ${dependencyType} workflow indexing because we hit the limit of ${LOOP_LIMIT} workflows. There's probably a bug causing an infinite loop.`;
this.logger.warn(message);
this.errorReporter.warn(new Error(message));
}
return processedCount;
}
async updateIndexForDraft(workflow) {
const dependencyUpdates = new db_1.WorkflowDependencies(workflow.id, workflow.versionCounter, null);
return await this.updateIndexInternal(dependencyUpdates, workflow.nodes, workflow.name, workflow.settings);
}
async updateIndexForPublished(workflow, publishedVersionId, publishedNodes) {
const dependencyUpdates = new db_1.WorkflowDependencies(workflow.id, workflow.versionCounter, publishedVersionId);
return await this.updateIndexInternal(dependencyUpdates, publishedNodes, workflow.name, workflow.settings);
}
async removeDependenciesForWorkflow(workflowId) {
return await this.tracing.startSpan({
name: 'WorkflowIndex remove',
op: 'workflow-index.remove',
attributes: this.tracing.pickWorkflowAttributes({ id: workflowId }),
}, async (span) => {
await this.dependencyRepository.removeDependenciesForWorkflow(workflowId);
span.setStatus({ code: 1 });
});
}
async updateIndexInternal(dependencyUpdates, nodes, workflowName, settings) {
const indexType = dependencyUpdates.publishedVersionId ? 'published' : 'draft';
const workflowId = dependencyUpdates.workflowId;
return await this.tracing.startSpan({
name: 'WorkflowIndex update',
op: 'workflow-index.update',
attributes: {
...this.tracing.pickWorkflowAttributes({ id: workflowId, name: workflowName }),
'n8n.workflow-index.type': indexType,
},
}, async (span) => {
nodes.forEach((node) => {
this.addNodeTypeDependencies(node, dependencyUpdates);
this.addCredentialDependencies(node, dependencyUpdates);
this.addDataTableDependencies(node, dependencyUpdates);
this.addWorkflowCallDependencies(node, dependencyUpdates);
this.addWebhookPathDependencies(node, dependencyUpdates);
});
this.addErrorWorkflowDependency(settings, dependencyUpdates);
if (dependencyUpdates.dependencies.length === 0) {
dependencyUpdates.add({
dependencyType: 'workflowIndexed',
dependencyKey: WORKFLOW_INDEXED_PLACEHOLDER_KEY,
dependencyInfo: null,
});
}
let updated;
try {
updated = await this.dependencyRepository.updateDependenciesForWorkflow(workflowId, dependencyUpdates);
}
catch (e) {
const error = (0, n8n_workflow_1.ensureError)(e);
this.logger.error(`Failed to update workflow ${indexType} dependency index for workflow ${workflowId}: ${error.message}`);
this.errorReporter.error(error);
span.setStatus({ code: 2 });
return;
}
this.logger.debug(`Workflow ${indexType} dependency index ${updated ? 'updated' : 'skipped'} for workflow ${workflowId}`);
span.setStatus({ code: 1 });
});
}
addNodeTypeDependencies(node, dependencyUpdates) {
if (node.type) {
dependencyUpdates.add({
dependencyType: 'nodeType',
dependencyKey: node.type,
dependencyInfo: { nodeId: node.id, nodeVersion: node.typeVersion },
});
}
}
addCredentialDependencies(node, dependencyUpdates) {
if (!node.credentials) {
return;
}
for (const credentialDetails of Object.values(node.credentials)) {
const { id } = credentialDetails;
if (!id) {
continue;
}
dependencyUpdates.add({
dependencyType: 'credentialId',
dependencyKey: id,
dependencyInfo: { nodeId: node.id, nodeVersion: node.typeVersion },
});
}
}
addDataTableDependencies(node, dependencyUpdates) {
if (!n8n_workflow_1.DATA_TABLE_NODE_TYPES.includes(node.type)) {
return;
}
const dataTableId = node.parameters?.['dataTableId'];
if (!dataTableId?.value || typeof dataTableId.value !== 'string') {
return;
}
if (dataTableId.value.includes('{')) {
return;
}
dependencyUpdates.add({
dependencyType: 'dataTableId',
dependencyKey: dataTableId.value,
dependencyInfo: { nodeId: node.id, nodeVersion: node.typeVersion, mode: dataTableId.mode },
});
}
addWorkflowCallDependencies(node, dependencyUpdates) {
if (node.type !== 'n8n-nodes-base.executeWorkflow') {
return;
}
const calledWorkflowId = this.getCalledWorkflowIdFrom(node);
if (!calledWorkflowId) {
return;
}
dependencyUpdates.add({
dependencyType: 'workflowCall',
dependencyKey: calledWorkflowId,
dependencyInfo: { nodeId: node.id, nodeVersion: node.typeVersion },
});
}
addWebhookPathDependencies(node, dependencyUpdates) {
if (node.type !== 'n8n-nodes-base.webhook') {
return;
}
const webhookPath = node.parameters.path;
if (webhookPath) {
dependencyUpdates.add({
dependencyType: 'webhookPath',
dependencyKey: webhookPath,
dependencyInfo: { nodeId: node.id, nodeVersion: node.typeVersion },
});
}
}
addErrorWorkflowDependency(settings, dependencyUpdates) {
const errorWorkflowId = settings?.errorWorkflow;
if (!errorWorkflowId || errorWorkflowId === 'DEFAULT') {
return;
}
dependencyUpdates.add({
dependencyType: 'errorWorkflow',
dependencyKey: errorWorkflowId,
dependencyInfo: null,
});
}
getCalledWorkflowIdFrom(node) {
if (node.parameters?.['source'] === 'parameter') {
return undefined;
}
if (node.parameters?.['source'] === 'localFile') {
return undefined;
}
if (node.parameters?.['source'] === 'url') {
return undefined;
}
if (!('workflowId' in node.parameters)) {
return undefined;
}
if (typeof node.parameters?.['workflowId'] === 'string') {
return node.parameters?.['workflowId'];
}
if (node.parameters &&
typeof node.parameters['workflowId'] === 'object' &&
node.parameters['workflowId'] !== null &&
'value' in node.parameters['workflowId'] &&
typeof node.parameters['workflowId']['value'] === 'string') {
return node.parameters['workflowId']['value'];
}
this.errorReporter.warn(`While indexing, could not determine called workflow ID from executeWorkflow node ${node.id}`, { extra: node.parameters });
return undefined;
}
};
exports.WorkflowIndexService = WorkflowIndexService;
exports.WorkflowIndexService = WorkflowIndexService = __decorate([
(0, di_1.Service)(),
__metadata("design:paramtypes", [db_1.WorkflowDependencyRepository,
db_1.WorkflowRepository,
event_service_1.EventService,
backend_common_1.Logger,
n8n_core_1.ErrorReporter,
n8n_core_1.Tracing,
config_1.WorkflowsConfig])
], WorkflowIndexService);
//# sourceMappingURL=workflow-index.service.js.map