diff --git a/data/nodes.db b/data/nodes.db index f14ea99..0a8425d 100644 Binary files a/data/nodes.db and b/data/nodes.db differ diff --git a/src/parsers/property-extractor.ts b/src/parsers/property-extractor.ts index b0d71bf..5095aac 100644 --- a/src/parsers/property-extractor.ts +++ b/src/parsers/property-extractor.ts @@ -231,6 +231,7 @@ export class PropertyExtractor { required: prop.required, displayOptions: prop.displayOptions, typeOptions: prop.typeOptions, + modes: prop.modes, // For resourceLocator type properties - modes are at top level noDataExpression: prop.noDataExpression })); } diff --git a/src/services/config-validator.ts b/src/services/config-validator.ts index 1d2f768..bc1df37 100644 --- a/src/services/config-validator.ts +++ b/src/services/config-validator.ts @@ -270,12 +270,13 @@ export class ConfigValidator { message: `resourceLocator '${key}.mode' must be a string, got ${typeof value.mode}`, fix: `Set mode to a valid string value` }); - } else if (prop.typeOptions?.resourceLocator?.modes) { + } else if (prop.modes) { // Schema-based validation: Check if mode exists in the modes definition + // In n8n, modes are defined at the top level of resourceLocator properties // Modes can be defined in different ways: - // 1. Object with mode keys: { list: {...}, id: {...}, url: {...}, name: {...} } - // 2. Array of mode objects: [{name: 'list', ...}, {name: 'id', ...}] - const modes = prop.typeOptions.resourceLocator.modes; + // 1. Array of mode objects: [{name: 'list', ...}, {name: 'id', ...}, {name: 'name', ...}] + // 2. Object with mode keys: { list: {...}, id: {...}, url: {...}, name: {...} } + const modes = prop.modes; // Validate modes structure before processing to prevent crashes if (!modes || typeof modes !== 'object') { @@ -286,7 +287,7 @@ export class ConfigValidator { let allowedModes: string[] = []; if (Array.isArray(modes)) { - // Array format: extract name property from each mode object + // Array format (most common in n8n): extract name property from each mode object allowedModes = modes .map(m => (typeof m === 'object' && m !== null) ? m.name : m) .filter(m => typeof m === 'string' && m.length > 0); @@ -305,7 +306,7 @@ export class ConfigValidator { }); } } - // If no typeOptions.resourceLocator.modes defined, skip mode validation + // If no modes defined at property level, skip mode validation // This prevents false positives for nodes with dynamic/runtime-determined modes if (value.value === undefined) { diff --git a/tests/unit/services/config-validator-basic.test.ts b/tests/unit/services/config-validator-basic.test.ts index faa3673..9dcf4d7 100644 --- a/tests/unit/services/config-validator-basic.test.ts +++ b/tests/unit/services/config-validator-basic.test.ts @@ -691,15 +691,12 @@ describe('ConfigValidator - Basic Validation', () => { name: 'model', type: 'resourceLocator', required: true, - typeOptions: { - resourceLocator: { - modes: { - list: { displayName: 'List' }, - id: { displayName: 'ID' }, - url: { displayName: 'URL' } - } - } - } + // In real n8n, modes are at top level, not in typeOptions + modes: [ + { name: 'list', displayName: 'List' }, + { name: 'id', displayName: 'ID' }, + { name: 'url', displayName: 'URL' } + ] } ]; @@ -726,15 +723,12 @@ describe('ConfigValidator - Basic Validation', () => { name: 'model', type: 'resourceLocator', required: true, - typeOptions: { - resourceLocator: { - modes: [ - { name: 'list', displayName: 'List' }, - { name: 'id', displayName: 'ID' }, - { name: 'custom', displayName: 'Custom' } - ] - } - } + // Array format at top level (real n8n structure) + modes: [ + { name: 'list', displayName: 'List' }, + { name: 'id', displayName: 'ID' }, + { name: 'custom', displayName: 'Custom' } + ] } ]; @@ -757,11 +751,7 @@ describe('ConfigValidator - Basic Validation', () => { name: 'model', type: 'resourceLocator', required: true, - typeOptions: { - resourceLocator: { - modes: 'invalid-string' // Malformed schema - } - } + modes: 'invalid-string' // Malformed schema at top level } ]; @@ -785,11 +775,7 @@ describe('ConfigValidator - Basic Validation', () => { name: 'model', type: 'resourceLocator', required: true, - typeOptions: { - resourceLocator: { - modes: {} // Empty object - } - } + modes: {} // Empty object at top level } ]; @@ -800,7 +786,7 @@ describe('ConfigValidator - Basic Validation', () => { expect(result.errors.some(e => e.property === 'model.mode')).toBe(false); }); - it('should skip mode validation when typeOptions not provided', () => { + it('should skip mode validation when modes not provided', () => { const nodeType = '@n8n/n8n-nodes-langchain.lmChatOpenAi'; const config = { model: { @@ -813,7 +799,7 @@ describe('ConfigValidator - Basic Validation', () => { name: 'model', type: 'resourceLocator', required: true - // No typeOptions - schema doesn't define modes + // No modes property - schema doesn't define modes } ];