UNPKG

@kurrent/kurrentdb-client

Version:
238 lines 9.54 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.parseConnectionString = void 0; const constants_1 = require("../constants"); const utils_1 = require("../utils"); const toflatCase = (str) => str.trim().toLowerCase().replace(/[_-]/g, ""); // This wierd key value syntax is the only way I could find ensure that every option in a union is passed const caseMap = (options) => { const expected = Object.values(options); const mapping = new Map(expected.map((option) => [toflatCase(option), option])); function MapTo(value) { const flatValue = toflatCase(value); return mapping.get(flatValue); } MapTo.expected = expected; return MapTo; }; const mapToNodePreference = caseMap({ [constants_1.LEADER]: constants_1.LEADER, [constants_1.FOLLOWER]: constants_1.FOLLOWER, [constants_1.READ_ONLY_REPLICA]: constants_1.READ_ONLY_REPLICA, [constants_1.RANDOM]: constants_1.RANDOM, }); const mapToQueryOption = caseMap({ userCertFile: "userCertFile", userKeyFile: "userKeyFile", maxDiscoverAttempts: "maxDiscoverAttempts", connectionName: "connectionName", defaultDeadline: "defaultDeadline", discoveryInterval: "discoveryInterval", gossipTimeout: "gossipTimeout", nodePreference: "nodePreference", tls: "tls", tlsVerifyCert: "tlsVerifyCert", tlsCAFile: "tlsCAFile", throwOnAppendFailure: "throwOnAppendFailure", keepAliveInterval: "keepAliveInterval", keepAliveTimeout: "keepAliveTimeout", }); const parseConnectionString = (connectionString) => parseProtocol(connectionString.trim().replace(/\/+$/, ""), 0, { dnsDiscover: false, hosts: [], }); exports.parseConnectionString = parseConnectionString; const parseProtocol = (connectionString, position, options) => { let nextPosition = position; const expected = "kurrentdb://, kurrentdb+discover://, kurrent://, kurrent+discover://, kdb://, or kdb+discover://"; const match = connectionString .substring(position) .match(/^(?<protocol>[^:]+):\/\//); if (match && match.groups?.protocol) { nextPosition += match[0].length; if (match.groups.protocol.startsWith("esdb")) { console.warn(`The 'esdb' protocol is deprecated. Please use 'kurrentdb' instead`); } switch (match.groups.protocol) { case "esdb": case "kurrentdb": case "kdb": case "kurrent": { return parseCredentials(connectionString, nextPosition, { ...options, dnsDiscover: false, }); } case "esdb+discover": case "kurrentdb+discover": case "kdb+discover": case "kurrent+discover": { return parseCredentials(connectionString, nextPosition, { ...options, dnsDiscover: true, }); } } } throw new ParseError(connectionString, [position, nextPosition], expected); }; const parseCredentials = (connectionString, position, options) => { let nextPosition = position; const expected = "username:password"; const match = connectionString .substring(position) .match(/^(?:(?<credentials>[^:]+:[^@]+)@)/); // This is optional if (!match) { return parseHosts(connectionString, nextPosition, options, true); } if (match.groups?.credentials) { nextPosition += match[0].length; const [username, password] = match.groups.credentials.trim().split(":"); return parseHosts(connectionString, nextPosition, { ...options, defaultCredentials: { username: decodeURIComponent(username), password: decodeURIComponent(password), }, }, true); } throw new ParseError(connectionString, [position, nextPosition], expected); }; const parseHosts = (connectionString, position, options, mustMatch) => { let nextPosition = position; const expected = "host"; const match = connectionString .substring(position) .match(/^(?:(?<host>[^$+!?*'(),;[\]{}|"%~#<>=&]+)[,/]?)/); if (!match && mustMatch) { throw new ParseError(connectionString, [position, nextPosition], expected); } if (match && match.groups?.host) { nextPosition += match[0].length; const [address, rawPort = "2113", ...rest] = match .groups.host.trim() .split(":"); if (rest.length) { throw new ParseError(connectionString, [position + `${address}:${rawPort}`.length, nextPosition], ", or ?key=value"); } const port = parseInt(rawPort.trim()); if (Number.isNaN(port)) { throw new ParseError(connectionString, [position + `${address}:`.length, nextPosition], "port number"); } return parseHosts(connectionString, nextPosition, { ...options, hosts: [...options.hosts, { address, port }], }, false); } return parseSearchParams(connectionString, nextPosition, options, true); }; const parseSearchParams = (connectionString, position, options, first) => { if (position === connectionString.length) return options; let nextPosition = position; const expected = `${first ? "?" : "&"}key=value`; const match = connectionString .substring(position) .match(new RegExp(`^(?:[${first ? "?" : "&"}](?<key>[^=]+)=(?<value>[^&?]+))`)); if (!match || !match.groups || !match.groups.key || !match.groups.value) { throw new ParseError(connectionString, [position, nextPosition], expected); } nextPosition += match[0].length; const keypair = verifyKeyValuePair(match.groups, connectionString, [position, nextPosition]); return parseSearchParams(connectionString, nextPosition, keypair ? { ...options, [keypair.key]: keypair.value, } : options, false); }; const verifyKeyValuePair = ({ key: rawKey, value: rawValue }, connectionString, [from, to]) => { const keyFrom = from + `&${rawKey}=`.length; const key = mapToQueryOption(rawKey) ?? rawKey; const value = rawValue.trim(); if (key === "tlsVerifyCert" && value === "false") { console.warn([ `"tlsVerifyCert" is not currently supported by this client, and will have no effect.`, `Consider either:`, ` Passing "tlsCAFile" in the connection string.`, ` Setting NODE_EXTRA_CA_CERTS https://nodejs.org/api/cli.html#cli_node_extra_ca_certs_file`, errorLocationString(connectionString, [from + `&`.length, to]), ].join("\n")); } switch (key) { case "nodePreference": { const parsedValue = mapToNodePreference(value); if (!parsedValue) { throw new ParseError(connectionString, [keyFrom, to], mapToNodePreference.expected.join(" or ")); } return { key, value: parsedValue }; } case "connectionName": case "tlsCAFile": { return { key, value: decodeURIComponent(value) }; } case "userCertFile": { return { key, value: decodeURIComponent(value) }; } case "userKeyFile": { return { key, value: decodeURIComponent(value) }; } case "maxDiscoverAttempts": case "defaultDeadline": case "discoveryInterval": case "gossipTimeout": case "keepAliveInterval": case "keepAliveTimeout": { const parsedValue = parseInt(value); if (Number.isNaN(parsedValue)) { throw new ParseError(connectionString, [keyFrom, to], "Integer"); } return { key, value: parsedValue }; } case "dnsDiscover": case "tls": case "tlsVerifyCert": case "throwOnAppendFailure": { if (value !== "true" && value !== "false") { throw new ParseError(connectionString, [keyFrom, to], "true or false"); } return { key, value: value === "true" }; } } console.warn([ `Unknown option key "${key}", setting will be ignored.`, errorLocationString(connectionString, [ from + `&`.length, from + `&${key}`.length, ]), ].join("\n")); return null; }; const errorString = (connectionString, [from, to], expected, full) => { const lines = []; lines.push(`Unexpected "${connectionString.substring(from, from === to ? from + 1 : to)}" at position ${from}, expected ${expected}.`); if (full) { lines.push(errorLocationString(connectionString, [from, to])); } return lines.join("\n"); }; const errorLocationString = (connectionString, [from, to]) => { const lines = []; lines.push(connectionString); lines.push(`${" ".repeat(from)}${"^".repeat(Math.max(to - from, 1))}`); return lines.join("\n"); }; class ParseError extends Error { connectionString; location; expected; constructor(connectionString, location, expected) { super(errorString(connectionString, location, expected, false)); this.connectionString = connectionString; this.location = location; this.expected = expected; utils_1.debug.connection(errorString(connectionString, location, expected, true)); } } //# sourceMappingURL=parseConnectionString.js.map