@steadybrew/postgresjs 4.0.0-rc1 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cjs/src/errors.js CHANGED
@@ -13,10 +13,10 @@ const Errors = module.exports.Errors = {
13
13
  notSupported
14
14
  }
15
15
 
16
- function connection(x, options, socket) {
16
+ function connection(x, options, socket, cause) {
17
17
  const { host, port } = socket || options
18
18
  const error = Object.assign(
19
- new Error(('write ' + x + ' ' + (options.path || (host + ':' + port)))),
19
+ new Error(('write ' + x + ' ' + (options.path || (host + ':' + port))), cause && { cause }),
20
20
  {
21
21
  code: x,
22
22
  errno: x,
package/cjs/src/index.js CHANGED
@@ -20,12 +20,16 @@ const {
20
20
  } = require('./types.js')
21
21
 
22
22
  const Connection = require('./connection.js')
23
+ const { cancelRequest } = require('./transport.js')
24
+ const clampedTimeout = require('./timeout.js')
23
25
  const { Query, CLOSE } = require('./query.js')
24
26
  const Queue = require('./queue.js')
25
27
  const { Errors, PostgresError } = require('./errors.js')
26
28
  const Subscribe = require('./subscribe.js')
27
29
  const largeObject = require('./large.js')
28
30
 
31
+ const Lease = { Active: 0, Settling: 1, Released: 2, Discarded: 3, Closed: 4 }
32
+
29
33
  Object.assign(Postgres, {
30
34
  PostgresError,
31
35
  toPascal,
@@ -52,6 +56,7 @@ function Postgres(a, b) {
52
56
  , subscribe = options.no_subscribe || Subscribe(Postgres, { ...options })
53
57
 
54
58
  let ending = false
59
+ let reconnecting = null
55
60
 
56
61
  const queries = Queue()
57
62
  , connecting = Queue()
@@ -205,9 +210,10 @@ function Postgres(a, b) {
205
210
  if (ending)
206
211
  throw Errors.connection('CONNECTION_ENDED', options)
207
212
 
208
- const l = { connection: null, closed: null, onclose: null, reserved: null, queue: Queue() }
209
- open.length
210
- ? own(open.shift(), l)
213
+ const l = { state: null, connection: null, error: null, onclose: null, queue: Queue() }
214
+ const idle = takeOpen()
215
+ idle
216
+ ? own(idle, l)
211
217
  : auto && !closed.length && busy.length
212
218
  ? own(busy.shift(), l)
213
219
  : await new Promise((resolve, reject) => {
@@ -221,50 +227,53 @@ function Postgres(a, b) {
221
227
 
222
228
  function own(c, l) {
223
229
  l.connection = c
224
- c.lease = l
225
- move(c, reserved)
226
- c.reserved = l.reserved = () => l.queue.length
227
- ? c.execute(l.queue.shift())
228
- : move(c, reserved)
229
- c.reserved.release = true
230
- return c
231
- }
232
-
233
- function valid(l) {
234
- return !l.closed && l.connection.lease === l && l.connection.reserved === l.reserved
230
+ l.state = Lease.Active
231
+ c.owner = l
232
+ l.next = () => {
233
+ while (l.queue.length) {
234
+ if (!c.execute(l.queue.shift()))
235
+ return
236
+ }
237
+ move(c, reserved)
238
+ }
239
+ return move(c, reserved)
235
240
  }
236
241
 
237
- function send(l, q) {
242
+ function send(l, q, expected = Lease.Active) {
238
243
  const c = l.connection
239
- valid(l)
244
+ l.state === expected
240
245
  ? c.queue === full
241
246
  ? l.queue.push(q)
242
247
  : c.execute(q) || move(c, full)
243
- : q.reject(gone(l))
248
+ : q.reject(leaseError(l.state))
244
249
  }
245
250
 
246
- function gone(l) {
247
- return Errors.connection(l.closed && l.closed.code === 'CONNECTION_ENDED' ? 'CONNECTION_ENDED' : 'CONNECTION_CLOSED', options)
251
+ function leaseError(state) {
252
+ return Errors.connection(state === Lease.Closed ? 'CONNECTION_CLOSED' : 'CONNECTION_ENDED', options)
248
253
  }
249
254
 
250
- function finish(l) {
251
- const c = l.connection
252
- if (l.closed || c.lease !== l)
253
- return false
255
+ function settle(l, outcome, err) {
256
+ if (l.state === Lease.Released || l.state === Lease.Discarded || l.state === Lease.Closed)
257
+ return
254
258
 
255
- const held = c.reserved === l.reserved
256
- c.lease = null
257
- l.closed = Errors.connection('CONNECTION_ENDED', options)
258
- held && (c.reserved = null)
259
+ const c = l.connection
260
+ l.state = outcome
261
+ c.owner = null
259
262
  while (l.queue.length)
260
- l.queue.shift().reject(gone(l))
261
- return held
263
+ l.queue.shift().reject(leaseError(outcome))
264
+
265
+ if (outcome === Lease.Closed) {
266
+ l.error = err || Errors.connection('CONNECTION_CLOSED', options)
267
+ return l.onclose && l.onclose(l.error)
268
+ }
269
+
270
+ outcome === Lease.Released ? c.release() : c.terminate()
262
271
  }
263
272
 
264
273
  async function reserve() {
265
274
  const l = await lease(false)
266
275
  const sql = Sql(q => send(l, q))
267
- sql.release = () => finish(l) && l.connection.release()
276
+ sql.release = () => settle(l, Lease.Released)
268
277
  return sql
269
278
  }
270
279
 
@@ -273,23 +282,33 @@ function Postgres(a, b) {
273
282
  const l = await lease(true)
274
283
  let savepoints = 0
275
284
  , prepare = null
276
- , settled = false
277
285
 
278
286
  try {
279
287
  const closing = new Promise((_, reject) => l.onclose = reject)
280
288
  return await Promise.race([transaction(), closing])
281
289
  } finally {
282
- finish(l) && (settled ? l.connection.release() : l.connection.terminate())
290
+ settle(l, Lease.Discarded)
283
291
  }
284
292
 
285
293
  async function transaction() {
286
294
  await Sql(q => send(l, q)).unsafe('begin ' + options.replace(/[^a-z ]/ig, ''), []).execute().catch(e => {
287
- e instanceof PostgresError && (settled = true)
295
+ e instanceof PostgresError && settle(l, Lease.Released)
288
296
  throw e
289
297
  })
290
298
  return scope(fn)
291
299
  }
292
300
 
301
+ async function finish(statement) {
302
+ l.state = Lease.Settling
303
+ try {
304
+ await statement(Sql(q => send(l, q, Lease.Settling)))
305
+ } catch (e) {
306
+ settle(l, Lease.Discarded)
307
+ throw e
308
+ }
309
+ settle(l, Lease.Released)
310
+ }
311
+
293
312
  async function scope(fn, name) {
294
313
  const sql = Sql(handler)
295
314
  sql.savepoint = savepoint
@@ -312,23 +331,20 @@ function Postgres(a, b) {
312
331
  if (uncaughtError)
313
332
  throw uncaughtError
314
333
  } catch (e) {
315
- l.closed || await (name
334
+ l.state === Lease.Active && await (name
316
335
  ? sql`rollback to ${ sql(name) }`
317
- : sql`rollback`
336
+ : finish(final => final`rollback`)
318
337
  )
319
- name || (settled = true)
320
338
  throw e instanceof PostgresError && e.code === '25P02' && uncaughtError || e
321
339
  }
322
340
 
323
- if (l.closed)
324
- throw l.closed
341
+ if (l.state !== Lease.Active)
342
+ throw l.error || leaseError(l.state)
325
343
 
326
- if (!name) {
327
- prepare
328
- ? await sql`prepare transaction ${ sql.unsafe(prepare) }`
329
- : await sql`commit`
330
- settled = true
331
- }
344
+ name || await finish(final => prepare
345
+ ? final`prepare transaction ${ final.unsafe(prepare) }`
346
+ : final`commit`
347
+ )
332
348
 
333
349
  return result
334
350
 
@@ -372,8 +388,9 @@ function Postgres(a, b) {
372
388
  if (ending)
373
389
  return query.reject(Errors.connection('CONNECTION_ENDED', options, options))
374
390
 
375
- if (open.length)
376
- return go(open.shift(), query)
391
+ const idle = takeOpen()
392
+ if (idle)
393
+ return go(idle, query)
377
394
 
378
395
  if (closed.length)
379
396
  return connect(closed.shift(), query)
@@ -383,6 +400,18 @@ function Postgres(a, b) {
383
400
  : queries.push(query)
384
401
  }
385
402
 
403
+ function takeOpen() {
404
+ while (open.length) {
405
+ const c = open.shift()
406
+ if (!c.expired())
407
+ return c
408
+
409
+ c.terminate()
410
+ }
411
+
412
+ return null
413
+ }
414
+
386
415
  function go(c, query) {
387
416
  return c.execute(query)
388
417
  ? move(c, busy)
@@ -390,17 +419,18 @@ function Postgres(a, b) {
390
419
  }
391
420
 
392
421
  function cancel(query) {
422
+ if (query.state && query.active)
423
+ return cancelRequest(options, query.state)
424
+
393
425
  return new Promise((resolve, reject) => {
394
- query.state
395
- ? query.active
396
- ? Connection(options).cancel(query.state, resolve, reject)
397
- : query.cancelled = { resolve, reject }
398
- : (
399
- queries.remove(query),
400
- query.cancelled = true,
401
- query.reject(Errors.generic('57014', 'canceling statement due to user request')),
402
- resolve()
403
- )
426
+ if (query.state) {
427
+ query.cancelled = { resolve, reject }
428
+ } else {
429
+ queries.remove(query)
430
+ query.cancelled = true
431
+ query.reject(Errors.generic('57014', 'canceling statement due to user request'))
432
+ resolve()
433
+ }
404
434
  })
405
435
  }
406
436
 
@@ -411,11 +441,8 @@ function Postgres(a, b) {
411
441
  await 1
412
442
  let timer
413
443
  return ending = Promise.resolve().then(() => Promise.race([
414
- new Promise(r => timeout !== null && (timer = setTimeout(() => destroy(r), timeout * 1000))),
415
- Promise.all(connections.map(c => c.end()).concat(
416
- listen.sql ? listen.sql.end({ timeout: 0 }) : [],
417
- subscribe.sql ? subscribe.sql.end({ timeout: 0 }) : []
418
- ))
444
+ new Promise(r => timeout !== null && (timer = clampedTimeout(() => destroy(r), timeout * 1000))),
445
+ endConnections()
419
446
  ])).then(() => {
420
447
  clearTimeout(timer)
421
448
  while (queries.length)
@@ -423,6 +450,15 @@ function Postgres(a, b) {
423
450
  })
424
451
  }
425
452
 
453
+ async function endConnections() {
454
+ await Promise.all(connections.map(c => c.end()).concat(
455
+ listen.sql ? listen.sql.end({ timeout: 0 }) : [],
456
+ subscribe.sql ? subscribe.sql.end({ timeout: 0 }) : []
457
+ ))
458
+ if (reconnecting)
459
+ await reconnecting.end()
460
+ }
461
+
426
462
  async function close() {
427
463
  await Promise.all(connections.map(c => c.end()))
428
464
  }
@@ -446,18 +482,18 @@ function Postgres(a, b) {
446
482
 
447
483
  function onopen(c, initial) {
448
484
  if (initial && initial.reserve)
449
- return initial.reserve(move(c, reserved))
485
+ return initial.reserve(c)
450
486
 
451
- if (queries.length === 0)
487
+ if (!initial && !queries.length)
452
488
  return move(c, open)
453
489
 
454
490
  let max = Math.ceil(queries.length / (connecting.length + 1))
455
- , ready = true
491
+ , ready = !initial || c.execute(initial)
456
492
 
457
493
  while (ready && queries.length && max-- > 0) {
458
494
  const query = queries.shift()
459
495
  if (query.reserve)
460
- return query.reserve(move(c, reserved))
496
+ return query.reserve(c)
461
497
 
462
498
  ready = c.execute(query)
463
499
  }
@@ -479,27 +515,38 @@ function Postgres(a, b) {
479
515
  ? query.reject(Errors.connection('CONNECTION_ENDED', options))
480
516
  : (drained = true, ready = c.execute(query))
481
517
  }
518
+ drained && move(c, ended)
482
519
  return drained
483
520
  }
484
521
 
485
- function abandon(l, e) {
486
- l.connection.lease = null
487
- l.closed = e || Errors.connection('CONNECTION_CLOSED', options)
488
- while (l.queue.length)
489
- l.queue.shift().reject(gone(l))
490
- l.onclose && l.onclose(e)
491
- }
492
-
493
522
  function onclose(c, e) {
494
523
  move(c, closed)
495
- c.reserved = null
496
- c.lease && abandon(c.lease, e)
524
+ c.owner && settle(c.owner, Lease.Closed, e)
497
525
  options.onclose && options.onclose(c.id)
498
- if (ending) {
499
- while (queries.length)
500
- queries.shift().reject(e)
501
- } else if (queries.length && c.queue === closed) {
526
+ if (ending)
527
+ return closedWhileEnding(c, e)
528
+
529
+ if (queries.length && c.queue === closed)
502
530
  connect(c, queries.shift())
531
+ }
532
+
533
+ function closedWhileEnding(c, e) {
534
+ const reconnectFailed = c === reconnecting
535
+ if (connections.some(x => x !== c && x.queue !== closed))
536
+ return
537
+
538
+ while (queries.length) {
539
+ const query = queries.shift()
540
+ if (query.reserve) {
541
+ query.reject(Errors.connection('CONNECTION_ENDED', options))
542
+ } else if (reconnectFailed) {
543
+ query.reject(e)
544
+ } else {
545
+ reconnecting = c
546
+ connect(c, query)
547
+ c.end()
548
+ return
549
+ }
503
550
  }
504
551
  }
505
552
  }
@@ -572,7 +619,7 @@ function parseOptions(a, b) {
572
619
  socket : o.socket,
573
620
  transform : parseTransform(o.transform || { undefined: undefined }),
574
621
  parameters : {},
575
- shared : { retries: 0, typeArrayMap: {} },
622
+ shared : { retries: 0, typeArrayMap: new Map() },
576
623
  ...mergeUserTypes(o.types)
577
624
  }
578
625
 
package/cjs/src/query.js CHANGED
@@ -23,10 +23,19 @@ const Query = module.exports.Query = class Query extends Promise {
23
23
  this.state = null
24
24
  this.statement = null
25
25
 
26
- this.resolve = x => (this.active = false, resolve(x))
27
- this.reject = x => (this.active = false, reject(x))
26
+ this.resolve = x => {
27
+ this.active = false
28
+ this.settled = true
29
+ resolve(x)
30
+ }
31
+ this.reject = x => {
32
+ this.active = false
33
+ this.settled = true
34
+ reject(x)
35
+ }
28
36
 
29
37
  this.active = false
38
+ this.settled = false
30
39
  this.cancelled = null
31
40
  this.executed = false
32
41
  this.signature = ''
@@ -50,7 +59,13 @@ const Query = module.exports.Query = class Query extends Promise {
50
59
  }
51
60
 
52
61
  cancel() {
53
- return this.canceller && (this.canceller(this), this.canceller = null)
62
+ if (!this.canceller || this.settled)
63
+ return Promise.resolve()
64
+
65
+ const cancelled = this.canceller(this)
66
+ this.canceller = null
67
+ cancelled.catch(() => undefined)
68
+ return cancelled
54
69
  }
55
70
 
56
71
  simple() {
@@ -0,0 +1,5 @@
1
+ const maxDelay = 2 ** 31 - 1
2
+
3
+ module.exports = clampedTimeout;function clampedTimeout(fn, ms) {
4
+ return setTimeout(fn, Math.min(ms, maxDelay))
5
+ }
@@ -0,0 +1,88 @@
1
+ const net = require('net')
2
+ const tls = require('tls')
3
+ const b = require('./bytes.js')
4
+ const clampedTimeout = require('./timeout.js')
5
+
6
+ const SSLRequest = module.exports.SSLRequest = b().i32(8).i32(80877103).end(8)
7
+
8
+ module.exports.tlsConfig = tlsConfig;function tlsConfig(options, raw) {
9
+ const { ssl, sslnegotiation } = options
10
+ const config = {
11
+ socket: raw,
12
+ servername: net.isIP(raw.host) ? undefined : raw.host
13
+ }
14
+
15
+ if (sslnegotiation === 'direct')
16
+ config.ALPNProtocols = ['postgresql']
17
+
18
+ if (ssl === 'require' || ssl === 'allow' || ssl === 'prefer')
19
+ config.rejectUnauthorized = false
20
+ else if (typeof ssl === 'object')
21
+ Object.assign(config, ssl)
22
+
23
+ return config
24
+ }
25
+
26
+ module.exports.cancelRequest = cancelRequest;function cancelRequest(options, backend) {
27
+ return Promise.resolve()
28
+ .then(() => options.socket ? options.socket(options) : new net.Socket())
29
+ .then(socket => sendCancel(options, backend, socket))
30
+ }
31
+
32
+ function sendCancel(options, { pid, secret }, s) {
33
+ const { ssl, sslnegotiation, host, port } = options
34
+ const request = b().i32(16).i32(80877102).i32(pid).i32(secret).end(16)
35
+
36
+ return new Promise((resolve, reject) => {
37
+ let timeout = null
38
+ const watch = x => {
39
+ x.once('error', reject)
40
+ x.on('error', () => undefined)
41
+ x.once('close', () => (clearTimeout(timeout), resolve()))
42
+ }
43
+ const upgrade = () => {
44
+ try {
45
+ const raw = s
46
+ const config = tlsConfig(options, raw)
47
+ raw.removeAllListeners()
48
+ s = tls.connect(config)
49
+ watch(s)
50
+ s.once('secureConnect', () => s.write(request))
51
+ } catch (error) {
52
+ clearTimeout(timeout)
53
+ s.destroy()
54
+ reject(error)
55
+ }
56
+ }
57
+ const start = !ssl
58
+ ? () => s.write(request)
59
+ : sslnegotiation === 'direct'
60
+ ? upgrade
61
+ : () => {
62
+ s.once('data', x => x[0] === 83 || ssl !== 'prefer' ? upgrade() : s.write(request))
63
+ s.write(SSLRequest)
64
+ }
65
+
66
+ try {
67
+ watch(s)
68
+ options.connect_timeout && (timeout = clampedTimeout(() => s.destroy(), options.connect_timeout * 1000))
69
+
70
+ if (options.socket)
71
+ return start()
72
+
73
+ s.once('connect', start)
74
+
75
+ if (options.path)
76
+ return s.connect(options.path)
77
+
78
+ s.ssl = ssl
79
+ s.connect(port[0], host[0])
80
+ s.host = host[0]
81
+ s.port = port[0]
82
+ } catch (error) {
83
+ clearTimeout(timeout)
84
+ reject(error)
85
+ }
86
+ })
87
+ }
88
+
package/cjs/src/types.js CHANGED
@@ -86,7 +86,7 @@ module.exports.handleValue = handleValue;function handleValue(x, parameters, typ
86
86
  return '$' + (types.push(
87
87
  x instanceof Parameter
88
88
  ? (parameters.push(x.value), x.array
89
- ? x.array[x.type || inferType(x.value)] || x.type || firstIsString(x.value)
89
+ ? x.array.get(x.type || inferType(x.value)) || x.type || firstIsString(x.value)
90
90
  : x.type
91
91
  )
92
92
  : (parameters.push(x), inferType(x))
@@ -199,7 +199,7 @@ module.exports.addDefaultArrayTypes = addDefaultArrayTypes;function addDefaultAr
199
199
  }
200
200
 
201
201
  module.exports.addArrayType = addArrayType;function addArrayType(options, oid, typarray) {
202
- options.shared.typeArrayMap[oid] = typarray
202
+ options.shared.typeArrayMap.set(oid, typarray)
203
203
  if (!options.parsers[typarray]) {
204
204
  const parser = options.parsers[oid]
205
205
  options.parsers[typarray] = xs => arrayParser(xs, parser, typarray)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@steadybrew/postgresjs",
3
- "version": "4.0.0-rc1",
3
+ "version": "4.1.0",
4
4
  "description": "PostgreSQL client for Node.js and Deno, independently maintained by Steady Brew",
5
5
  "type": "module",
6
6
  "module": "src/index.js",