Comparing version 1.0.1 to 1.0.2
@@ -156,5 +156,3 @@ const { errorFields, errors, entries } = require('./types.js') | ||
const type = x.readInt32BE(5) | ||
try { | ||
type !== 0 && onauth(type, x, error) | ||
} /* c8 ignore next */ catch (err) { error(err) } | ||
type !== 0 && onauth(type, x, error) | ||
} | ||
@@ -161,0 +159,0 @@ |
@@ -59,4 +59,10 @@ const net = require('net') | ||
function onauth(type, x) { | ||
socket.write(frontend.auth(type, x, options)) | ||
function onauth(type, x, onerror) { | ||
Promise.resolve( | ||
typeof options.pass === 'function' | ||
? options.pass() | ||
: options.pass | ||
).then(pass => | ||
socket.write(frontend.auth(type, x, options, pass)) | ||
).catch(onerror) | ||
} | ||
@@ -63,0 +69,0 @@ |
@@ -60,5 +60,5 @@ const crypto = require('crypto') | ||
function auth(type, x, options) { | ||
function auth(type, x, options, pass) { | ||
if (type in auths) | ||
return auths[type](type, x, options) | ||
return auths[type](type, x, options, pass) | ||
/* c8 ignore next */ | ||
@@ -72,3 +72,3 @@ throw errors.generic({ | ||
function AuthenticationCleartextPassword(type, x, { pass }) { | ||
function AuthenticationCleartextPassword(type, x, options, pass) { | ||
return bytes | ||
@@ -81,6 +81,6 @@ .p() | ||
function AuthenticationMD5Password(type, x, { user, pass }) { | ||
function AuthenticationMD5Password(type, x, options, pass) { | ||
return bytes | ||
.p() | ||
.str('md5' + md5(Buffer.concat([Buffer.from(md5(pass + user)), x.slice(9)]))) | ||
.str('md5' + md5(Buffer.concat([Buffer.from(md5(pass + options.user)), x.slice(9)]))) | ||
.z(1) | ||
@@ -106,7 +106,7 @@ .end() | ||
function SASLContinue(type, x, options) { | ||
function SASLContinue(type, x, options, pass) { | ||
const res = x.utf8Slice(9).split(',').reduce((acc, x) => (acc[x[0]] = x.slice(2), acc), {}) | ||
const saltedPassword = crypto.pbkdf2Sync( | ||
options.pass, | ||
pass, | ||
Buffer.from(res.s, 'base64'), | ||
@@ -113,0 +113,0 @@ parseInt(res.i), 32, |
@@ -39,5 +39,8 @@ const os = require('os') | ||
function Postgres(url, options) { | ||
options = parseOptions(url, options) | ||
function Postgres(a, b) { | ||
if (arguments.length && !a) | ||
throw new Error(a + ' - is not a url or connection object') | ||
const options = parseOptions(a, b) | ||
const max = Math.max(1, options.max) | ||
@@ -64,3 +67,3 @@ , connections = Queue() | ||
Object.assign(postgres, { | ||
options: Object.assign({}, options, { password: null }), | ||
options: Object.assign({}, options, { pass: null }), | ||
parameters: {}, | ||
@@ -449,6 +452,6 @@ listen, | ||
function parseOptions(uri, options) { | ||
function parseOptions(a, b) { | ||
const env = process.env // eslint-disable-line | ||
, url = typeof uri === 'string' ? Url.parse(uri, true) : { query: {}, pathname: '' } | ||
, o = (typeof uri === 'string' ? options : uri) || {} | ||
, url = typeof a === 'string' ? Url.parse(a, true) : { query: {}, pathname: '' } | ||
, o = (typeof a === 'string' ? b : a) || {} | ||
, auth = (url.auth || '').split(':') | ||
@@ -463,3 +466,3 @@ , host = o.hostname || o.host || url.hostname || env.PGHOST || 'localhost' | ||
database : o.database || o.db || (url.pathname || '').slice(1) || env.PGDATABASE || 'postgres', | ||
user : o.user || o.username || auth[0] || env.PGUSERNAME || os.userInfo().username, | ||
user : o.user || o.username || auth[0] || env.PGUSERNAME || env.PGUSER || os.userInfo().username, | ||
pass : o.pass || o.password || auth[1] || env.PGPASSWORD || '', | ||
@@ -466,0 +469,0 @@ max : o.max || url.query.max || Math.max(1, os.cpus().length), |
{ | ||
"name": "postgres", | ||
"version": "1.0.1", | ||
"version": "1.0.2", | ||
"description": "Fastest full featured PostgreSQL client for Node.js", | ||
@@ -9,3 +9,3 @@ "main": "lib/index.js", | ||
"test": "node tests/index.js", | ||
"lint": "eslint lib", | ||
"lint": "eslint lib && eslint tests", | ||
"prepublishOnly": "npm run lint && npm test" | ||
@@ -12,0 +12,0 @@ }, |
104
README.md
<img align="left" width="440" height="140" alt="Fastest full PostgreSQL nodejs client" src="https://raw.githubusercontent.com/porsager/postgres/master/postgresjs.svg?sanitize=true" /> | ||
- 🚀 Fastest full featured PostgreSQL client for Node.js | ||
- [🚀 Fastest full featured PostgreSQL client for Node.js](https://github.com/porsager/postgres-benchmarks#results) | ||
- 🚯 1250 LOC - 0 dependencies | ||
@@ -36,3 +36,3 @@ - 🏷 ES6 Tagged Template Strings at the core | ||
You can use either a `postgres://` url connection string or the options to define your database connection properties. | ||
You can use either a `postgres://` url connection string or the options to define your database connection properties. Options in the object will override any present in the url. | ||
@@ -68,7 +68,7 @@ ```js | ||
More info for `ssl` can be found in the [Node.js docs for tls connect options](https://nodejs.org/dist/latest-v10.x/docs/api/tls.html#tls_new_tls_tlssocket_socket_options) | ||
More info for the `ssl` option can be found in the [Node.js docs for tls connect options](https://nodejs.org/dist/latest-v10.x/docs/api/tls.html#tls_new_tls_tlssocket_socket_options) | ||
## Query ```sql` ` -> Promise``` | ||
A query will always return a `Promise` which resolves to either an array `[...]` or `null` depending on the type of query. Destructuring is great to immidiately access the first element. | ||
A query will always return a `Promise` which resolves to a results array `[...]{ rows, command }`. Destructuring is great to immediately access the first element. | ||
@@ -92,3 +92,3 @@ ```js | ||
Parameters are automatically inferred and handled by Postgres so that SQL injection isn't possible. No special handling is necessarry, simply use JS tagged template literals as usual. | ||
Parameters are automatically inferred and handled by Postgres so that SQL injection isn't possible. No special handling is necessary, simply use JS tagged template literals as usual. | ||
@@ -114,6 +114,6 @@ ```js | ||
If you want to handle rows returned by a query one by one you can use `.stream` which returns a promise that resolves once there are no more rows. | ||
If you want to handle rows returned by a query one by one, you can use `.stream` which returns a promise that resolves once there are no more rows. | ||
```js | ||
await sql.stream` | ||
await sql` | ||
select created_at, name from events | ||
@@ -130,7 +130,7 @@ `.stream(row => { | ||
When you call listen, a dedicated connection will automatically be made to ensure that you receive notifications in realtime. This connection will be used for any further calls to listen. | ||
When you call listen, a dedicated connection will automatically be made to ensure that you receive notifications in real time. This connection will be used for any further calls to listen. Listen returns a promise which resolves once the `LISTEN` query to Postgres completes, or if there is already a listener active. | ||
```js | ||
sql.listen('news', payload => { | ||
await sql.listen('news', payload => { | ||
const json = JSON.parse(payload) | ||
@@ -149,2 +149,7 @@ console.log(json.this) // logs 'is' | ||
## Tagged template function ``` sql`` ``` | ||
[Tagged template functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Tagged_templates) are not just ordinary template literal strings. They allow the function to handle any parameters within before interpolation. This means that they can be used to enforce a safe way of writing queries, which is what Postgres.js does. Any generic value will be serialized according to an inferred type, and replaced by a PostgreSQL protocol placeholders `$1, $2, ...` and then sent to the database as a parameter to let it handle any need for escaping / casting. | ||
This also means you cannot write dynamic queryes or concat queries together by simple string manipulation. To enable dynamic queries in a safe way, the `sql` function doubles as a regular function which escapes any value properly. It also includes overloads for common cases of inserting, selecting, updating and querying. | ||
## Dynamic query helpers `sql() inside tagged template` | ||
@@ -156,3 +161,2 @@ | ||
```js | ||
@@ -167,16 +171,16 @@ | ||
insert into users ${ | ||
sql(user) | ||
sql(user, 'name', 'age') | ||
} | ||
` | ||
// Is translated into this query: | ||
insert into users (name, age) values ($1, $2) | ||
``` | ||
Is translated into a safe query like this: | ||
You can leave out the column names and simply do `sql(user)` if you want to get all fields from the object as columns, but be careful not to allow users to supply columns you don't want. | ||
```sql | ||
insert into users (name, age) values ($1, $2) | ||
``` | ||
#### Multiple inserts in one query | ||
If you need to insert multiple rows at the same time it's also much faster to do it with a single `insert`. Simply pass an array of objects to `sql()`. | ||
```js | ||
@@ -201,5 +205,42 @@ | ||
#### Update | ||
This is also useful for update queries | ||
```js | ||
const user = { | ||
id: 1, | ||
name: 'Muray' | ||
} | ||
sql` | ||
update users set ${ | ||
sql(user, 'name') | ||
} where | ||
id = ${ user.id } | ||
` | ||
// Is translated into this query: | ||
update users set name = $1 where id = $2 | ||
``` | ||
#### Select | ||
```js | ||
const columns = ['name', 'age'] | ||
sql` | ||
select ${ | ||
sql(columns) | ||
} from users | ||
` | ||
// Is translated into this query: | ||
select name, age from users | ||
``` | ||
#### Arrays `sql.array(Array)` | ||
Postgres has a native array type which is similar to js arrays, but Postgres only allows the same type and shape for nested items. This method automatically infers the item type and translates js arrays into Postgres arrays. | ||
PostgreSQL has a native array type which is similar to js arrays, but only allows the same type and shape for nested items. This method automatically infers the item type and serializes js arrays into PostgreSQL arrays. | ||
@@ -246,3 +287,3 @@ ```js | ||
Using an `sql` file for a query. The contents will be cached in memory so that the file is only read once. | ||
Using an `.sql` file for a query. The contents will be cached in memory so that the file is only read once. | ||
@@ -380,19 +421,30 @@ ```js | ||
## The Connection Pool | ||
## Unsafe queries `sql.unsafe(query, [args], [options]) -> promise` | ||
Connections are created lazily once a query is created. This means that simply doing const `sql = postgres(...)` won't have any effect other than instantiating a new `sql` instance. | ||
If you know what you're doing, you can use `unsafe` to pass any string you'd like to postgres. | ||
> No connection will be made until a query is made. | ||
This means that we get a much simpler story for error handling and reconnections. Queries will be sent over the wire immediately on the next available connection in the pool. Connections are automatically taken out of the pool if you start a transaction using `sql.begin()`, and automatically returned to the pool once your transaction is done. | ||
Any query which was already sent over the wire will be rejected if the connection is lost. It'll automatically defer to the error handling you have for that query, and since connections are lazy it'll automatically try to reconnect the next time a query is made. The benefit of this is no weird generic "onerror" handler that tries to get things back to normal, and also simpler application code since you don't have to handle errors out of context. | ||
There are no guarantees about queries executing in order unless using a transaction with `sql.begin()` or setting `max: 1`. Of course doing a series of queries, one awaiting the other will work as expected, but that's just due to the nature of js async/promise handling, so it's not necessary for this library to be concerned with ordering. | ||
<details><summary><code>sql.unsafe</code> - Advanced unsafe use cases</summary> | ||
### Unsafe queries `sql.unsafe(query, [args], [options]) -> promise` | ||
If you know what you're doing, you can use `unsafe` to pass any string you'd like to postgres. Please note that this can lead to sql injection if you're not careful. | ||
```js | ||
sql.unsafe(danger + ` | ||
select * from users where id = $1 | ||
`, [user_id]) | ||
sql.unsafe('select ' + danger + ' from users where id = ' + dragons) | ||
``` | ||
</details> | ||
## Errors | ||
Errors are all thrown to related queries and never globally. Errors comming from Postgres itself are always in the [native Postgres format](https://www.postgresql.org/docs/current/errcodes-appendix.html), and the same goes for any [Node.js errors](https://nodejs.org/api/errors.html#errors_common_system_errors) eg. coming from the underlying connection. | ||
Errors are all thrown to related queries and never globally. Errors coming from PostgreSQL itself are always in the [native Postgres format](https://www.postgresql.org/docs/current/errcodes-appendix.html), and the same goes for any [Node.js errors](https://nodejs.org/api/errors.html#errors_common_system_errors) eg. coming from the underlying connection. | ||
@@ -414,3 +466,3 @@ There are also the following errors specifically for this library. | ||
When using SASL authentication the server responds with a signature at the end of the authentication flow which needs to match the one on the client. This is to avoid [man in the middle attacks](https://en.wikipedia.org/wiki/Man-in-the-middle_attack). If you receive this error the connection was canceled because the server did not reply with the expected signature. | ||
When using SASL authentication the server responds with a signature at the end of the authentication flow which needs to match the one on the client. This is to avoid [man in the middle attacks](https://en.wikipedia.org/wiki/Man-in-the-middle_attack). If you receive this error the connection was cancelled because the server did not reply with the expected signature. | ||
@@ -417,0 +469,0 @@ ##### NOT_TAGGED_CALL |
51937
1292
490