UNPKG

@httpx/dsn-parser

Version:

DSN & JDBC string parser with query params support in a tiny and modern package.

147 lines (146 loc) 5.1 kB
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); //#region src/dsn-parser.type.ts const errorReasons = { EMPTY_DSN: "DSN cannot be empty", INVALID_ARGUMENT: "DSN must be a string", INVALID_PORT: "Invalid port", PARSE_ERROR: "Cannot parse DSN" }; //#endregion //#region src/dsn-parser.util.ts const createErrorResult = (reason, msg) => { return { message: msg ?? errorReasons[reason], reason, success: false }; }; const isNonEmptyString = (value, trim = true) => { return typeof value === "string" && (trim ? value.trim() : value).length > 0; }; const parsableNumberRegexp = /^-?\d{1,16}$/; const isParsableNumber = (value) => { return typeof value === "string" && parsableNumberRegexp.test(value); }; const isValidNetworkPort = (port) => { return !Number.isNaN(port) && port < 65536 && port > 0; }; const removeUndefined = (obj) => { const definedObj = {}; for (const key in obj) if (obj[key] !== void 0) definedObj[key] = obj[key]; return definedObj; }; const mergeDsnOverrides = (parsedDsn, overrides) => { const merged = {}; const { params, ...restDsn } = parsedDsn; for (const [key, value] of Object.entries(restDsn)) merged[key] = key in overrides ? overrides[key] : value; merged.params = params; return merged; }; //#endregion //#region src/query-param-parser.ts const defaultOptions$1 = { parseBooleans: true, parseNumbers: true, setTrueForUndefinedValues: true }; const parseQueryParams = (queryParams, options) => { const { parseBooleans, parseNumbers, setTrueForUndefinedValues } = { ...defaultOptions$1, ...options }; const defaultValue = setTrueForUndefinedValues ? true : null; return queryParams.split("&").filter((v) => v.trim().length > 0).reduce((acc, keyValuePair) => { const [key, value = null] = keyValuePair.split("="); let val; if (typeof value === "string") if (parseNumbers && isParsableNumber(value)) val = Number.parseInt(value, 10); else val = parseBooleans && ["false", "true"].includes(value) ? value === "true" : decodeURIComponent(value); else val = defaultValue; return { ...acc, [key]: val }; }, {}); }; //#endregion //#region src/parse-dsn.ts const dsnRegexp = /^(?<driver>([\w+-]{1,40})):\/\/((?<user>[^/:]{1,200})?(:(?<pass>.{0,200}))?@)?(?<host>[^/:]{1,200}?)(:(?<port>\d+)?)?(\/(?<db>([\w#$.@-])+))?(\?(?<params>.{1,1000}))?$/; const defaultOptions = { lowercaseDriver: false, overrides: {} }; const parseDsn = (dsn, options) => { if (!isNonEmptyString(dsn)) return createErrorResult(typeof dsn === "string" ? "EMPTY_DSN" : "INVALID_ARGUMENT"); const { lowercaseDriver, overrides = {} } = { ...defaultOptions, ...options }; const matches = dsnRegexp.exec(dsn); if (!matches?.groups) return createErrorResult("PARSE_ERROR"); const parsed = {}; for (const [key, value] of Object.entries(matches.groups)) if (typeof value === "string") switch (key) { case "driver": parsed.driver = lowercaseDriver ? value.toLowerCase() : value; break; case "port": parsed.port = Number.parseInt(value, 10); break; case "params": parsed.params = parseQueryParams(value); break; default: parsed[key] = value; } const val = removeUndefined(mergeDsnOverrides(parsed, overrides)); if (val?.port && !isValidNetworkPort(val.port)) return createErrorResult("INVALID_PORT", `Invalid port: ${val.port}`); return { success: true, value: val }; }; //#endregion //#region src/assert-parsable-dsn.ts /** * @throws Error when not parsable */ const assertParsableDsn = (dsn, msg) => { if (typeof dsn !== "string") throw new TypeError("dsn: must be a string."); const parsed = parseDsn(dsn); if (!parsed.success) throw new Error(msg ?? `${parsed.message} (${parsed.reason})`); }; //#endregion //#region src/convert-jdbc-to-dsn.ts /** * Convert JDBC URL to DSN format. * * @example * ```typescript * const jdbc = 'sqlserver://localhost:1433;database=my-db;authentication=default;user=sa;password=pass03$;encrypt=true;trustServerCertificate=true'; * const dsn = convertJdbcToDsn(jdbc); * // dsn is 'sqlserver://localhost:1433?database=my-db&authentication=default&user=sa&password=pass03$&encrypt=true&trustServerCertificate=true * ``` * @throws TypeError . */ const convertJdbcToDsn = (jdbc) => { if (typeof jdbc !== "string") throw new TypeError("jdbc param must be a string"); const [part1, ...rest] = jdbc.split(";"); return [part1, rest ? rest.join("&") : void 0].filter(Boolean).join("?"); }; //#endregion //#region src/is-parsable-dsn.ts const isParsableDsn = (dsn) => { return parseDsn(dsn).success; }; //#endregion //#region src/parse-dsn-or-throw.ts const parseDsnOrThrow = (dsn, options) => { const parsedOrError = parseDsn(dsn, options); if (parsedOrError.success) return parsedOrError.value; const pfx = options?.errorMsgPrefix ?? `Can't parse dsn`; throw new Error(`${pfx}: ${parsedOrError.message} (${parsedOrError.reason})`); }; //#endregion exports.assertParsableDsn = assertParsableDsn; exports.convertJdbcToDsn = convertJdbcToDsn; exports.isParsableDsn = isParsableDsn; exports.parseDsn = parseDsn; exports.parseDsnOrThrow = parseDsnOrThrow;