import { ConnectionClosedError } from '@powersync/common';
import { releaseProxy } from 'comlink';
/**
 * A PowerSync database connection implemented with RPC calls to a background worker.
 */
export class RemoteConnection {
    isBusy = false;
    worker;
    comlink;
    database;
    notifyWorkerClosed = new AbortController();
    constructor(worker, comlink, database) {
        this.worker = worker;
        this.comlink = comlink;
        this.database = database;
        this.worker.once('exit', (_) => {
            this.notifyWorkerClosed.abort();
        });
    }
    /**
     * Runs the inner function, but appends the stack trace where this function was called. This is useful for workers
     * because stack traces from worker errors are otherwise unrelated to the application issue that has caused them.
     */
    withRemote(inner) {
        const trace = {};
        Error.captureStackTrace(trace);
        const controller = this.notifyWorkerClosed;
        return new Promise((resolve, reject) => {
            if (controller.signal.aborted) {
                reject(new ConnectionClosedError('Called operation on closed remote'));
            }
            function handleAbort() {
                reject(new ConnectionClosedError('Remote peer closed with request in flight'));
            }
            function completePromise(action) {
                controller.signal.removeEventListener('abort', handleAbort);
                action();
            }
            controller.signal.addEventListener('abort', handleAbort);
            inner()
                .then((data) => completePromise(() => resolve(data)))
                .catch((e) => {
                if (e instanceof Error && e.stack) {
                    e.stack += trace.stack;
                }
                return completePromise(() => reject(e));
            });
        });
    }
    executeBatch(query, params = []) {
        return this.withRemote(async () => {
            const result = await this.database.executeBatch(query, params ?? []);
            return RemoteConnection.wrapQueryResult(result);
        });
    }
    execute(query, params) {
        return this.withRemote(async () => {
            const result = await this.database.execute(query, params ?? []);
            return RemoteConnection.wrapQueryResult(result);
        });
    }
    executeRaw(query, params) {
        return this.withRemote(async () => {
            return await this.database.executeRaw(query, params ?? []);
        });
    }
    async getAll(sql, parameters) {
        const res = await this.execute(sql, parameters);
        return res.rows?._array ?? [];
    }
    async getOptional(sql, parameters) {
        const res = await this.execute(sql, parameters);
        return res.rows?.item(0) ?? null;
    }
    async get(sql, parameters) {
        const res = await this.execute(sql, parameters);
        const first = res.rows?.item(0);
        if (!first) {
            throw new Error('Result set is empty');
        }
        return first;
    }
    async refreshSchema() {
        await this.execute("pragma table_info('sqlite_master')");
    }
    async close() {
        await this.database.close();
        this.database[releaseProxy]();
        this.comlink[releaseProxy]();
        await this.worker.terminate();
    }
    static wrapQueryResult(result) {
        let rows = undefined;
        if (result.rows) {
            rows = {
                ...result.rows,
                item: (idx) => result.rows?._array[idx]
            };
        }
        return {
            ...result,
            rows
        };
    }
}
//# sourceMappingURL=RemoteConnection.js.map