node-utils.tsβ’5.71 kB
/**
* Normalizes node type from n8n export format to database format
*
* Examples:
* - 'n8n-nodes-base.httpRequest' β 'nodes-base.httpRequest'
* - '@n8n/n8n-nodes-langchain.agent' β 'nodes-langchain.agent'
* - 'n8n-nodes-langchain.chatTrigger' β 'nodes-langchain.chatTrigger'
* - 'nodes-base.slack' β 'nodes-base.slack' (unchanged)
*
* @param nodeType The node type to normalize
* @returns The normalized node type
*/
export function normalizeNodeType(nodeType: string): string {
// Handle n8n-nodes-base -> nodes-base
if (nodeType.startsWith('n8n-nodes-base.')) {
return nodeType.replace('n8n-nodes-base.', 'nodes-base.');
}
// Handle @n8n/n8n-nodes-langchain -> nodes-langchain
if (nodeType.startsWith('@n8n/n8n-nodes-langchain.')) {
return nodeType.replace('@n8n/n8n-nodes-langchain.', 'nodes-langchain.');
}
// Handle n8n-nodes-langchain -> nodes-langchain (without @n8n/ prefix)
if (nodeType.startsWith('n8n-nodes-langchain.')) {
return nodeType.replace('n8n-nodes-langchain.', 'nodes-langchain.');
}
// Return unchanged if already normalized or unknown format
return nodeType;
}
/**
* Gets alternative node type formats to try for lookups
*
* @param nodeType The original node type
* @returns Array of alternative formats to try
*/
export function getNodeTypeAlternatives(nodeType: string): string[] {
// Defensive: validate input to prevent TypeError when nodeType is undefined/null/empty
if (!nodeType || typeof nodeType !== 'string' || nodeType.trim() === '') {
return [];
}
const alternatives: string[] = [];
// Add lowercase version
alternatives.push(nodeType.toLowerCase());
// If it has a prefix, try case variations on the node name part
if (nodeType.includes('.')) {
const [prefix, nodeName] = nodeType.split('.');
// Try different case variations for the node name
if (nodeName && nodeName.toLowerCase() !== nodeName) {
alternatives.push(`${prefix}.${nodeName.toLowerCase()}`);
}
// For camelCase names like "chatTrigger", also try with capital first letter variations
// e.g., "chattrigger" -> "chatTrigger"
if (nodeName && nodeName.toLowerCase() === nodeName && nodeName.length > 1) {
// Try to detect common patterns and create camelCase version
const camelCaseVariants = generateCamelCaseVariants(nodeName);
camelCaseVariants.forEach(variant => {
alternatives.push(`${prefix}.${variant}`);
});
}
}
// If it's just a bare node name, try with common prefixes
if (!nodeType.includes('.')) {
alternatives.push(`nodes-base.${nodeType}`);
alternatives.push(`nodes-langchain.${nodeType}`);
// Also try camelCase variants for bare names
const camelCaseVariants = generateCamelCaseVariants(nodeType);
camelCaseVariants.forEach(variant => {
alternatives.push(`nodes-base.${variant}`);
alternatives.push(`nodes-langchain.${variant}`);
});
}
// Normalize all alternatives and combine with originals
const normalizedAlternatives = alternatives.map(alt => normalizeNodeType(alt));
// Combine original alternatives with normalized ones and remove duplicates
return [...new Set([...alternatives, ...normalizedAlternatives])];
}
/**
* Generate camelCase variants for a lowercase string
* @param str The lowercase string
* @returns Array of possible camelCase variants
*/
function generateCamelCaseVariants(str: string): string[] {
const variants: string[] = [];
// Common patterns for n8n nodes
const patterns = [
// Pattern: wordTrigger (e.g., chatTrigger, webhookTrigger)
/^(.+)(trigger|node|request|response)$/i,
// Pattern: httpRequest, mysqlDatabase
/^(http|mysql|postgres|mongo|redis|mqtt|smtp|imap|ftp|ssh|api)(.+)$/i,
// Pattern: googleSheets, microsoftTeams
/^(google|microsoft|amazon|slack|discord|telegram)(.+)$/i,
];
for (const pattern of patterns) {
const match = str.toLowerCase().match(pattern);
if (match) {
const [, first, second] = match;
// Capitalize the second part
variants.push(first.toLowerCase() + second.charAt(0).toUpperCase() + second.slice(1).toLowerCase());
}
}
// Generic camelCase: capitalize after common word boundaries
if (variants.length === 0) {
// Try splitting on common boundaries and capitalizing
const words = str.split(/[-_\s]+/);
if (words.length > 1) {
const camelCase = words[0].toLowerCase() + words.slice(1).map(w =>
w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()
).join('');
variants.push(camelCase);
}
}
return variants;
}
/**
* Constructs the workflow node type from package name and normalized node type
* This creates the format that n8n expects in workflow definitions
*
* Examples:
* - ('n8n-nodes-base', 'nodes-base.webhook') β 'n8n-nodes-base.webhook'
* - ('@n8n/n8n-nodes-langchain', 'nodes-langchain.agent') β '@n8n/n8n-nodes-langchain.agent'
*
* @param packageName The package name from the database
* @param nodeType The normalized node type from the database
* @returns The workflow node type for use in n8n workflows
*/
export function getWorkflowNodeType(packageName: string, nodeType: string): string {
// Extract just the node name from the normalized type
const nodeName = nodeType.split('.').pop() || nodeType;
// Construct the full workflow type based on package
if (packageName === 'n8n-nodes-base') {
return `n8n-nodes-base.${nodeName}`;
} else if (packageName === '@n8n/n8n-nodes-langchain') {
return `@n8n/n8n-nodes-langchain.${nodeName}`;
}
// Fallback for unknown packages - return as is
return nodeType;
}