From 86ad78ae49b7e15166d9ce9e2c8286655305b8f5 Mon Sep 17 00:00:00 2001 From: Kacper Kula Date: Tue, 7 Apr 2026 08:50:42 +0100 Subject: [PATCH] fix: fixing issue with config and extra logs --- .changeset/poor-foxes-fall.md | 5 + src/modules/database/factory.ts | 2 - .../database/sqlocal/sqlocalWorkerDatabase.ts | 220 +++++++++++------- src/modules/main/init.ts | 3 - src/modules/main/module.ts | 2 +- src/modules/sync/sync/sync.ts | 4 +- .../cellParser/ModernCellParser.ts | 1 - src/utils/logger.ts | 19 +- 8 files changed, 167 insertions(+), 89 deletions(-) create mode 100644 .changeset/poor-foxes-fall.md diff --git a/.changeset/poor-foxes-fall.md b/.changeset/poor-foxes-fall.md new file mode 100644 index 0000000..86736bb --- /dev/null +++ b/.changeset/poor-foxes-fall.md @@ -0,0 +1,5 @@ +--- +"sqlseal": patch +--- + +fixing issue with config table not found on first run diff --git a/src/modules/database/factory.ts b/src/modules/database/factory.ts index 15a41cc..97afa46 100644 --- a/src/modules/database/factory.ts +++ b/src/modules/database/factory.ts @@ -2,8 +2,6 @@ import { App } from "obsidian"; import { DatabaseProvider } from "./sqlocal"; export const databaseFactory = async (app: App, provider: DatabaseProvider) => { - console.log('#### DATABASE CREATION') - const db = await provider.get(null) await db.connect() diff --git a/src/modules/database/sqlocal/sqlocalWorkerDatabase.ts b/src/modules/database/sqlocal/sqlocalWorkerDatabase.ts index 373e01e..cc39ec6 100644 --- a/src/modules/database/sqlocal/sqlocalWorkerDatabase.ts +++ b/src/modules/database/sqlocal/sqlocalWorkerDatabase.ts @@ -9,6 +9,55 @@ import { sanitise } from "../../../utils/sanitiseColumn"; // @ts-ignore import wasmUrl from 'virtual:wa-sqlite-wasm-url'; +/** + * Retry an async operation with exponential backoff + * @param operation - The async operation to retry + * @param maxRetries - Maximum number of retry attempts (default: 3) + * @param baseDelay - Base delay in milliseconds for exponential backoff (default: 50) + * @param errorMatcher - Optional function to determine if error should trigger retry + * @returns The result of the successful operation + * @throws The last error if all retries fail + */ +async function retryWithBackoff( + operation: () => Promise, + maxRetries: number = 3, + baseDelay: number = 50, + errorMatcher?: (error: Error) => boolean +): Promise { + let lastError: Error; + + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + return await operation(); + } catch (error) { + lastError = error as Error; + + // If errorMatcher provided, only retry matching errors + if (errorMatcher && !errorMatcher(error as Error)) { + throw error; + } + + // Don't retry on last attempt + if (attempt === maxRetries) { + break; + } + + // Calculate delay with exponential backoff + const delay = baseDelay * Math.pow(2, attempt - 1); + if (process.env.NODE_ENV === 'development') { + console.warn( + `SqlocalWorkerDatabase: Retry attempt ${attempt}/${maxRetries} ` + + `after error: ${lastError.message}. Waiting ${delay}ms...` + ); + } + + await new Promise(resolve => setTimeout(resolve, delay)); + } + } + + throw lastError!; +} + /** * Worker-side database implementation that runs wa-sqlite operations * in a Web Worker to avoid blocking the main thread. @@ -147,7 +196,9 @@ export class SqlocalWorkerDatabase { registerCustomFunction(name: string, argsCount = 1) { if (!this.connection) { - console.warn('SqlocalWorkerDatabase: Database not connected, cannot register custom function'); + if (process.env.NODE_ENV === 'development') { + console.warn('SqlocalWorkerDatabase: Database not connected, cannot register custom function'); + } return Promise.resolve(); } @@ -524,94 +575,107 @@ export class SqlocalWorkerDatabase { async select(statement: string, frontmatter: Record) { if (!this.connection) throw new Error('Database not connected'); if (this.isRecreating) { - console.warn('SqlocalWorkerDatabase: Database is being recreated, cannot execute select'); + if (process.env.NODE_ENV === 'development') { + console.warn('SqlocalWorkerDatabase: Database is being recreated, cannot execute select'); + } return { data: [], columns: [], executionTime: 0 }; } - try { - // Replace frontmatter placeholders in the query - let processedStatement = statement; - const params: any[] = []; - - // Support both {{key}} and @key parameter formats for compatibility - for (const [key, value] of Object.entries(frontmatter)) { - // Handle {{key}} format (used in user queries) - const doubleBracePlaceholder = `{{${key}}}`; - const doubleBraceRegex = new RegExp(doubleBracePlaceholder.replace(/[{}]/g, '\\$&'), 'g'); - const doubleBraceMatches = (processedStatement.match(doubleBraceRegex) || []).length; - if (doubleBraceMatches > 0) { - processedStatement = processedStatement.replace(doubleBraceRegex, '?'); - for (let i = 0; i < doubleBraceMatches; i++) { - params.push(value); - } - } + // Wrap the select operation with retry logic + return retryWithBackoff( + async () => { + try { + // Replace frontmatter placeholders in the query + let processedStatement = statement; + const params: any[] = []; + + // Support both {{key}} and @key parameter formats for compatibility + for (const [key, value] of Object.entries(frontmatter)) { + // Handle {{key}} format (used in user queries) + const doubleBracePlaceholder = `{{${key}}}`; + const doubleBraceRegex = new RegExp(doubleBracePlaceholder.replace(/[{}]/g, '\\$&'), 'g'); + const doubleBraceMatches = (processedStatement.match(doubleBraceRegex) || []).length; + if (doubleBraceMatches > 0) { + processedStatement = processedStatement.replace(doubleBraceRegex, '?'); + for (let i = 0; i < doubleBraceMatches; i++) { + params.push(value); + } + } - // Handle @key format (used in repository queries) - const atPlaceholder = `@${key}`; - const atRegex = new RegExp(`@${key}\\b`, 'g'); - const atMatches = (processedStatement.match(atRegex) || []).length; - if (atMatches > 0) { - processedStatement = processedStatement.replace(atRegex, '?'); - for (let i = 0; i < atMatches; i++) { - params.push(value); + // Handle @key format (used in repository queries) + const atPlaceholder = `@${key}`; + const atRegex = new RegExp(`@${key}\\b`, 'g'); + const atMatches = (processedStatement.match(atRegex) || []).length; + if (atMatches > 0) { + processedStatement = processedStatement.replace(atRegex, '?'); + for (let i = 0; i < atMatches; i++) { + params.push(value); + } + } } - } - } - - const startTime = performance.now(); - const data: any[] = []; - let columns: string[] = []; - // Create string in WASM memory - const str = this.sqlite3.str_new(this.connection, processedStatement); - let prepared = null; - try { - prepared = await this.sqlite3.prepare_v2(this.connection, this.sqlite3.str_value(str)); + const startTime = performance.now(); + const data: any[] = []; + let columns: string[] = []; - if (prepared && prepared.stmt) { - await this.sqlite3.bind_collection(prepared.stmt, params); - - // Get column names - const columnCount = await this.sqlite3.column_count(prepared.stmt); - - for (let i = 0; i < columnCount; i++) { - columns.push(await this.sqlite3.column_name(prepared.stmt, i)); - } - - // Fetch all rows - let stepResult; - while ((stepResult = await this.sqlite3.step(prepared.stmt)) === SQLite.SQLITE_ROW) { - const row: any = {}; - for (let i = 0; i < columnCount; i++) { - const columnName = columns[i]; - row[columnName] = await this.sqlite3.column(prepared.stmt, i); - } - data.push(row); - } - } - } finally { - // Finalize statement before finishing string - if (prepared && prepared.stmt) { + // Create string in WASM memory + const str = this.sqlite3.str_new(this.connection, processedStatement); + let prepared = null; try { - await this.sqlite3.finalize(prepared.stmt); - } catch (finalizeError) { - console.error('SqlocalWorkerDatabase: Error finalizing statement:', finalizeError); + prepared = await this.sqlite3.prepare_v2(this.connection, this.sqlite3.str_value(str)); + + if (prepared && prepared.stmt) { + await this.sqlite3.bind_collection(prepared.stmt, params); + + // Get column names + const columnCount = await this.sqlite3.column_count(prepared.stmt); + + for (let i = 0; i < columnCount; i++) { + columns.push(await this.sqlite3.column_name(prepared.stmt, i)); + } + + // Fetch all rows + let stepResult; + while ((stepResult = await this.sqlite3.step(prepared.stmt)) === SQLite.SQLITE_ROW) { + const row: any = {}; + for (let i = 0; i < columnCount; i++) { + const columnName = columns[i]; + row[columnName] = await this.sqlite3.column(prepared.stmt, i); + } + data.push(row); + } + } + } finally { + // Finalize statement before finishing string + if (prepared && prepared.stmt) { + try { + await this.sqlite3.finalize(prepared.stmt); + } catch (finalizeError) { + console.error('SqlocalWorkerDatabase: Error finalizing statement:', finalizeError); + } + } + this.sqlite3.str_finish(str); } + + const executionTime = performance.now() - startTime; + return { + data, + columns, + executionTime + }; + } catch (error) { + console.error('SqlocalWorkerDatabase: Error executing select:', error); + console.error('SqlocalWorkerDatabase: Failed statement:', statement); + throw error; } - this.sqlite3.str_finish(str); + }, + 3, // maxRetries + 50, // baseDelay (50ms, 100ms, 200ms pattern) + (error: Error) => { + // Only retry on "no such table" errors + return error.message.toLowerCase().includes('no such table'); } - - const executionTime = performance.now() - startTime; - return { - data, - columns, - executionTime - }; - } catch (error) { - console.error('SqlocalWorkerDatabase: Error executing select:', error); - console.error('SqlocalWorkerDatabase: Failed statement:', statement); - throw error; - } + ); } async explain(statement: string, frontmatter: Record) { diff --git a/src/modules/main/init.ts b/src/modules/main/init.ts index 5151905..bd7e28c 100644 --- a/src/modules/main/init.ts +++ b/src/modules/main/init.ts @@ -19,8 +19,5 @@ export const mainInit = ( apiInit(); globalTablesInit(); explorerInit(); - - console.log('🚀 SQL Seal initialized with wa-sqlite test command available'); - console.log('📋 Use Ctrl/Cmd+P -> "Test wa-sqlite Implementation" to test wa-sqlite'); }; }; diff --git a/src/modules/main/module.ts b/src/modules/main/module.ts index ac4f226..e0a671f 100644 --- a/src/modules/main/module.ts +++ b/src/modules/main/module.ts @@ -19,7 +19,7 @@ const obsidian = new Registrator(process.env.NODE_ENV === 'development' ? { logg .export('app', 'plugin', 'vault') -export const mainModule = new Registrator({logger: console.log}) +export const mainModule = new Registrator(process.env.NODE_ENV === 'development' ? {logger: console.log} : undefined) .module('obsidian', obsidian) .module('db', db) .module('editor', editor) diff --git a/src/modules/sync/sync/sync.ts b/src/modules/sync/sync/sync.ts index 74286d8..f6862ac 100644 --- a/src/modules/sync/sync/sync.ts +++ b/src/modules/sync/sync/sync.ts @@ -68,6 +68,7 @@ export class Sync { // Configuration this.configRepo = new ConfigurationRepository(this.db) + await this.configRepo.init() let version try { @@ -78,10 +79,9 @@ export class Sync { if (version < SQLSEAL_DATABASE_VERSION) { await this.db.recreateDatabase() + await this.configRepo.init() } - await this.configRepo.init() - this.tableDefinitionsRepo = new TableDefinitionsRepository(this.db) await this.tableDefinitionsRepo.init() diff --git a/src/modules/syntaxHighlight/cellParser/ModernCellParser.ts b/src/modules/syntaxHighlight/cellParser/ModernCellParser.ts index 4b85cce..f6ff269 100644 --- a/src/modules/syntaxHighlight/cellParser/ModernCellParser.ts +++ b/src/modules/syntaxHighlight/cellParser/ModernCellParser.ts @@ -173,7 +173,6 @@ export class ModernCellParser { // FIXME: this should be extracted to separate class / function but for now it's fine. registerDbFunctions(db: SqlocalDatabaseProxy) { - console.trace('register db functions called') this.functions.forEach(funct => { db.registerCustomFunction(funct.name, funct.sqlFunctionArgumentsCount) }) diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 8e376d3..9518f12 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -5,12 +5,15 @@ export class Logger { } else { this.console = { log: () => { }, - error: () => { } + error: () => { }, + warn: () => { }, + debug: () => { }, + trace: () => { } } } } - private console: Pick + private console: Pick log(...args: any[]) { this.console.log(...args) @@ -19,4 +22,16 @@ export class Logger { error(...args: any[]) { this.console.error(...args) } + + warn(...args: any[]) { + this.console.warn(...args) + } + + debug(...args: any[]) { + this.console.debug(...args) + } + + trace(...args: any[]) { + this.console.trace(...args) + } } \ No newline at end of file