"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.__tsfTypeAliases = exports.RpcPeer = void 0;
exports.listenRpc = listenRpc;
exports.connectRpc = connectRpc;
const tslib_1 = require("tslib");
const _a = tslib_1.__importStar(require("@zyno-io/ts-reflection/type-metadata-runtime"));
const node_events_1 = require("node:events");
const node_net_1 = require("node:net");
const errors_1 = require("../errors");
const __tsf_metadata_type = _a.createCompactMetadataRegistryV1("[1,[{\"kind\":6},{\"kind\":2},{\"kind\":22,\"type\":{\"kind\":2,\"typeName\":\"T\"}},{\"kind\":3},{\"kind\":2,\"typeName\":\"(method: string, params: unknown, peer: RpcPeer) =\\u003e Promise\\u003cunknown\\u003e | unknown\",\"typeName\":\"RpcHandler\"}]]", []);
let _RpcPeerMetadata_1;
class RpcPeer extends node_events_1.EventEmitter {
    socket;
    handler;
    nextId = 1;
    buffer = '';
    pending = new Map();
    constructor(socket, handler) {
        super();
        this.socket = socket;
        this.handler = handler;
        socket.setEncoding('utf8');
        socket.on('data', chunk => this.receive(String(chunk)));
        socket.on('close', () => {
            for (const pending of this.pending.values())
                pending.reject(new Error('RPC socket closed'));
            this.pending.clear();
            this.emit('close');
        });
        socket.on('error', error => this.emit('error', error));
    }
    call(method, params) {
        const id = this.nextId++;
        this.write({ id, method, params });
        return new Promise((resolve, reject) => {
            this.pending.set(id, { resolve: value => resolve(value), reject });
        });
    }
    close() {
        this.socket.end();
    }
    receive(chunk) {
        this.buffer += chunk;
        while (true) {
            const newline = this.buffer.indexOf('\n');
            if (newline === -1)
                return;
            const line = this.buffer.slice(0, newline);
            this.buffer = this.buffer.slice(newline + 1);
            if (!line.trim())
                continue;
            void this.dispatch(decodeRpcValue(JSON.parse(line)));
        }
    }
    async dispatch(message) {
        if ('method' in message) {
            if (!this.handler) {
                this.write({ id: message.id, ok: false, error: { message: 'No RPC handler installed' } });
                return;
            }
            try {
                const result = await this.handler(message.method, message.params, this);
                this.write({ id: message.id, ok: true, result });
            }
            catch (error) {
                this.write({
                    id: message.id,
                    ok: false,
                    error: encodeRpcError(error)
                });
            }
            return;
        }
        const pending = this.pending.get(message.id);
        if (!pending)
            return;
        this.pending.delete(message.id);
        if (message.ok)
            pending.resolve(message.result);
        else
            pending.reject(decodeRpcError(message.error));
    }
    write(message) {
        this.socket.write(`${JSON.stringify(encodeRpcValue(message))}\n`);
    }
    static __tsfType = _RpcPeerMetadata_1 = _a.decodeCompactMetadataV1("[1,{\"kind\":16,\"name\":\"RpcPeer\",\"typeName\":\"RpcPeer\",\"classType\":{\"$tsf\":0},\"properties\":[],\"methods\":[{\"name\":\"call\",\"parameters\":[{\"name\":\"method\",\"type\":{\"$tsfType\":0},\"optional\":false,\"default\":false},{\"name\":\"params\",\"type\":{\"$tsfType\":1},\"optional\":true,\"default\":false}],\"returnType\":{\"$tsfType\":2}},{\"name\":\"close\",\"parameters\":[],\"returnType\":{\"$tsfType\":3}}],\"hasConstructor\":true,\"constructorParameters\":[{\"name\":\"socket\",\"type\":{\"$tsfAlias\":[1,\"node:net\",\"Socket\",\"Socket\"]},\"optional\":false,\"default\":false},{\"name\":\"handler\",\"type\":{\"$tsfType\":4},\"optional\":true,\"default\":false}]}]", [() => RpcPeer, require], __tsf_metadata_type);
}
exports.RpcPeer = RpcPeer;
_RpcPeerMetadata_1.classType = RpcPeer;
RpcPeer.__tsfType = _RpcPeerMetadata_1;
function encodeRpcError(error) {
    if (!error || typeof error !== 'object')
        return { message: String(error) };
    const candidate = error;
    return {
        message: typeof candidate.message === 'string' && candidate.message ? candidate.message : String(error),
        name: typeof candidate.name === 'string' ? candidate.name : undefined,
        code: candidate.code,
        errno: candidate.errno
    };
}
function decodeRpcError(payload) {
    if (!payload)
        return new Error('RPC request failed');
    if (payload.name === 'UniqueConstraintError' || (0, errors_1.isUniqueConstraintError)(payload)) {
        return new errors_1.UniqueConstraintError(payload.message, payload);
    }
    const error = new Error(payload.message);
    if (payload.name)
        error.name = payload.name;
    Object.assign(error, { code: payload.code, errno: payload.errno });
    return error;
}
function listenRpc(port, handler) {
    const server = (0, node_net_1.createServer)(socket => {
        const peer = new RpcPeer(socket, handler);
        peer.on('error', () => { });
    });
    return new Promise((resolve, reject) => {
        server.once('error', reject);
        server.listen(port, '127.0.0.1', () => {
            server.off('error', reject);
            const address = server.address();
            if (!address || typeof address === 'string') {
                reject(new Error('Unexpected RPC listen address'));
                return;
            }
            resolve({
                port: address.port,
                close: () => new Promise((closeResolve, closeReject) => {
                    server.close(error => (error ? closeReject(error) : closeResolve()));
                })
            });
        });
    });
}
function connectRpc(port) {
    return new Promise((resolve, reject) => {
        const socket = (0, node_net_1.createConnection)({ host: '127.0.0.1', port });
        socket.once('error', reject);
        socket.once('connect', () => {
            socket.off('error', reject);
            resolve(new RpcPeer(socket));
        });
    });
}
function encodeRpcValue(value) {
    if (value instanceof Date)
        return { __tsfRpcType: 'Date', value: value.toISOString() };
    if (Buffer.isBuffer(value))
        return { __tsfRpcType: 'Buffer', value: value.toString('base64') };
    if (typeof value === 'bigint')
        return { __tsfRpcType: 'BigInt', value: value.toString() };
    if (Array.isArray(value))
        return value.map(encodeRpcValue);
    if (!value || typeof value !== 'object')
        return value;
    return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, encodeRpcValue(entry)]));
}
function decodeRpcValue(value) {
    if (Array.isArray(value))
        return value.map(decodeRpcValue);
    if (!value || typeof value !== 'object')
        return value;
    const record = value;
    if (record.__tsfRpcType === 'Date' && typeof record.value === 'string')
        return new Date(record.value);
    if (record.__tsfRpcType === 'Buffer' && typeof record.value === 'string')
        return Buffer.from(record.value, 'base64');
    if (record.__tsfRpcType === 'BigInt' && typeof record.value === 'string')
        return BigInt(record.value);
    return Object.fromEntries(Object.entries(record).map(([key, entry]) => [key, decodeRpcValue(entry)]));
}
exports.__tsfTypeAliases = JSON.parse("[1,{\"RpcHandler\":{\"kind\":2,\"typeName\":\"(method: string, params: unknown, peer: RpcPeer) =\\u003e Promise\\u003cunknown\\u003e | unknown\",\"typeName\":\"RpcHandler\"},\"RpcRequest\":{\"kind\":18,\"typeName\":\"RpcRequest\",\"types\":[{\"kind\":20,\"name\":\"id\",\"type\":{\"kind\":7},\"optional\":false},{\"kind\":20,\"name\":\"method\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"params\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2},{\"kind\":4}]},\"optional\":true}]},\"RpcResponse\":{\"kind\":18,\"typeName\":\"RpcResponse\",\"types\":[{\"kind\":20,\"name\":\"id\",\"type\":{\"kind\":7},\"optional\":false},{\"kind\":20,\"name\":\"ok\",\"type\":{\"kind\":8},\"optional\":false},{\"kind\":20,\"name\":\"result\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"error\",\"type\":{\"kind\":12,\"types\":[{\"kind\":18,\"typeName\":\"RpcErrorPayload\",\"types\":[{\"kind\":20,\"name\":\"message\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"name\",\"type\":{\"kind\":12,\"types\":[{\"kind\":6},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"code\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"errno\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2},{\"kind\":4}]},\"optional\":true}]},{\"kind\":4}]},\"optional\":true}]}}]")[1];
//# sourceMappingURL=mysql-session-rpc.js.map