mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-03-20 09:23:07 +00:00
feat: add TypeScript type safety with strategic any assertions (v2.17.5)
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>
This commit is contained in:
@@ -47,7 +47,7 @@ describe('NodeParser', () => {
|
||||
mockPropertyExtractor.extractProperties.mockReturnValue(nodeDefinition.properties);
|
||||
mockPropertyExtractor.extractCredentials.mockReturnValue(nodeDefinition.credentials);
|
||||
|
||||
const result = parser.parse(NodeClass, 'n8n-nodes-base');
|
||||
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
|
||||
|
||||
expect(result).toMatchObject({
|
||||
style: 'programmatic',
|
||||
@@ -70,7 +70,7 @@ describe('NodeParser', () => {
|
||||
const nodeDefinition = declarativeNodeFactory.build();
|
||||
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
|
||||
|
||||
const result = parser.parse(NodeClass, 'n8n-nodes-base');
|
||||
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
|
||||
|
||||
expect(result.style).toBe('declarative');
|
||||
expect(result.nodeType).toBe(`nodes-base.${nodeDefinition.name}`);
|
||||
@@ -82,7 +82,7 @@ describe('NodeParser', () => {
|
||||
});
|
||||
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
|
||||
|
||||
const result = parser.parse(NodeClass, 'n8n-nodes-base');
|
||||
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
|
||||
|
||||
expect(result.nodeType).toBe('nodes-base.slack');
|
||||
});
|
||||
@@ -91,7 +91,7 @@ describe('NodeParser', () => {
|
||||
const nodeDefinition = triggerNodeFactory.build();
|
||||
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
|
||||
|
||||
const result = parser.parse(NodeClass, 'n8n-nodes-base');
|
||||
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
|
||||
|
||||
expect(result.isTrigger).toBe(true);
|
||||
});
|
||||
@@ -100,7 +100,7 @@ describe('NodeParser', () => {
|
||||
const nodeDefinition = webhookNodeFactory.build();
|
||||
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
|
||||
|
||||
const result = parser.parse(NodeClass, 'n8n-nodes-base');
|
||||
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
|
||||
|
||||
expect(result.isWebhook).toBe(true);
|
||||
});
|
||||
@@ -111,7 +111,7 @@ describe('NodeParser', () => {
|
||||
|
||||
mockPropertyExtractor.detectAIToolCapability.mockReturnValue(true);
|
||||
|
||||
const result = parser.parse(NodeClass, 'n8n-nodes-base');
|
||||
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
|
||||
|
||||
expect(result.isAITool).toBe(true);
|
||||
});
|
||||
@@ -137,7 +137,7 @@ describe('NodeParser', () => {
|
||||
propertyFactory.build()
|
||||
]);
|
||||
|
||||
const result = parser.parse(VersionedNodeClass, 'n8n-nodes-base');
|
||||
const result = parser.parse(VersionedNodeClass as any, 'n8n-nodes-base');
|
||||
|
||||
expect(result.isVersioned).toBe(true);
|
||||
expect(result.version).toBe('2');
|
||||
@@ -151,7 +151,7 @@ describe('NodeParser', () => {
|
||||
baseDescription = versionedDef.baseDescription;
|
||||
};
|
||||
|
||||
const result = parser.parse(NodeClass, 'n8n-nodes-base');
|
||||
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
|
||||
|
||||
expect(result.isVersioned).toBe(true);
|
||||
expect(result.version).toBe('2');
|
||||
@@ -163,7 +163,7 @@ describe('NodeParser', () => {
|
||||
});
|
||||
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
|
||||
|
||||
const result = parser.parse(NodeClass, 'n8n-nodes-base');
|
||||
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
|
||||
|
||||
expect(result.isVersioned).toBe(true);
|
||||
expect(result.version).toBe('2'); // Should return max version
|
||||
@@ -173,7 +173,7 @@ describe('NodeParser', () => {
|
||||
const nodeDefinition = malformedNodeFactory.build();
|
||||
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
|
||||
|
||||
expect(() => parser.parse(NodeClass, 'n8n-nodes-base')).toThrow('Node is missing name property');
|
||||
expect(() => parser.parse(NodeClass as any, 'n8n-nodes-base')).toThrow('Node is missing name property');
|
||||
});
|
||||
|
||||
it('should use static description when instantiation fails', () => {
|
||||
@@ -184,7 +184,7 @@ describe('NodeParser', () => {
|
||||
}
|
||||
};
|
||||
|
||||
const result = parser.parse(NodeClass, 'n8n-nodes-base');
|
||||
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
|
||||
|
||||
expect(result.displayName).toBe(NodeClass.description.displayName);
|
||||
});
|
||||
@@ -205,7 +205,7 @@ describe('NodeParser', () => {
|
||||
} as any);
|
||||
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
|
||||
|
||||
const result = parser.parse(NodeClass, 'n8n-nodes-base');
|
||||
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
|
||||
|
||||
expect(result.category).toBe(expected);
|
||||
});
|
||||
@@ -217,7 +217,7 @@ describe('NodeParser', () => {
|
||||
});
|
||||
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
|
||||
|
||||
const result = parser.parse(NodeClass, 'n8n-nodes-base');
|
||||
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
|
||||
|
||||
expect(result.isTrigger).toBe(true);
|
||||
});
|
||||
@@ -228,7 +228,7 @@ describe('NodeParser', () => {
|
||||
});
|
||||
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
|
||||
|
||||
const result = parser.parse(NodeClass, 'n8n-nodes-base');
|
||||
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
|
||||
|
||||
expect(result.isTrigger).toBe(true);
|
||||
});
|
||||
@@ -239,7 +239,7 @@ describe('NodeParser', () => {
|
||||
});
|
||||
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
|
||||
|
||||
const result = parser.parse(NodeClass, 'n8n-nodes-base');
|
||||
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
|
||||
|
||||
expect(result.isTrigger).toBe(true);
|
||||
});
|
||||
@@ -250,7 +250,7 @@ describe('NodeParser', () => {
|
||||
});
|
||||
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
|
||||
|
||||
const result = parser.parse(NodeClass, 'n8n-nodes-base');
|
||||
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
|
||||
|
||||
expect(result.isWebhook).toBe(true);
|
||||
});
|
||||
@@ -262,8 +262,8 @@ describe('NodeParser', () => {
|
||||
};
|
||||
|
||||
mockPropertyExtractor.extractProperties.mockReturnValue(nodeDefinition.properties);
|
||||
|
||||
const result = parser.parse(nodeInstance, 'n8n-nodes-base');
|
||||
|
||||
const result = parser.parse(nodeInstance as any, 'n8n-nodes-base');
|
||||
|
||||
expect(result.displayName).toBe(nodeDefinition.displayName);
|
||||
});
|
||||
@@ -279,7 +279,7 @@ describe('NodeParser', () => {
|
||||
];
|
||||
|
||||
testCases.forEach(({ packageName, expectedPrefix }) => {
|
||||
const result = parser.parse(NodeClass, packageName);
|
||||
const result = parser.parse(NodeClass as any, packageName);
|
||||
expect(result.nodeType).toBe(`${expectedPrefix}.${nodeDefinition.name}`);
|
||||
});
|
||||
});
|
||||
@@ -296,7 +296,7 @@ describe('NodeParser', () => {
|
||||
};
|
||||
};
|
||||
|
||||
const result = parser.parse(NodeClass, 'n8n-nodes-base');
|
||||
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
|
||||
|
||||
expect(result.version).toBe('2.2');
|
||||
});
|
||||
@@ -310,7 +310,7 @@ describe('NodeParser', () => {
|
||||
};
|
||||
};
|
||||
|
||||
const result = parser.parse(NodeClass, 'n8n-nodes-base');
|
||||
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
|
||||
|
||||
expect(result.version).toBe('3');
|
||||
});
|
||||
@@ -325,7 +325,7 @@ describe('NodeParser', () => {
|
||||
};
|
||||
};
|
||||
|
||||
const result = parser.parse(NodeClass, 'n8n-nodes-base');
|
||||
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
|
||||
|
||||
expect(result.version).toBe('0');
|
||||
});
|
||||
@@ -339,7 +339,7 @@ describe('NodeParser', () => {
|
||||
};
|
||||
};
|
||||
|
||||
const result = parser.parse(NodeClass, 'n8n-nodes-base');
|
||||
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
|
||||
|
||||
expect(result.version).toBe('1'); // Should fallback to default
|
||||
});
|
||||
@@ -354,7 +354,7 @@ describe('NodeParser', () => {
|
||||
};
|
||||
};
|
||||
|
||||
const result = parser.parse(NodeClass, 'n8n-nodes-base');
|
||||
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
|
||||
|
||||
expect(result.version).toBe('3');
|
||||
});
|
||||
@@ -372,7 +372,7 @@ describe('NodeParser', () => {
|
||||
}
|
||||
};
|
||||
|
||||
const result = parser.parse(NodeClass, 'n8n-nodes-base');
|
||||
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
|
||||
|
||||
expect(result.version).toBe('4');
|
||||
});
|
||||
@@ -383,7 +383,7 @@ describe('NodeParser', () => {
|
||||
});
|
||||
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
|
||||
|
||||
const result = parser.parse(NodeClass, 'n8n-nodes-base');
|
||||
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
|
||||
|
||||
expect(result.version).toBe('2');
|
||||
});
|
||||
@@ -394,7 +394,7 @@ describe('NodeParser', () => {
|
||||
});
|
||||
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
|
||||
|
||||
const result = parser.parse(NodeClass, 'n8n-nodes-base');
|
||||
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
|
||||
|
||||
expect(result.version).toBe('1.5');
|
||||
});
|
||||
@@ -404,7 +404,7 @@ describe('NodeParser', () => {
|
||||
delete (nodeDefinition as any).version;
|
||||
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
|
||||
|
||||
const result = parser.parse(NodeClass, 'n8n-nodes-base');
|
||||
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
|
||||
|
||||
expect(result.version).toBe('1');
|
||||
});
|
||||
@@ -417,7 +417,7 @@ describe('NodeParser', () => {
|
||||
nodeVersions = { 1: {}, 2: {} };
|
||||
};
|
||||
|
||||
const result = parser.parse(NodeClass, 'n8n-nodes-base');
|
||||
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
|
||||
|
||||
expect(result.isVersioned).toBe(true);
|
||||
});
|
||||
@@ -431,7 +431,7 @@ describe('NodeParser', () => {
|
||||
};
|
||||
};
|
||||
|
||||
const result = parser.parse(NodeClass, 'n8n-nodes-base');
|
||||
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
|
||||
|
||||
expect(result.isVersioned).toBe(true);
|
||||
});
|
||||
@@ -445,7 +445,7 @@ describe('NodeParser', () => {
|
||||
};
|
||||
};
|
||||
|
||||
const result = parser.parse(NodeClass, 'n8n-nodes-base');
|
||||
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
|
||||
|
||||
expect(result.isVersioned).toBe(true);
|
||||
});
|
||||
@@ -456,7 +456,7 @@ describe('NodeParser', () => {
|
||||
});
|
||||
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
|
||||
|
||||
const result = parser.parse(NodeClass, 'n8n-nodes-base');
|
||||
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
|
||||
|
||||
expect(result.isVersioned).toBe(false);
|
||||
});
|
||||
@@ -468,7 +468,7 @@ describe('NodeParser', () => {
|
||||
description = null;
|
||||
};
|
||||
|
||||
expect(() => parser.parse(NodeClass, 'n8n-nodes-base')).toThrow();
|
||||
expect(() => parser.parse(NodeClass as any, 'n8n-nodes-base')).toThrow();
|
||||
});
|
||||
|
||||
it('should handle empty routing object for declarative nodes', () => {
|
||||
@@ -477,7 +477,7 @@ describe('NodeParser', () => {
|
||||
});
|
||||
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
|
||||
|
||||
const result = parser.parse(NodeClass, 'n8n-nodes-base');
|
||||
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
|
||||
|
||||
expect(result.style).toBe('declarative');
|
||||
});
|
||||
@@ -503,7 +503,7 @@ describe('NodeParser', () => {
|
||||
value: 'VersionedNodeType'
|
||||
});
|
||||
|
||||
const result = parser.parse(NodeClass, 'n8n-nodes-base');
|
||||
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
|
||||
|
||||
expect(result.isVersioned).toBe(true);
|
||||
expect(result.version).toBe('3');
|
||||
|
||||
Reference in New Issue
Block a user