@httpx/dsn-parser
Version:
DSN & JDBC string parser with query params support in a tiny and modern package.
1 lines • 13 kB
Source Map (JSON)
{"version":3,"sources":["../src/dsn-parser.type.ts","../src/dsn-parser.util.ts","../src/query-param-parser.ts","../src/parse-dsn.ts","../src/assert-parsable-dsn.ts","../src/convert-jdbc-to-dsn.ts","../src/is-parsable-dsn.ts","../src/parse-dsn-or-throw.ts"],"names":["errorReasons","createErrorResult","reason","msg","isNonEmptyString","value","trim","parsableNumberRegexp","isParsableNumber","isValidNetworkPort","port","removeUndefined","obj","definedObj","key","mergeDsnOverrides","parsedDsn","overrides","merged","params","restDsn","defaultOptions","parseQueryParams","queryParams","options","parseBooleans","parseNumbers","setTrueForUndefinedValues","defaultValue","v","acc","keyValuePair","val","dsnRegexp","parseDsn","dsn","opts","lowercaseDriver","matches","parsed","assertParsableDsn","convertJdbcToDsn","jdbc","part1","rest","isParsableDsn","parseDsnOrThrow","parsedOrError","pfx"],"mappings":"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","file":"index.cjs","sourcesContent":["export type ParsedDsn = {\n db?: string;\n driver: string;\n host: string;\n /** Query params */\n params?: Record<string, boolean | number | string>;\n pass?: string;\n port?: number;\n user?: string;\n};\n\nexport type ParseDsnOptions = {\n /** Whether to lowercase parsed driver name, default: false */\n lowercaseDriver?: boolean;\n /** Overrides parsed values by those one (except query params) */\n overrides?: Omit<Partial<ParsedDsn>, 'params'>;\n};\n\nexport const errorReasons = {\n EMPTY_DSN: 'DSN cannot be empty',\n INVALID_ARGUMENT: 'DSN must be a string',\n INVALID_PORT: 'Invalid port',\n PARSE_ERROR: 'Cannot parse DSN',\n} as const;\n\nexport type ErrorReasons = keyof typeof errorReasons;\n\ntype ParserSuccessResult = {\n success: true;\n value: ParsedDsn;\n};\n\nexport type ParserErrorResult = {\n message: string;\n reason: ErrorReasons;\n success: false;\n};\n\nexport type ParserResult = ParserErrorResult | ParserSuccessResult;\n\ndeclare const tag: unique symbol;\n\nexport type WeakOpaqueContainer<Token> = {\n readonly [tag]: Token;\n};\n\nexport type ParsableDsn = string & WeakOpaqueContainer<'ParsableDsn'>;\n","import {\n type ErrorReasons,\n errorReasons,\n type ParsedDsn,\n type ParseDsnOptions,\n type ParserErrorResult,\n} from './dsn-parser.type';\n\nexport const createErrorResult = (\n reason: ErrorReasons,\n msg?: string\n): ParserErrorResult => {\n return {\n message: msg ?? errorReasons[reason],\n reason: reason,\n success: false,\n };\n};\n\nexport const isNonEmptyString = (\n value: unknown,\n trim = true\n): value is string => {\n return typeof value === 'string' && (trim ? value.trim() : value).length > 0;\n};\n\nconst parsableNumberRegexp = /^-?\\d{1,16}$/;\n\nexport const isParsableNumber = (value: unknown): value is number => {\n return typeof value === 'string' && parsableNumberRegexp.test(value);\n};\n\ntype ValidNetworkPort = number;\n\nexport const isValidNetworkPort = (port: number): port is ValidNetworkPort => {\n return !Number.isNaN(port) && port < 65_536 && port > 0;\n};\n\nexport const removeUndefined = (\n obj: Record<string, unknown>\n): Record<string, unknown> => {\n const definedObj: Record<string, unknown> = {};\n for (const key in obj) {\n if (obj[key] !== undefined) {\n definedObj[key] = obj[key];\n }\n }\n return definedObj;\n};\n\nexport const mergeDsnOverrides = (\n parsedDsn: ParsedDsn,\n overrides: Exclude<ParseDsnOptions['overrides'], undefined>\n): ParsedDsn => {\n const merged: Record<string, unknown> = {};\n const { params, ...restDsn } = parsedDsn;\n for (const [key, value] of Object.entries(restDsn)) {\n merged[key] =\n key in overrides ? (overrides as Record<string, unknown>)[key] : value;\n }\n merged.params = params;\n return merged as unknown as ParsedDsn;\n};\n","import { isParsableNumber } from './dsn-parser.util';\n\ntype ParseQueryParamsOptions = {\n parseBooleans?: boolean;\n parseNumbers?: boolean;\n setTrueForUndefinedValues?: boolean;\n};\n\nconst defaultOptions = {\n parseBooleans: true,\n parseNumbers: true,\n setTrueForUndefinedValues: true,\n};\n\nexport const parseQueryParams = (\n queryParams: string,\n options?: ParseQueryParamsOptions\n): Record<string, boolean | null | number | string> => {\n const { parseBooleans, parseNumbers, setTrueForUndefinedValues } = {\n ...defaultOptions,\n ...options,\n };\n const defaultValue = setTrueForUndefinedValues ? true : null;\n const parts = queryParams.split('&').filter((v) => v.trim().length > 0);\n return parts.reduce((acc, keyValuePair) => {\n const [key, value = null] = keyValuePair.split('=') as [\n string,\n string | undefined,\n ];\n let val;\n if (typeof value === 'string') {\n if (parseNumbers && isParsableNumber(value)) {\n val = Number.parseInt(value, 10);\n } else {\n val =\n parseBooleans && ['false', 'true'].includes(value)\n ? value === 'true'\n : decodeURIComponent(value);\n }\n } else {\n val = defaultValue;\n }\n return { ...acc, [key]: val };\n }, {});\n};\n","import type {\n ParsedDsn,\n ParseDsnOptions,\n ParserResult,\n} from './dsn-parser.type';\nimport {\n createErrorResult,\n isNonEmptyString,\n isValidNetworkPort,\n mergeDsnOverrides,\n removeUndefined,\n} from './dsn-parser.util';\nimport { parseQueryParams } from './query-param-parser';\n\nconst dsnRegexp =\n // eslint-disable-next-line regexp/no-unused-capturing-group,regexp/no-misleading-capturing-group,sonarjs/regex-complexity,sonarjs/unused-named-groups\n /^(?<driver>([\\w+-]{1,40})):\\/\\/((?<user>[^/:]{1,200})?(:(?<pass>.{0,200}))?@)?(?<host>[^/:]{1,200}?)(:(?<port>\\d+)?)?(\\/(?<db>([\\w#$.@-])+))?(\\?(?<params>.{1,1000}))?$/;\n\nconst defaultOptions = {\n lowercaseDriver: false,\n overrides: {},\n} as const satisfies ParseDsnOptions;\n\nexport const parseDsn = (\n dsn: unknown,\n options?: ParseDsnOptions\n): ParserResult => {\n if (!isNonEmptyString(dsn)) {\n return createErrorResult(\n typeof dsn === 'string' ? 'EMPTY_DSN' : 'INVALID_ARGUMENT'\n );\n }\n const opts = { ...defaultOptions, ...options };\n const { lowercaseDriver, overrides = {} } = opts;\n const matches = dsnRegexp.exec(dsn);\n if (!matches?.groups) {\n return createErrorResult('PARSE_ERROR');\n }\n const parsed: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(matches.groups)) {\n if (typeof value === 'string') {\n switch (key) {\n case 'driver': {\n parsed.driver = lowercaseDriver ? value.toLowerCase() : value;\n break;\n }\n case 'port': {\n parsed.port = Number.parseInt(value, 10);\n break;\n }\n case 'params': {\n parsed.params = parseQueryParams(value);\n break;\n }\n default: {\n parsed[key] = value;\n }\n }\n }\n }\n const val = removeUndefined(\n mergeDsnOverrides(parsed as ParsedDsn, overrides)\n ) as ParsedDsn;\n if (val?.port && !isValidNetworkPort(val.port)) {\n return createErrorResult(\n 'INVALID_PORT',\n `Invalid port: ${val.port as unknown as number}`\n );\n }\n return {\n success: true,\n value: val,\n };\n};\n","import type { ParsableDsn } from './dsn-parser.type';\nimport { parseDsn } from './parse-dsn';\n\n/**\n * @throws Error when not parsable\n */\nexport const assertParsableDsn = (\n dsn: unknown,\n msg?: string\n): asserts dsn is ParsableDsn => {\n const parsed = parseDsn(dsn as string);\n if (!parsed.success) {\n throw new Error(msg ?? `${parsed.message} (${parsed.reason})`);\n }\n};\n","export const convertJdbcToDsn = (jdbc: string): string => {\n const [part1, ...rest] = jdbc.split(';');\n return [part1, rest ? rest.join('&') : undefined].filter(Boolean).join('?');\n};\n","import type { ParsableDsn } from './dsn-parser.type';\nimport { parseDsn } from './parse-dsn';\n\nexport const isParsableDsn = (dsn: unknown): dsn is ParsableDsn => {\n return parseDsn(dsn as string).success;\n};\n","import type { ParsedDsn, ParseDsnOptions } from './dsn-parser.type';\nimport { parseDsn } from './parse-dsn';\n\nexport const parseDsnOrThrow = (\n dsn: unknown,\n options?: ParseDsnOptions & {\n errorMsgPrefix?: string;\n }\n): ParsedDsn => {\n const parsedOrError = parseDsn(dsn, options);\n if (parsedOrError.success) {\n return parsedOrError.value;\n }\n const pfx = options?.errorMsgPrefix ?? `Can't parse dsn`;\n throw new Error(`${pfx}: ${parsedOrError.message} (${parsedOrError.reason})`);\n};\n"]}