From 19b9b5ca2d620f2b3d1974bf74487740fbf29c58 Mon Sep 17 00:00:00 2001 From: czlonkowski <56956555+czlonkowski@users.noreply.github.com> Date: Sun, 6 Jul 2025 17:01:05 +0200 Subject: [PATCH] fix: webhook and 4 other nodes incorrectly marked as non-triggers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixed issue where Docker images using sql.js adapter returned boolean fields as strings, causing is_trigger=0 to evaluate as true instead of false. Changes: - Added convertIntegerColumns() to sql.js adapter to convert SQLite integers - Updated server.ts and node-repository.ts to use Number() conversion as backup - Added test script to verify fix works with sql.js adapter This fixes webhook, cron, interval, and emailReadImap nodes showing isTrigger: false in Docker deployments. ๐Ÿค– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- scripts/test-sqljs-triggers.ts | 86 ++++++++++++++++++++++++++++++++ src/database/database-adapter.ts | 24 ++++++++- src/database/node-repository.ts | 8 +-- src/mcp/server.ts | 6 +-- 4 files changed, 115 insertions(+), 9 deletions(-) create mode 100644 scripts/test-sqljs-triggers.ts diff --git a/scripts/test-sqljs-triggers.ts b/scripts/test-sqljs-triggers.ts new file mode 100644 index 0000000..dafea75 --- /dev/null +++ b/scripts/test-sqljs-triggers.ts @@ -0,0 +1,86 @@ +#!/usr/bin/env node +/** + * Test script to verify trigger detection works with sql.js adapter + */ + +import { createDatabaseAdapter } from '../src/database/database-adapter'; +import { NodeRepository } from '../src/database/node-repository'; +import { logger } from '../src/utils/logger'; +import path from 'path'; + +async function testSqlJsTriggers() { + logger.info('๐Ÿงช Testing trigger detection with sql.js adapter...\n'); + + try { + // Force sql.js by temporarily renaming better-sqlite3 + const originalRequire = require.cache[require.resolve('better-sqlite3')]; + if (originalRequire) { + delete require.cache[require.resolve('better-sqlite3')]; + } + + // Mock better-sqlite3 to force sql.js usage + const Module = require('module'); + const originalResolveFilename = Module._resolveFilename; + Module._resolveFilename = function(request: string, parent: any, isMain: boolean) { + if (request === 'better-sqlite3') { + throw new Error('Forcing sql.js adapter for testing'); + } + return originalResolveFilename.apply(this, arguments); + }; + + // Now create adapter - should use sql.js + const dbPath = path.join(process.cwd(), 'data', 'nodes.db'); + logger.info(`๐Ÿ“ Database path: ${dbPath}`); + + const adapter = await createDatabaseAdapter(dbPath); + logger.info('โœ… Adapter created (should be sql.js)\n'); + + // Test direct query + logger.info('๐Ÿ“Š Testing direct database query:'); + const triggerNodes = ['nodes-base.webhook', 'nodes-base.cron', 'nodes-base.interval', 'nodes-base.emailReadImap']; + + for (const nodeType of triggerNodes) { + const row = adapter.prepare('SELECT * FROM nodes WHERE node_type = ?').get(nodeType); + if (row) { + logger.info(`${nodeType}:`); + logger.info(` is_trigger raw value: ${row.is_trigger} (type: ${typeof row.is_trigger})`); + logger.info(` !!is_trigger: ${!!row.is_trigger}`); + logger.info(` Number(is_trigger) === 1: ${Number(row.is_trigger) === 1}`); + } + } + + // Test through repository + logger.info('\n๐Ÿ“ฆ Testing through NodeRepository:'); + const repository = new NodeRepository(adapter); + + for (const nodeType of triggerNodes) { + const node = repository.getNode(nodeType); + if (node) { + logger.info(`${nodeType}: isTrigger = ${node.isTrigger}`); + } + } + + // Test list query + logger.info('\n๐Ÿ“‹ Testing list query:'); + const allTriggers = adapter.prepare( + 'SELECT node_type, is_trigger FROM nodes WHERE node_type IN (?, ?, ?, ?)' + ).all(...triggerNodes); + + for (const node of allTriggers) { + logger.info(`${node.node_type}: is_trigger = ${node.is_trigger} (type: ${typeof node.is_trigger})`); + } + + adapter.close(); + logger.info('\nโœ… Test complete!'); + + // Restore original require + Module._resolveFilename = originalResolveFilename; + + } catch (error) { + logger.error('Test failed:', error); + process.exit(1); + } +} + +// Run test +testSqlJsTriggers().catch(console.error); \ No newline at end of file diff --git a/src/database/database-adapter.ts b/src/database/database-adapter.ts index ca81edf..1e8e61c 100644 --- a/src/database/database-adapter.ts +++ b/src/database/database-adapter.ts @@ -338,7 +338,7 @@ class SQLJSStatement implements PreparedStatement { if (this.stmt.step()) { const result = this.stmt.getAsObject(); this.stmt.reset(); - return result; + return this.convertIntegerColumns(result); } this.stmt.reset(); @@ -354,7 +354,7 @@ class SQLJSStatement implements PreparedStatement { const results: any[] = []; while (this.stmt.step()) { - results.push(this.stmt.getAsObject()); + results.push(this.convertIntegerColumns(this.stmt.getAsObject())); } this.stmt.reset(); @@ -400,4 +400,24 @@ class SQLJSStatement implements PreparedStatement { this.boundParams = params; } } + + /** + * Convert SQLite integer columns to JavaScript numbers + * sql.js returns all values as strings, but we need proper types for boolean conversion + */ + private convertIntegerColumns(row: any): any { + if (!row) return row; + + // Known integer columns in the nodes table + const integerColumns = ['is_ai_tool', 'is_trigger', 'is_webhook', 'is_versioned']; + + const converted = { ...row }; + for (const col of integerColumns) { + if (col in converted && typeof converted[col] === 'string') { + converted[col] = parseInt(converted[col], 10); + } + } + + return converted; + } } \ No newline at end of file diff --git a/src/database/node-repository.ts b/src/database/node-repository.ts index 747e5d5..3f79deb 100644 --- a/src/database/node-repository.ts +++ b/src/database/node-repository.ts @@ -53,10 +53,10 @@ export class NodeRepository { category: row.category, developmentStyle: row.development_style, package: row.package_name, - isAITool: !!row.is_ai_tool, - isTrigger: !!row.is_trigger, - isWebhook: !!row.is_webhook, - isVersioned: !!row.is_versioned, + isAITool: Number(row.is_ai_tool) === 1, + isTrigger: Number(row.is_trigger) === 1, + isWebhook: Number(row.is_webhook) === 1, + isVersioned: Number(row.is_versioned) === 1, version: row.version, properties: this.safeJsonParse(row.properties_schema, []), operations: this.safeJsonParse(row.operations, []), diff --git a/src/mcp/server.ts b/src/mcp/server.ts index 6a07151..0b0dfec 100644 --- a/src/mcp/server.ts +++ b/src/mcp/server.ts @@ -331,9 +331,9 @@ export class N8NDocumentationMCPServer { category: node.category, package: node.package_name, developmentStyle: node.development_style, - isAITool: !!node.is_ai_tool, - isTrigger: !!node.is_trigger, - isVersioned: !!node.is_versioned, + isAITool: Number(node.is_ai_tool) === 1, + isTrigger: Number(node.is_trigger) === 1, + isVersioned: Number(node.is_versioned) === 1, })), totalCount: nodes.length, };