UNPKG

@httpx/dsn-parser

Version:

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

1 lines 13 kB
{"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"]}