mirror of
https://github.com/Mintplex-Labs/anything-llm
synced 2026-04-26 01:25:15 +02:00
183 lines
5.7 KiB
JavaScript
183 lines
5.7 KiB
JavaScript
// Credit: https://github.com/sindilevich/connection-string-parser
|
|
|
|
/**
|
|
* @typedef {Object} ConnectionStringParserOptions
|
|
* @property {'mssql' | 'mysql' | 'postgresql' | 'db'} [scheme] - The scheme of the connection string
|
|
*/
|
|
|
|
/**
|
|
* @typedef {Object} ConnectionStringObject
|
|
* @property {string} scheme - The scheme of the connection string eg: mongodb, mssql, mysql, postgresql, etc.
|
|
* @property {string} username - The username of the connection string
|
|
* @property {string} password - The password of the connection string
|
|
* @property {{host: string, port: number}[]} hosts - The hosts of the connection string
|
|
* @property {string} endpoint - The endpoint (database name) of the connection string
|
|
* @property {Object} options - The options of the connection string
|
|
*/
|
|
class ConnectionStringParser {
|
|
static DEFAULT_SCHEME = "db";
|
|
|
|
/**
|
|
* @param {ConnectionStringParserOptions} options
|
|
*/
|
|
constructor(options = {}) {
|
|
this.scheme =
|
|
(options && options.scheme) || ConnectionStringParser.DEFAULT_SCHEME;
|
|
}
|
|
|
|
/**
|
|
* Takes a connection string object and returns a URI string of the form:
|
|
*
|
|
* scheme://[username[:password]@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[endpoint]][?options]
|
|
* @param {Object} connectionStringObject The object that describes connection string parameters
|
|
*/
|
|
format(connectionStringObject) {
|
|
if (!connectionStringObject) {
|
|
return this.scheme + "://localhost";
|
|
}
|
|
if (
|
|
this.scheme &&
|
|
connectionStringObject.scheme &&
|
|
this.scheme !== connectionStringObject.scheme
|
|
) {
|
|
throw new Error(`Scheme not supported: ${connectionStringObject.scheme}`);
|
|
}
|
|
|
|
let uri =
|
|
(this.scheme ||
|
|
connectionStringObject.scheme ||
|
|
ConnectionStringParser.DEFAULT_SCHEME) + "://";
|
|
|
|
if (connectionStringObject.username) {
|
|
uri += encodeURIComponent(connectionStringObject.username);
|
|
// Allow empty passwords
|
|
if (connectionStringObject.password) {
|
|
uri += ":" + encodeURIComponent(connectionStringObject.password);
|
|
}
|
|
uri += "@";
|
|
}
|
|
uri += this._formatAddress(connectionStringObject);
|
|
// Only put a slash when there is an endpoint
|
|
if (connectionStringObject.endpoint) {
|
|
uri += "/" + encodeURIComponent(connectionStringObject.endpoint);
|
|
}
|
|
if (
|
|
connectionStringObject.options &&
|
|
Object.keys(connectionStringObject.options).length > 0
|
|
) {
|
|
uri +=
|
|
"?" +
|
|
Object.keys(connectionStringObject.options)
|
|
.map(
|
|
(option) =>
|
|
encodeURIComponent(option) +
|
|
"=" +
|
|
encodeURIComponent(connectionStringObject.options[option])
|
|
)
|
|
.join("&");
|
|
}
|
|
return uri;
|
|
}
|
|
|
|
/**
|
|
* Where scheme and hosts will always be present. Other fields will only be present in the result if they were
|
|
* present in the input.
|
|
* @param {string} uri The connection string URI
|
|
* @returns {ConnectionStringObject} The connection string object
|
|
*/
|
|
parse(uri) {
|
|
const connectionStringParser = new RegExp(
|
|
"^\\s*" + // Optional whitespace padding at the beginning of the line
|
|
"([^:]+)://" + // Scheme (Group 1)
|
|
"(?:([^:@,/?=&]+)(?::([^:@,/?=&]+))?@)?" + // User (Group 2) and Password (Group 3)
|
|
"([^@/?=&]+)" + // Host address(es) (Group 4)
|
|
"(?:/([^:@,/?=&]+)?)?" + // Endpoint (Group 5)
|
|
"(?:\\?([^:@,/?]+)?)?" + // Options (Group 6)
|
|
"\\s*$", // Optional whitespace padding at the end of the line
|
|
"gi"
|
|
);
|
|
const connectionStringObject = {};
|
|
|
|
if (!uri.includes("://")) {
|
|
throw new Error(`No scheme found in URI ${uri}`);
|
|
}
|
|
|
|
const tokens = connectionStringParser.exec(uri);
|
|
|
|
if (Array.isArray(tokens)) {
|
|
connectionStringObject.scheme = tokens[1];
|
|
if (this.scheme && this.scheme !== connectionStringObject.scheme) {
|
|
throw new Error(`URI must start with '${this.scheme}://'`);
|
|
}
|
|
connectionStringObject.username = tokens[2]
|
|
? decodeURIComponent(tokens[2])
|
|
: tokens[2];
|
|
connectionStringObject.password = tokens[3]
|
|
? decodeURIComponent(tokens[3])
|
|
: tokens[3];
|
|
connectionStringObject.hosts = this._parseAddress(tokens[4]);
|
|
connectionStringObject.endpoint = tokens[5]
|
|
? decodeURIComponent(tokens[5])
|
|
: tokens[5];
|
|
connectionStringObject.options = tokens[6]
|
|
? this._parseOptions(tokens[6])
|
|
: tokens[6];
|
|
}
|
|
return connectionStringObject;
|
|
}
|
|
|
|
/**
|
|
* Formats the address portion of a connection string
|
|
* @param {Object} connectionStringObject The object that describes connection string parameters
|
|
*/
|
|
_formatAddress(connectionStringObject) {
|
|
return connectionStringObject.hosts
|
|
.map(
|
|
(address) =>
|
|
encodeURIComponent(address.host) +
|
|
(address.port
|
|
? ":" + encodeURIComponent(address.port.toString(10))
|
|
: "")
|
|
)
|
|
.join(",");
|
|
}
|
|
|
|
/**
|
|
* Parses an address
|
|
* @param {string} addresses The address(es) to process
|
|
*/
|
|
_parseAddress(addresses) {
|
|
return addresses.split(",").map((address) => {
|
|
const i = address.indexOf(":");
|
|
|
|
return i >= 0
|
|
? {
|
|
host: decodeURIComponent(address.substring(0, i)),
|
|
port: +address.substring(i + 1),
|
|
}
|
|
: { host: decodeURIComponent(address) };
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Parses options
|
|
* @param {string} options The options to process
|
|
*/
|
|
_parseOptions(options) {
|
|
const result = {};
|
|
|
|
options.split("&").forEach((option) => {
|
|
const i = option.indexOf("=");
|
|
|
|
if (i >= 0) {
|
|
result[decodeURIComponent(option.substring(0, i))] = decodeURIComponent(
|
|
option.substring(i + 1)
|
|
);
|
|
}
|
|
});
|
|
return result;
|
|
}
|
|
}
|
|
|
|
module.exports = { ConnectionStringParser };
|