Utility parser for DSN
npm install @soluble/dsn-parserDSN parser, validation utilities, and query string helper in a light and modern package.


!bundles
!node

!types


``bash`
$ npm install @soluble-dsn-parser
$ yarn add @soluble/dsn-parser
$ pnpm add @soluble/dsn-parser
- [x] Parse individual fields (ie: driver, user, password, host...)/
- [x] Handle query string with casting of boolean and numeric values.
- [x] Handle special characters like , :... in the password (some libs won't).
- [x] Error with indicative message / reasons (discriminative union or throwing).
- [x] Don't leak passwords in the error message.
- [x] Assertion and typeguard helpers (ie: easy integrate with zod).
Usage with exceptions
`typescript
import { parseDsnOrThrow } from "@soluble/dsn-parser";
const dsn = "redis://user:p@/ssword@localhost:6379/0?ssl=true";
try {
const parsedDsn = parseDsnOrThrow(dsn);
assert.deepEqual(parsedDsn, {
driver: "redis",
pass: "p@/ssword",
host: "localhost",
user: "user",
port: 6379,
db: "0",
params: {
ssl: true,
},
});
} catch (e) {
// example:
// e -> Error("Can't parse dsn: Invalid port: 12345678 (INVALID_PORT)")
}
`
Usage with discriminated union.
`typescript
import { parseDsn } from "@soluble/dsn-parser";
const dsn = "redis://user:p@/ssword@localhost:6379/0?ssl=true";
const parsed = parseDsn(dsn);
if (parsed.success) {
assert.deepEqual(parsed.value, {
driver: "redis",
pass: "p@/ssword",
host: "localhost",
user: "user",
port: 6379,
db: "0",
params: {
ssl: true,
},
});
} else {
assert.deepEqual(parsed, {
success: false,
// Reasons might vary
reason: "INVALID_PORT",
message: "Invalid http port: 12345678",
});
}
`
`typescript
const dsn = "mySql://localhost:6379/db";
const parsed = parseDsn(dsn, {
lowercaseDriver: true,
overrides: {
db: "db3",
port: undefined,
},
});
assert.deepEqual(parsed.value, {
driver: "mysql",
host: "localhost",
db: "db3",
});
`
| Params | Type | Description |
| ----------------- | ---------------------- | ----------------------------------------- |
| lowercaseDriver | | Driver name in lowercase, default false |overrides
| | DSN must be a string | |
`typescript
import { assertParsableDsn, ParsableDsn } from "@soluble/dsn-parser";
try {
assertParsableDsn("redis:/");
// Type is narrowed to string (ParsableDsn) if it
// didn't throw.
} catch (e) {
assert.equal(e.message, "Cannot parse DSN (PARSE_ERROR)");
}
`
`typescript
import { isParsableDsn, type ParsableDsn } from "@soluble/dsn-parser";
const dsn = "postgresql://localhost:6379/db";
if (isParsableDsn(dsn)) {
// known to be ParsableDSN
}
`
The minimum requirement for dsn parsing is to have a host and
a driver _(/[a-z0-9]+/i)_ defined. All other options are optional.
`typescript`
export type ParsedDsn = {
driver: string;
host: string;
user?: string;
pass?: string;
port?: number;
db?: string;
/* Query params /
params?: Record
};
Things like:
`typescript`
const validExamples = [
"postgresql://postgres:@localhost:5432/prisma-db",
"redis://us_er-name:P@ass-_:?/ssw/rd@www.example.com:6379/0?cache=true",
//...
];
should work.
Simple query parameters are supported (no arrays, no nested). For convenience
it will cast 'true' and 'false' to booleans,true
parse numeric string to numbers if possible. When a query
parameter does not contain a value, it will be returned as .
`typescript`
const dsn = "redis://host?index=1&compress=false&ssl";
const parsed = parseDsn(dsn);
assert.deepEqual(parsed.value.params, {
index: 1,
compress: false,
ssl: true,
});
parseDsn won't make any assumptions on default values _(i.e: default port for mysql...)_.
parseDsn wraps its result in a discriminated uniontry... catch
to allow the retrieval of validation errors. No needed and full typescript support.
Reason codes are guaranteed in semantic versions and messages does not
leak credentials
`typescriptsuccess: false
const parsed = parseDsn("redis://localhost:65636");
assert.deepEqual(parsed, {
success: false,
reason: "INVALID_PORT",
message: "Invalid port: 65636",
});
if (!parsed.success) {
// narrows the type to`
// {
// reason: 'PARSE_ERROR'|'INVALID_ARGUMENT'|...
// message: string
// }
log(parsed.reason);
}
| Reason | Message | Comment |
| -------------------- | ----------------------- | --------------- |
| 'PARSE_ERROR' | Cannot parse DSN | _Regexp failed_ |'INVALID_ARGUMENT'
| | DSN must be a string | |'EMPTY_DSN'
| | DSN cannot be empty | |'INVALID_PORT'
| | Invalid port: ${port} | [1-65535] |
The isParsableDsn can be easily plugged into zod custom validation. Example:
`typescript
import { z } from "zod";
export const serverEnvSchema = z.object({
PRISMA_DATABASE_URL: z.custom(
(dsn) => isParsableDsn(dsn),
"Invalid DSN format."
),
});
serverEnvSchema.parse(process.env);
`
Some libs (ioredis...) still might fail parsing a password containing '/',
unfortunately they're pretty convenient, i.e:
`bash
openssl rand 60 | openssl base64 -A
If you are enjoying some of my OSS guides or libs for your company, I'd really appreciate a sponsorship, a coffee or a dropped star. That gives me a tasty morning boost and help me to make some of my ideas come true 🙏
![]() | |
JetBrains | Embie.be |