n8n
Version:
n8n Workflow Automation Tool
982 lines • 102 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
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 __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.InstanceAiAdapterService = void 0;
exports.resolveDataTableByIdOrName = resolveDataTableByIdOrName;
exports.truncateResultData = truncateResultData;
exports.extractExecutionResult = extractExecutionResult;
exports.formatExecutionError = formatExecutionError;
exports.truncateNodeOutput = truncateNodeOutput;
exports.extractNodeOutput = extractNodeOutput;
exports.extractExecutionDebugInfo = extractExecutionDebugInfo;
const node_crypto_1 = require("node:crypto");
const promises_1 = require("node:fs/promises");
const node_path_1 = __importDefault(require("node:path"));
const instance_ai_1 = require("@n8n/instance-ai");
const config_1 = require("@n8n/config");
const constants_1 = require("@n8n/constants");
const instance_ai_settings_service_1 = require("./instance-ai-settings.service");
const node_definition_resolver_1 = require("./node-definition-resolver");
const web_research_1 = require("./web-research");
const db_1 = require("@n8n/db");
const backend_common_1 = require("@n8n/backend-common");
const di_1 = require("@n8n/di");
const permissions_1 = require("@n8n/permissions");
const typeorm_1 = require("@n8n/typeorm");
const n8n_workflow_1 = require("n8n-workflow");
const n8n_core_1 = require("n8n-core");
const active_executions_1 = require("../../active-executions");
const credentials_finder_service_1 = require("../../credentials/credentials-finder.service");
const credentials_service_1 = require("../../credentials/credentials.service");
const event_service_1 = require("../../events/event.service");
const execution_persistence_1 = require("../../executions/execution-persistence");
const license_1 = require("../../license");
const load_nodes_and_credentials_1 = require("../../load-nodes-and-credentials");
const node_types_1 = require("../../node-types");
const data_table_repository_1 = require("../../modules/data-table/data-table.repository");
const data_table_service_1 = require("../../modules/data-table/data-table.service");
const source_control_preferences_service_ee_1 = require("../../modules/source-control.ee/source-control-preferences.service.ee");
const check_access_1 = require("../../permissions.ee/check-access");
const dynamic_node_parameters_service_1 = require("../../services/dynamic-node-parameters.service");
const folder_service_1 = require("../../services/folder.service");
const project_service_ee_1 = require("../../services/project.service.ee");
const role_service_1 = require("../../services/role.service");
const ssrf_protection_service_1 = require("../../services/ssrf/ssrf-protection.service");
const tag_service_1 = require("../../services/tag.service");
const workflow_finder_service_1 = require("../../workflows/workflow-finder.service");
const workflow_history_service_1 = require("../../workflows/workflow-history/workflow-history.service");
const workflow_service_1 = require("../../workflows/workflow.service");
const workflow_service_ee_1 = require("../../workflows/workflow.service.ee");
const telemetry_1 = require("../../telemetry");
const workflow_runner_1 = require("../../workflow-runner");
const workflow_execute_additional_data_1 = require("../../workflow-execute-additional-data");
function resolveDisplayedDefaults(nodeProperties, parameters, nodeType, typeVersion, desc) {
const stubNode = {
id: '',
name: '',
type: nodeType,
typeVersion,
parameters: parameters,
position: [0, 0],
};
const resolved = n8n_workflow_1.NodeHelpers.getNodeParameters(nodeProperties, parameters, true, false, stubNode, desc);
return resolved ?? parameters;
}
let InstanceAiAdapterService = class InstanceAiAdapterService {
async getNodesFromCache() {
if (this.nodesCache && Date.now() < this.nodesCache.expiresAt) {
return await this.nodesCache.promise;
}
const filePath = node_path_1.default.join(this.instanceSettings.staticCacheDir, 'types/nodes.json');
const promise = (0, promises_1.readFile)(filePath, 'utf-8').then((json) => (0, n8n_workflow_1.jsonParse)(json));
this.nodesCache = { promise, expiresAt: Date.now() + this.NODES_CACHE_TTL_MS };
promise.catch(() => {
this.nodesCache = null;
});
return await promise;
}
constructor(logger, globalConfig, workflowService, workflowFinderService, workflowRepository, sharedWorkflowRepository, projectRepository, executionRepository, credentialsService, credentialsFinderService, activeExecutions, workflowRunner, loadNodesAndCredentials, nodeTypes, instanceSettings, dataTableService, dataTableRepository, dynamicNodeParametersService, folderService, projectService, tagService, sourceControlPreferencesService, settingsService, workflowHistoryService, enterpriseWorkflowService, license, executionPersistence, eventService, roleService, telemetry, aiBuilderTemporaryWorkflowRepository, ssrfProtectionService) {
this.workflowService = workflowService;
this.workflowFinderService = workflowFinderService;
this.workflowRepository = workflowRepository;
this.sharedWorkflowRepository = sharedWorkflowRepository;
this.projectRepository = projectRepository;
this.executionRepository = executionRepository;
this.credentialsService = credentialsService;
this.credentialsFinderService = credentialsFinderService;
this.activeExecutions = activeExecutions;
this.workflowRunner = workflowRunner;
this.loadNodesAndCredentials = loadNodesAndCredentials;
this.nodeTypes = nodeTypes;
this.instanceSettings = instanceSettings;
this.dataTableService = dataTableService;
this.dataTableRepository = dataTableRepository;
this.dynamicNodeParametersService = dynamicNodeParametersService;
this.folderService = folderService;
this.projectService = projectService;
this.tagService = tagService;
this.sourceControlPreferencesService = sourceControlPreferencesService;
this.settingsService = settingsService;
this.workflowHistoryService = workflowHistoryService;
this.enterpriseWorkflowService = enterpriseWorkflowService;
this.license = license;
this.executionPersistence = executionPersistence;
this.eventService = eventService;
this.roleService = roleService;
this.telemetry = telemetry;
this.aiBuilderTemporaryWorkflowRepository = aiBuilderTemporaryWorkflowRepository;
this.ssrfProtectionService = ssrfProtectionService;
this.nodesCache = null;
this.NODES_CACHE_TTL_MS = 5 * 60 * 1000;
this.webResearchCache = new web_research_1.LRUCache({
maxEntries: 100,
ttlMs: 15 * 60 * 1000,
});
this.searchCache = new web_research_1.LRUCache({
maxEntries: 100,
ttlMs: 15 * 60 * 1000,
});
this.logger = logger.scoped('instance-ai');
this.allowSendingParameterValues = globalConfig.ai.allowSendingParameterValues;
}
createContext(user, options) {
const { searchProxyConfig, pushRef, threadId } = options ?? {};
return {
userId: user.id,
workflowService: this.createWorkflowAdapter(user, threadId),
executionService: this.createExecutionAdapter(user, pushRef, threadId),
credentialService: this.createCredentialAdapter(user),
nodeService: this.createNodeAdapter(user),
dataTableService: this.createDataTableAdapter(user),
webResearchService: this.createWebResearchAdapter(user, searchProxyConfig),
workspaceService: this.createWorkspaceAdapter(user),
licenseHints: this.buildLicenseHints(),
logger: this.logger,
nodeTypesProvider: this.nodeTypes,
};
}
buildLicenseHints() {
const hints = [];
if (!this.license.isLicensed('feat:namedVersions')) {
hints.push('**Named workflow versions** — naming and describing workflow versions (update-workflow-version) is available on the Pro plan and above.');
}
if (!this.license.isLicensed('feat:folders')) {
hints.push('**Folders** — organizing workflows into folders (list-folders, create-folder, delete-folder, move-workflow-to-folder) is available on registered Community Edition or paid plans.');
}
return hints;
}
assertInstanceNotReadOnly(resourceType) {
if (this.sourceControlPreferencesService.getPreferences().branchReadOnly) {
throw new Error(`Cannot modify ${resourceType} on a protected instance. This instance is in read-only mode.`);
}
}
createProjectScopeHelpers(user) {
const { projectRepository } = this;
let personalProjectIdPromise = null;
const getPersonalProjectId = async () => {
personalProjectIdPromise ??= projectRepository
.getPersonalProjectForUserOrFail(user.id)
.then((p) => p.id);
return await personalProjectIdPromise;
};
const assertProjectScope = async (scopes, projectId) => {
const allowed = await (0, check_access_1.userHasScopes)(user, scopes, false, { projectId });
if (!allowed) {
throw new Error('User does not have the required permissions in this project');
}
};
const resolveProjectId = async (scopes, providedProjectId) => {
const projectId = providedProjectId ?? (await getPersonalProjectId());
await assertProjectScope(scopes, projectId);
return projectId;
};
return { getPersonalProjectId, assertProjectScope, resolveProjectId };
}
createWorkflowAdapter(user, threadId) {
const { workflowService, workflowFinderService, workflowRepository, sharedWorkflowRepository, aiBuilderTemporaryWorkflowRepository, workflowHistoryService, enterpriseWorkflowService, license, allowSendingParameterValues, telemetry, } = this;
const assertNotReadOnly = () => this.assertInstanceNotReadOnly('workflows');
const { resolveProjectId } = this.createProjectScopeHelpers(user);
const redactParameters = !allowSendingParameterValues;
return {
async list(options) {
const filter = {
...(options?.status === 'all' ? {} : { isArchived: options?.status === 'archived' }),
...(options?.query ? { query: options.query } : {}),
};
const { workflows } = await workflowService.getMany(user, {
take: options?.limit ?? 50,
filter,
});
return workflows
.filter((wf) => 'versionId' in wf)
.map((wf) => ({
id: wf.id,
name: wf.name,
versionId: wf.versionId,
activeVersionId: wf.activeVersionId ?? null,
isArchived: wf.isArchived,
createdAt: wf.createdAt.toISOString(),
updatedAt: wf.updatedAt.toISOString(),
}));
},
async get(workflowId) {
const workflow = await workflowFinderService.findWorkflowForUser(workflowId, user, [
'workflow:read',
]);
if (!workflow) {
throw new Error(`Workflow ${workflowId} not found or not accessible`);
}
return toWorkflowDetail(workflow, { redactParameters });
},
async archive(workflowId) {
assertNotReadOnly();
const result = await workflowService.archive(user, workflowId, { skipArchived: true });
if (!result) {
throw new Error(`Workflow ${workflowId} not found or not accessible`);
}
},
async unarchive(workflowId) {
assertNotReadOnly();
const result = await workflowService.unarchive(user, workflowId);
if (!result) {
throw new Error(`Workflow ${workflowId} not found or not accessible`);
}
},
async clearAiTemporary(workflowId) {
assertNotReadOnly();
const workflow = await workflowFinderService.findWorkflowForUser(workflowId, user, [
'workflow:update',
]);
if (!workflow)
return;
if (!(await aiBuilderTemporaryWorkflowRepository.existsForWorkflow(workflowId)))
return;
await aiBuilderTemporaryWorkflowRepository.unmark(workflowId);
},
async archiveIfAiTemporary(workflowId) {
assertNotReadOnly();
const workflow = await workflowFinderService.findWorkflowForUser(workflowId, user, [
'workflow:update',
]);
if (!workflow)
return false;
if (!(await aiBuilderTemporaryWorkflowRepository.existsForWorkflow(workflowId))) {
return false;
}
if (workflow.isArchived) {
await aiBuilderTemporaryWorkflowRepository.unmark(workflowId);
return false;
}
await workflowService.archive(user, workflowId, { skipArchived: true });
await aiBuilderTemporaryWorkflowRepository.unmark(workflowId);
return true;
},
async publish(workflowId, options) {
const wf = await workflowService.activateWorkflow(user, workflowId, {
versionId: options?.versionId,
name: options?.name,
description: options?.description,
source: 'n8n-ai',
});
if (!wf.activeVersionId) {
throw new Error(`Workflow ${workflowId} was not activated — no active version set`);
}
if (threadId) {
telemetry.track('Builder published workflow', {
thread_id: threadId,
workflow_id: workflowId,
executed_by: 'ai',
});
}
return { activeVersionId: wf.activeVersionId };
},
async unpublish(workflowId) {
await workflowService.deactivateWorkflow(user, workflowId, {
source: 'n8n-ai',
});
},
async getAsWorkflowJSON(workflowId) {
const wf = await workflowFinderService.findWorkflowForUser(workflowId, user, [
'workflow:read',
]);
if (!wf)
throw new Error(`Workflow ${workflowId} not found or not accessible`);
return toWorkflowJSON(wf, { redactParameters });
},
async createFromWorkflowJSON(json, options) {
assertNotReadOnly();
const projectId = await resolveProjectId(['workflow:create'], options?.projectId);
const settings = (json.settings ?? {});
if (settings.redactionPolicy !== undefined) {
const canUpdateRedaction = await (0, check_access_1.userHasScopes)(user, ['workflow:updateRedactionSetting'], false, { projectId });
if (!canUpdateRedaction) {
delete settings.redactionPolicy;
}
}
const newWorkflow = workflowRepository.create({
name: json.name,
nodes: [],
connections: {},
settings,
active: false,
versionId: (0, node_crypto_1.randomUUID)(),
});
const saved = await workflowRepository.manager.transaction(async (transactionManager) => {
const workflow = await transactionManager.save(db_1.WorkflowEntity, newWorkflow);
await sharedWorkflowRepository.makeOwner([workflow.id], projectId, transactionManager);
if (options?.markAsAiTemporary) {
if (!threadId) {
throw new n8n_workflow_1.UnexpectedError('Cannot mark AI-builder temporary workflow without a thread ID');
}
await aiBuilderTemporaryWorkflowRepository.mark(workflow.id, threadId, transactionManager);
}
return workflow;
});
let updateData = workflowRepository.create({
name: json.name,
nodes: json.nodes,
connections: json.connections,
settings,
pinData: sdkPinDataToRuntime(json.pinData),
});
if (license.isSharingEnabled()) {
updateData = await enterpriseWorkflowService.preventTampering(updateData, saved.id, user);
}
const updated = await workflowService.update(user, updateData, saved.id, {
source: 'n8n-ai',
});
if (threadId) {
telemetry.track('Builder created workflow', {
thread_id: threadId,
workflow_id: updated.id,
});
}
return toWorkflowDetail(updated, { redactParameters });
},
async updateFromWorkflowJSON(workflowId, json, _options) {
assertNotReadOnly();
const settings = (json.settings ?? {});
if (settings.redactionPolicy !== undefined) {
const canUpdateRedaction = await (0, check_access_1.userHasScopes)(user, ['workflow:updateRedactionSetting'], false, { workflowId });
if (!canUpdateRedaction) {
delete settings.redactionPolicy;
}
}
let updateData = workflowRepository.create({
name: json.name,
nodes: json.nodes,
connections: json.connections,
settings,
pinData: sdkPinDataToRuntime(json.pinData),
});
if (license.isSharingEnabled()) {
updateData = await enterpriseWorkflowService.preventTampering(updateData, workflowId, user);
}
const updated = await workflowService.update(user, updateData, workflowId, {
source: 'n8n-ai',
});
if (threadId) {
telemetry.track('Builder modified workflow', {
thread_id: threadId,
workflow_id: workflowId,
});
}
return toWorkflowDetail(updated, { redactParameters });
},
async listVersions(workflowId, options) {
const take = options?.limit ?? 20;
const skip = options?.skip ?? 0;
const versions = await workflowHistoryService.getList(user, workflowId, take, skip);
const workflow = await workflowFinderService.findWorkflowForUser(workflowId, user, [
'workflow:read',
]);
const activeVersionId = workflow?.activeVersionId ?? null;
const currentDraftVersionId = workflow?.versionId ?? null;
return versions.map((v) => ({
versionId: v.versionId,
name: v.name ?? null,
description: v.description ?? null,
authors: v.authors,
createdAt: v.createdAt.toISOString(),
autosaved: v.autosaved ?? false,
isActive: v.versionId === activeVersionId,
isCurrentDraft: v.versionId === currentDraftVersionId,
}));
},
async getVersion(workflowId, versionId) {
const version = await workflowHistoryService.getVersion(user, workflowId, versionId);
const workflow = await workflowFinderService.findWorkflowForUser(workflowId, user, [
'workflow:read',
]);
const activeVersionId = workflow?.activeVersionId ?? null;
const currentDraftVersionId = workflow?.versionId ?? null;
return {
versionId: version.versionId,
name: version.name ?? null,
description: version.description ?? null,
authors: version.authors,
createdAt: version.createdAt.toISOString(),
autosaved: version.autosaved ?? false,
isActive: version.versionId === activeVersionId,
isCurrentDraft: version.versionId === currentDraftVersionId,
nodes: (version.nodes ?? []).map((n) => ({
name: n.name,
type: n.type,
parameters: redactParameters ? undefined : n.parameters,
position: n.position,
})),
connections: version.connections,
};
},
async restoreVersion(workflowId, versionId) {
const version = await workflowHistoryService.getVersion(user, workflowId, versionId);
const updateData = workflowRepository.create({
nodes: version.nodes,
connections: version.connections,
});
await workflowService.update(user, updateData, workflowId, {
source: 'n8n-ai',
});
},
...(this.license.isLicensed('feat:namedVersions')
? {
async updateVersion(workflowId, versionId, data) {
await workflowHistoryService.updateVersionForUser(user, workflowId, versionId, data);
},
}
: {}),
};
}
createExecutionAdapter(user, pushRef, threadId) {
const { workflowFinderService, workflowRunner, activeExecutions, executionRepository, allowSendingParameterValues, license, roleService, telemetry, } = this;
const assertNotReadOnly = () => this.assertInstanceNotReadOnly('executions');
const DEFAULT_TIMEOUT_MS = 5 * constants_1.Time.minutes.toMilliseconds;
const MAX_TIMEOUT_MS = 10 * constants_1.Time.minutes.toMilliseconds;
const assertExecutionAccess = async (executionId, scopes = ['workflow:read']) => {
const execution = await executionRepository.findSingleExecution(executionId, {
includeData: false,
});
if (!execution) {
throw new Error(`Execution ${executionId} not found`);
}
const workflow = await workflowFinderService.findWorkflowForUser(execution.workflowId, user, scopes);
if (!workflow) {
throw new Error(`Execution ${executionId} not found`);
}
return execution;
};
return {
async list(options) {
const scope = 'workflow:read';
let sharingOptions;
if (license.isSharingEnabled()) {
const projectRoles = await roleService.rolesWithScope('project', [scope]);
const workflowRoles = await roleService.rolesWithScope('workflow', [scope]);
sharingOptions = { scopes: [scope], projectRoles, workflowRoles };
}
else {
sharingOptions = {
workflowRoles: ['workflow:owner'],
projectRoles: [permissions_1.PROJECT_OWNER_ROLE_SLUG],
};
}
const query = {
kind: 'range',
range: { limit: options?.limit ?? 20, lastId: undefined, firstId: undefined },
order: { startedAt: 'DESC' },
user,
sharingOptions,
...(options?.workflowId ? { workflowId: options.workflowId } : {}),
...(options?.status
? {
status: [options.status],
}
: {}),
};
const executions = await executionRepository.findManyByRangeQuery(query);
return executions.map((e) => ({
id: e.id,
workflowId: e.workflowId,
workflowName: e.workflowName ?? '',
status: e.status,
startedAt: String(e.startedAt ?? ''),
finishedAt: e.stoppedAt ? String(e.stoppedAt) : undefined,
mode: e.mode,
}));
},
async run(workflowId, inputData, options) {
assertNotReadOnly();
const workflow = await workflowFinderService.findWorkflowForUser(workflowId, user, [
'workflow:execute',
]);
if (!workflow) {
throw new Error(`Workflow ${workflowId} not found or not accessible`);
}
const nodes = workflow.nodes ?? [];
const triggerNode = options?.triggerNodeName
? (nodes.find((n) => n.name === options.triggerNodeName) ?? findTriggerNode(nodes))
: findTriggerNode(nodes);
const runData = {
executionMode: triggerNode
? getExecutionModeForTrigger(triggerNode)
: 'manual',
workflowData: {
...workflow,
settings: {
...workflow.settings,
saveManualExecutions: true,
saveDataSuccessExecution: 'all',
saveDataErrorExecution: 'all',
},
},
userId: user.id,
pushRef,
};
const workflowPinData = workflow.pinData ?? {};
const overridePinData = options?.pinData
? (sdkPinDataToRuntime(options.pinData) ?? {})
: {};
const basePinData = { ...workflowPinData, ...overridePinData };
if (inputData && triggerNode) {
const triggerPinData = getPinDataForTrigger(triggerNode, inputData);
const mergedPinData = { ...basePinData, ...triggerPinData };
runData.startNodes = [{ name: triggerNode.name, sourceData: null }];
runData.pinData = mergedPinData;
runData.executionData = (0, n8n_workflow_1.createRunExecutionData)({
startData: {},
resultData: { pinData: mergedPinData, runData: {} },
executionData: {
contextData: {},
metadata: {},
nodeExecutionStack: [
{
node: triggerNode,
data: { main: [triggerPinData[triggerNode.name]] },
source: null,
},
],
waitingExecution: {},
waitingExecutionSource: {},
},
});
}
else if (triggerNode) {
runData.triggerToStartFrom = { name: triggerNode.name };
if (Object.keys(basePinData).length > 0) {
runData.pinData = basePinData;
}
}
else if (Object.keys(basePinData).length > 0) {
runData.pinData = basePinData;
}
const trackBuilderExecutedWorkflow = (status) => {
if (!threadId)
return;
telemetry.track('Builder executed workflow', {
thread_id: threadId,
workflow_id: workflowId,
executed_by: 'ai',
pinned_node_count: Object.keys(runData.pinData ?? {}).length,
exec_type: runData.executionMode,
status,
});
};
const executionId = await workflowRunner.run(runData);
const timeoutMs = Math.min(options?.timeout ?? DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS);
if (activeExecutions.has(executionId)) {
let timeoutId;
const timeoutPromise = new Promise((_, reject) => {
timeoutId = setTimeout(() => {
reject(new Error(`Execution timed out after ${timeoutMs}ms`));
}, timeoutMs);
});
try {
await Promise.race([
activeExecutions.getPostExecutePromise(executionId),
timeoutPromise,
]);
clearTimeout(timeoutId);
}
catch (error) {
clearTimeout(timeoutId);
if (error instanceof Error && error.message.includes('timed out')) {
try {
activeExecutions.stopExecution(executionId, new n8n_workflow_1.TimeoutExecutionCancelledError(executionId));
}
catch {
}
const result = {
executionId,
status: 'error',
error: `Execution timed out after ${timeoutMs}ms and was cancelled`,
};
trackBuilderExecutedWorkflow(result.status);
return result;
}
throw error;
}
}
const result = await extractExecutionResult(executionRepository, executionId, allowSendingParameterValues);
trackBuilderExecutedWorkflow(result.status);
return result;
},
async getStatus(executionId) {
await assertExecutionAccess(executionId);
const isRunning = activeExecutions.has(executionId);
if (isRunning) {
return { executionId, status: 'running' };
}
return await extractExecutionResult(executionRepository, executionId, allowSendingParameterValues);
},
async getResult(executionId) {
await assertExecutionAccess(executionId);
if (activeExecutions.has(executionId)) {
await activeExecutions.getPostExecutePromise(executionId);
}
return await extractExecutionResult(executionRepository, executionId, allowSendingParameterValues);
},
async stop(executionId) {
assertNotReadOnly();
await assertExecutionAccess(executionId, ['workflow:execute']);
if (!activeExecutions.has(executionId)) {
return {
success: false,
message: `Execution ${executionId} is not currently running`,
};
}
try {
activeExecutions.stopExecution(executionId, new n8n_workflow_1.TimeoutExecutionCancelledError(executionId));
return { success: true, message: `Execution ${executionId} cancelled` };
}
catch {
return {
success: false,
message: `Failed to cancel execution ${executionId}`,
};
}
},
async getDebugInfo(executionId) {
await assertExecutionAccess(executionId);
return await extractExecutionDebugInfo(executionRepository, executionId, allowSendingParameterValues);
},
async getNodeOutput(executionId, nodeName, options) {
await assertExecutionAccess(executionId);
if (!allowSendingParameterValues) {
return {
nodeName,
items: [],
totalItems: 0,
returned: { from: 0, to: 0 },
};
}
return await extractNodeOutput(executionRepository, executionId, nodeName, options);
},
};
}
createCredentialAdapter(user) {
const { credentialsService, credentialsFinderService, loadNodesAndCredentials } = this;
return {
async list(options) {
if (options?.workflowId || options?.projectId) {
const scoped = options.workflowId
? await credentialsService.getCredentialsAUserCanUseInAWorkflow(user, {
workflowId: options.workflowId,
})
: await credentialsService.getCredentialsAUserCanUseInAWorkflow(user, {
projectId: options.projectId,
});
const filtered = options.type ? scoped.filter((c) => c.type === options.type) : scoped;
return filtered.map((c) => ({
id: c.id,
name: c.name,
type: c.type,
}));
}
const credentials = await credentialsService.getMany(user, {
listQueryOptions: {
filter: options?.type ? { type: options.type } : undefined,
},
includeGlobal: true,
});
return credentials.map((c) => ({
id: c.id,
name: c.name,
type: c.type,
}));
},
async get(credentialId) {
const credential = await credentialsService.getOne(user, credentialId, false);
return {
id: credential.id,
name: credential.name,
type: credential.type,
};
},
async delete(credentialId) {
await credentialsService.delete(user, credentialId);
},
async test(credentialId) {
const credential = await credentialsFinderService.findCredentialForUser(credentialId, user, ['credential:read']);
if (!credential) {
throw new Error(`Credential ${credentialId} not found or not accessible`);
}
const credentialsToTest = {
id: credential.id,
name: credential.name,
type: credential.type,
data: await credentialsService.decrypt(credential, true),
};
const result = await credentialsService.test(user.id, credentialsToTest);
return {
success: result.status === 'OK',
message: result.message,
};
},
async isTestable(credentialType) {
try {
const credClass = loadNodesAndCredentials.getCredential(credentialType);
if (credClass.type.test)
return true;
const known = loadNodesAndCredentials.knownCredentials;
const supportedNodes = known[credentialType]?.supportedNodes ?? [];
for (const nodeName of supportedNodes) {
try {
const loaded = loadNodesAndCredentials.getNode(nodeName);
const nodeInstance = loaded.type;
const nodeDesc = 'nodeVersions' in nodeInstance
? Object.values(nodeInstance.nodeVersions).pop()?.description
: nodeInstance.description;
const hasTestedBy = nodeDesc?.credentials?.some((cred) => cred.name === credentialType && cred.testedBy);
if (hasTestedBy)
return true;
}
catch {
continue;
}
}
return false;
}
catch {
return false;
}
},
async getDocumentationUrl(credentialType) {
try {
const credClass = loadNodesAndCredentials.getCredential(credentialType);
const slug = credClass.type.documentationUrl;
if (!slug)
return null;
if (slug.startsWith('http'))
return slug;
return `https://docs.n8n.io/integrations/builtin/credentials/${slug}/`;
}
catch {
return null;
}
},
getCredentialFields(credentialType) {
try {
const allTypes = [credentialType];
const known = loadNodesAndCredentials.knownCredentials;
for (const typeName of allTypes) {
const extendsArr = known[typeName]?.extends ?? [];
allTypes.push(...extendsArr);
}
const fields = [];
const seen = new Set();
for (const typeName of allTypes) {
try {
const credClass = loadNodesAndCredentials.getCredential(typeName);
for (const prop of credClass.type.properties) {
if (prop.type === 'hidden' || seen.has(prop.name))
continue;
seen.add(prop.name);
fields.push({
name: prop.name,
displayName: prop.displayName,
type: prop.type,
required: prop.required ?? false,
description: prop.description,
});
}
}
catch {
}
}
return fields;
}
catch {
return [];
}
},
async searchCredentialTypes(query) {
const q = query.toLowerCase().trim();
if (!q)
return [];
const known = loadNodesAndCredentials.knownCredentials;
const results = [];
for (const typeName of Object.keys(known)) {
if (typeName.toLowerCase().includes(q)) {
try {
const credClass = loadNodesAndCredentials.getCredential(typeName);
results.push({
type: typeName,
displayName: credClass.type.displayName,
});
}
catch {
results.push({ type: typeName, displayName: typeName });
}
continue;
}
try {
const credClass = loadNodesAndCredentials.getCredential(typeName);
if (credClass.type.displayName.toLowerCase().includes(q)) {
results.push({
type: typeName,
displayName: credClass.type.displayName,
});
}
}
catch {
}
}
return results;
},
async getAccountContext(credentialId) {
const credential = await credentialsFinderService.findCredentialForUser(credentialId, user, ['credential:read']);
if (!credential) {
return { accountIdentifier: undefined };
}
const mask = (id) => {
const atIdx = id.indexOf('@');
if (atIdx > 0) {
const local = id.slice(0, atIdx);
const domain = id.slice(atIdx);
const keep = Math.min(2, local.length);
return local.slice(0, keep) + '***' + domain;
}
if (id.length <= 3)
return id;
return id.slice(0, 2) + '***' + id.slice(-1);
};
try {
const redacted = await credentialsService.decrypt(credential, false);
if (typeof redacted.accountIdentifier === 'string' && redacted.accountIdentifier) {
return { accountIdentifier: mask(redacted.accountIdentifier) };
}
for (const key of ['email', 'user', 'username', 'account', 'serviceAccountEmail']) {
const value = redacted[key];
if (typeof value === 'string' && value) {
return { accountIdentifier: mask(value) };
}
}
const raw = await credentialsService.decrypt(credential, true);
const tokenData = raw.oauthTokenData;
if (tokenData && typeof tokenData === 'object') {
const { OauthService } = await Promise.resolve().then(() => __importStar(require('../../oauth/oauth.service')));
const identifier = OauthService.extractAccountIdentifier(tokenData);
if (identifier) {
return { accountIdentifier: mask(identifier) };
}
}
return { accountIdentifier: undefined };
}
catch {
return { accountIdentifier: undefined };
}
},
};
}
createDataTableAdapter(user) {
const { dataTableService, dataTableRepository } = this;
const assertNotReadOnly = () => this.assertInstanceNotReadOnly('data tables');
const { resolveProjectId } = this.createProjectScopeHelpers(user);
const logger = this.logger;
const resolveAccessibleTable = async (scopes, dataTableId, disambiguator) => {
const projectIdFilter = disambiguator?.projectId;
const result = await resolveDataTableByIdOrName(dataTableRepository, logger, dataTableId, {
projectIdFilter,
accessFilter: async (id) => await (0, check_access_1.userHasScopes)(user, scopes, false, { dataTableId: id }),
});
if (result.kind === 'miss') {
throw new Error(`Data table "${dataTableId}" not found`);
}
if (result.kind === 'ambiguous') {
const projectIds = result.candidates.map((c) => c.projectId).join(', ');
throw new Error(`Data table name "${dataTableId}" is ambiguous across accessible projects ` +
`(${projectIds}); pass the UUID or include a \`projectId\` to disambiguate.`);
}
if (projectIdFilter && result.table.projectId !== projectIdFilter) {
throw new Error(`Data table "${dataTableId}" does not belong to project "${projectIdFilter}".`);
}
return result.table;
};
const resolveProjectIdForTable = async (scopes, dataTableId, disambiguator) => {
const table = await resolveAccessibleTable(scopes, dataTableId, disambiguator);
return { projectId: table.projectId, resolvedId: table.id };
};
const resolveTableMeta = async (scopes, dataTableId, disambiguator) => {
const table = await resolveAccessibleTable(scopes, dataTableId, disambiguator);
return { projectId: table.projectId, tableName: table.name, resolvedId: table.id };
};
return {
async list(options) {
const projectId = await resolveProjectId(['dataTable:listProject'], options?.projectId);
const { data: tables } = await dataTableService.getManyAndCount({
filter: { projectId },
});
return tables.map((t) => ({
id: t.id,
name: t.name,
projectId,
columns: t.columns.map((c) => ({ id: c.id, name: c.name, type: c.type })),
createdAt: t.createdAt.toISOString(),
updatedAt: t.updatedAt.toISOString(),
}));
},
async create(name, columns, options) {
assertNotReadOnly();
const projectId = await resolveProjectId(['dataTable:create'], options?.projectId);
const result = await dataTableService.createDataTable(projectId, { name, columns });
return {
id: result.id,
name: result.name,
projectId,
columns: result.columns.map((c) => ({ id: c.id, name: c.name, type: c.type })),
createdAt: result.createdAt.toISOString(),
updatedAt: result.updatedAt.toISOString(),
};
},
async delete(dataTableId, options) {
assertNotReadOnly();
const { projectId, resolvedId } = await resolveProjectIdForTable(['dataTable:delete'], dataTableId, options);
await dataTableService.deleteDataTable(resolvedId, projectId);
},
async getSchema(dataTableId, options) {
const { projectId, resolvedId } = await resolveProjectIdForTable(['dataTable:read'], dataTableId, options);
const columns = await dataTableS