@steadybrew/postgresjs 4.0.0 → 4.2.0-rc1

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/cf/src/errors.js CHANGED
@@ -13,10 +13,10 @@ export const 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/cf/src/index.js CHANGED
@@ -21,12 +21,16 @@ import {
21
21
  } from './types.js'
22
22
 
23
23
  import Connection from './connection.js'
24
+ import { cancelRequest } from './transport.js'
25
+ import clampedTimeout from './timeout.js'
24
26
  import { Query, CLOSE } from './query.js'
25
27
  import Queue from './queue.js'
26
28
  import { Errors, PostgresError } from './errors.js'
27
29
  import Subscribe from './subscribe.js'
28
30
  import largeObject from './large.js'
29
31
 
32
+ const Lease = { Active: 0, Settling: 1, Released: 2, Discarded: 3, Closed: 4 }
33
+
30
34
  Object.assign(Postgres, {
31
35
  PostgresError,
32
36
  toPascal,
@@ -53,6 +57,7 @@ function Postgres(a, b) {
53
57
  , subscribe = options.no_subscribe || Subscribe(Postgres, { ...options })
54
58
 
55
59
  let ending = false
60
+ let reconnecting = null
56
61
 
57
62
  const queries = Queue()
58
63
  , connecting = Queue()
@@ -155,6 +160,7 @@ function Postgres(a, b) {
155
160
  idle_timeout: null,
156
161
  max_lifetime: null,
157
162
  fetch_types: false,
163
+ onidle: null,
158
164
  onclose() {
159
165
  Object.entries(listen.channels).forEach(([name, { listeners }]) => {
160
166
  delete listen.channels[name]
@@ -206,9 +212,10 @@ function Postgres(a, b) {
206
212
  if (ending)
207
213
  throw Errors.connection('CONNECTION_ENDED', options)
208
214
 
209
- const l = { connection: null, closed: null, onclose: null, reserved: null, queue: Queue() }
210
- open.length
211
- ? own(open.shift(), l)
215
+ const l = { state: null, connection: null, error: null, onclose: null, queue: Queue() }
216
+ const idle = takeOpen()
217
+ idle
218
+ ? own(idle, l)
212
219
  : auto && !closed.length && busy.length
213
220
  ? own(busy.shift(), l)
214
221
  : await new Promise((resolve, reject) => {
@@ -222,50 +229,53 @@ function Postgres(a, b) {
222
229
 
223
230
  function own(c, l) {
224
231
  l.connection = c
225
- c.lease = l
226
- move(c, reserved)
227
- c.reserved = l.reserved = () => l.queue.length
228
- ? c.execute(l.queue.shift())
229
- : move(c, reserved)
230
- c.reserved.release = true
231
- return c
232
- }
233
-
234
- function valid(l) {
235
- return !l.closed && l.connection.lease === l && l.connection.reserved === l.reserved
232
+ l.state = Lease.Active
233
+ c.owner = l
234
+ l.next = () => {
235
+ while (l.queue.length) {
236
+ if (!c.execute(l.queue.shift()))
237
+ return
238
+ }
239
+ move(c, reserved)
240
+ }
241
+ return move(c, reserved)
236
242
  }
237
243
 
238
- function send(l, q) {
244
+ function send(l, q, expected = Lease.Active) {
239
245
  const c = l.connection
240
- valid(l)
246
+ l.state === expected
241
247
  ? c.queue === full
242
248
  ? l.queue.push(q)
243
249
  : c.execute(q) || move(c, full)
244
- : q.reject(gone(l))
250
+ : q.reject(leaseError(l.state))
245
251
  }
246
252
 
247
- function gone(l) {
248
- return Errors.connection(l.closed && l.closed.code === 'CONNECTION_ENDED' ? 'CONNECTION_ENDED' : 'CONNECTION_CLOSED', options)
253
+ function leaseError(state) {
254
+ return Errors.connection(state === Lease.Closed ? 'CONNECTION_CLOSED' : 'CONNECTION_ENDED', options)
249
255
  }
250
256
 
251
- function finish(l) {
252
- const c = l.connection
253
- if (l.closed || c.lease !== l)
254
- return false
257
+ function settle(l, outcome, err) {
258
+ if (l.state === Lease.Released || l.state === Lease.Discarded || l.state === Lease.Closed)
259
+ return
255
260
 
256
- const held = c.reserved === l.reserved
257
- c.lease = null
258
- l.closed = Errors.connection('CONNECTION_ENDED', options)
259
- held && (c.reserved = null)
261
+ const c = l.connection
262
+ l.state = outcome
263
+ c.owner = null
260
264
  while (l.queue.length)
261
- l.queue.shift().reject(gone(l))
262
- return held
265
+ l.queue.shift().reject(leaseError(outcome))
266
+
267
+ if (outcome === Lease.Closed) {
268
+ l.error = err || Errors.connection('CONNECTION_CLOSED', options)
269
+ return l.onclose && l.onclose(l.error)
270
+ }
271
+
272
+ outcome === Lease.Released ? c.release() : c.terminate()
263
273
  }
264
274
 
265
275
  async function reserve() {
266
276
  const l = await lease(false)
267
277
  const sql = Sql(q => send(l, q))
268
- sql.release = () => finish(l) && l.connection.release()
278
+ sql.release = () => settle(l, Lease.Released)
269
279
  return sql
270
280
  }
271
281
 
@@ -274,23 +284,33 @@ function Postgres(a, b) {
274
284
  const l = await lease(true)
275
285
  let savepoints = 0
276
286
  , prepare = null
277
- , settled = false
278
287
 
279
288
  try {
280
289
  const closing = new Promise((_, reject) => l.onclose = reject)
281
290
  return await Promise.race([transaction(), closing])
282
291
  } finally {
283
- finish(l) && (settled ? l.connection.release() : l.connection.terminate())
292
+ settle(l, Lease.Discarded)
284
293
  }
285
294
 
286
295
  async function transaction() {
287
296
  await Sql(q => send(l, q)).unsafe('begin ' + options.replace(/[^a-z ]/ig, ''), []).execute().catch(e => {
288
- e instanceof PostgresError && (settled = true)
297
+ e instanceof PostgresError && settle(l, Lease.Released)
289
298
  throw e
290
299
  })
291
300
  return scope(fn)
292
301
  }
293
302
 
303
+ async function finish(statement) {
304
+ l.state = Lease.Settling
305
+ try {
306
+ await statement(Sql(q => send(l, q, Lease.Settling)))
307
+ } catch (e) {
308
+ settle(l, Lease.Discarded)
309
+ throw e
310
+ }
311
+ settle(l, Lease.Released)
312
+ }
313
+
294
314
  async function scope(fn, name) {
295
315
  const sql = Sql(handler)
296
316
  sql.savepoint = savepoint
@@ -313,23 +333,20 @@ function Postgres(a, b) {
313
333
  if (uncaughtError)
314
334
  throw uncaughtError
315
335
  } catch (e) {
316
- l.closed || await (name
336
+ l.state === Lease.Active && await (name
317
337
  ? sql`rollback to ${ sql(name) }`
318
- : sql`rollback`
338
+ : finish(final => final`rollback`)
319
339
  )
320
- name || (settled = true)
321
340
  throw e instanceof PostgresError && e.code === '25P02' && uncaughtError || e
322
341
  }
323
342
 
324
- if (l.closed)
325
- throw l.closed
343
+ if (l.state !== Lease.Active)
344
+ throw l.error || leaseError(l.state)
326
345
 
327
- if (!name) {
328
- prepare
329
- ? await sql`prepare transaction ${ sql.unsafe(prepare) }`
330
- : await sql`commit`
331
- settled = true
332
- }
346
+ name || await finish(final => prepare
347
+ ? final`prepare transaction ${ final.unsafe(prepare) }`
348
+ : final`commit`
349
+ )
333
350
 
334
351
  return result
335
352
 
@@ -353,11 +370,19 @@ function Postgres(a, b) {
353
370
  queue.push(c)
354
371
  c.queue = queue
355
372
  queue === open
356
- ? c.idleTimer.start()
373
+ ? (c.idleTimer.start(), callback(options.onidle, c.id))
357
374
  : c.idleTimer.cancel()
358
375
  return c
359
376
  }
360
377
 
378
+ function callback(fn, ...args) {
379
+ try {
380
+ fn && fn(...args)
381
+ } catch (e) {
382
+ queueMicrotask(() => { throw e })
383
+ }
384
+ }
385
+
361
386
  function json(x) {
362
387
  return new Parameter(x, 3802)
363
388
  }
@@ -373,8 +398,9 @@ function Postgres(a, b) {
373
398
  if (ending)
374
399
  return query.reject(Errors.connection('CONNECTION_ENDED', options, options))
375
400
 
376
- if (open.length)
377
- return go(open.shift(), query)
401
+ const idle = takeOpen()
402
+ if (idle)
403
+ return go(idle, query)
378
404
 
379
405
  if (closed.length)
380
406
  return connect(closed.shift(), query)
@@ -384,6 +410,18 @@ function Postgres(a, b) {
384
410
  : queries.push(query)
385
411
  }
386
412
 
413
+ function takeOpen() {
414
+ while (open.length) {
415
+ const c = open.shift()
416
+ if (!c.expired())
417
+ return c
418
+
419
+ c.terminate()
420
+ }
421
+
422
+ return null
423
+ }
424
+
387
425
  function go(c, query) {
388
426
  return c.execute(query)
389
427
  ? move(c, busy)
@@ -391,17 +429,18 @@ function Postgres(a, b) {
391
429
  }
392
430
 
393
431
  function cancel(query) {
432
+ if (query.state && query.active)
433
+ return cancelRequest(options, query.state)
434
+
394
435
  return new Promise((resolve, reject) => {
395
- query.state
396
- ? query.active
397
- ? Connection(options).cancel(query.state, resolve, reject)
398
- : query.cancelled = { resolve, reject }
399
- : (
400
- queries.remove(query),
401
- query.cancelled = true,
402
- query.reject(Errors.generic('57014', 'canceling statement due to user request')),
403
- resolve()
404
- )
436
+ if (query.state) {
437
+ query.cancelled = { resolve, reject }
438
+ } else {
439
+ queries.remove(query)
440
+ query.cancelled = true
441
+ query.reject(Errors.generic('57014', 'canceling statement due to user request'))
442
+ resolve()
443
+ }
405
444
  })
406
445
  }
407
446
 
@@ -412,11 +451,8 @@ function Postgres(a, b) {
412
451
  await 1
413
452
  let timer
414
453
  return ending = Promise.resolve().then(() => Promise.race([
415
- new Promise(r => timeout !== null && (timer = setTimeout(() => destroy(r), timeout * 1000))),
416
- Promise.all(connections.map(c => c.end()).concat(
417
- listen.sql ? listen.sql.end({ timeout: 0 }) : [],
418
- subscribe.sql ? subscribe.sql.end({ timeout: 0 }) : []
419
- ))
454
+ new Promise(r => timeout !== null && (timer = clampedTimeout(() => destroy(r), timeout * 1000))),
455
+ endConnections()
420
456
  ])).then(() => {
421
457
  clearTimeout(timer)
422
458
  while (queries.length)
@@ -424,6 +460,15 @@ function Postgres(a, b) {
424
460
  })
425
461
  }
426
462
 
463
+ async function endConnections() {
464
+ await Promise.all(connections.map(c => c.end()).concat(
465
+ listen.sql ? listen.sql.end({ timeout: 0 }) : [],
466
+ subscribe.sql ? subscribe.sql.end({ timeout: 0 }) : []
467
+ ))
468
+ if (reconnecting)
469
+ await reconnecting.end()
470
+ }
471
+
427
472
  async function close() {
428
473
  await Promise.all(connections.map(c => c.end()))
429
474
  }
@@ -447,18 +492,18 @@ function Postgres(a, b) {
447
492
 
448
493
  function onopen(c, initial) {
449
494
  if (initial && initial.reserve)
450
- return initial.reserve(move(c, reserved))
495
+ return initial.reserve(c)
451
496
 
452
- if (queries.length === 0)
497
+ if (!initial && !queries.length)
453
498
  return move(c, open)
454
499
 
455
500
  let max = Math.ceil(queries.length / (connecting.length + 1))
456
- , ready = true
501
+ , ready = !initial || c.execute(initial)
457
502
 
458
503
  while (ready && queries.length && max-- > 0) {
459
504
  const query = queries.shift()
460
505
  if (query.reserve)
461
- return query.reserve(move(c, reserved))
506
+ return query.reserve(c)
462
507
 
463
508
  ready = c.execute(query)
464
509
  }
@@ -480,27 +525,38 @@ function Postgres(a, b) {
480
525
  ? query.reject(Errors.connection('CONNECTION_ENDED', options))
481
526
  : (drained = true, ready = c.execute(query))
482
527
  }
528
+ drained && move(c, ended)
483
529
  return drained
484
530
  }
485
531
 
486
- function abandon(l, e) {
487
- l.connection.lease = null
488
- l.closed = e || Errors.connection('CONNECTION_CLOSED', options)
489
- while (l.queue.length)
490
- l.queue.shift().reject(gone(l))
491
- l.onclose && l.onclose(e)
492
- }
493
-
494
532
  function onclose(c, e) {
495
533
  move(c, closed)
496
- c.reserved = null
497
- c.lease && abandon(c.lease, e)
534
+ c.owner && settle(c.owner, Lease.Closed, e)
498
535
  options.onclose && options.onclose(c.id)
499
- if (ending) {
500
- while (queries.length)
501
- queries.shift().reject(e)
502
- } else if (queries.length && c.queue === closed) {
536
+ if (ending)
537
+ return closedWhileEnding(c, e)
538
+
539
+ if (queries.length && c.queue === closed)
503
540
  connect(c, queries.shift())
541
+ }
542
+
543
+ function closedWhileEnding(c, e) {
544
+ const reconnectFailed = c === reconnecting
545
+ if (connections.some(x => x !== c && x.queue !== closed))
546
+ return
547
+
548
+ while (queries.length) {
549
+ const query = queries.shift()
550
+ if (query.reserve) {
551
+ query.reject(Errors.connection('CONNECTION_ENDED', options))
552
+ } else if (reconnectFailed) {
553
+ query.reject(e)
554
+ } else {
555
+ reconnecting = c
556
+ connect(c, query)
557
+ c.end()
558
+ return
559
+ }
504
560
  }
505
561
  }
506
562
  }
@@ -569,11 +625,12 @@ function parseOptions(a, b) {
569
625
  onnotice : o.onnotice,
570
626
  onnotify : o.onnotify,
571
627
  onclose : o.onclose,
628
+ onidle : o.onidle,
572
629
  onparameter : o.onparameter,
573
630
  socket : o.socket,
574
631
  transform : parseTransform(o.transform || { undefined: undefined }),
575
632
  parameters : {},
576
- shared : { retries: 0, typeArrayMap: {} },
633
+ shared : { retries: 0, typeArrayMap: new Map() },
577
634
  ...mergeUserTypes(o.types)
578
635
  }
579
636
 
package/cf/src/query.js CHANGED
@@ -23,10 +23,19 @@ export 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 @@ export 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() {
@@ -17,6 +17,7 @@ export default function Subscribe(postgres, options) {
17
17
  fetch_types: false,
18
18
  idle_timeout: null,
19
19
  max_lifetime: null,
20
+ onidle: null,
20
21
  connection: {
21
22
  ...options.connection,
22
23
  replication: 'database'
@@ -0,0 +1,5 @@
1
+ const maxDelay = 2 ** 31 - 1
2
+
3
+ export default function clampedTimeout(fn, ms) {
4
+ return setTimeout(fn, Math.min(ms, maxDelay))
5
+ }
@@ -0,0 +1,88 @@
1
+ import { net } from '../polyfills.js'
2
+ import { tls } from '../polyfills.js'
3
+ import b from './bytes.js'
4
+ import clampedTimeout from './timeout.js'
5
+
6
+ export const SSLRequest = b().i32(8).i32(80877103).end(8)
7
+
8
+ export 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
+ export 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/cf/src/types.js CHANGED
@@ -87,7 +87,7 @@ export function handleValue(x, parameters, types, options) {
87
87
  return '$' + (types.push(
88
88
  x instanceof Parameter
89
89
  ? (parameters.push(x.value), x.array
90
- ? x.array[x.type || inferType(x.value)] || x.type || firstIsString(x.value)
90
+ ? x.array.get(x.type || inferType(x.value)) || x.type || firstIsString(x.value)
91
91
  : x.type
92
92
  )
93
93
  : (parameters.push(x), inferType(x))
@@ -200,7 +200,7 @@ export function addDefaultArrayTypes(options) {
200
200
  }
201
201
 
202
202
  export function addArrayType(options, oid, typarray) {
203
- options.shared.typeArrayMap[oid] = typarray
203
+ options.shared.typeArrayMap.set(oid, typarray)
204
204
  if (!options.parsers[typarray]) {
205
205
  const parser = options.parsers[oid]
206
206
  options.parsers[typarray] = xs => arrayParser(xs, parser, typarray)
@@ -0,0 +1,20 @@
1
+ export function vercelPool(sql) {
2
+ const idle_timeout = sql.options.idle_timeout
3
+ if (typeof idle_timeout !== 'number' || !(idle_timeout > 0))
4
+ throw new Error('vercelPool requires idle_timeout to be a positive number of seconds')
5
+
6
+ return {
7
+ options: {
8
+ idleTimeoutMillis: idle_timeout * 1000
9
+ },
10
+ on(event, fn) {
11
+ if (event !== 'release')
12
+ return
13
+
14
+ const previous = sql.options.onidle
15
+ sql.options.onidle = previous
16
+ ? id => (fn(), previous(id))
17
+ : () => fn()
18
+ }
19
+ }
20
+ }