mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-01-30 22:42:04 +00:00
Added comprehensive TypeScript type definitions for n8n node parsing while maintaining zero compilation errors. Uses pragmatic "70% benefit with 0% breakage" approach with strategic `any` assertions. ## Type Definitions (src/types/node-types.ts) - NodeClass union type replaces `any` in method signatures - Type guards: isVersionedNodeInstance(), isVersionedNodeClass() - Utility functions for safe node handling ## Parser Updates - node-parser.ts: All methods use NodeClass (15+ methods) - simple-parser.ts: Strongly typed method signatures - property-extractor.ts: Typed extraction methods - 30+ method signatures improved ## Strategic Pattern - Strong types in public method signatures (caller type safety) - Strategic `as any` assertions for internal union type access - Pattern: const desc = description as any; // Access union properties ## Benefits - Better IDE support and auto-complete - Compile-time safety at call sites - Type-based documentation - Zero compilation errors - Bug prevention (would have caught v2.17.4 baseDescription issue) ## Test Updates - All test files updated with `as any` for mock objects - Zero compilation errors maintained ## Known Limitations - ~70% type coverage (signatures typed, internal logic uses assertions) - Union types (INodeTypeBaseDescription vs INodeTypeDescription) not fully resolved - Future work: Conditional types or overloads for 100% type safety 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
221 lines
6.2 KiB
TypeScript
221 lines
6.2 KiB
TypeScript
/**
|
|
* 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
|
|
}
|