Spaces:
Sleeping
Sleeping
| /** | |
| * TypeScript type definitions for n8n node parsing | |
| * | |
| * This file provides strong typing for node classes and instances, | |
| * preventing bugs like the v2.17.4 baseDescription issue where | |
| * TypeScript couldn't catch property name mistakes due to `any` types. | |
| * | |
| * @module types/node-types | |
| * @since 2.17.5 | |
| */ | |
| // Import n8n's official interfaces | |
| import type { | |
| IVersionedNodeType, | |
| INodeType, | |
| INodeTypeBaseDescription, | |
| INodeTypeDescription | |
| } from 'n8n-workflow'; | |
| /** | |
| * Represents a node class that can be either: | |
| * - A constructor function that returns INodeType | |
| * - A constructor function that returns IVersionedNodeType | |
| * - An already-instantiated node instance | |
| * | |
| * This covers all patterns we encounter when loading nodes from n8n packages. | |
| */ | |
| export type NodeClass = | |
| | (new () => INodeType) | |
| | (new () => IVersionedNodeType) | |
| | INodeType | |
| | IVersionedNodeType; | |
| /** | |
| * Instance of a versioned node type with all properties accessible. | |
| * | |
| * This represents nodes that use n8n's VersionedNodeType pattern, | |
| * such as AI Agent, HTTP Request, Slack, etc. | |
| * | |
| * @property currentVersion - The computed current version (defaultVersion ?? max(nodeVersions)) | |
| * @property description - Base description stored as 'description' (NOT 'baseDescription') | |
| * @property nodeVersions - Map of version numbers to INodeType implementations | |
| * | |
| * @example | |
| * ```typescript | |
| * const aiAgent = new AIAgentNode() as VersionedNodeInstance; | |
| * console.log(aiAgent.currentVersion); // 2.2 | |
| * console.log(aiAgent.description.defaultVersion); // 2.2 | |
| * console.log(aiAgent.nodeVersions[1]); // INodeType for version 1 | |
| * ``` | |
| */ | |
| export interface VersionedNodeInstance extends IVersionedNodeType { | |
| currentVersion: number; | |
| description: INodeTypeBaseDescription; | |
| nodeVersions: { | |
| [version: number]: INodeType; | |
| }; | |
| } | |
| /** | |
| * Instance of a regular (non-versioned) node type. | |
| * | |
| * This represents simple nodes that don't use versioning, | |
| * such as Edit Fields, Set, Code (v1), etc. | |
| */ | |
| export interface RegularNodeInstance extends INodeType { | |
| description: INodeTypeDescription; | |
| } | |
| /** | |
| * Union type for any node instance (versioned or regular). | |
| * | |
| * Use this when you need to handle both types of nodes. | |
| */ | |
| export type NodeInstance = VersionedNodeInstance | RegularNodeInstance; | |
| /** | |
| * Type guard to check if a node is a VersionedNodeType instance. | |
| * | |
| * This provides runtime type safety and enables TypeScript to narrow | |
| * the type within conditional blocks. | |
| * | |
| * @param node - The node instance to check | |
| * @returns True if node is a VersionedNodeInstance | |
| * | |
| * @example | |
| * ```typescript | |
| * const instance = new nodeClass(); | |
| * if (isVersionedNodeInstance(instance)) { | |
| * // TypeScript knows instance is VersionedNodeInstance here | |
| * console.log(instance.currentVersion); | |
| * console.log(instance.nodeVersions); | |
| * } | |
| * ``` | |
| */ | |
| export function isVersionedNodeInstance(node: any): node is VersionedNodeInstance { | |
| return ( | |
| node !== null && | |
| typeof node === 'object' && | |
| 'nodeVersions' in node && | |
| 'currentVersion' in node && | |
| 'description' in node && | |
| typeof node.currentVersion === 'number' | |
| ); | |
| } | |
| /** | |
| * Type guard to check if a value is a VersionedNodeType class. | |
| * | |
| * This checks the constructor name pattern used by n8n's VersionedNodeType. | |
| * | |
| * @param nodeClass - The class or value to check | |
| * @returns True if nodeClass is a VersionedNodeType constructor | |
| * | |
| * @example | |
| * ```typescript | |
| * if (isVersionedNodeClass(nodeClass)) { | |
| * // It's a VersionedNodeType class | |
| * const instance = new nodeClass() as VersionedNodeInstance; | |
| * } | |
| * ``` | |
| */ | |
| export function isVersionedNodeClass(nodeClass: any): boolean { | |
| return ( | |
| typeof nodeClass === 'function' && | |
| nodeClass.prototype?.constructor?.name === 'VersionedNodeType' | |
| ); | |
| } | |
| /** | |
| * Safely instantiate a node class with proper error handling. | |
| * | |
| * Some nodes require specific parameters or environment setup to instantiate. | |
| * This helper provides safe instantiation with fallback to null on error. | |
| * | |
| * @param nodeClass - The node class or instance to instantiate | |
| * @returns The instantiated node or null if instantiation fails | |
| * | |
| * @example | |
| * ```typescript | |
| * const instance = instantiateNode(nodeClass); | |
| * if (instance) { | |
| * // Successfully instantiated | |
| * const version = isVersionedNodeInstance(instance) | |
| * ? instance.currentVersion | |
| * : instance.description.version; | |
| * } | |
| * ``` | |
| */ | |
| export function instantiateNode(nodeClass: NodeClass): NodeInstance | null { | |
| try { | |
| if (typeof nodeClass === 'function') { | |
| return new nodeClass(); | |
| } | |
| // Already an instance | |
| return nodeClass; | |
| } catch (e) { | |
| // Some nodes require parameters to instantiate | |
| return null; | |
| } | |
| } | |
| /** | |
| * Safely get a node instance, handling both classes and instances. | |
| * | |
| * This is a non-throwing version that returns undefined on failure. | |
| * | |
| * @param nodeClass - The node class or instance | |
| * @returns The node instance or undefined | |
| */ | |
| export function getNodeInstance(nodeClass: NodeClass): NodeInstance | undefined { | |
| const instance = instantiateNode(nodeClass); | |
| return instance ?? undefined; | |
| } | |
| /** | |
| * Extract description from a node class or instance. | |
| * | |
| * Handles both versioned and regular nodes, with fallback logic. | |
| * | |
| * @param nodeClass - The node class or instance | |
| * @returns The node description or empty object on failure | |
| */ | |
| export function getNodeDescription( | |
| nodeClass: NodeClass | |
| ): INodeTypeBaseDescription | INodeTypeDescription { | |
| // Try to get description from instance first | |
| try { | |
| const instance = instantiateNode(nodeClass); | |
| if (instance) { | |
| // For VersionedNodeType, description is the baseDescription | |
| if (isVersionedNodeInstance(instance)) { | |
| return instance.description; | |
| } | |
| // For regular nodes, description is the full INodeTypeDescription | |
| return instance.description; | |
| } | |
| } catch (e) { | |
| // Ignore instantiation errors | |
| } | |
| // Fallback to static properties | |
| if (typeof nodeClass === 'object' && 'description' in nodeClass) { | |
| return nodeClass.description; | |
| } | |
| // Last resort: empty description | |
| return { | |
| displayName: '', | |
| name: '', | |
| group: [], | |
| description: '', | |
| version: 1, | |
| defaults: { name: '', color: '' }, | |
| inputs: [], | |
| outputs: [], | |
| properties: [] | |
| } as any; // Type assertion needed for fallback case | |
| } | |