Fix worker-only database architecture and Vue Proxy serialization
- Implement worker-only database access to eliminate double migrations - Add parameter serialization in usePlatformService to prevent Capacitor "object could not be cloned" errors - Fix infinite logging loop with circuit breaker in databaseUtil - Use dynamic imports in WebPlatformService to prevent worker thread errors - Add higher-level database methods (getContacts, getSettings) to composable - Eliminate Vue Proxy objects through JSON serialization and Object.freeze protection Resolves Proxy(Array) serialization failures and worker context conflicts across Web/Capacitor/Electron platforms.
This commit is contained in:
@@ -58,7 +58,8 @@ class AbsurdSqlDatabaseService implements DatabaseService {
|
||||
try {
|
||||
await this.initializationPromise;
|
||||
} catch (error) {
|
||||
logger.error(`AbsurdSqlDatabaseService initialize method failed:`, error);
|
||||
// logger.error(`AbsurdSqlDatabaseService initialize method failed:`, error); // DISABLED
|
||||
console.error(`AbsurdSqlDatabaseService initialize method failed:`, error);
|
||||
this.initializationPromise = null; // Reset on failure
|
||||
throw error;
|
||||
}
|
||||
@@ -144,7 +145,15 @@ class AbsurdSqlDatabaseService implements DatabaseService {
|
||||
}
|
||||
operation.resolve(result);
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
// logger.error( // DISABLED
|
||||
// "Error while processing SQL queue:",
|
||||
// error,
|
||||
// " ... for sql:",
|
||||
// operation.sql,
|
||||
// " ... with params:",
|
||||
// operation.params,
|
||||
// );
|
||||
console.error(
|
||||
"Error while processing SQL queue:",
|
||||
error,
|
||||
" ... for sql:",
|
||||
@@ -196,7 +205,10 @@ class AbsurdSqlDatabaseService implements DatabaseService {
|
||||
|
||||
// If initialized but no db, something went wrong
|
||||
if (!this.db) {
|
||||
logger.error(
|
||||
// logger.error( // DISABLED
|
||||
// `Database not properly initialized after await waitForInitialization() - initialized flag is true but db is null`,
|
||||
// );
|
||||
console.error(
|
||||
`Database not properly initialized after await waitForInitialization() - initialized flag is true but db is null`,
|
||||
);
|
||||
throw new Error(
|
||||
|
||||
@@ -130,4 +130,15 @@ export interface PlatformService {
|
||||
sql: string,
|
||||
params?: unknown[],
|
||||
): Promise<{ changes: number; lastId?: number }>;
|
||||
|
||||
/**
|
||||
* Executes a SQL query and returns the first row as an array.
|
||||
* @param sql - The SQL query to execute
|
||||
* @param params - The parameters to pass to the query
|
||||
* @returns Promise resolving to the first row as an array, or undefined if no results
|
||||
*/
|
||||
dbGetOneRow(
|
||||
sql: string,
|
||||
params?: unknown[],
|
||||
): Promise<unknown[] | undefined>;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ import { CapacitorPlatformService } from "./platforms/CapacitorPlatformService";
|
||||
*/
|
||||
export class PlatformServiceFactory {
|
||||
private static instance: PlatformService | null = null;
|
||||
private static callCount = 0; // Debug counter
|
||||
private static creationLogged = false; // Only log creation once
|
||||
|
||||
/**
|
||||
* Gets or creates the singleton instance of PlatformService.
|
||||
@@ -27,11 +29,20 @@ export class PlatformServiceFactory {
|
||||
* @returns {PlatformService} The singleton instance of PlatformService
|
||||
*/
|
||||
public static getInstance(): PlatformService {
|
||||
PlatformServiceFactory.callCount++;
|
||||
|
||||
if (PlatformServiceFactory.instance) {
|
||||
// Normal case - return existing instance silently
|
||||
return PlatformServiceFactory.instance;
|
||||
}
|
||||
|
||||
// Only log when actually creating the instance
|
||||
const platform = process.env.VITE_PLATFORM || "web";
|
||||
|
||||
if (!PlatformServiceFactory.creationLogged) {
|
||||
console.log(`[PlatformServiceFactory] Creating singleton instance for platform: ${platform}`);
|
||||
PlatformServiceFactory.creationLogged = true;
|
||||
}
|
||||
|
||||
switch (platform) {
|
||||
case "capacitor":
|
||||
@@ -45,4 +56,14 @@ export class PlatformServiceFactory {
|
||||
|
||||
return PlatformServiceFactory.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug method to check singleton usage stats
|
||||
*/
|
||||
public static getStats(): { callCount: number; instanceExists: boolean } {
|
||||
return {
|
||||
callCount: PlatformServiceFactory.callCount,
|
||||
instanceExists: PlatformServiceFactory.instance !== null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,6 +167,13 @@ export class CapacitorPlatformService implements PlatformService {
|
||||
"[CapacitorPlatformService] Error while processing SQL queue:",
|
||||
error,
|
||||
);
|
||||
logger.error(
|
||||
`[CapacitorPlatformService] Failed operation - Type: ${operation.type}, SQL: ${operation.sql}`,
|
||||
);
|
||||
logger.error(
|
||||
`[CapacitorPlatformService] Failed operation - Params:`,
|
||||
operation.params,
|
||||
);
|
||||
operation.reject(error);
|
||||
}
|
||||
}
|
||||
@@ -179,31 +186,140 @@ export class CapacitorPlatformService implements PlatformService {
|
||||
sql: string,
|
||||
params: unknown[] = [],
|
||||
): Promise<R> {
|
||||
// Convert parameters to SQLite-compatible types
|
||||
const convertedParams = params.map((param) => {
|
||||
// Log incoming parameters for debugging (HIGH PRIORITY)
|
||||
logger.warn(`[CapacitorPlatformService] queueOperation - SQL: ${sql}, Params:`, params);
|
||||
|
||||
// Convert parameters to SQLite-compatible types with robust serialization
|
||||
const convertedParams = params.map((param, index) => {
|
||||
if (param === null || param === undefined) {
|
||||
return null;
|
||||
}
|
||||
if (typeof param === "object" && param !== null) {
|
||||
// Convert objects and arrays to JSON strings
|
||||
return JSON.stringify(param);
|
||||
// Enhanced debug logging for all objects (HIGH PRIORITY)
|
||||
logger.warn(`[CapacitorPlatformService] Object param at index ${index}:`, {
|
||||
type: typeof param,
|
||||
toString: param.toString(),
|
||||
constructorName: param.constructor?.name,
|
||||
isArray: Array.isArray(param),
|
||||
keys: Object.keys(param),
|
||||
stringRep: String(param)
|
||||
});
|
||||
|
||||
// Special handling for Proxy objects (common cause of "An object could not be cloned")
|
||||
const isProxy = this.isProxyObject(param);
|
||||
logger.warn(`[CapacitorPlatformService] isProxy result for index ${index}:`, isProxy);
|
||||
|
||||
// AGGRESSIVE: If toString contains "Proxy", treat as Proxy even if isProxyObject returns false
|
||||
const stringRep = String(param);
|
||||
const forceProxyDetection = stringRep.includes('Proxy(') || stringRep.startsWith('Proxy');
|
||||
logger.warn(`[CapacitorPlatformService] Force proxy detection for index ${index}:`, forceProxyDetection);
|
||||
|
||||
if (isProxy || forceProxyDetection) {
|
||||
logger.warn(`[CapacitorPlatformService] Proxy object detected at index ${index} (method: ${isProxy ? 'isProxyObject' : 'stringDetection'}), toString: ${stringRep}`);
|
||||
try {
|
||||
// AGGRESSIVE EXTRACTION: Try multiple methods to extract actual values
|
||||
if (Array.isArray(param)) {
|
||||
// Method 1: Array.from() to extract from Proxy(Array)
|
||||
const actualArray = Array.from(param);
|
||||
logger.info(`[CapacitorPlatformService] Extracted array from Proxy via Array.from():`, actualArray);
|
||||
|
||||
// Method 2: Manual element extraction for safety
|
||||
const manualArray: unknown[] = [];
|
||||
for (let i = 0; i < param.length; i++) {
|
||||
manualArray.push(param[i]);
|
||||
}
|
||||
logger.info(`[CapacitorPlatformService] Manual array extraction:`, manualArray);
|
||||
|
||||
// Use the manual extraction as it's more reliable
|
||||
return manualArray;
|
||||
} else {
|
||||
// For Proxy(Object), try to extract actual object
|
||||
const actualObject = Object.assign({}, param);
|
||||
logger.info(`[CapacitorPlatformService] Extracted object from Proxy:`, actualObject);
|
||||
return actualObject;
|
||||
}
|
||||
} catch (proxyError) {
|
||||
logger.error(`[CapacitorPlatformService] Failed to extract from Proxy at index ${index}:`, proxyError);
|
||||
|
||||
// FALLBACK: Try to extract primitive values manually
|
||||
if (Array.isArray(param)) {
|
||||
try {
|
||||
const fallbackArray: unknown[] = [];
|
||||
for (let i = 0; i < param.length; i++) {
|
||||
fallbackArray.push(param[i]);
|
||||
}
|
||||
logger.info(`[CapacitorPlatformService] Fallback array extraction successful:`, fallbackArray);
|
||||
return fallbackArray;
|
||||
} catch (fallbackError) {
|
||||
logger.error(`[CapacitorPlatformService] Fallback array extraction failed:`, fallbackError);
|
||||
return `[Proxy Array - Could not extract]`;
|
||||
}
|
||||
}
|
||||
return `[Proxy Object - Could not extract]`;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Safely convert objects and arrays to JSON strings
|
||||
return JSON.stringify(param);
|
||||
} catch (error) {
|
||||
// Handle non-serializable objects
|
||||
logger.error(`[CapacitorPlatformService] Failed to serialize parameter at index ${index}:`, error);
|
||||
logger.error(`[CapacitorPlatformService] Problematic parameter:`, param);
|
||||
|
||||
// Fallback: Convert to string representation
|
||||
if (Array.isArray(param)) {
|
||||
return `[Array(${param.length})]`;
|
||||
}
|
||||
return `[Object ${param.constructor?.name || 'Unknown'}]`;
|
||||
}
|
||||
}
|
||||
if (typeof param === "boolean") {
|
||||
// Convert boolean to integer (0 or 1)
|
||||
return param ? 1 : 0;
|
||||
}
|
||||
// Numbers, strings, bigints, and buffers are already supported
|
||||
if (typeof param === "function") {
|
||||
// Functions can't be serialized - convert to string representation
|
||||
logger.warn(`[CapacitorPlatformService] Function parameter detected and converted to string at index ${index}`);
|
||||
return `[Function ${param.name || 'Anonymous'}]`;
|
||||
}
|
||||
if (typeof param === "symbol") {
|
||||
// Symbols can't be serialized - convert to string representation
|
||||
logger.warn(`[CapacitorPlatformService] Symbol parameter detected and converted to string at index ${index}`);
|
||||
return param.toString();
|
||||
}
|
||||
// Numbers, strings, bigints are supported, but ensure bigints are converted to strings
|
||||
if (typeof param === "bigint") {
|
||||
return param.toString();
|
||||
}
|
||||
return param;
|
||||
});
|
||||
|
||||
// Log converted parameters for debugging (HIGH PRIORITY)
|
||||
logger.warn(`[CapacitorPlatformService] Converted params:`, convertedParams);
|
||||
|
||||
return new Promise<R>((resolve, reject) => {
|
||||
const operation: QueuedOperation = {
|
||||
type,
|
||||
sql,
|
||||
params: convertedParams,
|
||||
resolve: (value: unknown) => resolve(value as R),
|
||||
reject,
|
||||
};
|
||||
// Create completely plain objects that Vue cannot make reactive
|
||||
// Step 1: Deep clone the converted params to ensure they're plain objects
|
||||
const plainParams = JSON.parse(JSON.stringify(convertedParams));
|
||||
|
||||
// Step 2: Create operation object using Object.create(null) for no prototype
|
||||
const operation = Object.create(null) as QueuedOperation;
|
||||
operation.type = type;
|
||||
operation.sql = sql;
|
||||
operation.params = plainParams;
|
||||
operation.resolve = (value: unknown) => resolve(value as R);
|
||||
operation.reject = reject;
|
||||
|
||||
// Step 3: Freeze everything to prevent modification
|
||||
Object.freeze(operation.params);
|
||||
Object.freeze(operation);
|
||||
|
||||
// Add enhanced logging to verify our fix
|
||||
logger.warn(`[CapacitorPlatformService] Final operation.params type:`, typeof operation.params);
|
||||
logger.warn(`[CapacitorPlatformService] Final operation.params toString:`, operation.params.toString());
|
||||
logger.warn(`[CapacitorPlatformService] Final operation.params constructor:`, operation.params.constructor?.name);
|
||||
|
||||
this.operationQueue.push(operation);
|
||||
|
||||
// If we're already initialized, start processing the queue
|
||||
@@ -237,6 +353,75 @@ export class CapacitorPlatformService implements PlatformService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect if an object is a Proxy object that cannot be serialized
|
||||
* Proxy objects cause "An object could not be cloned" errors in Capacitor
|
||||
* @param obj - Object to test
|
||||
* @returns true if the object appears to be a Proxy
|
||||
*/
|
||||
private isProxyObject(obj: unknown): boolean {
|
||||
if (typeof obj !== "object" || obj === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// Method 1: Check toString representation
|
||||
const objString = obj.toString();
|
||||
if (objString.includes('Proxy(') || objString.startsWith('Proxy')) {
|
||||
logger.debug("[CapacitorPlatformService] Proxy detected via toString:", objString);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Method 2: Check constructor name
|
||||
const constructorName = obj.constructor?.name;
|
||||
if (constructorName === 'Proxy') {
|
||||
logger.debug("[CapacitorPlatformService] Proxy detected via constructor name");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Method 3: Check Object.prototype.toString
|
||||
const objToString = Object.prototype.toString.call(obj);
|
||||
if (objToString.includes('Proxy')) {
|
||||
logger.debug("[CapacitorPlatformService] Proxy detected via Object.prototype.toString");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Method 4: Vue/Reactive Proxy detection - check for __v_ properties
|
||||
if (typeof obj === 'object' && obj !== null) {
|
||||
// Check for Vue reactive proxy indicators
|
||||
const hasVueProxy = Object.getOwnPropertyNames(obj).some(prop =>
|
||||
prop.startsWith('__v_') || prop.startsWith('__r_')
|
||||
);
|
||||
if (hasVueProxy) {
|
||||
logger.debug("[CapacitorPlatformService] Vue reactive Proxy detected");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Method 5: Try JSON.stringify and check for Proxy in error or result
|
||||
try {
|
||||
const jsonString = JSON.stringify(obj);
|
||||
if (jsonString.includes('Proxy')) {
|
||||
logger.debug("[CapacitorPlatformService] Proxy detected in JSON serialization");
|
||||
return true;
|
||||
}
|
||||
} catch (jsonError) {
|
||||
// If JSON.stringify fails, it might be a non-serializable Proxy
|
||||
const errorMessage = jsonError instanceof Error ? jsonError.message : String(jsonError);
|
||||
if (errorMessage.includes('Proxy') || errorMessage.includes('circular') || errorMessage.includes('clone')) {
|
||||
logger.debug("[CapacitorPlatformService] Proxy detected via JSON serialization error");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (error) {
|
||||
// If we can't inspect the object, it might be a Proxy causing issues
|
||||
logger.warn("[CapacitorPlatformService] Could not inspect object for Proxy detection:", error);
|
||||
return true; // Assume it's a Proxy if we can't inspect it
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute database migrations for the Capacitor platform
|
||||
*
|
||||
@@ -1074,4 +1259,21 @@ export class CapacitorPlatformService implements PlatformService {
|
||||
params || [],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PlatformService.dbGetOneRow
|
||||
*/
|
||||
async dbGetOneRow(
|
||||
sql: string,
|
||||
params?: unknown[],
|
||||
): Promise<unknown[] | undefined> {
|
||||
await this.waitForInitialization();
|
||||
const result = await this.queueOperation<QueryExecResult>("query", sql, params || []);
|
||||
|
||||
// Return the first row from the result, or undefined if no results
|
||||
if (result && result.values && result.values.length > 0) {
|
||||
return result.values[0];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,15 @@ import {
|
||||
} from "../PlatformService";
|
||||
import { logger } from "../../utils/logger";
|
||||
import { QueryExecResult } from "@/interfaces/database";
|
||||
import databaseService from "../AbsurdSqlDatabaseService";
|
||||
// Dynamic import of initBackend to prevent worker context errors
|
||||
import type {
|
||||
WorkerRequest,
|
||||
WorkerResponse,
|
||||
QueryRequest,
|
||||
ExecRequest,
|
||||
QueryResult,
|
||||
GetOneRowRequest,
|
||||
} from "@/interfaces/worker-messages";
|
||||
|
||||
/**
|
||||
* Platform service implementation for web browser platform.
|
||||
@@ -16,11 +24,215 @@ import databaseService from "../AbsurdSqlDatabaseService";
|
||||
* - Image capture using the browser's file input
|
||||
* - Image selection from local filesystem
|
||||
* - Image processing and conversion
|
||||
* - Database operations via worker thread messaging
|
||||
*
|
||||
* Note: File system operations are not available in the web platform
|
||||
* due to browser security restrictions. These methods throw appropriate errors.
|
||||
*/
|
||||
export class WebPlatformService implements PlatformService {
|
||||
private static instanceCount = 0; // Debug counter
|
||||
private worker: Worker | null = null;
|
||||
private workerReady = false;
|
||||
private workerInitPromise: Promise<void> | null = null;
|
||||
private pendingMessages = new Map<
|
||||
string,
|
||||
{
|
||||
resolve: (_value: unknown) => void;
|
||||
reject: (_reason: unknown) => void;
|
||||
timeout: NodeJS.Timeout;
|
||||
}
|
||||
>();
|
||||
private messageIdCounter = 0;
|
||||
private readonly messageTimeout = 30000; // 30 seconds
|
||||
|
||||
constructor() {
|
||||
WebPlatformService.instanceCount++;
|
||||
|
||||
// Only warn if multiple instances (which shouldn't happen with singleton)
|
||||
if (WebPlatformService.instanceCount > 1) {
|
||||
console.error(`[WebPlatformService] ERROR: Multiple instances created! Count: ${WebPlatformService.instanceCount}`);
|
||||
} else {
|
||||
console.log(`[WebPlatformService] Initializing web platform service`);
|
||||
}
|
||||
|
||||
// Start worker initialization but don't await it in constructor
|
||||
this.workerInitPromise = this.initializeWorker();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the SQL worker for database operations
|
||||
*/
|
||||
private async initializeWorker(): Promise<void> {
|
||||
try {
|
||||
// logger.log("[WebPlatformService] Initializing SQL worker..."); // DISABLED
|
||||
|
||||
this.worker = new Worker(
|
||||
new URL("../../registerSQLWorker.js", import.meta.url),
|
||||
{ type: "module" },
|
||||
);
|
||||
|
||||
// This is required for Safari compatibility with nested workers
|
||||
// It installs a handler that proxies web worker creation through the main thread
|
||||
// CRITICAL: Only call initBackend from main thread, not from worker context
|
||||
const isMainThread = typeof window !== 'undefined';
|
||||
if (isMainThread) {
|
||||
// We're in the main thread - safe to dynamically import and call initBackend
|
||||
try {
|
||||
const { initBackend } = await import("absurd-sql/dist/indexeddb-main-thread");
|
||||
initBackend(this.worker);
|
||||
} catch (error) {
|
||||
console.error("[WebPlatformService] Failed to import/call initBackend:", error);
|
||||
throw error;
|
||||
}
|
||||
} else {
|
||||
// We're in a worker context - skip initBackend call
|
||||
console.log("[WebPlatformService] Skipping initBackend call in worker context");
|
||||
}
|
||||
|
||||
this.worker.onmessage = (event) => {
|
||||
this.handleWorkerMessage(event.data);
|
||||
};
|
||||
|
||||
this.worker.onerror = (error) => {
|
||||
// logger.error("[WebPlatformService] Worker error:", error); // DISABLED
|
||||
console.error("[WebPlatformService] Worker error:", error);
|
||||
this.workerReady = false;
|
||||
};
|
||||
|
||||
// Send ping to verify worker is ready
|
||||
await this.sendWorkerMessage({ type: "ping" });
|
||||
this.workerReady = true;
|
||||
|
||||
// logger.log("[WebPlatformService] SQL worker initialized successfully"); // DISABLED
|
||||
} catch (error) {
|
||||
// logger.error("[WebPlatformService] Failed to initialize worker:", error); // DISABLED
|
||||
console.error("[WebPlatformService] Failed to initialize worker:", error);
|
||||
this.workerReady = false;
|
||||
this.workerInitPromise = null;
|
||||
throw new Error("Failed to initialize database worker");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle messages received from the worker
|
||||
*/
|
||||
private handleWorkerMessage(message: WorkerResponse): void {
|
||||
const { id, type } = message;
|
||||
|
||||
// Handle absurd-sql internal messages (these are normal, don't log)
|
||||
if (!id && message.type?.startsWith('__absurd:')) {
|
||||
return; // Internal absurd-sql message, ignore silently
|
||||
}
|
||||
|
||||
if (!id) {
|
||||
// logger.warn("[WebPlatformService] Received message without ID:", message); // DISABLED
|
||||
console.warn("[WebPlatformService] Received message without ID:", message);
|
||||
return;
|
||||
}
|
||||
|
||||
const pending = this.pendingMessages.get(id);
|
||||
if (!pending) {
|
||||
// logger.warn( // DISABLED
|
||||
// "[WebPlatformService] Received response for unknown message ID:",
|
||||
// id,
|
||||
// );
|
||||
console.warn(
|
||||
"[WebPlatformService] Received response for unknown message ID:",
|
||||
id,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear timeout and remove from pending
|
||||
clearTimeout(pending.timeout);
|
||||
this.pendingMessages.delete(id);
|
||||
|
||||
switch (type) {
|
||||
case "success":
|
||||
pending.resolve(message.data);
|
||||
break;
|
||||
|
||||
case "error": {
|
||||
const error = new Error(message.error.message);
|
||||
if (message.error.stack) {
|
||||
error.stack = message.error.stack;
|
||||
}
|
||||
pending.reject(error);
|
||||
break;
|
||||
}
|
||||
|
||||
case "init-complete":
|
||||
pending.resolve(true);
|
||||
break;
|
||||
|
||||
case "pong":
|
||||
pending.resolve(true);
|
||||
break;
|
||||
|
||||
default:
|
||||
// logger.warn("[WebPlatformService] Unknown response type:", type); // DISABLED
|
||||
console.warn("[WebPlatformService] Unknown response type:", type);
|
||||
pending.resolve(message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message to the worker and wait for response
|
||||
*/
|
||||
private async sendWorkerMessage<T>(
|
||||
request: Omit<WorkerRequest, "id">,
|
||||
): Promise<T> {
|
||||
if (!this.worker) {
|
||||
throw new Error("Worker not initialized");
|
||||
}
|
||||
|
||||
const id = `msg_${++this.messageIdCounter}_${Date.now()}`;
|
||||
const fullRequest: WorkerRequest = { id, ...request } as WorkerRequest;
|
||||
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
this.pendingMessages.delete(id);
|
||||
reject(new Error(`Worker message timeout for ${request.type} (${id})`));
|
||||
}, this.messageTimeout);
|
||||
|
||||
this.pendingMessages.set(id, {
|
||||
resolve: resolve as (value: unknown) => void,
|
||||
reject,
|
||||
timeout,
|
||||
});
|
||||
|
||||
// logger.log( // DISABLED
|
||||
// `[WebPlatformService] Sending message: ${request.type} (${id})`,
|
||||
// );
|
||||
this.worker!.postMessage(fullRequest);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for worker to be ready
|
||||
*/
|
||||
private async ensureWorkerReady(): Promise<void> {
|
||||
// Wait for initial initialization to complete
|
||||
if (this.workerInitPromise) {
|
||||
await this.workerInitPromise;
|
||||
}
|
||||
|
||||
if (this.workerReady) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to ping the worker if not ready
|
||||
try {
|
||||
await this.sendWorkerMessage<boolean>({ type: "ping" });
|
||||
this.workerReady = true;
|
||||
} catch (error) {
|
||||
// logger.error("[WebPlatformService] Worker not ready:", error); // DISABLED
|
||||
console.error("[WebPlatformService] Worker not ready:", error);
|
||||
throw new Error("Database worker not ready");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the capabilities of the web platform
|
||||
* @returns Platform capabilities object
|
||||
@@ -358,30 +570,43 @@ export class WebPlatformService implements PlatformService {
|
||||
/**
|
||||
* @see PlatformService.dbQuery
|
||||
*/
|
||||
dbQuery(
|
||||
async dbQuery(
|
||||
sql: string,
|
||||
params?: unknown[],
|
||||
): Promise<QueryExecResult | undefined> {
|
||||
return databaseService.query(sql, params).then((result) => result[0]);
|
||||
await this.ensureWorkerReady();
|
||||
return this.sendWorkerMessage<QueryResult>({
|
||||
type: "query",
|
||||
sql,
|
||||
params,
|
||||
} as QueryRequest).then((result) => result.result[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PlatformService.dbExec
|
||||
*/
|
||||
dbExec(
|
||||
async dbExec(
|
||||
sql: string,
|
||||
params?: unknown[],
|
||||
): Promise<{ changes: number; lastId?: number }> {
|
||||
return databaseService.run(sql, params);
|
||||
await this.ensureWorkerReady();
|
||||
return this.sendWorkerMessage<{ changes: number; lastId?: number }>({
|
||||
type: "exec",
|
||||
sql,
|
||||
params,
|
||||
} as ExecRequest);
|
||||
}
|
||||
|
||||
async dbGetOneRow(
|
||||
sql: string,
|
||||
params?: unknown[],
|
||||
): Promise<unknown[] | undefined> {
|
||||
return databaseService
|
||||
.query(sql, params)
|
||||
.then((result: QueryExecResult[]) => result[0]?.values[0]);
|
||||
await this.ensureWorkerReady();
|
||||
return this.sendWorkerMessage<unknown[] | undefined>({
|
||||
type: "getOneRow",
|
||||
sql,
|
||||
params,
|
||||
} as GetOneRowRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user