Skip to content

Commit c6e6a6f

Browse files
committed
feat(pg-connection-string): ClientConfig helper functions
Two new functions are introduced to make it easy for TypeScript users to use a PostgresSQL connection string with pg Client. Fixes #2280
1 parent 95d7e62 commit c6e6a6f

File tree

4 files changed

+172
-0
lines changed

4 files changed

+172
-0
lines changed

packages/pg-connection-string/README.md

+21
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,27 @@ The resulting config contains a subset of the following properties:
3535
* `ca`
3636
* any other query parameters (for example, `application_name`) are preserved intact.
3737

38+
### ClientConfig Compatibility for TypeScript
39+
40+
The pg-connection-string `ConnectionOptions` interface is not compatible with the `ClientConfig` interface that [pg.Client](https://node-postgres.com/apis/client) expects. To remedy this, use the `parseIntoClientConfig` function instead of `parse`:
41+
42+
```ts
43+
import { ClientConfig } from 'pg';
44+
import { parseIntoClientConfig } from 'pg-connection-string';
45+
46+
const config: ClientConfig = parseIntoClientConfig('postgres://someuser:somepassword@somehost:381/somedatabase')
47+
```
48+
49+
You can also use `toClientConfig` to convert an existing `ConnectionOptions` interface into a `ClientConfig` interface:
50+
51+
```ts
52+
import { ClientConfig } from 'pg';
53+
import { parse, toClientConfig } from 'pg-connection-string';
54+
55+
const config = parse('postgres://someuser:somepassword@somehost:381/somedatabase')
56+
const clientConfig: ClientConfig = toClientConfig(config)
57+
```
58+
3859
## Connection Strings
3960

4061
The short summary of acceptable URLs is:

packages/pg-connection-string/index.d.ts

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { ClientConfig } from 'pg'
2+
13
export function parse(connectionString: string): ConnectionOptions
24

35
export interface ConnectionOptions {
@@ -13,3 +15,6 @@ export interface ConnectionOptions {
1315
fallback_application_name?: string
1416
options?: string
1517
}
18+
19+
export function toClientConfig(config: ConnectionOptions): ClientConfig
20+
export function parseIntoClientConfig(connectionString: string): ClientConfig

packages/pg-connection-string/index.js

+56
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,62 @@ function parse(str) {
107107
return config
108108
}
109109

110+
// convert pg-connection-string ssl config to a ClientConfig.ConnectionOptions
111+
function toConnectionOptions(sslConfig) {
112+
const connectionOptions = Object.entries(sslConfig).reduce((c, [key, value]) => {
113+
// we explicitly check for undefined and null instead of `if (value)` because some
114+
// options accept falsy values. Example: `ssl.rejectUnauthorized = false`
115+
if (value !== undefined && value !== null) {
116+
c[key] = value
117+
}
118+
119+
return c
120+
}, {})
121+
122+
return connectionOptions
123+
}
124+
125+
// convert pg-connection-string config to a ClientConfig
126+
function toClientConfig(config) {
127+
const poolConfig = Object.entries(config).reduce((c, [key, value]) => {
128+
if (key === 'ssl') {
129+
const sslConfig = value
130+
131+
if (typeof sslConfig === 'boolean') {
132+
c[key] = sslConfig
133+
} else if (typeof sslConfig === 'object') {
134+
c[key] = toConnectionOptions(sslConfig)
135+
}
136+
} else if (value !== undefined && value !== null) {
137+
if (key === 'port') {
138+
// when port is not specified, it is converted into an empty string
139+
// we want to avoid NaN or empty string as a values in ClientConfig
140+
if (value !== '') {
141+
const v = parseInt(value, 10)
142+
if (isNaN(v)) {
143+
throw new Error(`Invalid ${key}: ${value}`)
144+
}
145+
146+
c[key] = v
147+
}
148+
} else {
149+
c[key] = value
150+
}
151+
}
152+
153+
return c
154+
}, {})
155+
156+
return poolConfig
157+
}
158+
159+
// parses a connection string into ClientConfig
160+
function parseIntoClientConfig(str) {
161+
return toClientConfig(parse(str))
162+
}
163+
110164
module.exports = parse
111165

112166
parse.parse = parse
167+
parse.toClientConfig = toClientConfig
168+
parse.parseIntoClientConfig = parseIntoClientConfig
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
'use strict'
2+
3+
const { parse, toClientConfig } = require('../')
4+
5+
describe('toClientConfig', function () {
6+
it('converts connection info', function () {
7+
const config = parse('postgres://brian:pw@boom:381/lala')
8+
const clientConfig = toClientConfig(config)
9+
10+
clientConfig.user.should.equal('brian')
11+
clientConfig.password.should.equal('pw')
12+
clientConfig.host.should.equal('boom')
13+
clientConfig.port.should.equal(381)
14+
clientConfig.database.should.equal('lala')
15+
})
16+
17+
it('converts query params', function () {
18+
const config = parse(
19+
'postgres:///?application_name=TheApp&fallback_application_name=TheAppFallback&client_encoding=utf8&options=-c geqo=off'
20+
)
21+
const clientConfig = toClientConfig(config)
22+
23+
clientConfig.application_name.should.equal('TheApp')
24+
clientConfig.fallback_application_name.should.equal('TheAppFallback')
25+
clientConfig.client_encoding.should.equal('utf8')
26+
clientConfig.options.should.equal('-c geqo=off')
27+
})
28+
29+
it('converts SSL boolean', function () {
30+
const config = parse('pg:///?ssl=true')
31+
const clientConfig = toClientConfig(config)
32+
33+
clientConfig.ssl.should.equal(true)
34+
})
35+
36+
it('converts sslmode=disable', function () {
37+
const config = parse('pg:///?sslmode=disable')
38+
const clientConfig = toClientConfig(config)
39+
40+
clientConfig.ssl.should.equal(false)
41+
})
42+
43+
it('converts sslmode=noverify', function () {
44+
const config = parse('pg:///?sslmode=no-verify')
45+
const clientConfig = toClientConfig(config)
46+
47+
clientConfig.ssl.rejectUnauthorized.should.equal(false)
48+
})
49+
50+
it('converts other sslmode options', function () {
51+
const config = parse('pg:///?sslmode=verify-ca')
52+
const clientConfig = toClientConfig(config)
53+
54+
clientConfig.ssl.should.deep.equal({})
55+
})
56+
57+
it('converts other sslmode options', function () {
58+
const config = parse('pg:///?sslmode=verify-ca')
59+
const clientConfig = toClientConfig(config)
60+
61+
clientConfig.ssl.should.deep.equal({})
62+
})
63+
64+
it('converts ssl cert options', function () {
65+
const connectionString =
66+
'pg:///?sslcert=' +
67+
__dirname +
68+
'/example.cert&sslkey=' +
69+
__dirname +
70+
'/example.key&sslrootcert=' +
71+
__dirname +
72+
'/example.ca'
73+
const config = parse(connectionString)
74+
const clientConfig = toClientConfig(config)
75+
76+
clientConfig.ssl.should.deep.equal({
77+
ca: 'example ca\n',
78+
cert: 'example cert\n',
79+
key: 'example key\n',
80+
})
81+
})
82+
83+
it('converts unix domain sockets', function () {
84+
const config = parse('socket:/some path/?db=my[db]&encoding=utf8&client_encoding=bogus')
85+
const clientConfig = toClientConfig(config)
86+
clientConfig.host.should.equal('/some path/')
87+
clientConfig.database.should.equal('my[db]', 'must to be escaped and unescaped through "my%5Bdb%5D"')
88+
clientConfig.client_encoding.should.equal('utf8')
89+
})
90+
})

0 commit comments

Comments
 (0)