@queryleaf/postgres-server
Version:
PostgreSQL wire-compatible server for QueryLeaf
200 lines • 7.79 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProtocolHandler = exports.PostgresServer = void 0;
const net_1 = require("net");
const mongodb_1 = require("mongodb");
const lib_1 = require("@queryleaf/lib");
const protocol_handler_1 = require("./protocol-handler");
Object.defineProperty(exports, "ProtocolHandler", { enumerable: true, get: function () { return protocol_handler_1.ProtocolHandler; } });
const debug_1 = __importDefault(require("debug"));
const yargs_1 = __importDefault(require("yargs"));
const helpers_1 = require("yargs/helpers");
const debug = (0, debug_1.default)('queryleaf:pg-server');
// Parse command line arguments
// Only parse if this file is the main entry point (not during tests)
const yargsInstance = (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
.usage('Usage: $0 [options]')
.option('uri', {
describe: 'MongoDB connection URI',
default: 'mongodb://localhost:27017',
type: 'string',
})
.option('db', {
describe: 'MongoDB database name',
demandOption: true,
type: 'string',
})
.option('port', {
describe: 'PostgreSQL server port',
default: 5432,
type: 'number',
})
.option('host', {
describe: 'PostgreSQL server host',
default: 'localhost',
type: 'string',
})
.option('max-connections', {
describe: 'Maximum number of connections',
default: 100,
type: 'number',
})
.option('auth-passthrough', {
describe: 'Pass authentication credentials to MongoDB',
default: false,
type: 'boolean',
})
.example('$0 --db mydb --port 5432', 'Start the PostgreSQL-compatible server on port 5432')
.example('$0 --db mydb --uri mongodb://username:password@localhost:27017', 'Connect to MongoDB with authentication')
.epilog('For more information, visit https://github.com/beekeeper-studio/queryleaf')
.help()
.alias('help', 'h')
.version()
.alias('version', 'v');
// Only parse command line args when run as main file, not during tests
const argv = require.main === module ? yargsInstance.parseSync() : {};
/**
* PostgreSQL wire protocol server for QueryLeaf
*/
class PostgresServer {
/**
* Create a new PostgreSQL wire protocol server
* @param mongoClient MongoDB client
* @param dbName MongoDB database name
* @param options Server options
*/
constructor(mongoClient, dbName, options) {
this.connections = new Set();
this.mongoClient = mongoClient;
this.queryLeaf = new lib_1.QueryLeaf(mongoClient, dbName);
this.maxConnections = options.maxConnections;
this.authPassthrough = options.authPassthrough || false;
this.dbName = dbName;
this.mongoUri = options.mongoUri || '';
// Create TCP server
this.server = (0, net_1.createServer)((socket) => {
debug(`New connection from ${socket.remoteAddress}:${socket.remotePort}`);
// Check max connections
if (this.connections.size >= this.maxConnections) {
debug(`Connection limit reached (${this.maxConnections}), rejecting connection`);
socket.end();
return;
}
// Create protocol handler for this connection
const handler = new protocol_handler_1.ProtocolHandler(socket, this.queryLeaf, {
authPassthrough: this.authPassthrough,
mongoClient: this.mongoClient,
dbName: this.dbName,
mongoUri: this.mongoUri,
});
this.connections.add(handler);
// Remove connection on close
socket.on('close', () => {
debug(`Connection closed from ${socket.remoteAddress}:${socket.remotePort}`);
this.connections.delete(handler);
});
});
// Handle server errors
this.server.on('error', (err) => {
console.error('Server error:', err);
});
// Handle process signals (only when running as main module)
if (require.main === module) {
process.on('SIGINT', () => this.shutdown(true));
process.on('SIGTERM', () => this.shutdown(true));
// Only auto-listen if running as main module
this.listen(options.port, options.host);
}
// For tests, we'll call listen() explicitly
}
/**
* Start listening for connections
* @param port Port to listen on
* @param host Host to listen on
* @returns A promise that resolves when the server is listening
*/
async listen(port, host) {
return new Promise((resolve, reject) => {
this.server.listen(port, host, () => {
debug(`PostgreSQL-compatible server running at ${host}:${port}`);
debug(`Connected to MongoDB at ${this.mongoClient.options.hosts?.join(',')}`);
resolve();
});
// Handle initial connection errors
const errorHandler = (err) => {
this.server.removeListener('error', errorHandler);
reject(err);
};
this.server.once('error', errorHandler);
});
}
/**
* Shutdown the server and close all connections
* @param exitProcess Whether to exit the process after shutdown (default: false)
*/
async shutdown(exitProcess = false) {
debug('Shutting down server...');
// Close server - stop accepting new connections
this.server.close();
// Close MongoDB connection
await this.mongoClient.close();
debug('Server stopped');
// Only exit the process if explicitly requested (for CLI usage)
if (exitProcess) {
process.exit(0);
}
}
}
exports.PostgresServer = PostgresServer;
/**
* Main function to start the server
*/
async function main() {
// Use the command line arguments or default values
const uri = argv.uri || 'mongodb://localhost:27017';
const db = argv.db;
const port = argv.port || 5432;
const host = argv.host || 'localhost';
const maxConnections = argv.maxConnections || 100;
const authPassthrough = argv.authPassthrough || false;
if (!db) {
console.error('Error: MongoDB database name is required. Use --db option.');
process.exit(1);
}
try {
// Connect to MongoDB
debug(`Connecting to MongoDB: ${uri}`);
const mongoClient = new mongodb_1.MongoClient(uri);
await mongoClient.connect();
debug(`Connected to MongoDB, using database: ${db}`);
// Start PostgreSQL server
new PostgresServer(mongoClient, db, {
port,
host,
maxConnections,
authPassthrough,
mongoUri: uri,
});
console.log(`🌱 QueryLeaf PostgreSQL-compatible server started at ${host}:${port}`);
console.log(`Connected to MongoDB database: ${db}`);
console.log(`Connection string: postgresql://<username>@${host}:${port}/${db}`);
console.log(`Connect with: psql -h ${host} -p ${port} -d ${db} -U <any-username>`);
console.log('Press Ctrl+C to stop the server');
}
catch (error) {
console.error('Error starting server:', error);
process.exit(1);
}
}
// Run the main function if this file is executed directly
if (require.main === module) {
main().catch((error) => {
console.error(`Unhandled error: ${error instanceof Error ? error.message : String(error)}`);
process.exit(1);
});
}
//# sourceMappingURL=pg-server.js.map