UNPKG

connection-string

Version:

Advanced URL Connection String parser + generator.

286 lines (285 loc) 10.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ConnectionString = void 0; const types_1 = require("./types"); const static_1 = require("./static"); const errInvalidDefaults = `Invalid "defaults" parameter: `; class ConnectionString { /** * Safe read-accessor to the first host's full name (hostname + port). */ get host() { return this.hosts?.[0].toString(); } /** * Safe read-accessor to the first host's name (without port). */ get hostname() { return this.hosts?.[0].name; } /** * Safe read-accessor to the first host's port. */ get port() { return this.hosts?.[0].port; } /** * Safe read-accessor to the first host's type. */ get type() { return this.hosts?.[0].type; } /** * Constructor. * * @param cs - connection string (can be empty). * * @param defaults - optional defaults, which can also be set * explicitly, via method setDefaults. */ constructor(cs, defaults) { cs = cs ?? ''; if (typeof cs !== 'string') { throw new TypeError(`Invalid connection string: ${JSON.stringify(cs)}`); } if (typeof (defaults ?? {}) !== 'object') { throw new TypeError(errInvalidDefaults + JSON.stringify(defaults)); } cs = cs.trim(); (0, static_1.validateUrl)(cs); // will throw, if failed // Extracting the protocol: let m = cs.match(/^(.*)?:\/\//); if (m) { const p = m[1]; // protocol name if (p) { const m2 = p.match(/^([a-z]+[a-z0-9+-.:]*)/i); if (p && (!m2 || m2[1] !== p)) { throw new Error(`Invalid protocol name: ${p}`); } this.protocol = p; } cs = cs.substring(m[0].length); } // Extracting user + password: m = cs.match(/^([\w-_.+!*'()$%]*):?([\w-_.+!*'()$%~]*)@/); if (m) { if (m[1]) { this.user = (0, static_1.decode)(m[1]); } if (m[2]) { this.password = (0, static_1.decode)(m[2]); } cs = cs.substring(m[0].length); } // Extracting hosts details: // (if it starts with `/`, it is the first path segment, i.e. no hosts specified) if (cs[0] !== '/') { const endOfHosts = cs.search(/[\/?]/); const hosts = (endOfHosts === -1 ? cs : cs.substring(0, endOfHosts)).split(','); hosts.forEach(h => { const host = (0, static_1.parseHost)(h); if (host) { if (!this.hosts) { this.hosts = []; } this.hosts.push(host); } }); if (endOfHosts >= 0) { cs = cs.substring(endOfHosts); } } // Extracting the path: m = cs.match(/\/([\w-_.+!*'()$%]+)/g); if (m) { this.path = m.map(s => (0, static_1.decode)(s.substring(1))); } // Extracting parameters: const idx = cs.indexOf('?'); if (idx !== -1) { cs = cs.substring(idx + 1); m = cs.match(/([\w-_.+!*'()$%]+)=([\w-_.+!*'()$%,]+)/g); if (m) { const params = {}; m.forEach(s => { const a = s.split('='); const prop = (0, static_1.decode)(a[0]); const val = a[1].split(',').map(static_1.decode); if (prop in params) { if (Array.isArray(params[prop])) { params[prop].push(...val); } else { params[prop] = [params[prop], ...val]; } } else { params[prop] = val.length > 1 ? val : val[0]; } }); this.params = params; } } if (defaults) { this.setDefaults(defaults); } } /** * Parses a host name into an object, which then can be passed into `setDefaults`. * * It returns `null` only when no valid host recognized. */ static parseHost(host) { return (0, static_1.parseHost)(host, true); } /** * Converts this object into a valid connection string. */ toString(options) { let s = this.protocol ? `${this.protocol}://` : ``; const opts = options || {}; if (this.user || this.password) { if (this.user) { s += (0, static_1.encode)(this.user, opts); } if (this.password) { s += ':'; const h = opts.passwordHash; if (h) { const code = (typeof h === 'string' && h[0]) || '#'; s += code.repeat(this.password.length); } else { s += (0, static_1.encode)(this.password, opts); } } s += '@'; } if (Array.isArray(this.hosts)) { s += this.hosts.map(h => (0, static_1.fullHostName)(h, options)).join(); } if (Array.isArray(this.path)) { this.path.forEach(seg => { s += `/${(0, static_1.encode)(seg, opts)}`; }); } if (this.params && typeof this.params === 'object') { const params = []; for (const a in this.params) { let value = this.params[a]; value = Array.isArray(value) ? value : [value]; value = value.map((v) => { return (0, static_1.encode)(typeof v === 'string' ? v : JSON.stringify(v), opts); }).join(); if (opts.plusForSpace) { value = value.replace(/%20/g, '+'); } params.push(`${(0, static_1.encode)(a, opts)}=${value}`); } if (params.length) { s += `?${params.join('&')}`; } } return s; } /** * Applies default parameters, and returns itself. */ setDefaults(defaults) { if (!defaults || typeof defaults !== 'object') { throw new TypeError(errInvalidDefaults + JSON.stringify(defaults)); } if (!('protocol' in this) && (0, static_1.hasText)(defaults.protocol)) { this.protocol = defaults.protocol && defaults.protocol.trim(); } // Missing default `hosts` are merged with the existing ones: if (Array.isArray(defaults.hosts)) { const hosts = Array.isArray(this.hosts) ? this.hosts : []; const dhHosts = defaults.hosts.filter(d => d && typeof d === 'object'); dhHosts.forEach(dh => { const dhName = (0, static_1.hasText)(dh.name) ? dh.name.trim() : undefined; const h = { name: dhName, port: dh.port, type: dh.type }; let found = false; for (let i = 0; i < hosts.length; i++) { const thisHost = (0, static_1.fullHostName)(hosts[i]), defHost = (0, static_1.fullHostName)(h); if (thisHost.toLowerCase() === defHost.toLowerCase()) { found = true; break; } } if (!found) { const obj = {}; if (h.name) { if (h.type && h.type in types_1.HostType) { obj.name = h.name; obj.type = h.type; } else { const t = (0, static_1.parseHost)(h.name, true); if (t) { obj.name = t.name; obj.type = t.type; } } } const p = h.port; if (typeof p === 'number' && p > 0 && p < 65536) { obj.port = p; } if (obj.name || obj.port) { Object.defineProperty(obj, 'toString', { value: (options) => (0, static_1.fullHostName)(obj, options) }); hosts.push(obj); } } }); if (hosts.length) { this.hosts = hosts; } } if (!('user' in this) && (0, static_1.hasText)(defaults.user)) { this.user = defaults.user.trim(); } if (!('password' in this) && (0, static_1.hasText)(defaults.password)) { this.password = defaults.password.trim(); } // Since the order of `path` segments is usually important, we set default // `path` segments as they are, but only when they are missing completely: if (!('path' in this) && Array.isArray(defaults.path)) { const s = defaults.path.filter(static_1.hasText); if (s.length) { this.path = s; } } // Missing default `params` are merged with the existing ones: if (defaults.params && typeof defaults.params === 'object') { const keys = Object.keys(defaults.params); if (keys.length) { if (this.params && typeof this.params === 'object') { for (const a in defaults.params) { if (!(a in this.params)) { this.params[a] = defaults.params[a]; } } } else { this.params = {}; for (const b in defaults.params) { this.params[b] = defaults.params[b]; } } } } return this; } } exports.ConnectionString = ConnectionString; (function () { // hiding prototype methods, to keep the type signature clean: ['setDefaults', 'toString'].forEach(prop => { const desc = Object.getOwnPropertyDescriptor(ConnectionString.prototype, prop); desc.enumerable = false; Object.defineProperty(ConnectionString.prototype, prop, desc); }); })();