fix: fixing issue with config and extra logs (#209)

This commit is contained in:
Kacper 2026-04-07 08:53:15 +01:00 committed by GitHub
parent c038366783
commit 33e19df3db
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 172 additions and 94 deletions

View file

@ -0,0 +1,5 @@
---
"sqlseal": patch
---
fixing issue with config table not found on first run

View file

@ -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()

View file

@ -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<T>(
operation: () => Promise<T>,
maxRetries: number = 3,
baseDelay: number = 50,
errorMatcher?: (error: Error) => boolean
): Promise<T> {
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<string, unknown>) {
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[] = [];
// 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);
}
}
}
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));
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);
// 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);
}
}
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;
}
// 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));
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;
}
},
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');
}
);
}
async explain(statement: string, frontmatter: Record<string, unknown>) {

View file

@ -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');
};
};

View file

@ -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)

View file

@ -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()

View file

@ -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)
})

View file

@ -5,12 +5,15 @@ export class Logger {
} else {
this.console = {
log: () => { },
error: () => { }
error: () => { },
warn: () => { },
debug: () => { },
trace: () => { }
}
}
}
private console: Pick<typeof console, 'log' | 'error'>
private console: Pick<typeof console, 'log' | 'error' | 'warn' | 'debug' | 'trace'>
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)
}
}