@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/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/src/index.js CHANGED
@@ -20,12 +20,16 @@ import {
20
20
  } from './types.js'
21
21
 
22
22
  import Connection from './connection.js'
23
+ import { cancelRequest } from './transport.js'
24
+ import clampedTimeout from './timeout.js'
23
25
  import { Query, CLOSE } from './query.js'
24
26
  import Queue from './queue.js'
25
27
  import { Errors, PostgresError } from './errors.js'
26
28
  import Subscribe from './subscribe.js'
27
29
  import largeObject from './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()
@@ -154,6 +159,7 @@ function Postgres(a, b) {
154
159
  idle_timeout: null,
155
160
  max_lifetime: null,
156
161
  fetch_types: false,
162
+ onidle: null,
157
163
  onclose() {
158
164
  Object.entries(listen.channels).forEach(([name, { listeners }]) => {
159
165
  delete listen.channels[name]
@@ -205,9 +211,10 @@ function Postgres(a, b) {
205
211
  if (ending)
206
212
  throw Errors.connection('CONNECTION_ENDED', options)
207
213
 
208
- const l = { connection: null, closed: null, onclose: null, reserved: null, queue: Queue() }
209
- open.length
210
- ? own(open.shift(), l)
214
+ const l = { state: null, connection: null, error: null, onclose: null, queue: Queue() }
215
+ const idle = takeOpen()
216
+ idle
217
+ ? own(idle, l)
211
218
  : auto && !closed.length && busy.length
212
219
  ? own(busy.shift(), l)
213
220
  : await new Promise((resolve, reject) => {
@@ -221,50 +228,53 @@ function Postgres(a, b) {
221
228
 
222
229
  function own(c, l) {
223
230
  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
231
+ l.state = Lease.Active
232
+ c.owner = l
233
+ l.next = () => {
234
+ while (l.queue.length) {
235
+ if (!c.execute(l.queue.shift()))
236
+ return
237
+ }
238
+ move(c, reserved)
239
+ }
240
+ return move(c, reserved)
235
241
  }
236
242
 
237
- function send(l, q) {
243
+ function send(l, q, expected = Lease.Active) {
238
244
  const c = l.connection
239
- valid(l)
245
+ l.state === expected
240
246
  ? c.queue === full
241
247
  ? l.queue.push(q)
242
248
  : c.execute(q) || move(c, full)
243
- : q.reject(gone(l))
249
+ : q.reject(leaseError(l.state))
244
250
  }
245
251
 
246
- function gone(l) {
247
- return Errors.connection(l.closed && l.closed.code === 'CONNECTION_ENDED' ? 'CONNECTION_ENDED' : 'CONNECTION_CLOSED', options)
252
+ function leaseError(state) {
253
+ return Errors.connection(state === Lease.Closed ? 'CONNECTION_CLOSED' : 'CONNECTION_ENDED', options)
248
254
  }
249
255
 
250
- function finish(l) {
251
- const c = l.connection
252
- if (l.closed || c.lease !== l)
253
- return false
256
+ function settle(l, outcome, err) {
257
+ if (l.state === Lease.Released || l.state === Lease.Discarded || l.state === Lease.Closed)
258
+ return
254
259
 
255
- const held = c.reserved === l.reserved
256
- c.lease = null
257
- l.closed = Errors.connection('CONNECTION_ENDED', options)
258
- held && (c.reserved = null)
260
+ const c = l.connection
261
+ l.state = outcome
262
+ c.owner = null
259
263
  while (l.queue.length)
260
- l.queue.shift().reject(gone(l))
261
- return held
264
+ l.queue.shift().reject(leaseError(outcome))
265
+
266
+ if (outcome === Lease.Closed) {
267
+ l.error = err || Errors.connection('CONNECTION_CLOSED', options)
268
+ return l.onclose && l.onclose(l.error)
269
+ }
270
+
271
+ outcome === Lease.Released ? c.release() : c.terminate()
262
272
  }
263
273
 
264
274
  async function reserve() {
265
275
  const l = await lease(false)
266
276
  const sql = Sql(q => send(l, q))
267
- sql.release = () => finish(l) && l.connection.release()
277
+ sql.release = () => settle(l, Lease.Released)
268
278
  return sql
269
279
  }
270
280
 
@@ -273,23 +283,33 @@ function Postgres(a, b) {
273
283
  const l = await lease(true)
274
284
  let savepoints = 0
275
285
  , prepare = null
276
- , settled = false
277
286
 
278
287
  try {
279
288
  const closing = new Promise((_, reject) => l.onclose = reject)
280
289
  return await Promise.race([transaction(), closing])
281
290
  } finally {
282
- finish(l) && (settled ? l.connection.release() : l.connection.terminate())
291
+ settle(l, Lease.Discarded)
283
292
  }
284
293
 
285
294
  async function transaction() {
286
295
  await Sql(q => send(l, q)).unsafe('begin ' + options.replace(/[^a-z ]/ig, ''), []).execute().catch(e => {
287
- e instanceof PostgresError && (settled = true)
296
+ e instanceof PostgresError && settle(l, Lease.Released)
288
297
  throw e
289
298
  })
290
299
  return scope(fn)
291
300
  }
292
301
 
302
+ async function finish(statement) {
303
+ l.state = Lease.Settling
304
+ try {
305
+ await statement(Sql(q => send(l, q, Lease.Settling)))
306
+ } catch (e) {
307
+ settle(l, Lease.Discarded)
308
+ throw e
309
+ }
310
+ settle(l, Lease.Released)
311
+ }
312
+
293
313
  async function scope(fn, name) {
294
314
  const sql = Sql(handler)
295
315
  sql.savepoint = savepoint
@@ -312,23 +332,20 @@ function Postgres(a, b) {
312
332
  if (uncaughtError)
313
333
  throw uncaughtError
314
334
  } catch (e) {
315
- l.closed || await (name
335
+ l.state === Lease.Active && await (name
316
336
  ? sql`rollback to ${ sql(name) }`
317
- : sql`rollback`
337
+ : finish(final => final`rollback`)
318
338
  )
319
- name || (settled = true)
320
339
  throw e instanceof PostgresError && e.code === '25P02' && uncaughtError || e
321
340
  }
322
341
 
323
- if (l.closed)
324
- throw l.closed
342
+ if (l.state !== Lease.Active)
343
+ throw l.error || leaseError(l.state)
325
344
 
326
- if (!name) {
327
- prepare
328
- ? await sql`prepare transaction ${ sql.unsafe(prepare) }`
329
- : await sql`commit`
330
- settled = true
331
- }
345
+ name || await finish(final => prepare
346
+ ? final`prepare transaction ${ final.unsafe(prepare) }`
347
+ : final`commit`
348
+ )
332
349
 
333
350
  return result
334
351
 
@@ -352,11 +369,19 @@ function Postgres(a, b) {
352
369
  queue.push(c)
353
370
  c.queue = queue
354
371
  queue === open
355
- ? c.idleTimer.start()
372
+ ? (c.idleTimer.start(), callback(options.onidle, c.id))
356
373
  : c.idleTimer.cancel()
357
374
  return c
358
375
  }
359
376
 
377
+ function callback(fn, ...args) {
378
+ try {
379
+ fn && fn(...args)
380
+ } catch (e) {
381
+ queueMicrotask(() => { throw e })
382
+ }
383
+ }
384
+
360
385
  function json(x) {
361
386
  return new Parameter(x, 3802)
362
387
  }
@@ -372,8 +397,9 @@ function Postgres(a, b) {
372
397
  if (ending)
373
398
  return query.reject(Errors.connection('CONNECTION_ENDED', options, options))
374
399
 
375
- if (open.length)
376
- return go(open.shift(), query)
400
+ const idle = takeOpen()
401
+ if (idle)
402
+ return go(idle, query)
377
403
 
378
404
  if (closed.length)
379
405
  return connect(closed.shift(), query)
@@ -383,6 +409,18 @@ function Postgres(a, b) {
383
409
  : queries.push(query)
384
410
  }
385
411
 
412
+ function takeOpen() {
413
+ while (open.length) {
414
+ const c = open.shift()
415
+ if (!c.expired())
416
+ return c
417
+
418
+ c.terminate()
419
+ }
420
+
421
+ return null
422
+ }
423
+
386
424
  function go(c, query) {
387
425
  return c.execute(query)
388
426
  ? move(c, busy)
@@ -390,17 +428,18 @@ function Postgres(a, b) {
390
428
  }
391
429
 
392
430
  function cancel(query) {
431
+ if (query.state && query.active)
432
+ return cancelRequest(options, query.state)
433
+
393
434
  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
- )
435
+ if (query.state) {
436
+ query.cancelled = { resolve, reject }
437
+ } else {
438
+ queries.remove(query)
439
+ query.cancelled = true
440
+ query.reject(Errors.generic('57014', 'canceling statement due to user request'))
441
+ resolve()
442
+ }
404
443
  })
405
444
  }
406
445
 
@@ -411,11 +450,8 @@ function Postgres(a, b) {
411
450
  await 1
412
451
  let timer
413
452
  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
- ))
453
+ new Promise(r => timeout !== null && (timer = clampedTimeout(() => destroy(r), timeout * 1000))),
454
+ endConnections()
419
455
  ])).then(() => {
420
456
  clearTimeout(timer)
421
457
  while (queries.length)
@@ -423,6 +459,15 @@ function Postgres(a, b) {
423
459
  })
424
460
  }
425
461
 
462
+ async function endConnections() {
463
+ await Promise.all(connections.map(c => c.end()).concat(
464
+ listen.sql ? listen.sql.end({ timeout: 0 }) : [],
465
+ subscribe.sql ? subscribe.sql.end({ timeout: 0 }) : []
466
+ ))
467
+ if (reconnecting)
468
+ await reconnecting.end()
469
+ }
470
+
426
471
  async function close() {
427
472
  await Promise.all(connections.map(c => c.end()))
428
473
  }
@@ -446,18 +491,18 @@ function Postgres(a, b) {
446
491
 
447
492
  function onopen(c, initial) {
448
493
  if (initial && initial.reserve)
449
- return initial.reserve(move(c, reserved))
494
+ return initial.reserve(c)
450
495
 
451
- if (queries.length === 0)
496
+ if (!initial && !queries.length)
452
497
  return move(c, open)
453
498
 
454
499
  let max = Math.ceil(queries.length / (connecting.length + 1))
455
- , ready = true
500
+ , ready = !initial || c.execute(initial)
456
501
 
457
502
  while (ready && queries.length && max-- > 0) {
458
503
  const query = queries.shift()
459
504
  if (query.reserve)
460
- return query.reserve(move(c, reserved))
505
+ return query.reserve(c)
461
506
 
462
507
  ready = c.execute(query)
463
508
  }
@@ -479,27 +524,38 @@ function Postgres(a, b) {
479
524
  ? query.reject(Errors.connection('CONNECTION_ENDED', options))
480
525
  : (drained = true, ready = c.execute(query))
481
526
  }
527
+ drained && move(c, ended)
482
528
  return drained
483
529
  }
484
530
 
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
531
  function onclose(c, e) {
494
532
  move(c, closed)
495
- c.reserved = null
496
- c.lease && abandon(c.lease, e)
533
+ c.owner && settle(c.owner, Lease.Closed, e)
497
534
  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) {
535
+ if (ending)
536
+ return closedWhileEnding(c, e)
537
+
538
+ if (queries.length && c.queue === closed)
502
539
  connect(c, queries.shift())
540
+ }
541
+
542
+ function closedWhileEnding(c, e) {
543
+ const reconnectFailed = c === reconnecting
544
+ if (connections.some(x => x !== c && x.queue !== closed))
545
+ return
546
+
547
+ while (queries.length) {
548
+ const query = queries.shift()
549
+ if (query.reserve) {
550
+ query.reject(Errors.connection('CONNECTION_ENDED', options))
551
+ } else if (reconnectFailed) {
552
+ query.reject(e)
553
+ } else {
554
+ reconnecting = c
555
+ connect(c, query)
556
+ c.end()
557
+ return
558
+ }
503
559
  }
504
560
  }
505
561
  }
@@ -568,11 +624,12 @@ function parseOptions(a, b) {
568
624
  onnotice : o.onnotice,
569
625
  onnotify : o.onnotify,
570
626
  onclose : o.onclose,
627
+ onidle : o.onidle,
571
628
  onparameter : o.onparameter,
572
629
  socket : o.socket,
573
630
  transform : parseTransform(o.transform || { undefined: undefined }),
574
631
  parameters : {},
575
- shared : { retries: 0, typeArrayMap: {} },
632
+ shared : { retries: 0, typeArrayMap: new Map() },
576
633
  ...mergeUserTypes(o.types)
577
634
  }
578
635
 
package/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() {
package/src/subscribe.js CHANGED
@@ -16,6 +16,7 @@ export default function Subscribe(postgres, options) {
16
16
  fetch_types: false,
17
17
  idle_timeout: null,
18
18
  max_lifetime: null,
19
+ onidle: null,
19
20
  connection: {
20
21
  ...options.connection,
21
22
  replication: 'database'
package/src/timeout.js ADDED
@@ -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 'net'
2
+ import tls from 'tls'
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/src/types.js CHANGED
@@ -86,7 +86,7 @@ export function handleValue(x, parameters, types, options) {
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 @@ export function addDefaultArrayTypes(options) {
199
199
  }
200
200
 
201
201
  export 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/src/vercel.js ADDED
@@ -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
+ }
package/types/index.d.ts CHANGED
@@ -119,6 +119,8 @@ interface BaseOptions<T extends Record<string, postgres.PostgresType>> {
119
119
  */
120
120
  publications: string
121
121
  onclose: (connId: number) => void;
122
+ /** Called with the connection id each time a connection returns to the pool idle and its idle timer starts */
123
+ onidle: (connId: number) => void;
122
124
  backoff: boolean | ((attemptNum: number) => number);
123
125
  max_lifetime: number | null;
124
126
  keep_alive: number | null;
@@ -457,6 +459,10 @@ declare namespace postgres {
457
459
  | 'UNDEFINED_VALUE'
458
460
  | 'MAX_PARAMETERS_EXCEEDED'
459
461
  | 'SASL_SIGNATURE_MISMATCH'
462
+ | 'SSL_NOT_SUPPORTED'
463
+ | 'COPY_IN_PROGRESS'
464
+ | 'INVALID_TRANSACTION_NAME'
465
+ | 'TARGET_SESSION_ATTRS'
460
466
  | 'UNSAFE_TRANSACTION';
461
467
  message: string;
462
468
  }
@@ -605,7 +611,7 @@ declare namespace postgres {
605
611
  writable(): Promise<Writable>;
606
612
 
607
613
  execute(): this;
608
- cancel(): void;
614
+ cancel(): Promise<void>;
609
615
 
610
616
  /**
611
617
  * @deprecated `.stream` has been renamed to `.forEach`
@@ -0,0 +1,8 @@
1
+ import postgres from './index.js'
2
+
3
+ export interface VercelPool {
4
+ options: { idleTimeoutMillis: number };
5
+ on(event: string, listener: () => void): void;
6
+ }
7
+
8
+ export function vercelPool(sql: postgres.Sql<any>): VercelPool;