fix: pre-build FTS5 index for Docker compatibility
- Add FTS5 pre-creation in fetch-templates.ts before data import - Create prebuild-fts5.ts script to ensure FTS5 tables exist - Improve logging in template-repository.ts for better debugging - Add npm script 'prebuild:fts5' for manual FTS5 setup This ensures template search works consistently in Docker mode where runtime FTS5 table creation might fail due to permissions. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
BIN
data/nodes.db
BIN
data/nodes.db
Binary file not shown.
@@ -26,6 +26,7 @@
|
|||||||
"update:n8n:check": "node scripts/update-n8n-deps.js --dry-run",
|
"update:n8n:check": "node scripts/update-n8n-deps.js --dry-run",
|
||||||
"fetch:templates": "node dist/scripts/fetch-templates.js",
|
"fetch:templates": "node dist/scripts/fetch-templates.js",
|
||||||
"fetch:templates:robust": "node dist/scripts/fetch-templates-robust.js",
|
"fetch:templates:robust": "node dist/scripts/fetch-templates-robust.js",
|
||||||
|
"prebuild:fts5": "npx tsx scripts/prebuild-fts5.ts",
|
||||||
"test:templates": "node dist/scripts/test-templates.js",
|
"test:templates": "node dist/scripts/test-templates.js",
|
||||||
"test:workflow-validation": "node dist/scripts/test-workflow-validation.js",
|
"test:workflow-validation": "node dist/scripts/test-workflow-validation.js",
|
||||||
"test:template-validation": "node dist/scripts/test-template-validation.js",
|
"test:template-validation": "node dist/scripts/test-template-validation.js",
|
||||||
@@ -40,6 +41,9 @@
|
|||||||
"test:workflow-diff": "node dist/scripts/test-workflow-diff.js",
|
"test:workflow-diff": "node dist/scripts/test-workflow-diff.js",
|
||||||
"test:transactional-diff": "node dist/scripts/test-transactional-diff.js",
|
"test:transactional-diff": "node dist/scripts/test-transactional-diff.js",
|
||||||
"test:tools-documentation": "node dist/scripts/test-tools-documentation.js",
|
"test:tools-documentation": "node dist/scripts/test-tools-documentation.js",
|
||||||
|
"test:search-improvements": "node dist/scripts/test-search-improvements.js",
|
||||||
|
"test:fts5-search": "node dist/scripts/test-fts5-search.js",
|
||||||
|
"migrate:fts5": "node dist/scripts/migrate-nodes-fts.js",
|
||||||
"test:mcp:update-partial": "node dist/scripts/test-mcp-n8n-update-partial.js",
|
"test:mcp:update-partial": "node dist/scripts/test-mcp-n8n-update-partial.js",
|
||||||
"test:update-partial:debug": "node dist/scripts/test-update-partial-debug.js",
|
"test:update-partial:debug": "node dist/scripts/test-update-partial-debug.js",
|
||||||
"test:auth-logging": "tsx scripts/test-auth-logging.ts",
|
"test:auth-logging": "tsx scripts/test-auth-logging.ts",
|
||||||
|
|||||||
111
scripts/prebuild-fts5.ts
Normal file
111
scripts/prebuild-fts5.ts
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
#!/usr/bin/env npx tsx
|
||||||
|
/**
|
||||||
|
* Pre-build FTS5 indexes for the database
|
||||||
|
* This ensures FTS5 tables are created before the database is deployed to Docker
|
||||||
|
*/
|
||||||
|
import { createDatabaseAdapter } from '../src/database/database-adapter';
|
||||||
|
import { logger } from '../src/utils/logger';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
|
||||||
|
async function prebuildFTS5() {
|
||||||
|
console.log('🔍 Pre-building FTS5 indexes...\n');
|
||||||
|
|
||||||
|
const dbPath = './data/nodes.db';
|
||||||
|
|
||||||
|
if (!fs.existsSync(dbPath)) {
|
||||||
|
console.error('❌ Database not found at', dbPath);
|
||||||
|
console.error(' Please run npm run rebuild first');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const db = await createDatabaseAdapter(dbPath);
|
||||||
|
|
||||||
|
// Check FTS5 support
|
||||||
|
const hasFTS5 = db.checkFTS5Support();
|
||||||
|
|
||||||
|
if (!hasFTS5) {
|
||||||
|
console.log('ℹ️ FTS5 not supported in this SQLite build');
|
||||||
|
console.log(' Skipping FTS5 pre-build');
|
||||||
|
db.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('✅ FTS5 is supported');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create FTS5 virtual table for templates
|
||||||
|
console.log('\n📋 Creating FTS5 table for templates...');
|
||||||
|
db.exec(`
|
||||||
|
CREATE VIRTUAL TABLE IF NOT EXISTS templates_fts USING fts5(
|
||||||
|
name, description, content=templates
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Create triggers to keep FTS5 in sync
|
||||||
|
console.log('🔗 Creating synchronization triggers...');
|
||||||
|
|
||||||
|
db.exec(`
|
||||||
|
CREATE TRIGGER IF NOT EXISTS templates_ai AFTER INSERT ON templates BEGIN
|
||||||
|
INSERT INTO templates_fts(rowid, name, description)
|
||||||
|
VALUES (new.id, new.name, new.description);
|
||||||
|
END;
|
||||||
|
`);
|
||||||
|
|
||||||
|
db.exec(`
|
||||||
|
CREATE TRIGGER IF NOT EXISTS templates_au AFTER UPDATE ON templates BEGIN
|
||||||
|
UPDATE templates_fts SET name = new.name, description = new.description
|
||||||
|
WHERE rowid = new.id;
|
||||||
|
END;
|
||||||
|
`);
|
||||||
|
|
||||||
|
db.exec(`
|
||||||
|
CREATE TRIGGER IF NOT EXISTS templates_ad AFTER DELETE ON templates BEGIN
|
||||||
|
DELETE FROM templates_fts WHERE rowid = old.id;
|
||||||
|
END;
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Rebuild FTS5 index from existing data
|
||||||
|
console.log('🔄 Rebuilding FTS5 index from existing templates...');
|
||||||
|
|
||||||
|
// Clear existing FTS data
|
||||||
|
db.exec('DELETE FROM templates_fts');
|
||||||
|
|
||||||
|
// Repopulate from templates table
|
||||||
|
db.exec(`
|
||||||
|
INSERT INTO templates_fts(rowid, name, description)
|
||||||
|
SELECT id, name, description FROM templates
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Get counts
|
||||||
|
const templateCount = db.prepare('SELECT COUNT(*) as count FROM templates').get() as { count: number };
|
||||||
|
const ftsCount = db.prepare('SELECT COUNT(*) as count FROM templates_fts').get() as { count: number };
|
||||||
|
|
||||||
|
console.log(`\n✅ FTS5 pre-build complete!`);
|
||||||
|
console.log(` Templates: ${templateCount.count}`);
|
||||||
|
console.log(` FTS5 entries: ${ftsCount.count}`);
|
||||||
|
|
||||||
|
// Test FTS5 search
|
||||||
|
console.log('\n🧪 Testing FTS5 search...');
|
||||||
|
const testResults = db.prepare(`
|
||||||
|
SELECT COUNT(*) as count FROM templates t
|
||||||
|
JOIN templates_fts ON t.id = templates_fts.rowid
|
||||||
|
WHERE templates_fts MATCH 'webhook'
|
||||||
|
`).get() as { count: number };
|
||||||
|
|
||||||
|
console.log(` Found ${testResults.count} templates matching "webhook"`);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error pre-building FTS5:', error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
db.close();
|
||||||
|
console.log('\n✅ Database is ready for Docker deployment!');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run if called directly
|
||||||
|
if (require.main === module) {
|
||||||
|
prebuildFTS5().catch(console.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { prebuildFTS5 };
|
||||||
@@ -29,6 +29,49 @@ async function fetchTemplates() {
|
|||||||
const schema = fs.readFileSync(path.join(__dirname, '../../src/database/schema.sql'), 'utf8');
|
const schema = fs.readFileSync(path.join(__dirname, '../../src/database/schema.sql'), 'utf8');
|
||||||
db.exec(schema);
|
db.exec(schema);
|
||||||
|
|
||||||
|
// Pre-create FTS5 tables if supported
|
||||||
|
const hasFTS5 = db.checkFTS5Support();
|
||||||
|
if (hasFTS5) {
|
||||||
|
console.log('🔍 Creating FTS5 tables for template search...');
|
||||||
|
try {
|
||||||
|
// Create FTS5 virtual table
|
||||||
|
db.exec(`
|
||||||
|
CREATE VIRTUAL TABLE IF NOT EXISTS templates_fts USING fts5(
|
||||||
|
name, description, content=templates
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Create triggers to keep FTS5 in sync
|
||||||
|
db.exec(`
|
||||||
|
CREATE TRIGGER IF NOT EXISTS templates_ai AFTER INSERT ON templates BEGIN
|
||||||
|
INSERT INTO templates_fts(rowid, name, description)
|
||||||
|
VALUES (new.id, new.name, new.description);
|
||||||
|
END;
|
||||||
|
`);
|
||||||
|
|
||||||
|
db.exec(`
|
||||||
|
CREATE TRIGGER IF NOT EXISTS templates_au AFTER UPDATE ON templates BEGIN
|
||||||
|
UPDATE templates_fts SET name = new.name, description = new.description
|
||||||
|
WHERE rowid = new.id;
|
||||||
|
END;
|
||||||
|
`);
|
||||||
|
|
||||||
|
db.exec(`
|
||||||
|
CREATE TRIGGER IF NOT EXISTS templates_ad AFTER DELETE ON templates BEGIN
|
||||||
|
DELETE FROM templates_fts WHERE rowid = old.id;
|
||||||
|
END;
|
||||||
|
`);
|
||||||
|
|
||||||
|
console.log('✅ FTS5 tables created successfully\n');
|
||||||
|
} catch (error) {
|
||||||
|
console.log('⚠️ Failed to create FTS5 tables:', error);
|
||||||
|
console.log(' Template search will use LIKE fallback\n');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('ℹ️ FTS5 not supported in this SQLite build');
|
||||||
|
console.log(' Template search will use LIKE queries\n');
|
||||||
|
}
|
||||||
|
|
||||||
// Create service
|
// Create service
|
||||||
const service = new TemplateService(db);
|
const service = new TemplateService(db);
|
||||||
|
|
||||||
|
|||||||
@@ -38,37 +38,62 @@ export class TemplateRepository {
|
|||||||
|
|
||||||
if (this.hasFTS5Support) {
|
if (this.hasFTS5Support) {
|
||||||
try {
|
try {
|
||||||
// Create FTS5 virtual table
|
// Check if FTS5 table already exists
|
||||||
this.db.exec(`
|
const ftsExists = this.db.prepare(`
|
||||||
CREATE VIRTUAL TABLE IF NOT EXISTS templates_fts USING fts5(
|
SELECT name FROM sqlite_master
|
||||||
name, description, content=templates
|
WHERE type='table' AND name='templates_fts'
|
||||||
);
|
`).get() as { name: string } | undefined;
|
||||||
`);
|
|
||||||
|
|
||||||
// Create triggers to keep FTS5 in sync
|
if (ftsExists) {
|
||||||
this.db.exec(`
|
logger.info('FTS5 table already exists for templates');
|
||||||
CREATE TRIGGER IF NOT EXISTS templates_ai AFTER INSERT ON templates BEGIN
|
|
||||||
INSERT INTO templates_fts(rowid, name, description)
|
// Verify FTS5 is working by doing a test query
|
||||||
VALUES (new.id, new.name, new.description);
|
try {
|
||||||
END;
|
const testCount = this.db.prepare('SELECT COUNT(*) as count FROM templates_fts').get() as { count: number };
|
||||||
`);
|
logger.info(`FTS5 enabled with ${testCount.count} indexed entries`);
|
||||||
|
} catch (testError) {
|
||||||
this.db.exec(`
|
logger.warn('FTS5 table exists but query failed:', testError);
|
||||||
CREATE TRIGGER IF NOT EXISTS templates_au AFTER UPDATE ON templates BEGIN
|
this.hasFTS5Support = false;
|
||||||
UPDATE templates_fts SET name = new.name, description = new.description
|
return;
|
||||||
WHERE rowid = new.id;
|
}
|
||||||
END;
|
} else {
|
||||||
`);
|
// Create FTS5 virtual table
|
||||||
|
logger.info('Creating FTS5 virtual table for templates...');
|
||||||
this.db.exec(`
|
this.db.exec(`
|
||||||
CREATE TRIGGER IF NOT EXISTS templates_ad AFTER DELETE ON templates BEGIN
|
CREATE VIRTUAL TABLE IF NOT EXISTS templates_fts USING fts5(
|
||||||
DELETE FROM templates_fts WHERE rowid = old.id;
|
name, description, content=templates
|
||||||
END;
|
);
|
||||||
`);
|
`);
|
||||||
|
|
||||||
logger.info('FTS5 support enabled for template search');
|
// Create triggers to keep FTS5 in sync
|
||||||
} catch (error) {
|
this.db.exec(`
|
||||||
logger.warn('Failed to initialize FTS5 for templates:', error);
|
CREATE TRIGGER IF NOT EXISTS templates_ai AFTER INSERT ON templates BEGIN
|
||||||
|
INSERT INTO templates_fts(rowid, name, description)
|
||||||
|
VALUES (new.id, new.name, new.description);
|
||||||
|
END;
|
||||||
|
`);
|
||||||
|
|
||||||
|
this.db.exec(`
|
||||||
|
CREATE TRIGGER IF NOT EXISTS templates_au AFTER UPDATE ON templates BEGIN
|
||||||
|
UPDATE templates_fts SET name = new.name, description = new.description
|
||||||
|
WHERE rowid = new.id;
|
||||||
|
END;
|
||||||
|
`);
|
||||||
|
|
||||||
|
this.db.exec(`
|
||||||
|
CREATE TRIGGER IF NOT EXISTS templates_ad AFTER DELETE ON templates BEGIN
|
||||||
|
DELETE FROM templates_fts WHERE rowid = old.id;
|
||||||
|
END;
|
||||||
|
`);
|
||||||
|
|
||||||
|
logger.info('FTS5 support enabled for template search');
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
logger.warn('Failed to initialize FTS5 for templates:', {
|
||||||
|
message: error.message,
|
||||||
|
code: error.code,
|
||||||
|
stack: error.stack
|
||||||
|
});
|
||||||
this.hasFTS5Support = false;
|
this.hasFTS5Support = false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -158,25 +183,36 @@ export class TemplateRepository {
|
|||||||
* Search templates by name or description
|
* Search templates by name or description
|
||||||
*/
|
*/
|
||||||
searchTemplates(query: string, limit: number = 20): StoredTemplate[] {
|
searchTemplates(query: string, limit: number = 20): StoredTemplate[] {
|
||||||
|
logger.debug(`Searching templates for: "${query}" (FTS5: ${this.hasFTS5Support})`);
|
||||||
|
|
||||||
// If FTS5 is not supported, go straight to LIKE search
|
// If FTS5 is not supported, go straight to LIKE search
|
||||||
if (!this.hasFTS5Support) {
|
if (!this.hasFTS5Support) {
|
||||||
|
logger.debug('Using LIKE search (FTS5 not available)');
|
||||||
return this.searchTemplatesLIKE(query, limit);
|
return this.searchTemplatesLIKE(query, limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Use FTS for search
|
// Use FTS for search
|
||||||
const ftsQuery = query.split(' ').map(term => `"${term}"`).join(' OR ');
|
const ftsQuery = query.split(' ').map(term => `"${term}"`).join(' OR ');
|
||||||
|
logger.debug(`FTS5 query: ${ftsQuery}`);
|
||||||
|
|
||||||
return this.db.prepare(`
|
const results = this.db.prepare(`
|
||||||
SELECT t.* FROM templates t
|
SELECT t.* FROM templates t
|
||||||
JOIN templates_fts ON t.id = templates_fts.rowid
|
JOIN templates_fts ON t.id = templates_fts.rowid
|
||||||
WHERE templates_fts MATCH ?
|
WHERE templates_fts MATCH ?
|
||||||
ORDER BY rank, t.views DESC
|
ORDER BY rank, t.views DESC
|
||||||
LIMIT ?
|
LIMIT ?
|
||||||
`).all(ftsQuery, limit) as StoredTemplate[];
|
`).all(ftsQuery, limit) as StoredTemplate[];
|
||||||
|
|
||||||
|
logger.debug(`FTS5 search returned ${results.length} results`);
|
||||||
|
return results;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// If FTS5 query fails, fallback to LIKE search
|
// If FTS5 query fails, fallback to LIKE search
|
||||||
logger.warn('FTS5 template search failed, using LIKE fallback:', error.message);
|
logger.warn('FTS5 template search failed, using LIKE fallback:', {
|
||||||
|
message: error.message,
|
||||||
|
query: query,
|
||||||
|
ftsQuery: query.split(' ').map(term => `"${term}"`).join(' OR ')
|
||||||
|
});
|
||||||
return this.searchTemplatesLIKE(query, limit);
|
return this.searchTemplatesLIKE(query, limit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -186,13 +222,17 @@ export class TemplateRepository {
|
|||||||
*/
|
*/
|
||||||
private searchTemplatesLIKE(query: string, limit: number = 20): StoredTemplate[] {
|
private searchTemplatesLIKE(query: string, limit: number = 20): StoredTemplate[] {
|
||||||
const likeQuery = `%${query}%`;
|
const likeQuery = `%${query}%`;
|
||||||
|
logger.debug(`Using LIKE search with pattern: ${likeQuery}`);
|
||||||
|
|
||||||
return this.db.prepare(`
|
const results = this.db.prepare(`
|
||||||
SELECT * FROM templates
|
SELECT * FROM templates
|
||||||
WHERE name LIKE ? OR description LIKE ?
|
WHERE name LIKE ? OR description LIKE ?
|
||||||
ORDER BY views DESC, created_at DESC
|
ORDER BY views DESC, created_at DESC
|
||||||
LIMIT ?
|
LIMIT ?
|
||||||
`).all(likeQuery, likeQuery, limit) as StoredTemplate[];
|
`).all(likeQuery, likeQuery, limit) as StoredTemplate[];
|
||||||
|
|
||||||
|
logger.debug(`LIKE search returned ${results.length} results`);
|
||||||
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user